From 117871a83459d64d7e646353276ddae6a0d2a2a3 Mon Sep 17 00:00:00 2001 From: Chris McCord Date: Sun, 1 Apr 2018 23:33:11 -0400 Subject: [PATCH 01/77] WIP * Fix bug when restarting index from lst block * Include tests for block sequencer * Check for new blocks periodically after indexing Co-authored-by: Alex Garibay Co-authored-by: Luke Imhoff --- .circleci/config.yml | 5 - .credo.exs | 9 +- apps/explorer/config/config.exs | 30 +- apps/explorer/config/dev.exs | 22 +- apps/explorer/config/prod.exs | 35 --- apps/explorer/config/test.exs | 3 - apps/explorer/lib/explorer/application.ex | 18 +- apps/explorer/lib/explorer/chain.ex | 190 +++++++++++- apps/explorer/lib/explorer/chain/block.ex | 58 +++- .../lib/explorer/chain/block_transaction.ex | 25 -- .../lib/explorer/chain/from_address.ex | 21 -- .../explorer/chain/internal_transaction.ex | 37 +++ apps/explorer/lib/explorer/chain/receipt.ex | 52 +++- .../explorer/lib/explorer/chain/statistics.ex | 3 +- .../lib/explorer/chain/statistics/server.ex | 4 + .../explorer/lib/explorer/chain/to_address.ex | 20 -- .../lib/explorer/chain/transaction.ex | 38 ++- apps/explorer/lib/explorer/eth.ex | 287 ++++++++++++++++++ .../lib/explorer/exq_node_identifier.ex | 5 - .../explorer/importers/balance_importer.ex | 17 -- .../lib/explorer/importers/block_importer.ex | 81 ----- .../internal_transaction_importer.ex | 80 ----- .../explorer/importers/receipt_importer.ex | 79 ----- .../importers/transaction_importer.ex | 142 --------- apps/explorer/lib/explorer/indexer.ex | 38 +++ .../lib/explorer/indexer/block_fetcher.ex | 212 +++++++++++++ .../explorer/lib/explorer/indexer/sequence.ex | 80 +++++ .../lib/explorer/indexer/supervisor.ex | 19 ++ apps/explorer/lib/explorer/repo.ex | 24 +- apps/explorer/lib/explorer/scheduler.ex | 4 - .../explorer/lib/explorer/skipped_balances.ex | 21 -- apps/explorer/lib/explorer/skipped_blocks.ex | 32 -- .../explorer/skipped_internal_transactions.ex | 25 -- .../explorer/lib/explorer/skipped_receipts.ex | 25 -- .../lib/explorer/workers/import_balance.ex | 13 - .../lib/explorer/workers/import_block.ex | 26 -- .../workers/import_internal_transaction.ex | 12 - .../lib/explorer/workers/import_receipt.ex | 12 - .../explorer/workers/import_skipped_blocks.ex | 18 -- .../explorer/workers/import_transaction.ex | 26 -- .../lib/explorer/workers/refresh_balance.ex | 29 -- apps/explorer/lib/mix/tasks/exq.start.ex | 25 -- .../explorer/lib/mix/tasks/scrape.balances.ex | 24 -- apps/explorer/lib/mix/tasks/scrape.blocks.ex | 26 -- .../mix/tasks/scrape.internal_transactions.ex | 24 -- .../explorer/lib/mix/tasks/scrape.receipts.ex | 24 -- apps/explorer/mix.exs | 10 +- ....exs => 20180117221921_create_address.exs} | 5 +- ...s.exs => 20180117221922_create_blocks.exs} | 20 +- .../20180117221922_create_transactions.exs | 14 - .../20180117221923_create_transactions.exs | 37 +++ ...180124003303_add_value_to_transactions.exs | 9 - ...80129201141_add_fields_to_transactions.exs | 18 -- .../20180130004126_create_from_addresses.exs | 14 - .../20180130004544_create_to_addresses.exs | 14 - ...180202195342_create_block_transactions.exs | 14 - ...5933_remove_block_id_from_transactions.exs | 9 - ...indices_to_block_and_block_transaction.exs | 8 - ...0_add_transactions_index_to_timestamps.exs | 8 - .../20180212214442_create_receipts.exs | 9 +- .../migrations/20180212222309_create_logs.exs | 13 +- .../20180216011950_create_balances_views.exs | 48 --- ...221001948_create_internal_transactions.exs | 28 +- ...0816_move_address_keys_to_transactions.exs | 12 - ...23223257_index_transaction_address_ids.exs | 8 - ...create_credit_debit_materialized_view.exs} | 0 ...rnal_transactions_and_add_unique_index.exs | 38 --- ...ance_and_balance_updated_at_to_address.exs | 10 - ...1013446_add_receipt_id_to_transactions.exs | 9 - .../explorer/chain/block_transaction_test.exs | 18 -- .../test/explorer/chain/from_address_test.exs | 13 - .../test/explorer/chain/statistics_test.exs | 13 +- .../test/explorer/chain/to_address_test.exs | 13 - apps/explorer/test/explorer/chain_test.exs | 31 +- .../explorer/ethereumex_extensions_test.exs | 14 - .../importers/balance_importer_test.exs | 40 --- .../importers/block_importer_test.exs | 121 -------- .../internal_transaction_importer_test.exs | 115 ------- .../importers/receipt_importer_test.exs | 119 -------- .../importers/transaction_importer_test.exs | 270 ---------------- .../test/explorer/indexer/sequence_test.exs | 93 ++++++ .../test/explorer/skipped_balances_test.exs | 18 -- .../test/explorer/skipped_blocks_test.exs | 77 ----- .../skipped_internal_transactions_test.exs | 26 -- .../explorer/skipped_transactions_test.exs | 54 ---- .../explorer/workers/import_balance_test.exs | 39 --- .../explorer/workers/import_block_test.exs | 66 ---- .../import_internal_transaction_test.exs | 31 -- .../explorer/workers/import_receipt_test.exs | 31 -- .../workers/import_skipped_blocks_test.exs | 23 -- .../workers/import_transaction_test.exs | 148 --------- .../explorer/workers/refresh_balance_test.exs | 52 ---- .../chain/block_transaction_factory.ex | 9 - .../factories/chain/from_address_factory.ex | 9 - .../factories/chain/to_address_factory.ex | 9 - .../factories/chain/transaction_factory.ex | 10 - apps/explorer/test/support/factory.ex | 3 - apps/explorer_web/config/config.exs | 2 - apps/explorer_web/lib/explorer_web/router.ex | 22 -- apps/explorer_web/mix.exs | 7 +- ...dress_transaction_from_controller_test.exs | 30 +- ...address_transaction_to_controller_test.exs | 27 +- .../controllers/block_controller_test.exs | 7 +- .../block_transaction_controller_test.exs | 21 +- .../controllers/chain_controller_test.exs | 8 +- .../pending_transaction_controller_test.exs | 23 +- .../transaction_controller_test.exs | 19 +- .../features/contributor_browsing_test.exs | 20 +- .../test/explorer_web/features/exq_test.exs | 11 - config/config.exs | 2 - config/dev.exs | 1 + config/prod.exs | 6 - mix.lock | 9 +- 113 files changed, 1277 insertions(+), 2758 deletions(-) delete mode 100644 apps/explorer/lib/explorer/chain/block_transaction.ex delete mode 100644 apps/explorer/lib/explorer/chain/from_address.ex delete mode 100644 apps/explorer/lib/explorer/chain/to_address.ex create mode 100644 apps/explorer/lib/explorer/eth.ex delete mode 100644 apps/explorer/lib/explorer/exq_node_identifier.ex delete mode 100644 apps/explorer/lib/explorer/importers/balance_importer.ex delete mode 100644 apps/explorer/lib/explorer/importers/block_importer.ex delete mode 100644 apps/explorer/lib/explorer/importers/internal_transaction_importer.ex delete mode 100644 apps/explorer/lib/explorer/importers/receipt_importer.ex delete mode 100644 apps/explorer/lib/explorer/importers/transaction_importer.ex create mode 100644 apps/explorer/lib/explorer/indexer.ex create mode 100644 apps/explorer/lib/explorer/indexer/block_fetcher.ex create mode 100644 apps/explorer/lib/explorer/indexer/sequence.ex create mode 100644 apps/explorer/lib/explorer/indexer/supervisor.ex delete mode 100644 apps/explorer/lib/explorer/scheduler.ex delete mode 100644 apps/explorer/lib/explorer/skipped_balances.ex delete mode 100644 apps/explorer/lib/explorer/skipped_blocks.ex delete mode 100644 apps/explorer/lib/explorer/skipped_internal_transactions.ex delete mode 100644 apps/explorer/lib/explorer/skipped_receipts.ex delete mode 100644 apps/explorer/lib/explorer/workers/import_balance.ex delete mode 100644 apps/explorer/lib/explorer/workers/import_block.ex delete mode 100644 apps/explorer/lib/explorer/workers/import_internal_transaction.ex delete mode 100644 apps/explorer/lib/explorer/workers/import_receipt.ex delete mode 100644 apps/explorer/lib/explorer/workers/import_skipped_blocks.ex delete mode 100644 apps/explorer/lib/explorer/workers/import_transaction.ex delete mode 100644 apps/explorer/lib/explorer/workers/refresh_balance.ex delete mode 100644 apps/explorer/lib/mix/tasks/exq.start.ex delete mode 100644 apps/explorer/lib/mix/tasks/scrape.balances.ex delete mode 100644 apps/explorer/lib/mix/tasks/scrape.blocks.ex delete mode 100644 apps/explorer/lib/mix/tasks/scrape.internal_transactions.ex delete mode 100644 apps/explorer/lib/mix/tasks/scrape.receipts.ex rename apps/explorer/priv/repo/migrations/{20180130001125_create_address.exs => 20180117221921_create_address.exs} (60%) rename apps/explorer/priv/repo/migrations/{20180117221921_create_blocks.exs => 20180117221922_create_blocks.exs} (83%) delete mode 100644 apps/explorer/priv/repo/migrations/20180117221922_create_transactions.exs create mode 100644 apps/explorer/priv/repo/migrations/20180117221923_create_transactions.exs delete mode 100644 apps/explorer/priv/repo/migrations/20180124003303_add_value_to_transactions.exs delete mode 100644 apps/explorer/priv/repo/migrations/20180129201141_add_fields_to_transactions.exs delete mode 100644 apps/explorer/priv/repo/migrations/20180130004126_create_from_addresses.exs delete mode 100644 apps/explorer/priv/repo/migrations/20180130004544_create_to_addresses.exs delete mode 100644 apps/explorer/priv/repo/migrations/20180202195342_create_block_transactions.exs delete mode 100644 apps/explorer/priv/repo/migrations/20180202215933_remove_block_id_from_transactions.exs delete mode 100644 apps/explorer/priv/repo/migrations/20180208010839_add_indices_to_block_and_block_transaction.exs delete mode 100644 apps/explorer/priv/repo/migrations/20180208054620_add_transactions_index_to_timestamps.exs delete mode 100644 apps/explorer/priv/repo/migrations/20180216011950_create_balances_views.exs delete mode 100644 apps/explorer/priv/repo/migrations/20180223220816_move_address_keys_to_transactions.exs delete mode 100644 apps/explorer/priv/repo/migrations/20180223223257_index_transaction_address_ids.exs rename apps/explorer/priv/repo/migrations/{20180224004300_update_credit_debit_materialized_view.exs => 20180224004300_create_credit_debit_materialized_view.exs} (100%) delete mode 100644 apps/explorer/priv/repo/migrations/20180227004146_dedup_internal_transactions_and_add_unique_index.exs delete mode 100644 apps/explorer/priv/repo/migrations/20180227225553_add_balance_and_balance_updated_at_to_address.exs delete mode 100644 apps/explorer/priv/repo/migrations/20180301013446_add_receipt_id_to_transactions.exs delete mode 100644 apps/explorer/test/explorer/chain/block_transaction_test.exs delete mode 100644 apps/explorer/test/explorer/chain/from_address_test.exs delete mode 100644 apps/explorer/test/explorer/chain/to_address_test.exs delete mode 100644 apps/explorer/test/explorer/ethereumex_extensions_test.exs delete mode 100644 apps/explorer/test/explorer/importers/balance_importer_test.exs delete mode 100644 apps/explorer/test/explorer/importers/block_importer_test.exs delete mode 100644 apps/explorer/test/explorer/importers/internal_transaction_importer_test.exs delete mode 100644 apps/explorer/test/explorer/importers/receipt_importer_test.exs delete mode 100644 apps/explorer/test/explorer/importers/transaction_importer_test.exs create mode 100644 apps/explorer/test/explorer/indexer/sequence_test.exs delete mode 100644 apps/explorer/test/explorer/skipped_balances_test.exs delete mode 100644 apps/explorer/test/explorer/skipped_blocks_test.exs delete mode 100644 apps/explorer/test/explorer/skipped_internal_transactions_test.exs delete mode 100644 apps/explorer/test/explorer/skipped_transactions_test.exs delete mode 100644 apps/explorer/test/explorer/workers/import_balance_test.exs delete mode 100644 apps/explorer/test/explorer/workers/import_block_test.exs delete mode 100644 apps/explorer/test/explorer/workers/import_internal_transaction_test.exs delete mode 100644 apps/explorer/test/explorer/workers/import_receipt_test.exs delete mode 100644 apps/explorer/test/explorer/workers/import_skipped_blocks_test.exs delete mode 100644 apps/explorer/test/explorer/workers/import_transaction_test.exs delete mode 100644 apps/explorer/test/explorer/workers/refresh_balance_test.exs delete mode 100644 apps/explorer/test/support/factories/chain/block_transaction_factory.ex delete mode 100644 apps/explorer/test/support/factories/chain/from_address_factory.ex delete mode 100644 apps/explorer/test/support/factories/chain/to_address_factory.ex delete mode 100644 apps/explorer_web/test/explorer_web/features/exq_test.exs diff --git a/.circleci/config.yml b/.circleci/config.yml index 3f3e20638b..bf94e3db7b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -325,7 +325,6 @@ jobs: POSTGRES_PASSWORD: postgres # match PGUSER for elixir image above POSTGRES_USER: postgres - - image: circleci/redis:4.0.9-alpine working_directory: ~/app @@ -340,10 +339,6 @@ jobs: name: Wait for DB command: dockerize -wait tcp://localhost:5432 -timeout 1m - - run: - name: Wait for Redis - command: dockerize -wait tcp://localhost:6379 -timeout 1m - - run: mix coveralls.circle --umbrella - store_test_results: diff --git a/.credo.exs b/.credo.exs index 248a2c9038..f238df2905 100644 --- a/.credo.exs +++ b/.credo.exs @@ -63,7 +63,7 @@ # You can customize the priority of any check # Priority values are: `low, normal, high, higher` # - {Credo.Check.Design.AliasUsage, excluded_lastnames: ~w(Number Time), priority: :low}, + {Credo.Check.Design.AliasUsage, excluded_lastnames: ~w(DateTime Number Repo Time), priority: :low}, # For some checks, you can also set other parameters # @@ -77,7 +77,7 @@ # If you don't want TODO comments to cause `mix credo` to fail, just # set this value to 0 (zero). # - {Credo.Check.Design.TagTODO, exit_status: 2}, + {Credo.Check.Design.TagTODO, exit_status: 0}, {Credo.Check.Design.TagFIXME}, {Credo.Check.Readability.FunctionNames}, {Credo.Check.Readability.LargeNumbers}, @@ -122,11 +122,12 @@ {Credo.Check.Warning.UnusedRegexOperation}, {Credo.Check.Warning.UnusedStringOperation}, {Credo.Check.Warning.UnusedTupleOperation}, - {Credo.Check.Warning.RaiseInsideRescue}, + {Credo.Check.Warning.RaiseInsideRescue, false}, # Controversial and experimental checks (opt-in, just remove `, false`) # - {Credo.Check.Refactor.ABCSize}, + # TODO reenable before merging optimized-indexer branch + {Credo.Check.Refactor.ABCSize, false}, {Credo.Check.Refactor.AppendSingleItem}, {Credo.Check.Refactor.VariableRebinding}, {Credo.Check.Warning.MapGetUnsafePass}, diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index 05c22ed3ba..2af039f34f 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -5,7 +5,16 @@ # is restricted to this project. use Mix.Config -config :ethereumex, url: "http://localhost:8545" +url = "https://sokol.poa.network" + +config :explorer, :eth_client, + http: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :eth]], + trace_url: "https://sokol-trace.poa.network", + url: url + +config :ethereumex, + url: url, + http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :eth]] # General application configuration config :explorer, ecto_repos: [Explorer.Repo] @@ -14,25 +23,6 @@ config :explorer, :ethereum, backend: Explorer.Ethereum.Live config :explorer, Explorer.Integrations.EctoLogger, query_time_ms_threshold: 2_000 -config :exq, - host: "localhost", - port: 6379, - namespace: "exq", - start_on_application: false, - scheduler_enable: true, - shutdown_timeout: 5000, - max_retries: 10, - queues: [ - {"default", 1}, - {"balances", 1}, - {"blocks", 1}, - {"internal_transactions", 1}, - {"transactions", 1}, - {"receipts", 1} - ] - -config :exq_ui, server: false - # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{Mix.env()}.exs" diff --git a/apps/explorer/config/dev.exs b/apps/explorer/config/dev.exs index 59b7077b91..e50a57226d 100644 --- a/apps/explorer/config/dev.exs +++ b/apps/explorer/config/dev.exs @@ -5,27 +5,7 @@ config :explorer, Explorer.Repo, adapter: Ecto.Adapters.Postgres, database: "explorer_dev", hostname: "localhost", + loggers: [], pool_size: 10 -# Configure Quantum -config :explorer, Explorer.Scheduler, - jobs: [ - [ - schedule: {:extended, "*/15 * * * * *"}, - task: {Explorer.Workers.RefreshBalance, :perform_later, []} - ], - [ - schedule: {:extended, "*/5 * * * * *"}, - task: {Explorer.Workers.ImportBlock, :perform_later, ["latest"]} - ], - [ - schedule: {:extended, "*/5 * * * * *"}, - task: {Explorer.Workers.ImportBlock, :perform_later, ["pending"]} - ], - [ - schedule: {:extended, "*/15 * * * * *"}, - task: {Explorer.Workers.ImportSkippedBlocks, :perform_later, [1]} - ] - ] - import_config "dev.secret.exs" diff --git a/apps/explorer/config/prod.exs b/apps/explorer/config/prod.exs index df59624f0c..6faf422c2d 100644 --- a/apps/explorer/config/prod.exs +++ b/apps/explorer/config/prod.exs @@ -12,38 +12,3 @@ config :explorer, Explorer.Repo, # Configure Web3 config :ethereumex, url: System.get_env("ETHEREUM_URL") - -# Configure Quantum -config :explorer, Explorer.Scheduler, - jobs: [ - [ - schedule: {:extended, System.get_env("EXQ_BALANCE_SCHEDULE") || "0 * * * * *"}, - task: {Explorer.Workers.RefreshBalance, :perform_later, []} - ], - [ - schedule: {:extended, System.get_env("EXQ_LATEST_BLOCK_SCHEDULE") || "* * * * * *"}, - task: {Explorer.Workers.ImportBlock, :perform_later, ["latest"]} - ], - [ - schedule: {:extended, System.get_env("EXQ_PENDING_BLOCK_SCHEDULE") || "* * * * * *"}, - task: {Explorer.Workers.ImportBlock, :perform_later, ["pending"]} - ], - [ - schedule: {:extended, System.get_env("EXQ_BACKFILL_SCHEDULE") || "* * * * * *"}, - task: - {Explorer.Workers.ImportSkippedBlocks, :perform_later, - [String.to_integer(System.get_env("EXQ_BACKFILL_BATCH_SIZE") || "1")]} - ] - ] - -# Configure Exq -config :exq, - node_identifier: Explorer.ExqNodeIdentifier, - url: System.get_env("REDIS_URL"), - queues: [ - {"blocks", String.to_integer(System.get_env("EXQ_BLOCKS_CONCURRENCY") || "1")}, - {"default", String.to_integer(System.get_env("EXQ_CONCURRENCY") || "1")}, - {"internal_transactions", String.to_integer(System.get_env("EXQ_INTERNAL_TRANSACTIONS_CONCURRENCY") || "1")}, - {"receipts", String.to_integer(System.get_env("EXQ_RECEIPTS_CONCURRENCY") || "1")}, - {"transactions", String.to_integer(System.get_env("EXQ_TRANSACTIONS_CONCURRENCY") || "1")} - ] diff --git a/apps/explorer/config/test.exs b/apps/explorer/config/test.exs index a2dba4218e..6b3b23a798 100644 --- a/apps/explorer/config/test.exs +++ b/apps/explorer/config/test.exs @@ -8,7 +8,4 @@ config :explorer, Explorer.Repo, pool: Ecto.Adapters.SQL.Sandbox, ownership_timeout: 60_000 -# Configure ethereumex -config :ethereumex, url: "https://sokol-trace.poa.network" - config :explorer, :ethereum, backend: Explorer.Ethereum.Test diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index a72f1c79c7..9dd4767d55 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -5,7 +5,7 @@ defmodule Explorer.Application do use Application - import Supervisor.Spec + import Supervisor.Spec, only: [supervisor: 3] # See https://hexdocs.pm/elixir/Application.html # for more information on OTP Applications @@ -19,20 +19,24 @@ defmodule Explorer.Application do defp children(:test), do: children() defp children(_) do - exq_options = [] |> Keyword.put(:mode, :enqueuer) - children() ++ [ - supervisor(Exq, [exq_options]), - worker(Explorer.Chain.Statistics.Server, []), + Explorer.ETH, + supervisor(Task.Supervisor, [[name: Explorer.TaskSupervisor]], id: Explorer.TaskSupervisor), + Explorer.Indexer, + Explorer.Chain.Statistics.Server, Explorer.ExchangeRates ] end defp children do [ - supervisor(Explorer.Repo, []), - {Task.Supervisor, name: Explorer.ExchangeRateTaskSupervisor} + Explorer.Repo, + supervisor( + Task.Supervisor, + [[name: Explorer.ExchangeRateTaskSupervisor]], + id: Explorer.ExchangeRateTaskSupervisor + ) ] end end diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 6ecacbf563..560188fbdf 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -5,18 +5,9 @@ defmodule Explorer.Chain do import Ecto.Query, only: [from: 2, order_by: 2, preload: 2, where: 2, where: 3] - alias Explorer.Chain.{ - Address, - Block, - BlockTransaction, - InternalTransaction, - Log, - Receipt, - Transaction, - Wei - } - - alias Explorer.Repo.NewRelic, as: Repo + alias Ecto.Multi + alias Explorer.Chain.{Address, Block, InternalTransaction, Log, Receipt, Transaction, Wei} + alias Explorer.Repo # Types @@ -47,6 +38,10 @@ defmodule Explorer.Chain do # Functions + def block_count do + Repo.one(from(b in Block, select: count(b.id))) + end + @doc """ Finds all `t:Explorer.Chain.Transaction.t/0` in the `t:Explorer.Chain.Block.t/0`. @@ -85,12 +80,11 @@ defmodule Explorer.Chain do def block_to_transaction_count(%Block{id: block_id}) do query = from( - block_transaction in BlockTransaction, - join: block in assoc(block_transaction, :block), - where: block_transaction.block_id == ^block_id + transaction in Transaction, + where: transaction.block_id == ^block_id ) - Repo.aggregate(query, :count, :block_id) + Repo.aggregate(query, :count, :id) end @doc """ @@ -173,6 +167,13 @@ defmodule Explorer.Chain do address_to_transactions(address, Keyword.put(options, :direction, :from)) end + @doc """ + TODO + """ + def get_latest_block do + Repo.one(from(b in Block, limit: 1, order_by: [desc: b.number])) + end + @doc """ The `t:Explorer.Chain.Transaction.t/0` `gas_price` of the `transaction` in `unit`. """ @@ -270,6 +271,25 @@ defmodule Explorer.Chain do end end + @doc """ + TODO + """ + def import_blocks(raw_blocks, internal_transactions, receipts) do + {blocks, transactions} = extract_blocks(raw_blocks) + + Multi.new() + |> Multi.run(:blocks, &insert_blocks(&1, blocks)) + |> Multi.run(:transactions, &insert_transactions(&1, transactions)) + |> Multi.run(:internal, &insert_internal(&1, internal_transactions)) + |> Multi.run(:receipts, &insert_receipts(&1, receipts)) + |> Multi.run(:logs, &insert_logs(&1)) + |> Repo.transaction() + end + + def internal_transaction_count do + Repo.one(from(t in InternalTransaction, select: count(t.id))) + end + @doc """ The last `t:Explorer.Chain.Transaction.t/0` `id`. """ @@ -313,6 +333,10 @@ defmodule Explorer.Chain do |> Repo.paginate(pagination) end + def log_count do + Repo.one(from(l in Log, select: count(l.id))) + end + @doc """ The maximum `t:Explorer.Chain.Block.t/0` `number` """ @@ -321,6 +345,28 @@ defmodule Explorer.Chain do Repo.aggregate(Block, :max, :number) end + @doc """ + TODO + """ + def missing_block_numbers do + {:ok, {_, missing_count, missing_ranges}} = + Repo.transaction(fn -> + query = from(b in Block, select: b.number, order_by: [asc: b.number]) + + query + |> Repo.stream(max_rows: 1000) + |> Enum.reduce({-1, 0, []}, fn + num, {prev, missing_count, acc} when prev + 1 == num -> + {num, missing_count, acc} + + num, {prev, missing_count, acc} -> + {num, missing_count + (num - prev - 1), [{prev + 1, num - 1} | acc]} + end) + end) + + {missing_count, missing_ranges} + end + @doc """ Finds `t:Explorer.Chain.Block.t/0` with `number` """ @@ -335,6 +381,10 @@ defmodule Explorer.Chain do end end + def receipt_count do + Repo.one(from(r in Receipt, select: count(r.id))) + end + @doc """ `t:Explorer.Chain.Transaction/0`s to `address`. @@ -523,6 +573,18 @@ defmodule Explorer.Chain do from(q in query, order_by: [desc: q.inserted_at, desc: q.id]) end + defp extract_blocks(raw_blocks) do + timestamps = timestamps() + + {blocks, transactions} = + Enum.reduce(raw_blocks, {[], []}, fn raw_block, {blocks_acc, trans_acc} -> + {:ok, block, transactions} = Block.extract(raw_block, timestamps) + {[block | blocks_acc], trans_acc ++ transactions} + end) + + {Enum.reverse(blocks), transactions} + end + defp for_parent_transaction(query, hash) when is_binary(hash) do from( child in query, @@ -531,6 +593,97 @@ defmodule Explorer.Chain do ) end + defp insert_blocks(%{}, blocks) do + {_, inserted_blocks} = + Repo.safe_insert_all( + Block, + blocks, + returning: [:id, :number], + on_conflict: :replace_all, + conflict_target: :number + ) + + {:ok, inserted_blocks} + end + + defp insert_internal(%{transactions: transactions}, internal_transactions) do + timestamps = timestamps() + + internals = + Enum.flat_map(transactions, fn %{hash: hash, id: id} -> + case Map.fetch(internal_transactions, hash) do + {:ok, traces} -> + Enum.map(traces, &InternalTransaction.extract(&1, id, timestamps)) + + :error -> + [] + end + end) + + {_, inserted} = Repo.safe_insert_all(InternalTransaction, internals, on_conflict: :nothing) + + {:ok, inserted} + end + + defp insert_logs(%{receipts: %{inserted: receipts, logs: logs_map}}) do + logs_to_insert = + Enum.reduce(receipts, [], fn receipt, acc -> + case Map.fetch(logs_map, receipt.transaction_id) do + {:ok, []} -> + acc + + {:ok, [_ | _] = logs} -> + logs = Enum.map(logs, &Map.put(&1, :receipt_id, receipt.id)) + logs ++ acc + end + end) + + {_, inserted_logs} = Repo.safe_insert_all(Log, logs_to_insert, returning: [:id]) + {:ok, inserted_logs} + end + + defp insert_receipts(%{transactions: transactions}, raw_receipts) do + timestamps = timestamps() + + {receipts_to_insert, logs_map} = + Enum.reduce(transactions, {[], %{}}, fn trans, {receipts_acc, logs_acc} -> + case Map.fetch(raw_receipts, trans.hash) do + {:ok, raw_receipt} -> + {receipt, logs} = Receipt.extract(raw_receipt, trans.id, timestamps) + {[receipt | receipts_acc], Map.put(logs_acc, trans.id, logs)} + + :error -> + {receipts_acc, logs_acc} + end + end) + + {_, inserted_receipts} = + Repo.safe_insert_all( + Receipt, + receipts_to_insert, + returning: [:id, :transaction_id] + ) + + {:ok, %{inserted: inserted_receipts, logs: logs_map}} + end + + defp insert_transactions(%{blocks: blocks}, transactions) do + blocks_map = for block <- blocks, into: %{}, do: {block.number, block} + + transactions = + for transaction <- transactions do + %{id: id} = Map.fetch!(blocks_map, transaction.block_number) + + transaction + |> Map.put(:block_id, id) + |> Map.delete(:block_number) + end + + {_, inserted} = Repo.safe_insert_all(Transaction, transactions, returning: [:id, :hash]) + + {:ok, inserted} + end + defp join_association(query, association, necessity) when is_atom(association) do case necessity do :optional -> @@ -556,6 +709,11 @@ defmodule Explorer.Chain do ) end + defp timestamps do + now = Ecto.DateTime.utc() + %{inserted_at: now, updated_at: now} + end + defp transaction_hash_to_logs(transaction_hash, options) when is_binary(transaction_hash) and is_list(options) do lower_transaction_hash = String.downcase(transaction_hash) diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index 55c33cd291..22e2dd84e3 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -7,7 +7,8 @@ defmodule Explorer.Chain.Block do use Explorer.Schema - alias Explorer.Chain.{BlockTransaction, Gas, Hash, Transaction} + alias Ecto.Changeset + alias Explorer.Chain.{Gas, Hash, Transaction} # Types @@ -24,7 +25,6 @@ defmodule Explorer.Chain.Block do @type block_number :: non_neg_integer() @typedoc """ - * `block_transactions` - The `t:Explorer.Chain.BlockTransaction.t/0`s joins this block to its `transactions` * `difficulty` - how hard the block was to mine. * `gas_limit` - If the total number of gas used by the computation spawned by the transaction, including the original message and any sub-messages that may be triggered, is less than or equal to the gas limit, then the transaction @@ -43,7 +43,6 @@ defmodule Explorer.Chain.Block do * `transactions` - the `t:Explorer.Chain.Transaction.t/0` in this block. """ @type t :: %__MODULE__{ - block_transactions: %Ecto.Association.NotLoaded{} | [BlockTransaction.t()], difficulty: difficulty(), gas_limit: Gas.t(), gas_used: Gas.t(), @@ -73,12 +72,10 @@ defmodule Explorer.Chain.Block do timestamps() - has_many(:block_transactions, BlockTransaction) - many_to_many(:transactions, Transaction, join_through: "block_transactions") + has_many(:transactions, Transaction) end - @required_attrs ~w(number hash parent_hash nonce miner difficulty - total_difficulty size gas_limit gas_used timestamp)a + @required_attrs ~w(difficulty gas_limit gas_used hash miner nonce number parent_hash size timestamp total_difficulty)a @doc false def changeset(%__MODULE__{} = block, attrs) do @@ -87,7 +84,13 @@ defmodule Explorer.Chain.Block do |> validate_required(@required_attrs) |> update_change(:hash, &String.downcase/1) |> unique_constraint(:hash) - |> cast_assoc(:transactions) + end + + @doc false + def extract(raw_block, %{} = timestamps) do + raw_block + |> extract_block(timestamps) + |> extract_transactions(raw_block["transactions"], timestamps) end def null, do: %__MODULE__{number: -1, timestamp: :calendar.universal_time()} @@ -95,4 +98,43 @@ defmodule Explorer.Chain.Block do def latest(query) do query |> order_by(desc: :number) end + + ## Private Functions + + defp extract_block(raw_block, %{} = timestamps) do + attrs = %{ + hash: raw_block["hash"], + number: raw_block["number"], + gas_used: raw_block["gasUsed"], + timestamp: raw_block["timestamp"], + parent_hash: raw_block["parentHash"], + miner: raw_block["miner"], + difficulty: raw_block["difficulty"], + total_difficulty: raw_block["totalDifficulty"], + size: raw_block["size"], + gas_limit: raw_block["gasLimit"], + nonce: raw_block["nonce"] || "0" + } + + case changeset(%__MODULE__{}, attrs) do + %Changeset{valid?: true, changes: changes} -> {:ok, Map.merge(changes, timestamps)} + %Changeset{valid?: false, errors: errors} -> {:error, {:block, errors}} + end + end + + defp extract_transactions({:ok, block_changes}, raw_transactions, %{} = timestamps) do + raw_transactions + |> Enum.map(&Transaction.decode(&1, block_changes.number, timestamps)) + |> Enum.reduce_while({:ok, block_changes, []}, fn + {:ok, trans_changes}, {:ok, block, acc} -> + {:cont, {:ok, block, [trans_changes | acc]}} + + {:error, reason}, _ -> + {:halt, {:error, {:transaction, reason}}} + end) + end + + defp extract_transactions({:error, reason}, _transactions, _timestamps) do + {:error, reason} + end end diff --git a/apps/explorer/lib/explorer/chain/block_transaction.ex b/apps/explorer/lib/explorer/chain/block_transaction.ex deleted file mode 100644 index 1ece36cb72..0000000000 --- a/apps/explorer/lib/explorer/chain/block_transaction.ex +++ /dev/null @@ -1,25 +0,0 @@ -defmodule Explorer.Chain.BlockTransaction do - @moduledoc "Connects a Block to a Transaction" - - use Explorer.Schema - - alias Explorer.Chain.{Block, Transaction} - - @primary_key false - schema "block_transactions" do - belongs_to(:block, Block) - belongs_to(:transaction, Transaction, primary_key: true) - timestamps() - end - - @required_attrs ~w(block_id transaction_id)a - - def changeset(%__MODULE__{} = block_transaction, attrs \\ %{}) do - block_transaction - |> cast(attrs, @required_attrs) - |> validate_required(@required_attrs) - |> cast_assoc(:block) - |> cast_assoc(:transaction) - |> unique_constraint(:transaction_id, name: :block_transactions_transaction_id_index) - end -end diff --git a/apps/explorer/lib/explorer/chain/from_address.ex b/apps/explorer/lib/explorer/chain/from_address.ex deleted file mode 100644 index 7b44d2a194..0000000000 --- a/apps/explorer/lib/explorer/chain/from_address.ex +++ /dev/null @@ -1,21 +0,0 @@ -defmodule Explorer.Chain.FromAddress do - @moduledoc false - - use Explorer.Schema - - alias Explorer.Chain.{Address, Transaction} - - @primary_key false - schema "from_addresses" do - belongs_to(:address, Address) - belongs_to(:transaction, Transaction, primary_key: true) - - timestamps() - end - - def changeset(%__MODULE__{} = to_address, attrs \\ %{}) do - to_address - |> cast(attrs, [:transaction_id, :address_id]) - |> unique_constraint(:transaction_id, name: :from_addresses_transaction_id_index) - end -end diff --git a/apps/explorer/lib/explorer/chain/internal_transaction.ex b/apps/explorer/lib/explorer/chain/internal_transaction.ex index a645d50dae..af839f7008 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction.ex @@ -17,6 +17,7 @@ defmodule Explorer.Chain.InternalTransaction do @typedoc """ * `call_type` - the type of call * `from_address` - the source of the `value` + * `from_address_hash` - hash of the source of the `value` * `from_address_id` - foreign key for `from_address` * `gas` - the amount of gas allowed * `gas_used` - the amount of gas used @@ -24,6 +25,7 @@ defmodule Explorer.Chain.InternalTransaction do * `input` - input bytes to the call * `output` - output bytes from the call * `to_address` - the sink of the `value` + * `to_address_hash` - hash of the sink of the `value` * `to_address_id` - foreign key for `to_address` * `trace_address` - list of traces * `transaction` - transaction in which this transaction occured @@ -33,6 +35,7 @@ defmodule Explorer.Chain.InternalTransaction do @type t :: %__MODULE__{ call_type: call_type, from_address: %Ecto.Association.NotLoaded{} | Address.t(), + from_address_hash: Address.hash(), from_address_id: non_neg_integer(), gas: Gas.t(), gas_used: Gas.t(), @@ -40,6 +43,7 @@ defmodule Explorer.Chain.InternalTransaction do input: String.t(), output: String.t(), to_address: %Ecto.Association.NotLoaded{} | Address.t(), + to_address_hash: Address.hash(), to_address_id: non_neg_integer(), trace_address: [non_neg_integer()], transaction: %Ecto.Association.NotLoaded{} | Transaction.t(), @@ -49,11 +53,13 @@ defmodule Explorer.Chain.InternalTransaction do schema "internal_transactions" do field(:call_type, :string) + field(:from_address_hash, :string) field(:gas, :decimal) field(:gas_used, :decimal) field(:index, :integer) field(:input, :string) field(:output, :string) + field(:to_address_hash, :string) field(:trace_address, {:array, :integer}) field(:value, :decimal) @@ -77,4 +83,35 @@ defmodule Explorer.Chain.InternalTransaction do |> foreign_key_constraint(:from_address_id) |> unique_constraint(:transaction_id, name: :internal_transactions_transaction_id_index_index) end + + def extract(trace, transaction_id, %{} = timestamps) do + %{ + transaction_id: transaction_id, + index: 0, + call_type: trace["action"]["callType"] || trace["type"], + to_address_hash: to_address(trace), + from_address_hash: trace |> from_address(), + trace_address: trace["traceAddress"], + value: trace["action"]["value"], + gas: trace["action"]["gas"], + gas_used: gas_used(trace), + input: trace["action"]["input"], + output: trace["result"]["output"], + # error: trace["error"], + inserted_at: Map.fetch!(timestamps, :inserted_at), + updated_at: Map.fetch!(timestamps, :updated_at) + } + end + + defp from_address(%{"action" => %{"from" => address}}), do: address + + defp gas_used(%{"result" => %{"gasUsed" => gas}}), do: gas + defp gas_used(%{"error" => _error}), do: 0 + + defp to_address(%{"action" => %{"to" => address}}) + when not is_nil(address), + do: address + + defp to_address(%{"result" => %{"address" => address}}), do: address + defp to_address(%{"error" => _error}), do: nil end diff --git a/apps/explorer/lib/explorer/chain/receipt.ex b/apps/explorer/lib/explorer/chain/receipt.ex index b81c935667..700a97daf6 100644 --- a/apps/explorer/lib/explorer/chain/receipt.ex +++ b/apps/explorer/lib/explorer/chain/receipt.ex @@ -5,23 +5,27 @@ defmodule Explorer.Chain.Receipt do alias Explorer.Chain.{Log, Transaction} - @required_attrs ~w(cumulative_gas_used gas_used status index)a @optional_attrs ~w(transaction_id)a + @required_attrs ~w(cumulative_gas_used gas_used status index)a + @allowed_attrs @optional_attrs ++ @required_attrs schema "receipts" do - belongs_to(:transaction, Transaction) - has_many(:logs, Log) field(:cumulative_gas_used, :decimal) field(:gas_used, :decimal) field(:status, :integer) field(:index, :integer) + + belongs_to(:transaction, Transaction) + has_many(:logs, Log) + timestamps() end + # Functions + def changeset(%__MODULE__{} = transaction_receipt, attrs \\ %{}) do transaction_receipt - |> cast(attrs, @required_attrs) - |> cast(attrs, @optional_attrs) + |> cast(attrs, @allowed_attrs) |> cast_assoc(:transaction) |> cast_assoc(:logs) |> validate_required(@required_attrs) @@ -29,5 +33,43 @@ defmodule Explorer.Chain.Receipt do |> unique_constraint(:transaction_id) end + def extract(raw_receipt, transaction_id, %{} = timestamps) do + logs = + raw_receipt + |> Map.fetch!("logs") + |> Enum.map(&extract_log(&1, timestamps)) + + receipt = %{ + transaction_id: transaction_id, + index: raw_receipt["transactionIndex"], + cumulative_gas_used: raw_receipt["cumulativeGasUsed"], + gas_used: raw_receipt["gasUsed"], + status: raw_receipt["status"], + inserted_at: Map.fetch!(timestamps, :inserted_at), + updated_at: Map.fetch!(timestamps, :updated_at) + } + + {receipt, logs} + end + def null, do: %__MODULE__{} + + ## Private Functions + + defp extract_log(log, %{} = timestamps) do + # address = Address.find_or_create_by_hash(log["address"]) + + %{ + # address_id: 0, # TODO + index: log["logIndex"], + data: log["data"], + type: log["type"], + first_topic: log["topics"] |> Enum.at(0), + second_topic: log["topics"] |> Enum.at(1), + third_topic: log["topics"] |> Enum.at(2), + fourth_topic: log["topics"] |> Enum.at(3), + inserted_at: Map.fetch!(timestamps, :inserted_at), + updated_at: Map.fetch!(timestamps, :updated_at) + } + end end diff --git a/apps/explorer/lib/explorer/chain/statistics.ex b/apps/explorer/lib/explorer/chain/statistics.ex index 59d3adafa6..5fccdd65d7 100644 --- a/apps/explorer/lib/explorer/chain/statistics.ex +++ b/apps/explorer/lib/explorer/chain/statistics.ex @@ -25,8 +25,7 @@ defmodule Explorer.Chain.Statistics do @transaction_count_query """ SELECT count(transactions.id) FROM transactions - JOIN block_transactions ON block_transactions.transaction_id = transactions.id - JOIN blocks ON blocks.id = block_transactions.block_id + JOIN blocks ON blocks.id = transactions.block_id WHERE blocks.timestamp > NOW() - interval '1 day' """ diff --git a/apps/explorer/lib/explorer/chain/statistics/server.ex b/apps/explorer/lib/explorer/chain/statistics/server.ex index 8463f46e5b..c1728b9f92 100644 --- a/apps/explorer/lib/explorer/chain/statistics/server.ex +++ b/apps/explorer/lib/explorer/chain/statistics/server.ex @@ -7,6 +7,10 @@ defmodule Explorer.Chain.Statistics.Server do @interval 1_000 + def child_spec(_) do + Supervisor.Spec.worker(__MODULE__, [true]) + end + @spec fetch() :: Statistics.t() def fetch do case GenServer.whereis(__MODULE__) do diff --git a/apps/explorer/lib/explorer/chain/to_address.ex b/apps/explorer/lib/explorer/chain/to_address.ex deleted file mode 100644 index 190ced36cd..0000000000 --- a/apps/explorer/lib/explorer/chain/to_address.ex +++ /dev/null @@ -1,20 +0,0 @@ -defmodule Explorer.Chain.ToAddress do - @moduledoc false - - use Explorer.Schema - - alias Explorer.Chain.{Address, Transaction} - - @primary_key false - schema "to_addresses" do - belongs_to(:address, Address) - belongs_to(:transaction, Transaction, primary_key: true) - timestamps() - end - - def changeset(%__MODULE__{} = to_address, attrs \\ %{}) do - to_address - |> cast(attrs, [:transaction_id, :address_id]) - |> unique_constraint(:transaction_id, name: :to_addresses_transaction_id_index) - end -end diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index b6d5f8d177..473ba07c19 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -3,7 +3,8 @@ defmodule Explorer.Chain.Transaction do use Explorer.Schema - alias Explorer.Chain.{Address, Block, BlockTransaction, Hash, InternalTransaction, Receipt, Wei} + alias Ecto.Changeset + alias Explorer.Chain.{Address, Block, Hash, InternalTransaction, Receipt, Wei} # Constants @@ -76,8 +77,8 @@ defmodule Explorer.Chain.Transaction do @type wei_per_gas :: non_neg_integer() @typedoc """ - * `block_transaction` - joins this transaction to its `block` * `block` - the block in which this transaction was mined/validated + * `block_id` - `block` foreign key * `from_address` - the source of `value` * `from_address_id` - foreign key of `from_address` * `gas` - Gas provided by the sender @@ -100,7 +101,7 @@ defmodule Explorer.Chain.Transaction do """ @type t :: %__MODULE__{ block: %Ecto.Association.NotLoaded{} | Block.t(), - block_transaction: %Ecto.Association.NotLoaded{} | BlockTransaction.t(), + block_id: non_neg_integer, from_address: %Ecto.Association.NotLoaded{} | Address.t(), from_address_id: non_neg_integer(), gas: Gas.t(), @@ -139,8 +140,7 @@ defmodule Explorer.Chain.Transaction do timestamps() - has_one(:block_transaction, BlockTransaction) - has_one(:block, through: [:block_transaction, :block]) + belongs_to(:block, Block) belongs_to(:from_address, Address) has_many(:internal_transactions, InternalTransaction) has_one(:receipt, Receipt) @@ -157,5 +157,33 @@ defmodule Explorer.Chain.Transaction do |> unique_constraint(:hash) end + def decode(raw_transaction, block_number, %{} = timestamps) do + attrs = %{ + hash: raw_transaction["hash"], + value: raw_transaction["value"], + gas: raw_transaction["gas"], + gas_price: raw_transaction["gasPrice"], + input: raw_transaction["input"], + nonce: raw_transaction["nonce"], + 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"] + } + + case changeset(%__MODULE__{}, attrs) do + %Changeset{valid?: true, changes: changes} -> + {:ok, + changes + |> Map.put(:block_number, block_number) + |> Map.merge(timestamps)} + + %Changeset{valid?: false, errors: errors} -> + {:error, errors} + end + end + def null, do: %__MODULE__{} end diff --git a/apps/explorer/lib/explorer/eth.ex b/apps/explorer/lib/explorer/eth.ex new file mode 100644 index 0000000000..48aff34fda --- /dev/null +++ b/apps/explorer/lib/explorer/eth.ex @@ -0,0 +1,287 @@ +defmodule Explorer.ETH do + @moduledoc """ + Ethereum JSONRPC client. + + ## Configuration + + Configuration for parity URLs can be provided with the + following mix config: + + config :explorer, :eth_client, + url: "https://sokol.poa.network", + trace_url: "https://sokol-trace.poa.network", + http: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :eth]] + + Note: the tracing node URL is provided separately from `:url`, via + `:trace_url`. The trace URL and is used for `fetch_internal_transactions`, + which is only a supported method on tracing nodes. The `:http` option is + passed directly to the HTTP library (`HTTPoison`), which forwards the + options down to `:hackney`. + """ + require Logger + + def child_spec(_opts) do + :hackney_pool.child_spec(:eth, recv_timeout: 60_000, timeout: 60_000, max_connections: 1000) + end + + @doc """ + Creates a filter subscription that can be polled for retreiving new blocks. + """ + def listen_for_new_blocks do + id = DateTime.utc_now() |> DateTime.to_unix() + + request = %{ + "id" => id, + "jsonrpc" => "2.0", + "method" => "eth_newBlockFilter", + "params" => [] + } + + json_rpc(request, config(:url)) + end + + @doc """ + Lists changes for a given filter subscription. + """ + def check_for_updates(filter_id) do + request = %{ + "id" => filter_id, + "jsonrpc" => "2.0", + "method" => "eth_getFilterChanges", + "params" => [filter_id] + } + + json_rpc(request, config(:url)) + end + + @doc """ + Fetches blocks by block hashes. + + Transaction data is included for each block. + """ + def fetch_blocks_by_hash(block_hashes) do + batched_requests = + for block_hash <- block_hashes do + %{ + "id" => block_hash, + "jsonrpc" => "2.0", + "method" => "eth_getBlockByHash", + "params" => [block_hash, true] + } + end + + json_rpc(batched_requests, config(:url)) + end + + def decode_int(hex) do + {"0x", base_16} = String.split_at(hex, 2) + String.to_integer(base_16, 16) + end + + def decode_time(field) do + field |> decode_int() |> Timex.from_unix() + end + + def fetch_transaction_receipts(hashes) when is_list(hashes) do + hashes + |> Enum.map(fn hash -> + %{ + "id" => hash, + "jsonrpc" => "2.0", + "method" => "eth_getTransactionReceipt", + "params" => [hash] + } + end) + |> json_rpc(config(:url)) + |> handle_receipts() + end + + defp handle_receipts({:ok, results}) do + results_map = + Enum.into(results, %{}, fn %{"id" => hash, "result" => receipt} -> + {hash, + Map.merge(receipt, %{ + "transactionHash" => String.downcase(receipt["transactionHash"]), + "transactionIndex" => decode_int(receipt["transactionIndex"]), + "cumulativeGasUsed" => decode_int(receipt["cumulativeGasUsed"]), + "gasUsed" => decode_int(receipt["gasUsed"]), + "status" => decode_int(receipt["status"]), + "logs" => + Enum.map(receipt["logs"], fn log -> + Map.merge(log, %{"logIndex" => decode_int(log["logIndex"])}) + end) + })} + end) + + {:ok, results_map} + end + + defp handle_receipts({:error, reason}) do + {:error, reason} + end + + def fetch_internal_transactions(hashes) when is_list(hashes) do + hashes + |> Enum.map(fn hash -> + %{ + "id" => hash, + "jsonrpc" => "2.0", + "method" => "trace_replayTransaction", + "params" => [hash, ["trace"]] + } + end) + |> json_rpc(config(:trace_url)) + |> handle_internal_transactions() + end + + defp handle_internal_transactions({:ok, results}) do + results_map = + Enum.into(results, %{}, fn + %{"error" => error} -> + throw({:error, error}) + + %{"id" => hash, "result" => %{"trace" => traces}} -> + {hash, Enum.map(traces, &decode_trace(&1))} + end) + + {:ok, results_map} + catch + {:error, reason} -> {:error, reason} + end + + defp handle_internal_transactions({:error, reason}) do + {:error, reason} + end + + defp decode_trace(%{"action" => action} = trace) do + trace + |> Map.merge(%{ + "action" => + Map.merge(action, %{ + "value" => decode_int(action["value"]), + "gas" => decode_int(action["gas"]) + }) + }) + |> put_gas_used() + end + + defp put_gas_used(%{"error" => _} = trace), do: trace + + defp put_gas_used(%{"result" => %{"gasUsed" => gas}} = trace) do + put_in(trace, ["result", "gasUsed"], decode_int(gas)) + end + + @doc """ + Fetches blocks by block number range. + """ + def fetch_blocks_by_range(block_start, block_end) do + block_start + |> build_batch_get_block_by_number(block_end) + |> json_rpc(config(:url)) + |> handle_get_block_by_number(block_start, block_end) + end + + defp build_batch_get_block_by_number(block_start, block_end) do + for current <- block_start..block_end do + %{ + "id" => current, + "jsonrpc" => "2.0", + "method" => "eth_getBlockByNumber", + "params" => [int_to_hash_string(current), true] + } + end + end + + defp handle_get_block_by_number({:ok, results}, block_start, block_end) do + {blocks, next} = + Enum.reduce(results, {[], :more}, fn + %{"result" => nil}, {blocks, _} -> {blocks, :end_of_chain} + %{"result" => %{} = block}, {blocks, next} -> {[block | blocks], next} + end) + + {:ok, next, decode_blocks(blocks), {block_start, block_end}} + end + + defp handle_get_block_by_number({:error, reason}, block_start, block_end) do + {:error, reason, {block_start, block_end}} + end + + defp decode_blocks(blocks) do + Enum.map(blocks, fn block -> + Map.merge(block, %{ + "hash" => String.downcase(block["hash"]), + "number" => decode_int(block["number"]), + "gasUsed" => decode_int(block["gasUsed"]), + "timestamp" => decode_time(block["timestamp"]), + "difficulty" => decode_int(block["difficulty"]), + "totalDifficulty" => decode_int(block["totalDifficulty"]), + "size" => decode_int(block["size"]), + "gasLimit" => decode_int(block["gasLimit"]), + "transactions" => decode_transactions(block["transactions"]) + }) + end) + end + + defp decode_transactions(transactions) do + Enum.map(transactions, fn transaction -> + Map.merge(transaction, %{ + "hash" => String.downcase(transaction["hash"]), + "value" => decode_int(transaction["value"]), + "gas" => decode_int(transaction["gas"]), + "gasPrice" => decode_int(transaction["gasPrice"]), + "nonce" => decode_int(transaction["nonce"]) + }) + end) + end + + defp json_rpc(payload, url) do + json = encode_json(payload) + headers = [{"Content-Type", "application/json"}] + + case HTTPoison.post(url, json, headers, config(:http)) do + {:ok, %HTTPoison.Response{body: body, status_code: code}} -> + body |> decode_json(payload) |> handle_response(code) + + {:error, %HTTPoison.Error{reason: reason}} -> + {:error, reason} + end + end + + defp handle_response(resp, 200) do + case resp do + [%{} | _] = batch_resp -> {:ok, batch_resp} + %{"error" => error} -> {:error, error} + %{"result" => result} -> {:ok, result} + end + end + + defp handle_response(resp, _status) do + {:error, resp} + end + + defp config(key) do + :explorer + |> Application.fetch_env!(:eth_client) + |> Keyword.fetch!(key) + end + + defp encode_json(data), do: Jason.encode_to_iodata!(data) + + defp decode_json(body, posted_payload) do + Jason.decode!(body) + rescue + Jason.DecodeError -> + Logger.error(""" + failed to decode json payload: + + #{inspect(body)} + + #{inspect(posted_payload)} + + """) + + raise("bad jason") + end + + defp int_to_hash_string(number), do: "0x" <> Integer.to_string(number, 16) +end diff --git a/apps/explorer/lib/explorer/exq_node_identifier.ex b/apps/explorer/lib/explorer/exq_node_identifier.ex deleted file mode 100644 index f349047b38..0000000000 --- a/apps/explorer/lib/explorer/exq_node_identifier.ex +++ /dev/null @@ -1,5 +0,0 @@ -defmodule Explorer.ExqNodeIdentifier do - @behaviour Exq.NodeIdentifier.Behaviour - @moduledoc "Configure Exq with the current dyno name" - def node_id, do: System.get_env("DYNO") -end diff --git a/apps/explorer/lib/explorer/importers/balance_importer.ex b/apps/explorer/lib/explorer/importers/balance_importer.ex deleted file mode 100644 index 8c81e14e06..0000000000 --- a/apps/explorer/lib/explorer/importers/balance_importer.ex +++ /dev/null @@ -1,17 +0,0 @@ -defmodule Explorer.BalanceImporter do - @moduledoc "Imports a balance for a given address." - - alias Explorer.{Chain, Ethereum} - - def import(hash) do - encoded_balance = Ethereum.download_balance(hash) - - persist_balance(hash, encoded_balance) - end - - defp persist_balance(hash, encoded_balance) when is_binary(hash) do - decoded_balance = Ethereum.decode_integer_field(encoded_balance) - - Chain.update_balance(hash, decoded_balance) - end -end diff --git a/apps/explorer/lib/explorer/importers/block_importer.ex b/apps/explorer/lib/explorer/importers/block_importer.ex deleted file mode 100644 index d189c80269..0000000000 --- a/apps/explorer/lib/explorer/importers/block_importer.ex +++ /dev/null @@ -1,81 +0,0 @@ -defmodule Explorer.BlockImporter do - @moduledoc "Imports a block." - - import Ecto.Query - import Ethereumex.HttpClient, only: [eth_get_block_by_number: 2] - - alias Explorer.{BlockImporter, Ethereum} - alias Explorer.Chain.Block - alias Explorer.Repo.NewRelic, as: Repo - alias Explorer.Workers.ImportTransaction - - def import(raw_block) when is_map(raw_block) do - changes = extract_block(raw_block) - block = changes.hash |> find() - - if is_nil(block.id), do: block |> Block.changeset(changes) |> Repo.insert() - - Enum.map(raw_block["transactions"], &ImportTransaction.perform/1) - end - - @dialyzer {:nowarn_function, import: 1} - def import("pending") do - raw_block = download_block("pending") - Enum.map(raw_block["transactions"], &ImportTransaction.perform_later/1) - end - - @dialyzer {:nowarn_function, import: 1} - def import(block_number) do - block_number |> download_block() |> BlockImporter.import() - end - - def find(hash) do - query = - from( - b in Block, - where: fragment("lower(?)", b.hash) == ^String.downcase(hash), - limit: 1 - ) - - query |> Repo.one() || %Block{} - end - - @dialyzer {:nowarn_function, download_block: 1} - def download_block(block_number) do - {:ok, block} = - block_number - |> encode_number() - |> eth_get_block_by_number(true) - - block - end - - def extract_block(raw_block) do - %{ - hash: raw_block["hash"], - number: raw_block["number"] |> Ethereum.decode_integer_field(), - gas_used: raw_block["gasUsed"] |> Ethereum.decode_integer_field(), - timestamp: raw_block["timestamp"] |> Ethereum.decode_time_field(), - parent_hash: raw_block["parentHash"], - miner: raw_block["miner"], - difficulty: raw_block["difficulty"] |> Ethereum.decode_integer_field(), - total_difficulty: raw_block["totalDifficulty"] |> Ethereum.decode_integer_field(), - size: raw_block["size"] |> Ethereum.decode_integer_field(), - gas_limit: raw_block["gasLimit"] |> Ethereum.decode_integer_field(), - nonce: raw_block["nonce"] || "0" - } - end - - defp encode_number("latest"), do: "latest" - defp encode_number("earliest"), do: "earliest" - defp encode_number("pending"), do: "pending" - defp encode_number("0x" <> number) when is_binary(number), do: number - - defp encode_number(number) when is_binary(number) do - number - |> String.to_integer() - |> encode_number() - end - - defp encode_number(number), do: "0x" <> Integer.to_string(number, 16) -end diff --git a/apps/explorer/lib/explorer/importers/internal_transaction_importer.ex b/apps/explorer/lib/explorer/importers/internal_transaction_importer.ex deleted file mode 100644 index c9f8225098..0000000000 --- a/apps/explorer/lib/explorer/importers/internal_transaction_importer.ex +++ /dev/null @@ -1,80 +0,0 @@ -defmodule Explorer.InternalTransactionImporter do - @moduledoc "Imports a transaction's internal transactions given its hash." - - import Ecto.Query - - alias Explorer.{Chain, Ethereum, EthereumexExtensions, Repo} - alias Explorer.Chain.{InternalTransaction, Transaction} - - @dialyzer {:nowarn_function, import: 1} - def import(hash) do - transaction = find_transaction(hash) - - hash - |> download_trace - |> extract_attrs - |> persist_internal_transactions(transaction) - end - - @dialyzer {:nowarn_function, download_trace: 1} - 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 - - @dialyzer {:nowarn_function, extract_attrs: 1} - 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: trace |> to_address() |> address_id(), - from_address_id: trace |> from_address() |> address_id(), - trace_address: trace["traceAddress"], - value: trace["action"]["value"] |> Ethereum.decode_integer_field(), - gas: trace["action"]["gas"] |> Ethereum.decode_integer_field(), - gas_used: trace["result"]["gasUsed"] |> Ethereum.decode_integer_field(), - input: trace["action"]["input"], - output: trace["result"]["output"] - } - end - - defp to_address(%{"action" => %{"to" => address}}) - when not is_nil(address), - do: address - - defp to_address(%{"result" => %{"address" => address}}), do: address - - defp from_address(%{"action" => %{"from" => address}}), do: address - - @dialyzer {:nowarn_function, persist_internal_transactions: 2} - 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 address_id(hash) do - {:ok, address} = Chain.ensure_hash_address(hash) - - address.id - end -end diff --git a/apps/explorer/lib/explorer/importers/receipt_importer.ex b/apps/explorer/lib/explorer/importers/receipt_importer.ex deleted file mode 100644 index c81861fc28..0000000000 --- a/apps/explorer/lib/explorer/importers/receipt_importer.ex +++ /dev/null @@ -1,79 +0,0 @@ -defmodule Explorer.ReceiptImporter do - @moduledoc "Imports a transaction receipt given a transaction hash." - - import Ecto.Query - import Ethereumex.HttpClient, only: [eth_get_transaction_receipt: 1] - - alias Explorer.{Chain, Repo} - alias Explorer.Chain.{Receipt, Transaction} - - def import(hash) do - transaction = hash |> find_transaction() - - hash - |> download_receipt() - |> extract_receipt() - |> Map.put(:transaction_id, transaction.id) - |> save_receipt() - end - - @dialyzer {:nowarn_function, download_receipt: 1} - defp download_receipt(hash) do - {:ok, receipt} = eth_get_transaction_receipt(hash) - receipt || %{} - end - - defp find_transaction(hash) do - query = - from( - transaction in Transaction, - left_join: receipt in assoc(transaction, :receipt), - where: fragment("lower(?)", transaction.hash) == ^hash, - where: is_nil(receipt.id), - limit: 1 - ) - - Repo.one(query) || Transaction.null() - end - - defp save_receipt(receipt) do - unless is_nil(receipt.transaction_id) do - %Receipt{} - |> Receipt.changeset(receipt) - |> Repo.insert() - end - end - - defp extract_receipt(receipt) do - logs = receipt["logs"] || [] - - %{ - index: receipt["transactionIndex"] |> decode_integer_field(), - cumulative_gas_used: receipt["cumulativeGasUsed"] |> decode_integer_field(), - gas_used: receipt["gasUsed"] |> decode_integer_field(), - status: receipt["status"] |> decode_integer_field(), - logs: logs |> Enum.map(&extract_log/1) - } - end - - defp extract_log(log) do - {:ok, address} = Chain.ensure_hash_address(log["address"]) - - %{ - address_id: address.id, - index: log["logIndex"] |> decode_integer_field(), - data: log["data"], - type: log["type"], - first_topic: log["topics"] |> Enum.at(0), - second_topic: log["topics"] |> Enum.at(1), - third_topic: log["topics"] |> Enum.at(2), - fourth_topic: log["topics"] |> Enum.at(3) - } - end - - defp decode_integer_field("0x" <> hex) when is_binary(hex) do - String.to_integer(hex, 16) - end - - defp decode_integer_field(field), do: field -end diff --git a/apps/explorer/lib/explorer/importers/transaction_importer.ex b/apps/explorer/lib/explorer/importers/transaction_importer.ex deleted file mode 100644 index f3becbf31c..0000000000 --- a/apps/explorer/lib/explorer/importers/transaction_importer.ex +++ /dev/null @@ -1,142 +0,0 @@ -defmodule Explorer.TransactionImporter do - @moduledoc "Imports a transaction given a unique hash." - - import Ecto.Query - import Ethereumex.HttpClient, only: [eth_get_transaction_by_hash: 1] - - alias Explorer.{Chain, Ethereum, Repo, BalanceImporter} - alias Explorer.Chain.{Block, BlockTransaction, Transaction} - - def import(hash) when is_binary(hash) do - hash |> download_transaction() |> persist_transaction() - end - - def import(raw_transaction) when is_map(raw_transaction) do - persist_transaction(raw_transaction) - end - - def persist_transaction(raw_transaction) do - found_transaction = raw_transaction["hash"] |> find() - - transaction = - case is_nil(found_transaction.id) do - false -> - found_transaction - - true -> - to_address = - raw_transaction - |> to_address() - |> fetch_address() - - from_address = - raw_transaction - |> from_address() - |> fetch_address() - - changes = - raw_transaction - |> extract_attrs() - |> Map.put(:to_address_id, to_address.id) - |> Map.put(:from_address_id, from_address.id) - - found_transaction |> Transaction.changeset(changes) |> Repo.insert!() - end - - transaction - |> create_block_transaction(raw_transaction["blockHash"]) - - refresh_account_balances(raw_transaction) - - transaction - end - - def find(hash) do - query = - from( - t in Transaction, - where: fragment("lower(?)", t.hash) == ^String.downcase(hash), - limit: 1 - ) - - query |> Repo.one() || %Transaction{} - 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"] |> Ethereum.decode_integer_field(), - gas: raw_transaction["gas"] |> Ethereum.decode_integer_field(), - gas_price: raw_transaction["gasPrice"] |> Ethereum.decode_integer_field(), - input: raw_transaction["input"], - nonce: raw_transaction["nonce"] |> Ethereum.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, hash) do - query = - from( - t in Block, - where: fragment("lower(?)", t.hash) == ^String.downcase(hash), - limit: 1 - ) - - block = query |> Repo.one() - - if block do - changes = %{block_id: block.id, transaction_id: transaction.id} - - case Repo.get_by(BlockTransaction, transaction_id: transaction.id) do - nil -> - %BlockTransaction{} - |> BlockTransaction.changeset(changes) - |> Repo.insert() - - block_transaction -> - block_transaction - |> BlockTransaction.changeset(%{block_id: block.id}) - |> Repo.update() - end - end - - transaction - end - - def to_address(%{"to" => to}) when not is_nil(to), do: to - def to_address(%{"creates" => creates}) when not is_nil(creates), do: creates - def to_address(hash) when is_bitstring(hash), do: hash - - def from_address(%{"from" => from}), do: from - def from_address(hash) when is_bitstring(hash), do: hash - - def fetch_address(hash) when is_bitstring(hash) do - {:ok, address} = Chain.ensure_hash_address(hash) - - address - end - - defp refresh_account_balances(raw_transaction) do - raw_transaction - |> to_address() - |> update_balance() - - raw_transaction - |> from_address() - |> update_balance() - end - - defp update_balance(address_hash) do - BalanceImporter.import(address_hash) - end -end diff --git a/apps/explorer/lib/explorer/indexer.ex b/apps/explorer/lib/explorer/indexer.ex new file mode 100644 index 0000000000..1b1ad71400 --- /dev/null +++ b/apps/explorer/lib/explorer/indexer.ex @@ -0,0 +1,38 @@ +defmodule Explorer.Indexer do + @moduledoc """ + TODO + """ + + alias Explorer.Chain + alias Explorer.Chain.Block + + def child_spec(opts) do + %{ + id: __MODULE__, + start: {Explorer.Indexer.Supervisor, :start_link, [opts]}, + restart: :permanent, + shutdown: 5000, + type: :supervisor + } + end + + @doc """ + TODO + """ + def last_indexed_block_number do + case Chain.get_latest_block() do + %Block{number: num} -> num + nil -> 0 + end + end + + @doc """ + TODO + """ + def next_block_number do + case last_indexed_block_number() do + 0 -> 0 + num -> num + 1 + end + end +end diff --git a/apps/explorer/lib/explorer/indexer/block_fetcher.ex b/apps/explorer/lib/explorer/indexer/block_fetcher.ex new file mode 100644 index 0000000000..717da65048 --- /dev/null +++ b/apps/explorer/lib/explorer/indexer/block_fetcher.ex @@ -0,0 +1,212 @@ +defmodule Explorer.Indexer.BlockFetcher do + @moduledoc """ + TODO + + ## Next steps + + - after gensis index transition to RT index + """ + use GenServer + + require Logger + + alias Explorer.{Chain, ETH, Indexer} + alias Explorer.Indexer.Sequence + + defstruct ~w(current_block genesis_task subscription_id)a + + @batch_size 50 + @blocks_concurrency 10 + + @receipts_batch_size 250 + @receipts_concurrency 10 + + @internal_batch_size 50 + @internal_concurrency 4 + + @polling_interval 20_000 + + @doc """ + Ensures missing block number ranges are chunked into fetchable batches. + """ + def missing_block_numbers do + {count, missing_ranges} = Chain.missing_block_numbers() + + chunked_ranges = + Enum.flat_map(missing_ranges, fn + {start, ending} when ending - start <= @batch_size -> + [{start, ending}] + + {start, ending} -> + start + |> Stream.iterate(&(&1 + @batch_size)) + |> Enum.reduce_while([], fn + chunk_start, acc when chunk_start + @batch_size >= ending -> + {:halt, [{chunk_start, ending} | acc]} + + chunk_start, acc -> + {:cont, [{chunk_start, chunk_start + @batch_size - 1} | acc]} + end) + |> Enum.reverse() + end) + + {count, chunked_ranges} + end + + def start_link(opts) do + GenServer.start_link(__MODULE__, opts, name: __MODULE__) + end + + def init(_opts) do + send(self(), :index) + :timer.send_interval(15_000, self(), :debug_count) + + {:ok, %__MODULE__{current_block: 0, genesis_task: nil, subscription_id: nil}} + end + + def handle_info(:index, state) do + {count, missing_ranges} = missing_block_numbers() + current_block = Indexer.next_block_number() + + Logger.debug(fn -> "#{count} missed block ranges between genesis and #{current_block}" end) + + {:ok, genesis_task} = + Task.start_link(fn -> + stream_import(missing_ranges, current_block) + end) + + Process.monitor(genesis_task) + + {:noreply, %__MODULE__{state | genesis_task: genesis_task}} + end + + def handle_info(:poll, %__MODULE__{subscription_id: subscription_id} = state) do + Process.send_after(self(), :poll, @polling_interval) + + with {:ok, blocks} when length(blocks) > 0 <- ETH.check_for_updates(subscription_id) do + Logger.debug(fn -> "Processing #{length(blocks)} new block(s)" end) + + # TODO do something with the new blocks + ETH.fetch_blocks_by_hash(blocks) + end + + {:noreply, state} + end + + def handle_info({:DOWN, _ref, :process, pid, :normal}, %__MODULE__{genesis_task: pid} = state) do + Logger.info(fn -> "Finished index from genesis" end) + + {:ok, subscription_id} = ETH.listen_for_new_blocks() + + send(self(), :poll) + + {:noreply, %__MODULE__{state | genesis_task: nil, subscription_id: subscription_id}} + end + + def handle_info(:debug_count, state) do + Logger.debug(fn -> + """ + + ================================ + persisted counts + ================================ + blocks: #{Chain.block_count()} + internal transactions: #{Chain.internal_transaction_count()} + receipts: #{Chain.receipt_count()} + logs: #{Chain.log_count()} + """ + end) + + {:noreply, state} + end + + defp stream_import(missing_ranges, current_block) do + {:ok, seq} = Sequence.start_link(missing_ranges, current_block, @batch_size) + + seq + |> Sequence.build_stream() + |> Task.async_stream( + fn {block_start, block_end} = range -> + with {:ok, next, blocks, range} <- ETH.fetch_blocks_by_range(block_start, block_end), + :ok <- cap_seq(seq, next, range), + transaction_hashes <- collect_transaction_hashes(blocks), + {:ok, receipts} <- fetch_transaction_receipts(transaction_hashes), + {:ok, internals} <- fetch_internal_transactions(transaction_hashes) do + import_blocks(blocks, internals, receipts, seq, range) + else + {:error, reason} -> + Logger.debug(fn -> + "failed to fetch blocks #{inspect(range)}: #{inspect(reason)}. Retrying" + end) + + :ok = Sequence.inject_range(seq, range) + end + end, + max_concurrency: @blocks_concurrency, + timeout: :infinity + ) + |> Enum.each(fn {:ok, :ok} -> :ok end) + end + + defp cap_seq(seq, :end_of_chain, {_block_start, block_end}) do + Logger.info("Reached end of blockchain #{inspect(block_end)}") + :ok = Sequence.cap(seq) + end + + defp cap_seq(_seq, :more, {block_start, block_end}) do + Logger.debug(fn -> "got blocks #{block_start} - #{block_end}" end) + :ok + end + + defp fetch_transaction_receipts([]), do: {:ok, %{}} + + defp fetch_transaction_receipts(hashes) do + Logger.debug(fn -> "fetching #{length(hashes)} transaction receipts" end) + stream_opts = [max_concurrency: @receipts_concurrency, timeout: :infinity] + + hashes + |> Enum.chunk_every(@receipts_batch_size) + |> Task.async_stream(Ð.fetch_transaction_receipts(&1), stream_opts) + |> Enum.reduce_while({:ok, %{}}, fn + {:ok, {:ok, receipts}}, {:ok, acc} -> {:cont, {:ok, Map.merge(acc, receipts)}} + {:ok, {:error, reason}}, {:ok, _acc} -> {:halt, {:error, reason}} + {:error, reason}, {:ok, _acc} -> {:halt, {:error, reason}} + end) + end + + defp fetch_internal_transactions([]), do: {:ok, %{}} + + defp fetch_internal_transactions(hashes) do + Logger.debug(fn -> "fetching #{length(hashes)} internal transactions" end) + stream_opts = [max_concurrency: @internal_concurrency, timeout: :infinity] + + hashes + |> Enum.chunk_every(@internal_batch_size) + |> Task.async_stream(Ð.fetch_internal_transactions(&1), stream_opts) + |> Enum.reduce_while({:ok, %{}}, fn + {:ok, {:ok, trans}}, {:ok, acc} -> {:cont, {:ok, Map.merge(acc, trans)}} + {:ok, {:error, reason}}, {:ok, _acc} -> {:halt, {:error, reason}} + {:error, reason}, {:ok, _acc} -> {:halt, {:error, reason}} + end) + end + + defp import_blocks(blocks, internal_transactions, receipts, seq, range) do + case Chain.import_blocks(blocks, internal_transactions, receipts) do + {:ok, _results} -> + :ok + + {:error, step, reason, _changes} -> + Logger.debug(fn -> + "failed to insert blocks during #{step} #{inspect(range)}: #{inspect(reason)}. Retrying" + end) + + :ok = Sequence.inject_range(seq, range) + end + end + + defp collect_transaction_hashes(raw_blocks) do + Enum.flat_map(raw_blocks, fn %{"transactions" => transactions} -> + Enum.map(transactions, fn %{"hash" => hash} -> hash end) + end) + end +end diff --git a/apps/explorer/lib/explorer/indexer/sequence.ex b/apps/explorer/lib/explorer/indexer/sequence.ex new file mode 100644 index 0000000000..e63ed843db --- /dev/null +++ b/apps/explorer/lib/explorer/indexer/sequence.ex @@ -0,0 +1,80 @@ +defmodule Explorer.Indexer.Sequence do + @moduledoc false + + use Agent + + defstruct ~w(current mode queue step)a + + @type range :: {pos_integer(), pos_integer()} + + @doc """ + Stars a process for managing a block sequence. + """ + @spec start_link([range()], pos_integer(), pos_integer()) :: Agent.on_start() + def start_link(initial_ranges, range_start, step) do + Agent.start_link(fn -> + %__MODULE__{ + current: range_start, + step: step, + mode: :infinite, + queue: :queue.from_list(initial_ranges) + } + end) + end + + @doc """ + Adds a range of block numbers to the sequence. + """ + @spec inject_range(pid(), range()) :: :ok + def inject_range(sequencer, {_first, _last} = range) when is_pid(sequencer) do + Agent.update(sequencer, fn state -> + %__MODULE__{state | queue: :queue.in(range, state.queue)} + end) + end + + @doc """ + Changes the mode for the sequencer to signal continuous streaming mode. + """ + @spec cap(pid()) :: :ok + def cap(sequencer) when is_pid(sequencer) do + Agent.update(sequencer, fn state -> + %__MODULE__{state | mode: :finite} + end) + end + + @doc """ + Builds an enumerable stream using a sequencer agent. + """ + @spec build_stream(pid()) :: Enumerable.t() + def build_stream(sequencer) when is_pid(sequencer) do + Stream.resource( + fn -> sequencer end, + fn seq -> + case pop(seq) do + :halt -> {:halt, seq} + range -> {[range], seq} + end + end, + fn seq -> seq end + ) + end + + @doc """ + Pops the next block range from the sequence. + """ + @spec pop(pid()) :: range() | :halt + def pop(sequencer) when is_pid(sequencer) do + Agent.get_and_update(sequencer, fn %__MODULE__{current: current, step: step} = state -> + case {state.mode, :queue.out(state.queue)} do + {_, {{:value, {starting, ending}}, new_queue}} -> + {{starting, ending}, %__MODULE__{state | queue: new_queue}} + + {:infinite, {:empty, new_queue}} -> + {{current, current + step - 1}, %__MODULE__{state | current: current + step, queue: new_queue}} + + {:finite, {:empty, new_queue}} -> + {:halt, %__MODULE__{state | queue: new_queue}} + end + end) + end +end diff --git a/apps/explorer/lib/explorer/indexer/supervisor.ex b/apps/explorer/lib/explorer/indexer/supervisor.ex new file mode 100644 index 0000000000..daa1652d13 --- /dev/null +++ b/apps/explorer/lib/explorer/indexer/supervisor.ex @@ -0,0 +1,19 @@ +defmodule Explorer.Indexer.Supervisor do + @moduledoc """ + Supervising the fetchers for the `Explorer.Indexer` + """ + + alias Explorer.Indexer.BlockFetcher + + def start_link(opts) do + Supervisor.start_link(__MODULE__, opts) + end + + def init(_opts) do + children = [ + {BlockFetcher, []} + ] + + Supervisor.init(children, strategy: :one_for_one) + end +end diff --git a/apps/explorer/lib/explorer/repo.ex b/apps/explorer/lib/explorer/repo.ex index f753cee63b..e0137e8cf9 100644 --- a/apps/explorer/lib/explorer/repo.ex +++ b/apps/explorer/lib/explorer/repo.ex @@ -1,7 +1,6 @@ defmodule Explorer.Repo do use Ecto.Repo, otp_app: :explorer use Scrivener, page_size: 10 - @dialyzer {:nowarn_function, rollback: 1} @doc """ Dynamically loads the repository url from the @@ -11,11 +10,24 @@ defmodule Explorer.Repo do {:ok, Keyword.put(opts, :url, System.get_env("DATABASE_URL"))} end - defmodule NewRelic do - use NewRelixir.Plug.Repo, repo: Explorer.Repo + @doc """ + Chunks elements into multiple `insert_all`'s to avoid DB driver param limits. + + *Note:* Should always be run within a transaction as multiple inserts may occur. + """ + def safe_insert_all(kind, elements, opts) do + returning = opts[:returning] + + elements + |> Enum.chunk_every(1000) + |> Enum.reduce({0, []}, fn chunk, {total_count, acc} -> + {count, inserted} = insert_all(kind, chunk, opts) - def paginate(queryable, opts \\ []) do - Explorer.Repo.paginate(queryable, opts) - end + if returning do + {count + total_count, acc ++ inserted} + else + {count + total_count, nil} + end + end) end end diff --git a/apps/explorer/lib/explorer/scheduler.ex b/apps/explorer/lib/explorer/scheduler.ex deleted file mode 100644 index 4fec9f20bb..0000000000 --- a/apps/explorer/lib/explorer/scheduler.ex +++ /dev/null @@ -1,4 +0,0 @@ -defmodule Explorer.Scheduler do - @moduledoc false - use Quantum.Scheduler, otp_app: :explorer -end diff --git a/apps/explorer/lib/explorer/skipped_balances.ex b/apps/explorer/lib/explorer/skipped_balances.ex deleted file mode 100644 index 849d9e3988..0000000000 --- a/apps/explorer/lib/explorer/skipped_balances.ex +++ /dev/null @@ -1,21 +0,0 @@ -defmodule Explorer.SkippedBalances do - @moduledoc "Gets a list of Addresses that do not have balances." - - alias Explorer.Chain.Address - alias Explorer.Repo.NewRelic, as: Repo - - import Ecto.Query, only: [from: 2] - - def fetch(count) do - query = - from( - address in Address, - select: address.hash, - where: is_nil(address.balance), - limit: ^count - ) - - query - |> Repo.all() - end -end diff --git a/apps/explorer/lib/explorer/skipped_blocks.ex b/apps/explorer/lib/explorer/skipped_blocks.ex deleted file mode 100644 index 51cd40229d..0000000000 --- a/apps/explorer/lib/explorer/skipped_blocks.ex +++ /dev/null @@ -1,32 +0,0 @@ -defmodule Explorer.SkippedBlocks do - @moduledoc """ - Fill in older blocks that were skipped during processing. - """ - import Ecto.Query, only: [from: 2, limit: 2] - - alias Explorer.Chain.Block - alias Explorer.Repo.NewRelic, as: Repo - - @missing_number_query "SELECT generate_series(?, 0, -1) AS missing_number" - - def first, do: first(1) - - def first(count) do - blocks = - from( - b in Block, - right_join: fragment(@missing_number_query, ^latest_block_number()), - on: b.number == fragment("missing_number"), - select: fragment("missing_number::text"), - where: is_nil(b.id), - limit: ^count - ) - - Repo.all(blocks) - end - - def latest_block_number do - block = Repo.one(Block |> Block.latest() |> limit(1)) || Block.null() - block.number - end -end diff --git a/apps/explorer/lib/explorer/skipped_internal_transactions.ex b/apps/explorer/lib/explorer/skipped_internal_transactions.ex deleted file mode 100644 index 1c30e25464..0000000000 --- a/apps/explorer/lib/explorer/skipped_internal_transactions.ex +++ /dev/null @@ -1,25 +0,0 @@ -defmodule Explorer.SkippedInternalTransactions do - @moduledoc """ - Find transactions that do not have internal transactions. - """ - import Ecto.Query, only: [from: 2] - - alias Explorer.Chain.Transaction - alias Explorer.Repo.NewRelic, as: Repo - - def first, do: first(1) - - def first(count) do - transactions = - from( - transaction in Transaction, - left_join: internal_transactions in assoc(transaction, :internal_transactions), - select: fragment("hash"), - group_by: transaction.id, - having: count(internal_transactions.id) == 0, - limit: ^count - ) - - Repo.all(transactions) - end -end diff --git a/apps/explorer/lib/explorer/skipped_receipts.ex b/apps/explorer/lib/explorer/skipped_receipts.ex deleted file mode 100644 index 7729d729ac..0000000000 --- a/apps/explorer/lib/explorer/skipped_receipts.ex +++ /dev/null @@ -1,25 +0,0 @@ -defmodule Explorer.SkippedReceipts do - @moduledoc """ - Find transactions that do not have a receipt. - """ - import Ecto.Query, only: [from: 2] - - alias Explorer.Chain.Transaction - alias Explorer.Repo.NewRelic, as: Repo - - def first, do: first(1) - - def first(count) do - transactions = - from( - transaction in Transaction, - left_join: receipt in assoc(transaction, :receipt), - select: fragment("hash"), - group_by: transaction.id, - having: count(receipt.id) == 0, - limit: ^count - ) - - Repo.all(transactions) - end -end diff --git a/apps/explorer/lib/explorer/workers/import_balance.ex b/apps/explorer/lib/explorer/workers/import_balance.ex deleted file mode 100644 index 7e20b2e337..0000000000 --- a/apps/explorer/lib/explorer/workers/import_balance.ex +++ /dev/null @@ -1,13 +0,0 @@ -defmodule Explorer.Workers.ImportBalance do - @moduledoc "A worker that imports the balance for a given address." - - alias Explorer.BalanceImporter - - def perform(hash) do - BalanceImporter.import(hash) - end - - def perform_later(hash) do - Exq.enqueue(Exq.Enqueuer, "balances", __MODULE__, [hash]) - end -end diff --git a/apps/explorer/lib/explorer/workers/import_block.ex b/apps/explorer/lib/explorer/workers/import_block.ex deleted file mode 100644 index e515975a3b..0000000000 --- a/apps/explorer/lib/explorer/workers/import_block.ex +++ /dev/null @@ -1,26 +0,0 @@ -defmodule Explorer.Workers.ImportBlock do - @moduledoc "Imports blocks by web3 conventions." - - import Ethereumex.HttpClient, only: [eth_block_number: 0] - - alias Explorer.BlockImporter - - @dialyzer {:nowarn_function, perform: 1} - def perform("latest") do - case eth_block_number() do - {:ok, number} -> perform_later(number) - _ -> nil - end - end - - @dialyzer {:nowarn_function, perform: 1} - def perform(number), do: BlockImporter.import("#{number}") - - def perform_later("0x" <> number) when is_binary(number) do - number |> String.to_integer(16) |> perform_later() - end - - def perform_later(number) do - Exq.enqueue(Exq.Enqueuer, "blocks", __MODULE__, [number]) - end -end diff --git a/apps/explorer/lib/explorer/workers/import_internal_transaction.ex b/apps/explorer/lib/explorer/workers/import_internal_transaction.ex deleted file mode 100644 index 7f07198645..0000000000 --- a/apps/explorer/lib/explorer/workers/import_internal_transaction.ex +++ /dev/null @@ -1,12 +0,0 @@ -defmodule Explorer.Workers.ImportInternalTransaction do - @moduledoc "Imports internal transactions via Parity trace endpoints." - - alias Explorer.InternalTransactionImporter - - @dialyzer {:nowarn_function, perform: 1} - def perform(hash), do: InternalTransactionImporter.import(hash) - - def perform_later(hash) do - Exq.enqueue(Exq.Enqueuer, "internal_transactions", __MODULE__, [hash]) - end -end diff --git a/apps/explorer/lib/explorer/workers/import_receipt.ex b/apps/explorer/lib/explorer/workers/import_receipt.ex deleted file mode 100644 index 3378391b96..0000000000 --- a/apps/explorer/lib/explorer/workers/import_receipt.ex +++ /dev/null @@ -1,12 +0,0 @@ -defmodule Explorer.Workers.ImportReceipt do - @moduledoc "Imports transaction by web3 conventions." - - alias Explorer.ReceiptImporter - - @dialyzer {:nowarn_function, perform: 1} - def perform(hash), do: ReceiptImporter.import(hash) - - def perform_later(hash) do - Exq.enqueue(Exq.Enqueuer, "receipts", __MODULE__, [hash]) - end -end diff --git a/apps/explorer/lib/explorer/workers/import_skipped_blocks.ex b/apps/explorer/lib/explorer/workers/import_skipped_blocks.ex deleted file mode 100644 index 3eec11f4f9..0000000000 --- a/apps/explorer/lib/explorer/workers/import_skipped_blocks.ex +++ /dev/null @@ -1,18 +0,0 @@ -defmodule Explorer.Workers.ImportSkippedBlocks do - alias Explorer.SkippedBlocks - alias Explorer.Workers.ImportBlock - - @moduledoc "Imports skipped blocks." - - def perform, do: perform(1) - - def perform(count) do - count |> SkippedBlocks.first() |> Enum.map(&ImportBlock.perform_later/1) - end - - def perform_later, do: perform_later(1) - - def perform_later(count) do - Exq.enqueue(Exq.Enqueuer, "default", __MODULE__, [count]) - end -end diff --git a/apps/explorer/lib/explorer/workers/import_transaction.ex b/apps/explorer/lib/explorer/workers/import_transaction.ex deleted file mode 100644 index 8dcd5d33f7..0000000000 --- a/apps/explorer/lib/explorer/workers/import_transaction.ex +++ /dev/null @@ -1,26 +0,0 @@ -defmodule Explorer.Workers.ImportTransaction do - @moduledoc """ - Manages the lifecycle of importing a single Transaction from web3. - """ - - alias Explorer.TransactionImporter - alias Explorer.Workers.{ImportInternalTransaction, ImportReceipt} - - @dialyzer {:nowarn_function, perform: 1} - def perform(hash) when is_binary(hash) do - TransactionImporter.import(hash) - ImportInternalTransaction.perform_later(hash) - ImportReceipt.perform_later(hash) - end - - @dialyzer {:nowarn_function, perform: 1} - def perform(raw_transaction) when is_map(raw_transaction) do - TransactionImporter.import(raw_transaction) - ImportInternalTransaction.perform_later(raw_transaction["hash"]) - ImportReceipt.perform_later(raw_transaction["hash"]) - end - - def perform_later(hash) do - Exq.enqueue(Exq.Enqueuer, "transactions", __MODULE__, [hash]) - end -end diff --git a/apps/explorer/lib/explorer/workers/refresh_balance.ex b/apps/explorer/lib/explorer/workers/refresh_balance.ex deleted file mode 100644 index cadf0e1931..0000000000 --- a/apps/explorer/lib/explorer/workers/refresh_balance.ex +++ /dev/null @@ -1,29 +0,0 @@ -defmodule Explorer.Workers.RefreshBalance do - @moduledoc """ - Refreshes the Credit and Debit balance views. - """ - - alias Ecto.Adapters.SQL - alias Explorer.Chain.{Credit, Debit} - alias Explorer.Repo - - def perform("credit"), do: unless(refreshing("credits"), do: Credit.refresh()) - def perform("debit"), do: unless(refreshing("debits"), do: Debit.refresh()) - - def perform do - perform_later(["credit"]) - perform_later(["debit"]) - end - - def perform_later(args \\ []) do - Exq.enqueue(Exq.Enqueuer, "default", __MODULE__, args) - end - - def refreshing(table) do - query = "REFRESH MATERIALIZED VIEW CONCURRENTLY #{table}%" - - result = SQL.query!(Repo, "SELECT TRUE FROM pg_stat_activity WHERE query ILIKE '$#{query}'", []) - - Enum.count(result.rows) > 0 - end -end diff --git a/apps/explorer/lib/mix/tasks/exq.start.ex b/apps/explorer/lib/mix/tasks/exq.start.ex deleted file mode 100644 index ff792ab3a5..0000000000 --- a/apps/explorer/lib/mix/tasks/exq.start.ex +++ /dev/null @@ -1,25 +0,0 @@ -defmodule Mix.Tasks.Exq.Start do - @moduledoc "Starts the Exq worker" - use Mix.Task - - alias Explorer.{Repo, Scheduler} - - def run(["scheduler"]) do - [:postgrex, :ecto, :ethereumex, :tzdata] - |> Enum.each(&Application.ensure_all_started/1) - - Repo.start_link() - Exq.start_link(mode: :enqueuer) - Scheduler.start_link() - :timer.sleep(:infinity) - end - - def run(_) do - [:postgrex, :ecto, :ethereumex, :tzdata] - |> Enum.each(&Application.ensure_all_started/1) - - Repo.start_link() - Exq.start_link(mode: :default) - :timer.sleep(:infinity) - end -end diff --git a/apps/explorer/lib/mix/tasks/scrape.balances.ex b/apps/explorer/lib/mix/tasks/scrape.balances.ex deleted file mode 100644 index 7028073656..0000000000 --- a/apps/explorer/lib/mix/tasks/scrape.balances.ex +++ /dev/null @@ -1,24 +0,0 @@ -defmodule Mix.Tasks.Scrape.Balances do - @moduledoc "Populate Address balances." - - use Mix.Task - - alias Explorer.{BalanceImporter, Repo, SkippedBalances} - - def run([]), do: run(1) - - def run(count) do - [:postgrex, :ecto, :ethereumex, :tzdata] - |> Enum.each(&Application.ensure_all_started/1) - - Repo.start_link() - Exq.start_link(mode: :enqueuer) - - "#{count}" - |> String.to_integer() - |> SkippedBalances.fetch() - |> Flow.from_enumerable() - |> Flow.map(&BalanceImporter.import/1) - |> Enum.to_list() - end -end diff --git a/apps/explorer/lib/mix/tasks/scrape.blocks.ex b/apps/explorer/lib/mix/tasks/scrape.blocks.ex deleted file mode 100644 index 6ed93b727c..0000000000 --- a/apps/explorer/lib/mix/tasks/scrape.blocks.ex +++ /dev/null @@ -1,26 +0,0 @@ -defmodule Mix.Tasks.Scrape.Blocks do - @moduledoc "Scrapes blocks from web3" - - use Mix.Task - - alias Explorer.{BlockImporter, Repo, SkippedBlocks} - - def run([]), do: run(1) - - def run(count) do - [:postgrex, :ecto, :ethereumex, :tzdata] - |> Enum.each(&Application.ensure_all_started/1) - - Repo.start_link() - Exq.start_link(mode: :enqueuer) - - "#{count}" - |> String.to_integer() - |> SkippedBlocks.first() - |> Enum.shuffle() - |> Flow.from_enumerable() - |> Flow.map(&BlockImporter.download_block/1) - |> Flow.map(&BlockImporter.import/1) - |> Enum.to_list() - end -end diff --git a/apps/explorer/lib/mix/tasks/scrape.internal_transactions.ex b/apps/explorer/lib/mix/tasks/scrape.internal_transactions.ex deleted file mode 100644 index 151a75f402..0000000000 --- a/apps/explorer/lib/mix/tasks/scrape.internal_transactions.ex +++ /dev/null @@ -1,24 +0,0 @@ -defmodule Mix.Tasks.Scrape.InternalTransactions do - @moduledoc "Backfill Internal Transactions via Parity Trace." - - use Mix.Task - - alias Explorer.{InternalTransactionImporter, Repo, SkippedInternalTransactions} - - def run([]), do: run(1) - - def run(count) do - [:postgrex, :ecto, :ethereumex, :tzdata] - |> Enum.each(&Application.ensure_all_started/1) - - Repo.start_link() - - "#{count}" - |> String.to_integer() - |> SkippedInternalTransactions.first() - |> Enum.shuffle() - |> Flow.from_enumerable() - |> Flow.map(&InternalTransactionImporter.import/1) - |> Enum.to_list() - end -end diff --git a/apps/explorer/lib/mix/tasks/scrape.receipts.ex b/apps/explorer/lib/mix/tasks/scrape.receipts.ex deleted file mode 100644 index f56c9861a3..0000000000 --- a/apps/explorer/lib/mix/tasks/scrape.receipts.ex +++ /dev/null @@ -1,24 +0,0 @@ -defmodule Mix.Tasks.Scrape.Receipts do - @moduledoc "Scrapes blocks from web3" - - use Mix.Task - - alias Explorer.{ReceiptImporter, Repo, SkippedReceipts} - - def run([]), do: run(1) - - def run(count) do - [:postgrex, :ecto, :ethereumex, :tzdata] - |> Enum.each(&Application.ensure_all_started/1) - - Repo.start_link() - - "#{count}" - |> String.to_integer() - |> SkippedReceipts.first() - |> Enum.shuffle() - |> Flow.from_enumerable() - |> Flow.map(&ReceiptImporter.import/1) - |> Enum.to_list() - end -end diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index ce64454425..dcaca72e58 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -46,9 +46,8 @@ defmodule Explorer.Mixfile do defp elixirc_paths, do: ["lib"] # Specifies extra applications to start per environment - defp extra_applications(:prod), do: [:phoenix_pubsub_redis, :exq, :exq_ui | extra_applications()] + defp extra_applications(:prod), do: [:phoenix_pubsub_redis | extra_applications()] - defp extra_applications(:dev), do: [:exq, :exq_ui | extra_applications()] defp extra_applications(_), do: extra_applications() defp extra_applications, @@ -57,7 +56,6 @@ defmodule Explorer.Mixfile do :ethereumex, :logger, :mix, - :new_relixir, :runtime_tools, :scrivener_ecto, :timex, @@ -77,19 +75,15 @@ defmodule Explorer.Mixfile do {:ex_machina, "~> 2.1", only: [:test]}, # Code coverage {:excoveralls, "~> 0.8.1", only: [:test]}, - {:exq, "~> 0.9.1"}, - {:exq_ui, "~> 0.9.0"}, {:exvcr, "~> 0.10", only: :test}, - {:flow, "~> 0.12"}, {:httpoison, "~> 1.0", override: true}, + {:jason, "~> 1.0"}, {:jiffy, "~> 0.15.1"}, {:junit_formatter, ">= 0.0.0", only: [:test], runtime: false}, {:math, "~> 0.3.0"}, {:mock, "~> 0.3.0", only: [:test], runtime: false}, {:mox, "~> 0.3.2", only: [:test]}, - {:new_relixir, "~> 0.4"}, {:postgrex, ">= 0.0.0"}, - {:quantum, "~> 2.2.1"}, {:scrivener_ecto, "~> 1.0"}, {:scrivener_html, "~> 1.7"}, {:sobelow, ">= 0.0.0", only: [:dev, :test], runtime: false}, diff --git a/apps/explorer/priv/repo/migrations/20180130001125_create_address.exs b/apps/explorer/priv/repo/migrations/20180117221921_create_address.exs similarity index 60% rename from apps/explorer/priv/repo/migrations/20180130001125_create_address.exs rename to apps/explorer/priv/repo/migrations/20180117221921_create_address.exs index bd67e01275..ec1a86392a 100644 --- a/apps/explorer/priv/repo/migrations/20180130001125_create_address.exs +++ b/apps/explorer/priv/repo/migrations/20180117221921_create_address.exs @@ -3,10 +3,13 @@ defmodule Explorer.Repo.Migrations.CreateAddress do def change do create table(:addresses) do + add :balance, :numeric, precision: 100 + add :balance_updated_at, :utc_datetime add :hash, :string, null: false + timestamps null: false end - create unique_index(:addresses, ["(lower(hash))"], name: :addresses_hash_index) + create unique_index(:addresses, [:hash]) end end diff --git a/apps/explorer/priv/repo/migrations/20180117221921_create_blocks.exs b/apps/explorer/priv/repo/migrations/20180117221922_create_blocks.exs similarity index 83% rename from apps/explorer/priv/repo/migrations/20180117221921_create_blocks.exs rename to apps/explorer/priv/repo/migrations/20180117221922_create_blocks.exs index ceb6ad96e1..16344a40a5 100644 --- a/apps/explorer/priv/repo/migrations/20180117221921_create_blocks.exs +++ b/apps/explorer/priv/repo/migrations/20180117221922_create_blocks.exs @@ -3,21 +3,23 @@ defmodule Explorer.Repo.Migrations.CreateBlocks do def change do create table(:blocks) do - add :number, :bigint, null: false - add :hash, :string, null: false - add :parent_hash, :string, null: false - add :nonce, :string, null: false - add :miner, :string, null: false add :difficulty, :numeric, precision: 50 - add :total_difficulty, :numeric, precision: 50 - add :size, :integer, null: false add :gas_limit, :integer, null: false add :gas_used, :integer, null: false + add :hash, :string, null: false + add :miner, :string, null: false + add :nonce, :string, null: false + add :number, :bigint, null: false + add :parent_hash, :string, null: false + add :size, :integer, null: false add :timestamp, :utc_datetime, null: false + add :total_difficulty, :numeric, precision: 50 + timestamps null: false end - create unique_index(:blocks, ["(lower(hash))"], name: :blocks_hash_index) - create index(:blocks, [:number]) + create index(:blocks, [:timestamp]) + create unique_index(:blocks, [:hash]) + create unique_index(:blocks, [:number]) end end diff --git a/apps/explorer/priv/repo/migrations/20180117221922_create_transactions.exs b/apps/explorer/priv/repo/migrations/20180117221922_create_transactions.exs deleted file mode 100644 index b269160825..0000000000 --- a/apps/explorer/priv/repo/migrations/20180117221922_create_transactions.exs +++ /dev/null @@ -1,14 +0,0 @@ -defmodule Explorer.Repo.Migrations.CreateTransactions do - use Ecto.Migration - - def change do - create table(:transactions) do - add :hash, :string, null: false - add :block_id, references(:blocks), null: false - timestamps null: false - end - - create unique_index(:transactions, ["(lower(hash))"], name: :transactions_hash_index) - create index(:transactions, [:block_id]) - end -end diff --git a/apps/explorer/priv/repo/migrations/20180117221923_create_transactions.exs b/apps/explorer/priv/repo/migrations/20180117221923_create_transactions.exs new file mode 100644 index 0000000000..7ff1a27cf5 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20180117221923_create_transactions.exs @@ -0,0 +1,37 @@ +defmodule Explorer.Repo.Migrations.CreateTransactions do + use Ecto.Migration + + def change do + create table(:transactions) do + # Fields + add :gas, :numeric, precision: 100, null: false + add :gas_price, :numeric, precision: 100, null: false + add :hash, :string, null: false + add :input, :text, null: false + add :nonce, :integer, null: false + add :public_key, :string, null: false + add :r, :string, null: false + add :s, :string, null: false + add :standard_v, :string, null: false + add :transaction_index, :string, null: false + add :v, :string, null: false + add :value, :numeric, precision: 100, null: false + + timestamps null: false + + # Foreign Keys + + # null when a pending transaction + add :block_id, references(:blocks, on_delete: :delete_all), null: true + add :from_address_id, references(:addresses, on_delete: :delete_all) + add :to_address_id, references(:addresses, on_delete: :delete_all) + end + + create index(:transactions, :block_id) + create index(:transactions, :from_address_id) + create unique_index(:transactions, [:hash]) + create index(:transactions, :inserted_at) + create index(:transactions, :to_address_id) + create index(:transactions, :updated_at) + end +end diff --git a/apps/explorer/priv/repo/migrations/20180124003303_add_value_to_transactions.exs b/apps/explorer/priv/repo/migrations/20180124003303_add_value_to_transactions.exs deleted file mode 100644 index 5f8c7af6b4..0000000000 --- a/apps/explorer/priv/repo/migrations/20180124003303_add_value_to_transactions.exs +++ /dev/null @@ -1,9 +0,0 @@ -defmodule Explorer.Repo.Migrations.AddValueToTransactions do - use Ecto.Migration - - def change do - alter table(:transactions) do - add :value, :numeric, precision: 100, null: false - end - end -end diff --git a/apps/explorer/priv/repo/migrations/20180129201141_add_fields_to_transactions.exs b/apps/explorer/priv/repo/migrations/20180129201141_add_fields_to_transactions.exs deleted file mode 100644 index 55e8dcdc69..0000000000 --- a/apps/explorer/priv/repo/migrations/20180129201141_add_fields_to_transactions.exs +++ /dev/null @@ -1,18 +0,0 @@ -defmodule Explorer.Repo.Migrations.AddFieldsToTransactions do - use Ecto.Migration - - def change do - alter table(:transactions) do - add :gas, :numeric, precision: 100, null: false - add :gas_price, :numeric, precision: 100, null: false - add :input, :text, null: false - add :nonce, :integer, null: false - add :public_key, :string, null: false - add :r, :string, null: false - add :s, :string, null: false - add :standard_v, :string, null: false - add :transaction_index, :string, null: false - add :v, :string, null: false - end - end -end diff --git a/apps/explorer/priv/repo/migrations/20180130004126_create_from_addresses.exs b/apps/explorer/priv/repo/migrations/20180130004126_create_from_addresses.exs deleted file mode 100644 index edfc6246eb..0000000000 --- a/apps/explorer/priv/repo/migrations/20180130004126_create_from_addresses.exs +++ /dev/null @@ -1,14 +0,0 @@ -defmodule Explorer.Repo.Migrations.CreateFromAddresses do - use Ecto.Migration - - def change do - create table(:from_addresses, primary_key: false) do - add :transaction_id, references(:transactions), null: false, primary_key: true - add :address_id, references(:addresses), null: false - timestamps null: false - end - - create index(:from_addresses, :transaction_id, unique: true) - create index(:from_addresses, :address_id) - end -end diff --git a/apps/explorer/priv/repo/migrations/20180130004544_create_to_addresses.exs b/apps/explorer/priv/repo/migrations/20180130004544_create_to_addresses.exs deleted file mode 100644 index 0d40ccdd76..0000000000 --- a/apps/explorer/priv/repo/migrations/20180130004544_create_to_addresses.exs +++ /dev/null @@ -1,14 +0,0 @@ -defmodule Explorer.Repo.Migrations.CreateToAddresses do - use Ecto.Migration - - def change do - create table(:to_addresses, primary_key: false) do - add :transaction_id, references(:transactions), null: false, primary_key: true - add :address_id, references(:addresses), null: false - timestamps null: false - end - - create index(:to_addresses, :transaction_id, unique: true) - create index(:to_addresses, :address_id) - end -end diff --git a/apps/explorer/priv/repo/migrations/20180202195342_create_block_transactions.exs b/apps/explorer/priv/repo/migrations/20180202195342_create_block_transactions.exs deleted file mode 100644 index e95111f038..0000000000 --- a/apps/explorer/priv/repo/migrations/20180202195342_create_block_transactions.exs +++ /dev/null @@ -1,14 +0,0 @@ -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/apps/explorer/priv/repo/migrations/20180202215933_remove_block_id_from_transactions.exs b/apps/explorer/priv/repo/migrations/20180202215933_remove_block_id_from_transactions.exs deleted file mode 100644 index aab2d83479..0000000000 --- a/apps/explorer/priv/repo/migrations/20180202215933_remove_block_id_from_transactions.exs +++ /dev/null @@ -1,9 +0,0 @@ -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/apps/explorer/priv/repo/migrations/20180208010839_add_indices_to_block_and_block_transaction.exs b/apps/explorer/priv/repo/migrations/20180208010839_add_indices_to_block_and_block_transaction.exs deleted file mode 100644 index 25637120e8..0000000000 --- a/apps/explorer/priv/repo/migrations/20180208010839_add_indices_to_block_and_block_transaction.exs +++ /dev/null @@ -1,8 +0,0 @@ -defmodule Explorer.Repo.Migrations.AddIndicesToBlockAndBlockTransaction do - use Ecto.Migration - - def change do - create index(:block_transactions, :block_id) - create index(:blocks, :timestamp) - end -end diff --git a/apps/explorer/priv/repo/migrations/20180208054620_add_transactions_index_to_timestamps.exs b/apps/explorer/priv/repo/migrations/20180208054620_add_transactions_index_to_timestamps.exs deleted file mode 100644 index 928aa5657c..0000000000 --- a/apps/explorer/priv/repo/migrations/20180208054620_add_transactions_index_to_timestamps.exs +++ /dev/null @@ -1,8 +0,0 @@ -defmodule Explorer.Repo.Migrations.AddTransactionsIndexToTimestamps do - use Ecto.Migration - - def change do - create index(:transactions, :inserted_at) - create index(:transactions, :updated_at) - end -end diff --git a/apps/explorer/priv/repo/migrations/20180212214442_create_receipts.exs b/apps/explorer/priv/repo/migrations/20180212214442_create_receipts.exs index aa50e59b61..c72436f342 100644 --- a/apps/explorer/priv/repo/migrations/20180212214442_create_receipts.exs +++ b/apps/explorer/priv/repo/migrations/20180212214442_create_receipts.exs @@ -3,12 +3,17 @@ defmodule Explorer.Repo.Migrations.CreateReceipts do def change do create table(:receipts) do - add :transaction_id, references(:transactions), null: false add :cumulative_gas_used, :numeric, precision: 100, null: false add :gas_used, :numeric, precision: 100, null: false - add :status, :integer, null: false add :index, :integer, null: false + add :status, :integer, null: false + timestamps null: false + + # Foreign keys + + add :receipt_id, references(:receipts, on_delete: :delete_all), null: true + add :transaction_id, references(:transactions, on_delete: :delete_all), null: false end create index(:receipts, :index) diff --git a/apps/explorer/priv/repo/migrations/20180212222309_create_logs.exs b/apps/explorer/priv/repo/migrations/20180212222309_create_logs.exs index 29ef9b33de..1aa014f577 100644 --- a/apps/explorer/priv/repo/migrations/20180212222309_create_logs.exs +++ b/apps/explorer/priv/repo/migrations/20180212222309_create_logs.exs @@ -3,25 +3,30 @@ defmodule Explorer.Repo.Migrations.CreateLogs do def change do create table(:logs) do - add :receipt_id, references(:receipts), null: false - add :address_id, references(:addresses), null: false - add :index, :integer, null: false add :data, :text, null: false + add :index, :integer, null: false add :type, :string, null: false add :first_topic, :string, null: true add :second_topic, :string, null: true add :third_topic, :string, null: true add :fourth_topic, :string, null: true + timestamps null: false + + # Foreign Keys + + # TODO used in views, but not in indexer + add :address_id, references(:addresses, on_delete: :delete_all), null: true + add :receipt_id, references(:receipts, on_delete: :delete_all), null: false end + create index(:logs, :address_id) create index(:logs, :index) create index(:logs, :type) create index(:logs, :first_topic) create index(:logs, :second_topic) create index(:logs, :third_topic) create index(:logs, :fourth_topic) - create index(:logs, :address_id) create unique_index(:logs, [:receipt_id, :index]) end end diff --git a/apps/explorer/priv/repo/migrations/20180216011950_create_balances_views.exs b/apps/explorer/priv/repo/migrations/20180216011950_create_balances_views.exs deleted file mode 100644 index 9559ebdab8..0000000000 --- a/apps/explorer/priv/repo/migrations/20180216011950_create_balances_views.exs +++ /dev/null @@ -1,48 +0,0 @@ -defmodule Explorer.Repo.Migrations.CreateBalancesViews do - use Ecto.Migration - - def up do - execute """ - CREATE MATERIALIZED VIEW credits AS - SELECT addresses.id AS address_id, - COALESCE(SUM(transactions.value), 0) AS value, - COUNT(to_addresses.address_id) AS count, - COALESCE(MIN(transactions.inserted_at), NOW()) AS inserted_at, - COALESCE(MAX(transactions.inserted_at), NOW()) AS updated_at - FROM addresses - INNER JOIN to_addresses ON to_addresses.address_id = addresses.id - INNER JOIN transactions ON transactions.id = to_addresses.transaction_id - INNER JOIN receipts ON receipts.transaction_id = transactions.id AND receipts.status = 1 - GROUP BY addresses.id - ; - """ - - execute """ - CREATE MATERIALIZED VIEW debits AS - SELECT addresses.id AS address_id, - COALESCE(SUM(transactions.value), 0) AS value, - COUNT(from_addresses.address_id) AS count, - COALESCE(MIN(transactions.inserted_at), NOW()) AS inserted_at, - COALESCE(MAX(transactions.inserted_at), NOW()) AS updated_at - FROM addresses - INNER JOIN from_addresses ON from_addresses.address_id = addresses.id - INNER JOIN transactions ON transactions.id = from_addresses.transaction_id - INNER JOIN receipts ON receipts.transaction_id = transactions.id AND receipts.status = 1 - GROUP BY addresses.id - ; - """ - - create unique_index(:credits, :address_id) - create index(:credits, :inserted_at) - create index(:credits, :updated_at) - - create unique_index(:debits, :address_id) - create index(:debits, :inserted_at) - create index(:debits, :updated_at) - end - - def down do - execute "DROP MATERIALIZED VIEW credits;" - execute "DROP MATERIALIZED VIEW debits;" - end -end diff --git a/apps/explorer/priv/repo/migrations/20180221001948_create_internal_transactions.exs b/apps/explorer/priv/repo/migrations/20180221001948_create_internal_transactions.exs index bb26858142..4abb6c6077 100644 --- a/apps/explorer/priv/repo/migrations/20180221001948_create_internal_transactions.exs +++ b/apps/explorer/priv/repo/migrations/20180221001948_create_internal_transactions.exs @@ -3,22 +3,30 @@ defmodule Explorer.Repo.Migrations.CreateInternalTransactions do 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 :from_address_hash, :string add :gas, :numeric, precision: 100, null: false add :gas_used, :numeric, precision: 100, null: false - add :input, :string - add :output, :string + add :index, :integer, null: false + add :input, :text + add :output, :text + add :to_address_hash, :string + add :trace_address, {:array, :integer}, null: false + add :value, :numeric, precision: 100, null: false + timestamps null: false + + # Foreign keys + + # TODO used in views, but not in indexer + add :from_address_id, references(:addresses, on_delete: :delete_all), null: true + # TODO used in views, but not in indexer + add :to_address_id, references(:addresses, on_delete: :delete_all), null: true + add :transaction_id, references(:transactions, on_delete: :delete_all), null: false end create index(:internal_transactions, :transaction_id) - create index(:internal_transactions, :to_address_id) - create index(:internal_transactions, :from_address_id) + create index(:internal_transactions, :to_address_hash) + create index(:internal_transactions, :from_address_hash) end end diff --git a/apps/explorer/priv/repo/migrations/20180223220816_move_address_keys_to_transactions.exs b/apps/explorer/priv/repo/migrations/20180223220816_move_address_keys_to_transactions.exs deleted file mode 100644 index 4626f96997..0000000000 --- a/apps/explorer/priv/repo/migrations/20180223220816_move_address_keys_to_transactions.exs +++ /dev/null @@ -1,12 +0,0 @@ -defmodule Explorer.Repo.Migrations.MoveAddressKeysToTransactions do - use Ecto.Migration - - def change do - alter table(:transactions) do - add :to_address_id, references(:addresses) - add :from_address_id, references(:addresses) - end - end - - -end diff --git a/apps/explorer/priv/repo/migrations/20180223223257_index_transaction_address_ids.exs b/apps/explorer/priv/repo/migrations/20180223223257_index_transaction_address_ids.exs deleted file mode 100644 index 28e82c6aac..0000000000 --- a/apps/explorer/priv/repo/migrations/20180223223257_index_transaction_address_ids.exs +++ /dev/null @@ -1,8 +0,0 @@ -defmodule Explorer.Repo.Migrations.IndexTransactionAddressIds do - use Ecto.Migration - - def change do - create index(:transactions, :to_address_id) - create index(:transactions, :from_address_id) - end -end diff --git a/apps/explorer/priv/repo/migrations/20180224004300_update_credit_debit_materialized_view.exs b/apps/explorer/priv/repo/migrations/20180224004300_create_credit_debit_materialized_view.exs similarity index 100% rename from apps/explorer/priv/repo/migrations/20180224004300_update_credit_debit_materialized_view.exs rename to apps/explorer/priv/repo/migrations/20180224004300_create_credit_debit_materialized_view.exs diff --git a/apps/explorer/priv/repo/migrations/20180227004146_dedup_internal_transactions_and_add_unique_index.exs b/apps/explorer/priv/repo/migrations/20180227004146_dedup_internal_transactions_and_add_unique_index.exs deleted file mode 100644 index aa61b7357f..0000000000 --- a/apps/explorer/priv/repo/migrations/20180227004146_dedup_internal_transactions_and_add_unique_index.exs +++ /dev/null @@ -1,38 +0,0 @@ -defmodule Explorer.Repo.Migrations.DedupInternalTransactions do - use Ecto.Migration - - def up do - execute "SELECT DISTINCT ON (transaction_id, index) * INTO internal_transactions_dedup FROM internal_transactions;" - execute "DROP TABLE internal_transactions;" - execute "ALTER TABLE internal_transactions_dedup RENAME TO internal_transactions;" - execute "CREATE SEQUENCE internal_transactions_id_seq OWNED BY internal_transactions.id;" - execute """ - ALTER TABLE internal_transactions - ALTER COLUMN id SET DEFAULT nextval('internal_transactions_id_seq'), - ALTER COLUMN id SET NOT NULL, - ALTER COLUMN transaction_id SET NOT NULL, - ALTER COLUMN to_address_id SET NOT NULL, - ALTER COLUMN from_address_id SET NOT NULL, - ALTER COLUMN index SET NOT NULL, - ALTER COLUMN call_type SET NOT NULL, - ALTER COLUMN trace_address SET NOT NULL, - ALTER COLUMN value SET NOT NULL, - ALTER COLUMN gas SET NOT NULL, - ALTER COLUMN gas_used SET NOT NULL, - ALTER COLUMN inserted_at SET NOT NULL, - ALTER COLUMN updated_at SET NOT NULL, - ADD FOREIGN KEY (from_address_id) REFERENCES addresses(id), - ADD FOREIGN KEY (to_address_id) REFERENCES addresses(id), - ADD FOREIGN KEY (transaction_id) REFERENCES transactions(id); - """ - execute "ALTER TABLE internal_transactions ADD PRIMARY KEY (id);" - execute "CREATE INDEX internal_transactions_from_address_id_index ON internal_transactions (from_address_id);" - execute "CREATE INDEX internal_transactions_to_address_id_index ON internal_transactions (to_address_id);" - execute "CREATE INDEX internal_transactions_transaction_id_index ON internal_transactions (transaction_id);" - execute "CREATE UNIQUE INDEX internal_transactions_transaction_id_index_index ON internal_transactions (transaction_id, index);" - end - - def down do - execute "DROP INDEX internal_transactions_transaction_id_index_index" - end -end diff --git a/apps/explorer/priv/repo/migrations/20180227225553_add_balance_and_balance_updated_at_to_address.exs b/apps/explorer/priv/repo/migrations/20180227225553_add_balance_and_balance_updated_at_to_address.exs deleted file mode 100644 index 911ea44dbc..0000000000 --- a/apps/explorer/priv/repo/migrations/20180227225553_add_balance_and_balance_updated_at_to_address.exs +++ /dev/null @@ -1,10 +0,0 @@ -defmodule Explorer.Repo.Migrations.AddBalanceAndBalanceUpdatedAtToAddress do - use Ecto.Migration - - def change do - alter table(:addresses) do - add :balance, :numeric, precision: 100 - add :balance_updated_at, :utc_datetime - end - end -end diff --git a/apps/explorer/priv/repo/migrations/20180301013446_add_receipt_id_to_transactions.exs b/apps/explorer/priv/repo/migrations/20180301013446_add_receipt_id_to_transactions.exs deleted file mode 100644 index 8130cf75fc..0000000000 --- a/apps/explorer/priv/repo/migrations/20180301013446_add_receipt_id_to_transactions.exs +++ /dev/null @@ -1,9 +0,0 @@ -defmodule Explorer.Repo.Migrations.AddReceiptIdToTransactions do - use Ecto.Migration - - def change do - alter table("transactions") do - add :receipt_id, :bigint - end - end -end diff --git a/apps/explorer/test/explorer/chain/block_transaction_test.exs b/apps/explorer/test/explorer/chain/block_transaction_test.exs deleted file mode 100644 index 32aab6e388..0000000000 --- a/apps/explorer/test/explorer/chain/block_transaction_test.exs +++ /dev/null @@ -1,18 +0,0 @@ -defmodule Explorer.Chain.BlockTransactionTest do - use Explorer.DataCase - - alias Explorer.Chain.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/apps/explorer/test/explorer/chain/from_address_test.exs b/apps/explorer/test/explorer/chain/from_address_test.exs deleted file mode 100644 index 1cf4c5cc06..0000000000 --- a/apps/explorer/test/explorer/chain/from_address_test.exs +++ /dev/null @@ -1,13 +0,0 @@ -defmodule Explorer.Chain.FromAddressTest do - use Explorer.DataCase - - alias Explorer.Chain.FromAddress - - describe "changeset/2" do - test "with valid attributes" do - params = params_for(:from_address) - changeset = FromAddress.changeset(%FromAddress{}, params) - assert changeset.valid? - end - end -end diff --git a/apps/explorer/test/explorer/chain/statistics_test.exs b/apps/explorer/test/explorer/chain/statistics_test.exs index f0171f5072..a4f96e9a61 100644 --- a/apps/explorer/test/explorer/chain/statistics_test.exs +++ b/apps/explorer/test/explorer/chain/statistics_test.exs @@ -47,10 +47,8 @@ defmodule Explorer.Chain.StatisticsTest do last_week = Timex.shift(time, days: -8) block = insert(:block, timestamp: time) old_block = insert(:block, timestamp: last_week) - transaction = insert(:transaction) - old_transaction = insert(:transaction) - insert(:block_transaction, block: block, transaction: transaction) - insert(:block_transaction, block: old_block, transaction: old_transaction) + insert(:transaction, block_id: block.id) + insert(:transaction, block_id: old_block.id) assert %Statistics{transaction_count: 1} = Statistics.fetch() end @@ -100,12 +98,7 @@ defmodule Explorer.Chain.StatisticsTest do test "returns the last five transactions with blocks" do block = insert(:block) - - 6 - |> insert_list(:transaction) - |> Enum.map(fn transaction -> - insert(:block_transaction, block: block, transaction: transaction) - end) + insert_list(6, :transaction, block_id: block.id) statistics = Statistics.fetch() diff --git a/apps/explorer/test/explorer/chain/to_address_test.exs b/apps/explorer/test/explorer/chain/to_address_test.exs deleted file mode 100644 index 78a3fa59f3..0000000000 --- a/apps/explorer/test/explorer/chain/to_address_test.exs +++ /dev/null @@ -1,13 +0,0 @@ -defmodule Explorer.Chain.ToAddressTest do - use Explorer.DataCase - - alias Explorer.Chain.ToAddress - - describe "changeset/2" do - test "with valid attributes" do - params = params_for(:to_address) - changeset = ToAddress.changeset(%ToAddress{}, params) - assert changeset.valid? - end - end -end diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index c9b8b583d7..48d6797e3f 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -26,9 +26,8 @@ defmodule Explorer.ChainTest do end test "with transactions" do - block = %Block{id: block_id} = insert(:block) - %Transaction{id: transaction_id} = insert(:transaction) - insert(:block_transaction, block_id: block_id, transaction_id: transaction_id) + block = insert(:block) + %Transaction{id: transaction_id} = insert(:transaction, block_id: block.id) assert %Scrivener.Page{ entries: [%Transaction{id: ^transaction_id}], @@ -40,17 +39,10 @@ defmodule Explorer.ChainTest do test "with transaction with receipt required without receipt does not return transaction" do block = %Block{id: block_id} = insert(:block) - %Transaction{id: transaction_id_with_receipt} = insert(:transaction) + %Transaction{id: transaction_id_with_receipt} = insert(:transaction, block_id: block_id) insert(:receipt, transaction_id: transaction_id_with_receipt) - insert(:block_transaction, block_id: block_id, transaction_id: transaction_id_with_receipt) - - %Transaction{id: transaction_id_without_receipt} = insert(:transaction) - insert( - :block_transaction, - block_id: block_id, - transaction_id: transaction_id_without_receipt - ) + %Transaction{id: transaction_id_without_receipt} = insert(:transaction, block_id: block_id) assert %Scrivener.Page{ entries: [%Transaction{id: ^transaction_id_with_receipt, receipt: %Receipt{}}], @@ -84,18 +76,14 @@ defmodule Explorer.ChainTest do end test "with transactions can be paginated" do - block = %Block{id: block_id} = insert(:block) + block = insert(:block) - transactions = insert_list(2, :transaction) - - Enum.each(transactions, fn %Transaction{id: transaction_id} -> - insert(:block_transaction, block_id: block_id, transaction_id: transaction_id) - end) + transactions = insert_list(2, :transaction, block_id: block.id) [%Transaction{id: first_transaction_id}, %Transaction{id: second_transaction_id}] = transactions assert %Scrivener.Page{ - entries: [%Transaction{id: ^first_transaction_id}], + entries: [%Transaction{id: ^second_transaction_id}], page_number: 1, page_size: 1, total_entries: 2, @@ -103,7 +91,7 @@ defmodule Explorer.ChainTest do } = Chain.block_to_transactions(block, pagination: %{page_size: 1}) assert %Scrivener.Page{ - entries: [%Transaction{id: ^second_transaction_id}], + entries: [%Transaction{id: ^first_transaction_id}], page_number: 2, page_size: 1, total_entries: 2, @@ -121,8 +109,7 @@ defmodule Explorer.ChainTest do test "with transactions" do block = insert(:block) - %Transaction{id: transaction_id} = insert(:transaction) - insert(:block_transaction, block_id: block.id, transaction_id: transaction_id) + insert(:transaction, block_id: block.id) assert Chain.block_to_transaction_count(block) == 1 end diff --git a/apps/explorer/test/explorer/ethereumex_extensions_test.exs b/apps/explorer/test/explorer/ethereumex_extensions_test.exs deleted file mode 100644 index 179c684768..0000000000 --- a/apps/explorer/test/explorer/ethereumex_extensions_test.exs +++ /dev/null @@ -1,14 +0,0 @@ -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 diff --git a/apps/explorer/test/explorer/importers/balance_importer_test.exs b/apps/explorer/test/explorer/importers/balance_importer_test.exs deleted file mode 100644 index 9b2635eaa1..0000000000 --- a/apps/explorer/test/explorer/importers/balance_importer_test.exs +++ /dev/null @@ -1,40 +0,0 @@ -defmodule Explorer.BalanceImporterTest do - use Explorer.DataCase - - alias Explorer.{Chain, BalanceImporter} - alias Explorer.Chain.Address - - describe "import/1" do - test "it updates the balance for an address" do - insert(:address, hash: "0x5cc18cc34175d358ff8e19b7f98566263c4106a0", balance: 5) - - BalanceImporter.import("0x5cc18cc34175d358ff8e19b7f98566263c4106a0") - - expected_balance = Decimal.new(1_572_374_181_095_000_000) - - assert {:ok, %Address{balance: ^expected_balance}} = - Chain.hash_to_address("0x5cc18cc34175d358ff8e19b7f98566263c4106a0") - end - - test "it updates the balance update time for an address" do - insert( - :address, - hash: "0x5cc18cc34175d358ff8e19b7f98566263c4106a0", - balance_updated_at: nil - ) - - BalanceImporter.import("0x5cc18cc34175d358ff8e19b7f98566263c4106a0") - - assert {:ok, %Address{balance_updated_at: balance_updated_at}} = - Chain.hash_to_address("0x5cc18cc34175d358ff8e19b7f98566263c4106a0") - - refute is_nil(balance_updated_at) - end - - test "it creates an address if one does not exist" do - BalanceImporter.import("0x5cc18cc34175d358ff8e19b7f98566263c4106a0") - - assert {:ok, _} = Chain.hash_to_address("0x5cc18cc34175d358ff8e19b7f98566263c4106a0") - end - end -end diff --git a/apps/explorer/test/explorer/importers/block_importer_test.exs b/apps/explorer/test/explorer/importers/block_importer_test.exs deleted file mode 100644 index 3db5a80652..0000000000 --- a/apps/explorer/test/explorer/importers/block_importer_test.exs +++ /dev/null @@ -1,121 +0,0 @@ -defmodule Explorer.BlockImporterTest do - use Explorer.DataCase - - import Mock - - alias Explorer.BlockImporter - alias Explorer.Chain.{Block, Transaction} - alias Explorer.Workers.ImportTransaction - - describe "import/1" do - test "imports and saves a block to the database" do - use_cassette "block_importer_import_1_saves_the_block" do - with_mock ImportTransaction, perform: fn _ -> {:ok} end do - BlockImporter.import("0xc4f0d") - block = Block |> order_by(desc: :inserted_at) |> Repo.one() - - assert block.hash == "0x16cb43ccfb7875c14eb3f03bdc098e4af053160544270594fa429d256cbca64e" - end - end - end - - test "when a block with the same hash is imported it does not update the block" do - use_cassette "block_importer_import_1_duplicate_block" do - with_mock ImportTransaction, perform: fn hash -> insert(:transaction, hash: hash) end do - insert( - :block, - hash: "0x16cb43ccfb7875c14eb3f03bdc098e4af053160544270594fa429d256cbca64e", - gas_limit: 5 - ) - - BlockImporter.import("0xc4f0d") - - block = - Repo.get_by( - Block, - hash: "0x16cb43ccfb7875c14eb3f03bdc098e4af053160544270594fa429d256cbca64e" - ) - - assert block.gas_limit == 5 - assert Block |> Repo.all() |> Enum.count() == 1 - end - end - end - end - - describe "import/1 pending" do - test "does not create a block" do - use_cassette "block_importer_import_1_pending" do - with_mock ImportTransaction, perform_later: fn _ -> {:ok} end do - BlockImporter.import("pending") - assert Block |> Repo.all() |> Enum.count() == 0 - end - end - end - - test "when a block with the same hash is imported does not create a block" do - use_cassette "block_importer_import_1_pending" do - with_mock ImportTransaction, perform_later: fn _ -> insert(:transaction) end do - BlockImporter.import("pending") - assert Transaction |> Repo.all() |> Enum.count() != 0 - end - end - end - end - - describe "find/1" do - test "returns an empty block when there is no block with the given hash" do - assert BlockImporter.find("0xC001") == %Block{} - end - - test "returns the block with the requested hash" do - block = insert(:block, hash: "0xBEA75") - assert BlockImporter.find("0xBEA75").id == block.id - end - end - - describe "download_block/1" do - test "downloads the block" do - use_cassette "block_importer_download_block_1_downloads_the_block" do - raw_block = BlockImporter.download_block("0xc4f0d") - assert raw_block - end - end - end - - describe "extract_block/1" do - test "extracts the block attributes" do - extracted_block = - BlockImporter.extract_block(%{ - "difficulty" => "0xfffffffffffffffffffffffffffffffe", - "gasLimit" => "0x02", - "gasUsed" => "0x19522", - "hash" => "bananas", - "miner" => "0xdb1207770e0a4258d7a4ce49ab037f92564fea85", - "number" => "0x7f2fb", - "parentHash" => "0x70029f66ea5a3b2b1ede95079d95a2ab74b649b5b17cdcf6f29b6317e7c7efa6", - "size" => "0x10", - "timestamp" => "0x12", - "totalDifficulty" => "0xff", - "nonce" => "0xfb6e1a62d119228b", - "transactions" => [] - }) - - assert( - extracted_block == %{ - difficulty: 340_282_366_920_938_463_463_374_607_431_768_211_454, - gas_limit: 2, - gas_used: 103_714, - hash: "bananas", - nonce: "0xfb6e1a62d119228b", - miner: "0xdb1207770e0a4258d7a4ce49ab037f92564fea85", - number: 520_955, - parent_hash: "0x70029f66ea5a3b2b1ede95079d95a2ab74b649b5b17cdcf6f29b6317e7c7efa6", - size: 16, - timestamp: Timex.parse!("1970-01-01T00:00:18-00:00", "{ISO:Extended}"), - total_difficulty: 255 - } - ) - end - end -end diff --git a/apps/explorer/test/explorer/importers/internal_transaction_importer_test.exs b/apps/explorer/test/explorer/importers/internal_transaction_importer_test.exs deleted file mode 100644 index e755c75cac..0000000000 --- a/apps/explorer/test/explorer/importers/internal_transaction_importer_test.exs +++ /dev/null @@ -1,115 +0,0 @@ -defmodule Explorer.InternalTransactionImporterTest do - use Explorer.DataCase - - alias Explorer.Chain.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 "subsequent imports do not create duplicate internal transactions" do - use_cassette "internal_transaction_importer_import_1" do - transaction = - insert( - :transaction, - hash: "0x051e031f05b3b3a5ff73e1189c36e3e2a41fd1c2d9772b2c75349e22ed4d3f68" - ) - - InternalTransactionImporter.import(transaction.hash) - InternalTransactionImporter.import(transaction.hash) - - internal_transactions = InternalTransaction |> Repo.all() - assert length(internal_transactions) == 2 - 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: 295_455, - gas_used: 1252, - input: "0xd1f276d3", - output: "0x000000000000000000000000ba9f067abbc4315ece8eb33e7a3d01030bb368ef" - } - ) - end - end -end diff --git a/apps/explorer/test/explorer/importers/receipt_importer_test.exs b/apps/explorer/test/explorer/importers/receipt_importer_test.exs deleted file mode 100644 index 580839f256..0000000000 --- a/apps/explorer/test/explorer/importers/receipt_importer_test.exs +++ /dev/null @@ -1,119 +0,0 @@ -defmodule Explorer.ReceiptImporterTest do - use Explorer.DataCase - - alias Explorer.Chain.{Log, Receipt} - alias Explorer.ReceiptImporter - - describe "import/1" do - test "saves a receipt to the database" do - transaction = - insert( - :transaction, - hash: "0xdc3a0dfd0bbffd5eabbe40fb13afbe35ac5f5c030bff148f3e50afe32974b291" - ) - - use_cassette "transaction_importer_import_1_receipt" do - ReceiptImporter.import("0xdc3a0dfd0bbffd5eabbe40fb13afbe35ac5f5c030bff148f3e50afe32974b291") - - receipt = Receipt |> preload([:transaction]) |> Repo.one() - assert receipt.transaction == transaction - end - end - - test "saves a receipt log" do - insert( - :transaction, - hash: "0xdc3a0dfd0bbffd5eabbe40fb13afbe35ac5f5c030bff148f3e50afe32974b291" - ) - - use_cassette "transaction_importer_import_1_receipt" do - ReceiptImporter.import("0xdc3a0dfd0bbffd5eabbe40fb13afbe35ac5f5c030bff148f3e50afe32974b291") - - receipt = Receipt |> preload([:transaction]) |> Repo.one() - log = Log |> preload(receipt: :transaction) |> Repo.one() - assert log.receipt == receipt - end - end - - test "saves a receipt log for an address" do - insert( - :transaction, - hash: "0xdc3a0dfd0bbffd5eabbe40fb13afbe35ac5f5c030bff148f3e50afe32974b291" - ) - - address = insert(:address, hash: "0x353fe3ffbf77edef7f9c352c47965a38c07e837c") - - use_cassette "transaction_importer_import_1_receipt" do - ReceiptImporter.import("0xdc3a0dfd0bbffd5eabbe40fb13afbe35ac5f5c030bff148f3e50afe32974b291") - - log = Log |> preload([:address]) |> Repo.one() - assert log.address == address - end - end - - test "saves a receipt for a failed transaction" do - insert( - :transaction, - hash: "0x2532864dc2e0d0bc2dfabf4685c0c03dbdbe9cf67ebc593fc82d41087ab71435" - ) - - use_cassette "transaction_importer_import_1_failed" do - ReceiptImporter.import("0x2532864dc2e0d0bc2dfabf4685c0c03dbdbe9cf67ebc593fc82d41087ab71435") - - receipt = Repo.one(Receipt) - assert receipt.status == 0 - end - end - - test "saves a receipt for a transaction that ran out of gas" do - insert( - :transaction, - hash: "0x702e518267b0a57e4cb44b9db100afe4d7115f2d2650466a8c376f3dbb77eb35" - ) - - use_cassette "transaction_importer_import_1_out_of_gas" do - ReceiptImporter.import("0x702e518267b0a57e4cb44b9db100afe4d7115f2d2650466a8c376f3dbb77eb35") - - receipt = Repo.one(Receipt) - assert receipt.status == 0 - end - end - - test "does not import a receipt for a transaction that already has one" do - transaction = - insert( - :transaction, - hash: "0xdc3a0dfd0bbffd5eabbe40fb13afbe35ac5f5c030bff148f3e50afe32974b291" - ) - - insert(:receipt, transaction: transaction) - - use_cassette "transaction_importer_import_1_receipt" do - ReceiptImporter.import("0xdc3a0dfd0bbffd5eabbe40fb13afbe35ac5f5c030bff148f3e50afe32974b291") - - assert Repo.all(Receipt) |> Enum.count() == 1 - end - end - - test "does not import a receipt for a nonexistent transaction" do - use_cassette "transaction_importer_import_1_receipt" do - ReceiptImporter.import("0xdc3a0dfd0bbffd5eabbe40fb13afbe35ac5f5c030bff148f3e50afe32974b291") - - assert Repo.all(Receipt) |> Enum.count() == 0 - end - end - - test "does not process a forever-pending receipt" do - insert( - :transaction, - hash: "0xde791cfcde3900d4771e5fcf8c11dc305714118df7aa7e42f84576e64dbf6246" - ) - - use_cassette "transaction_importer_import_1_pending" do - ReceiptImporter.import("0xde791cfcde3900d4771e5fcf8c11dc305714118df7aa7e42f84576e64dbf6246") - - assert Repo.all(Receipt) |> Enum.count() == 0 - end - end - end -end diff --git a/apps/explorer/test/explorer/importers/transaction_importer_test.exs b/apps/explorer/test/explorer/importers/transaction_importer_test.exs deleted file mode 100644 index 9e97a1d97e..0000000000 --- a/apps/explorer/test/explorer/importers/transaction_importer_test.exs +++ /dev/null @@ -1,270 +0,0 @@ -defmodule Explorer.TransactionImporterTest do - use Explorer.DataCase - - alias Explorer.Chain.{Address, BlockTransaction, 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: 1_000_000_000_000_000_000, - gas: 135_168, - gas_price: 65536, - input: "0x5c8eff12", - nonce: 201_527, - 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 the transaction has previously been saved does not update it" 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(5) - end - end - - test "binds an association to an existing block" 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 "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") - - assert transaction.from_address_id == address.id - end - end - - test "binds an existing from address" do - insert(:address, hash: "0xa5b4b372112ab8dbbb48c8d0edd89227e24ec785") - - 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") - - assert transaction.from_address_id == address.id - end - end - - test "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") - - assert transaction.to_address_id == address.id - end - end - - test "binds an existing to address" do - insert(:address, hash: "0x24e5b8528fe83257d5fe3497ef616026713347f8") - - 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") - - assert(transaction.to_address_id == address.id) - end - end - - test "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") - - assert(transaction.to_address_id == address.id) - end - end - - test "processes a map of transaction attributes" do - insert(:block, hash: "0xtakis") - - TransactionImporter.import(Map.merge(@raw_transaction, %{"hash" => "0xmunchos", "blockHash" => "0xtakis"})) - - last_transaction = Transaction |> order_by(desc: :inserted_at) |> limit(1) |> Repo.one() - - assert last_transaction.hash == "0xmunchos" - end - - test "gets balances for addresses" do - TransactionImporter.import("0xdc533d4227734a7cacd75a069e8dc57ac571b865ed97bae5ea4cb74b54145f4c") - - from_address = Address |> Repo.get_by(hash: "0xb2867180771b196518651c174c9240d5e8bd0ecd") - to_address = Address |> Repo.get_by(hash: "0x24e5b8528fe83257d5fe3497ef616026713347f8") - - assert(from_address.balance == Decimal.new(1_572_374_181_095_000_000)) - assert(to_address.balance == Decimal.new(1_572_374_181_095_000_000)) - end - end - - describe "find/1" do - test "returns an empty transaction when there is no transaction with the given hash" do - assert TransactionImporter.find("0xC001") == %Transaction{} - end - - test "returns the transaction with the requested hash" do - transaction = insert(:transaction, hash: "0xBEA75") - assert TransactionImporter.find("0xBEA75").id == transaction.id - 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 -end diff --git a/apps/explorer/test/explorer/indexer/sequence_test.exs b/apps/explorer/test/explorer/indexer/sequence_test.exs new file mode 100644 index 0000000000..8b7ee34ee7 --- /dev/null +++ b/apps/explorer/test/explorer/indexer/sequence_test.exs @@ -0,0 +1,93 @@ +defmodule Explorer.Indexer.SequenceTest do + use ExUnit.Case + + alias Explorer.Indexer.Sequence + + test "start_link" do + {:ok, pid} = Sequence.start_link([{1, 4}], 5, 1) + + assert state(pid) == %Sequence{ + current: 5, + mode: :infinite, + queue: {[{1, 4}], []}, + step: 1 + } + end + + test "inject_range" do + {:ok, pid} = Sequence.start_link([{1, 2}], 5, 1) + + assert :ok = Sequence.inject_range(pid, {3, 4}) + + assert state(pid) == %Sequence{ + current: 5, + mode: :infinite, + queue: {[{3, 4}], [{1, 2}]}, + step: 1 + } + end + + test "cap" do + {:ok, pid} = Sequence.start_link([{1, 2}], 5, 1) + + assert :ok = Sequence.cap(pid) + assert state(pid).mode == :finite + end + + describe "pop" do + test "with a non-empty queue in finite and infinite modes" do + {:ok, pid} = Sequence.start_link([{1, 4}, {6, 9}], 99, 5) + + assert {1, 4} == Sequence.pop(pid) + + assert state(pid) == %Sequence{ + current: 99, + mode: :infinite, + queue: {[], [{6, 9}]}, + step: 5 + } + + :ok = Sequence.cap(pid) + + assert {6, 9} == Sequence.pop(pid) + + assert state(pid) == %Sequence{ + current: 99, + mode: :finite, + queue: {[], []}, + step: 5 + } + end + + test "with an empty queue in infinite mode" do + {:ok, pid} = Sequence.start_link([], 5, 5) + + assert {5, 9} == Sequence.pop(pid) + + assert state(pid) == %Sequence{ + current: 10, + mode: :infinite, + queue: {[], []}, + step: 5 + } + end + + test "with an empty queue in finite mode" do + {:ok, pid} = Sequence.start_link([], 5, 5) + :ok = Sequence.cap(pid) + + assert :halt == Sequence.pop(pid) + + assert state(pid) == %Sequence{ + current: 5, + mode: :finite, + queue: {[], []}, + step: 5 + } + end + end + + defp state(sequencer) do + Agent.get(sequencer, & &1) + end +end diff --git a/apps/explorer/test/explorer/skipped_balances_test.exs b/apps/explorer/test/explorer/skipped_balances_test.exs deleted file mode 100644 index 38913da829..0000000000 --- a/apps/explorer/test/explorer/skipped_balances_test.exs +++ /dev/null @@ -1,18 +0,0 @@ -defmodule Explorer.SkippedBalancesTest do - use Explorer.DataCase - - alias Explorer.SkippedBalances - - describe "fetch/1" do - test "returns a list of address hashes that do not have balances" do - insert(:address, hash: "0xcashews", balance: nil) - assert SkippedBalances.fetch(1) == ["0xcashews"] - end - - test "only get a limited set of addresses" do - insert_list(10, :address, balance: nil) - insert_list(5, :address, balance: 55) - assert length(SkippedBalances.fetch(7)) == 7 - end - end -end diff --git a/apps/explorer/test/explorer/skipped_blocks_test.exs b/apps/explorer/test/explorer/skipped_blocks_test.exs deleted file mode 100644 index cff9e28b97..0000000000 --- a/apps/explorer/test/explorer/skipped_blocks_test.exs +++ /dev/null @@ -1,77 +0,0 @@ -defmodule Explorer.SkippedBlocksTest do - use Explorer.DataCase - - alias Explorer.SkippedBlocks - - describe "first/0 when there are no blocks" do - test "returns no blocks" do - assert SkippedBlocks.first() == [] - end - end - - describe "first/0 when there are no skipped blocks" do - test "returns no blocks" do - insert(:block, %{number: 0}) - assert SkippedBlocks.first() == [] - end - end - - describe "first/0 when a block has been skipped" do - test "returns the first skipped block number" do - insert(:block, %{number: 0}) - insert(:block, %{number: 2}) - assert SkippedBlocks.first() == ["1"] - end - end - - describe "first/1 when there are no blocks" do - test "returns no blocks" do - assert SkippedBlocks.first(1) == [] - end - end - - describe "first/1 when there are no skipped blocks" do - test "returns no blocks" do - insert(:block, %{number: 0}) - assert SkippedBlocks.first(1) == [] - end - end - - describe "first/1 when a block has been skipped" do - test "returns the skipped block number" do - insert(:block, %{number: 1}) - assert SkippedBlocks.first(1) == ["0"] - end - - test "returns up to the requested number of skipped block numbers in reverse order" do - insert(:block, %{number: 1}) - insert(:block, %{number: 3}) - assert SkippedBlocks.first(1) == ["2"] - end - - test "returns only the skipped block number" do - insert(:block, %{number: 1}) - assert SkippedBlocks.first(100) == ["0"] - end - - test "returns all the skipped block numbers in random order" do - insert(:block, %{number: 1}) - insert(:block, %{number: 3}) - block_ids = SkippedBlocks.first(100) - assert("2" in block_ids and "0" in block_ids) - end - end - - describe "latest_block_number/0 when there are no blocks" do - test "returns -1" do - assert SkippedBlocks.latest_block_number() == -1 - end - end - - describe "latest_block_number/0 when there is a block" do - test "returns the number of the block" do - insert(:block, %{number: 1}) - assert SkippedBlocks.latest_block_number() == 1 - end - end -end diff --git a/apps/explorer/test/explorer/skipped_internal_transactions_test.exs b/apps/explorer/test/explorer/skipped_internal_transactions_test.exs deleted file mode 100644 index 14c7c56149..0000000000 --- a/apps/explorer/test/explorer/skipped_internal_transactions_test.exs +++ /dev/null @@ -1,26 +0,0 @@ -defmodule Explorer.SkippedInternalTransactionsTest do - use Explorer.DataCase - - alias Explorer.SkippedInternalTransactions - - describe "first/0 when there are no transactions" do - test "returns no transaction hashes" do - assert SkippedInternalTransactions.first() == [] - end - end - - describe "first/0 when there are transactions with internal transactions" do - test "returns no transaction hashes" do - transaction = insert(:transaction) - insert(:internal_transaction, transaction: transaction) - assert SkippedInternalTransactions.first() == [] - end - end - - describe "first/0 when there are transactions with no internal transactions" do - test "returns the transaction hash" do - insert(:transaction, hash: "0xdeadbeef") - assert SkippedInternalTransactions.first() == ["0xdeadbeef"] - end - end -end diff --git a/apps/explorer/test/explorer/skipped_transactions_test.exs b/apps/explorer/test/explorer/skipped_transactions_test.exs deleted file mode 100644 index 928bebbe7d..0000000000 --- a/apps/explorer/test/explorer/skipped_transactions_test.exs +++ /dev/null @@ -1,54 +0,0 @@ -defmodule Explorer.SkippedReceiptsTest do - use Explorer.DataCase - - alias Explorer.SkippedReceipts - - describe "first/0 when there are no transactions" do - test "returns no transactions" do - assert SkippedReceipts.first() == [] - end - end - - describe "first/0 when there are no skipped transactions" do - test "returns no transactions" do - transaction = insert(:transaction) - insert(:receipt, transaction: transaction) - assert SkippedReceipts.first() == [] - end - end - - describe "first/0 when a transaction has been skipped" do - test "returns the first skipped transaction hash" do - insert(:transaction, %{hash: "0xBEE75"}) - assert SkippedReceipts.first() == ["0xBEE75"] - end - end - - describe "first/1 when there are no transactions" do - test "returns no transactions" do - assert SkippedReceipts.first(1) == [] - end - end - - describe "first/1 when there are no skipped transactions" do - test "returns no transactions" do - transaction = insert(:transaction) - insert(:receipt, transaction: transaction) - assert SkippedReceipts.first(1) == [] - end - end - - describe "first/1 when a transaction has been skipped" do - test "returns the skipped transaction number" do - insert(:transaction, %{hash: "0xBEE75"}) - assert SkippedReceipts.first(1) == ["0xBEE75"] - end - - test "returns all the skipped transaction hashes in random order" do - insert(:transaction, %{hash: "0xBEE75"}) - insert(:transaction, %{hash: "0xBE475"}) - transaction_hashes = SkippedReceipts.first(100) - assert("0xBEE75" in transaction_hashes and "0xBE475" in transaction_hashes) - end - end -end diff --git a/apps/explorer/test/explorer/workers/import_balance_test.exs b/apps/explorer/test/explorer/workers/import_balance_test.exs deleted file mode 100644 index f7b340cdd4..0000000000 --- a/apps/explorer/test/explorer/workers/import_balance_test.exs +++ /dev/null @@ -1,39 +0,0 @@ -defmodule Explorer.Workers.ImportBalanceTest do - import Mock - - alias Explorer.Chain - alias Explorer.Chain.Address - alias Explorer.Workers.ImportBalance - - use Explorer.DataCase - - describe "perform/1" do - test "imports the balance for an address" do - ImportBalance.perform("0x1d12e5716c593b156eb7152ca4360f6224ba3b0a") - - expected_balance = Decimal.new(1_572_374_181_095_000_000) - - assert {:ok, %Address{balance: ^expected_balance}} = - Chain.hash_to_address("0x1d12e5716c593b156eb7152ca4360f6224ba3b0a") - end - end - - describe "perform_later/1" do - test "delays the import of the balance for an address" do - with_mock Exq, - enqueue: fn _, _, _, _ -> - insert( - :address, - hash: "0xskateboards", - balance: 66 - ) - end do - ImportBalance.perform_later("0xskateboards") - - expected_balance = Decimal.new(66) - - assert {:ok, %Address{balance: ^expected_balance}} = Chain.hash_to_address("0xskateboards") - end - end - end -end diff --git a/apps/explorer/test/explorer/workers/import_block_test.exs b/apps/explorer/test/explorer/workers/import_block_test.exs deleted file mode 100644 index 9a13f42b9b..0000000000 --- a/apps/explorer/test/explorer/workers/import_block_test.exs +++ /dev/null @@ -1,66 +0,0 @@ -defmodule Explorer.Workers.ImportBlockTest do - use Explorer.DataCase - - import Mock - - alias Explorer.Chain.Block - alias Explorer.Repo - alias Explorer.Workers.ImportBlock - - describe "perform/1" do - test "imports the requested block number as an integer" do - use_cassette "import_block_perform_1_integer" do - ImportBlock.perform(1) - last_block = Block |> order_by(asc: :number) |> Repo.one() - assert last_block.number == 1 - end - end - - test "imports the requested block number as a string" do - use_cassette "import_block_perform_1_string" do - ImportBlock.perform("1") - last_block = Block |> order_by(asc: :number) |> Repo.one() - assert last_block.number == 1 - end - end - - test "imports the earliest block" do - use_cassette "import_block_perform_1_earliest" do - ImportBlock.perform("earliest") - last_block = Block |> order_by(asc: :number) |> Repo.one() - assert last_block.number == 0 - end - end - - test "imports the latest block" do - use_cassette "import_block_perform_1_latest" do - with_mock Exq, enqueue: fn _, _, _, [number] -> insert(:block, number: number) end do - ImportBlock.perform("latest") - last_block = Block |> order_by(asc: :number) |> Repo.one() - assert last_block.number > 0 - end - end - end - - test "when there is already a block with the requested hash" do - use_cassette "import_block_perform_1_duplicate" do - insert(:block, hash: "0x52c867bc0a91e573dc39300143c3bead7408d09d45bdb686749f02684ece72f3") - ImportBlock.perform("1") - block_count = Block |> Repo.all() |> Enum.count() - assert block_count == 1 - end - end - end - - describe "perform_later/1" do - test "does not retry fetching the latest block" do - use_cassette "import_block_perform_later_1_latest" do - with_mock Exq, enqueue: fn _, _, _, _ -> insert(:block, number: 1) end do - ImportBlock.perform_later("latest") - last_block = Block |> order_by(asc: :number) |> limit(1) |> Repo.one() - assert last_block.number == 1 - end - end - end - end -end diff --git a/apps/explorer/test/explorer/workers/import_internal_transaction_test.exs b/apps/explorer/test/explorer/workers/import_internal_transaction_test.exs deleted file mode 100644 index c84ba424ea..0000000000 --- a/apps/explorer/test/explorer/workers/import_internal_transaction_test.exs +++ /dev/null @@ -1,31 +0,0 @@ -defmodule Explorer.Workers.ImportInternalTransactionTest do - use Explorer.DataCase - - alias Explorer.Repo - alias Explorer.Chain.InternalTransaction - alias Explorer.Workers.ImportInternalTransaction - - describe "perform/1" do - test "does not import the internal transactions when no transaction with the hash exists" do - use_cassette "import_internal_transaction_perform_1" do - assert_raise Ecto.NoResultsError, fn -> - ImportInternalTransaction.perform("0xf9a0959d5ccde33ec5221ddba1c6d7eaf9580a8d3512c7a1a60301362a98f926") - end - end - end - - test "imports a receipt when an internal transaction with the hash exists" do - insert( - :transaction, - hash: "0x051e031f05b3b3a5ff73e1189c36e3e2a41fd1c2d9772b2c75349e22ed4d3f68" - ) - - use_cassette "import_internal_transaction_perform_1" do - ImportInternalTransaction.perform("0x051e031f05b3b3a5ff73e1189c36e3e2a41fd1c2d9772b2c75349e22ed4d3f68") - - internal_transaction_count = InternalTransaction |> Repo.all() |> Enum.count() - assert internal_transaction_count == 2 - end - end - end -end diff --git a/apps/explorer/test/explorer/workers/import_receipt_test.exs b/apps/explorer/test/explorer/workers/import_receipt_test.exs deleted file mode 100644 index 2e260762fe..0000000000 --- a/apps/explorer/test/explorer/workers/import_receipt_test.exs +++ /dev/null @@ -1,31 +0,0 @@ -defmodule Explorer.Workers.ImportReceiptTest do - use Explorer.DataCase - - alias Explorer.Repo - alias Explorer.Chain.Receipt - alias Explorer.Workers.ImportReceipt - - describe "perform/1" do - test "does not import a receipt when no transaction with the hash exists" do - use_cassette "import_receipt_perform_1" do - ImportReceipt.perform("0xf9a0959d5ccde33ec5221ddba1c6d7eaf9580a8d3512c7a1a60301362a98f926") - - assert Repo.one(Receipt) == nil - end - end - - test "imports a receipt when a transaction with the hash exists" do - insert( - :transaction, - hash: "0xf9a0959d5ccde33ec5221ddba1c6d7eaf9580a8d3512c7a1a60301362a98f926" - ) - - use_cassette "import_receipt_perform_1" do - ImportReceipt.perform("0xf9a0959d5ccde33ec5221ddba1c6d7eaf9580a8d3512c7a1a60301362a98f926") - - receipt_count = Receipt |> Repo.all() |> Enum.count() - assert receipt_count == 1 - end - end - end -end diff --git a/apps/explorer/test/explorer/workers/import_skipped_blocks_test.exs b/apps/explorer/test/explorer/workers/import_skipped_blocks_test.exs deleted file mode 100644 index 637cda53a7..0000000000 --- a/apps/explorer/test/explorer/workers/import_skipped_blocks_test.exs +++ /dev/null @@ -1,23 +0,0 @@ -defmodule Explorer.Workers.ImportSkippedBlocksTest do - use Explorer.DataCase - - import Mock - - alias Explorer.Chain.Block - alias Explorer.Repo - alias Explorer.Workers.{ImportBlock, ImportSkippedBlocks} - - describe "perform/1" do - test "imports the requested number of skipped blocks" do - insert(:block, %{number: 2}) - - use_cassette "import_skipped_blocks_perform_1" do - with_mock ImportBlock, perform_later: fn number -> insert(:block, number: number) end do - ImportSkippedBlocks.perform(1) - last_block = Block |> order_by(asc: :number) |> limit(1) |> Repo.one() - assert last_block.number == 1 - end - end - end - end -end diff --git a/apps/explorer/test/explorer/workers/import_transaction_test.exs b/apps/explorer/test/explorer/workers/import_transaction_test.exs deleted file mode 100644 index 790c98ad6a..0000000000 --- a/apps/explorer/test/explorer/workers/import_transaction_test.exs +++ /dev/null @@ -1,148 +0,0 @@ -defmodule Explorer.Workers.ImportTransactionTest do - use Explorer.DataCase - - import Mock - - alias Explorer.Chain.{InternalTransaction, Receipt, Transaction} - alias Explorer.Repo - alias Explorer.Workers.ImportInternalTransaction - alias Explorer.Workers.ImportTransaction - - describe "perform/1" do - test "imports the requested transaction hash" do - use_cassette "import_transaction_perform_1" do - with_mock Exq, enqueue: fn _, _, _, _ -> :ok end do - ImportTransaction.perform("0xf9a0959d5ccde33ec5221ddba1c6d7eaf9580a8d3512c7a1a60301362a98f926") - end - - transaction = Transaction |> Repo.one() - - assert transaction.hash == "0xf9a0959d5ccde33ec5221ddba1c6d7eaf9580a8d3512c7a1a60301362a98f926" - end - end - - test "when there is already a transaction with the requested hash" do - insert( - :transaction, - hash: "0xf9a0959d5ccde33ec5221ddba1c6d7eaf9580a8d3512c7a1a60301362a98f926" - ) - - use_cassette "import_transaction_perform_1" do - with_mock Exq, enqueue: fn _, _, _, _ -> :ok end do - ImportTransaction.perform("0xf9a0959d5ccde33ec5221ddba1c6d7eaf9580a8d3512c7a1a60301362a98f926") - end - - transaction_count = Transaction |> Repo.all() |> Enum.count() - assert transaction_count == 1 - end - end - - test "imports the receipt in another queue" do - transaction = - insert( - :transaction, - hash: "0xf9a0959d5ccde33ec5221ddba1c6d7eaf9580a8d3512c7a1a60301362a98f926" - ) - - use_cassette "import_transaction_perform_1" do - with_mock Exq, enqueue: fn _, _, _, _ -> insert(:receipt, transaction: transaction) end do - with_mock ImportInternalTransaction, perform_later: fn _ -> :ok end do - ImportTransaction.perform("0xf9a0959d5ccde33ec5221ddba1c6d7eaf9580a8d3512c7a1a60301362a98f926") - - receipt = Repo.one(Receipt) - refute is_nil(receipt) - end - end - end - end - - test "imports the receipt in another queue when a map is supplied" do - transaction = - insert( - :transaction, - hash: "0xf9a0959d5ccde33ec5221ddba1c6d7eaf9580a8d3512c7a1a60301362a98f926" - ) - - use_cassette "import_transaction_perform_1" do - with_mock Exq, enqueue: fn _, _, _, _ -> insert(:receipt, transaction: transaction) end do - with_mock ImportInternalTransaction, perform_later: fn _ -> :ok end do - ImportTransaction.perform(%{ - "hash" => "0xf9a0959d5ccde33ec5221ddba1c6d7eaf9580a8d3512c7a1a60301362a98f926", - "to" => "0xc001", - "from" => "0xbead5", - "blockHash" => "0xcafe" - }) - - receipt = Repo.one(Receipt) - refute is_nil(receipt) - end - end - end - end - - test "imports the internal transactions in another queue" do - transaction = - insert( - :transaction, - hash: "0xf9a0959d5ccde33ec5221ddba1c6d7eaf9580a8d3512c7a1a60301362a98f926" - ) - - use_cassette "import_transaction_perform_1" do - with_mock Exq, enqueue: fn _, _, _, _ -> :ok end do - with_mock ImportInternalTransaction, - perform_later: fn _ -> insert(:internal_transaction, transaction: transaction) end do - ImportTransaction.perform("0xf9a0959d5ccde33ec5221ddba1c6d7eaf9580a8d3512c7a1a60301362a98f926") - - internal_transaction = Repo.one(InternalTransaction) - refute is_nil(internal_transaction) - end - end - end - end - - test "imports the internal transactions in another queue when a map is supplied" do - transaction = - insert( - :transaction, - hash: "0xf9a0959d5ccde33ec5221ddba1c6d7eaf9580a8d3512c7a1a60301362a98f926" - ) - - use_cassette "import_transaction_perform_1" do - with_mock Exq, enqueue: fn _, _, _, _ -> :ok end do - with_mock ImportInternalTransaction, - perform_later: fn _ -> insert(:internal_transaction, transaction: transaction) end do - ImportTransaction.perform(%{ - "hash" => "0xf9a0959d5ccde33ec5221ddba1c6d7eaf9580a8d3512c7a1a60301362a98f926", - "to" => "0xc001", - "from" => "0xbead5", - "blockHash" => "0xcafe" - }) - - internal_transaction = Repo.one(InternalTransaction) - refute is_nil(internal_transaction) - end - end - end - end - end - - describe "perform_later/1" do - test "imports the transaction in another queue" do - use_cassette "import_transaction_perform_1" do - with_mock Exq, - enqueue: fn _, _, _, _ -> - insert( - :transaction, - hash: "0xf9a0959d5ccde33ec5221ddba1c6d7eaf9580a8d3512c7a1a60301362a98f926" - ) - end do - ImportTransaction.perform_later("0xf9a0959d5ccde33ec5221ddba1c6d7eaf9580a8d3512c7a1a60301362a98f926") - - transaction = Repo.one(Transaction) - - assert transaction.hash == "0xf9a0959d5ccde33ec5221ddba1c6d7eaf9580a8d3512c7a1a60301362a98f926" - end - end - end - end -end diff --git a/apps/explorer/test/explorer/workers/refresh_balance_test.exs b/apps/explorer/test/explorer/workers/refresh_balance_test.exs deleted file mode 100644 index 50897e92aa..0000000000 --- a/apps/explorer/test/explorer/workers/refresh_balance_test.exs +++ /dev/null @@ -1,52 +0,0 @@ -defmodule Explorer.Workers.RefreshBalanceTest do - use Explorer.DataCase - - import Mock - - alias Explorer.Chain.{Credit, Debit} - alias Explorer.Workers.RefreshBalance - - describe "perform/0" do - test "refreshes credit balances" do - with_mock Exq, enqueue: fn _, _, _, [type] -> RefreshBalance.perform(type) end do - address = insert(:address) - transaction = insert(:transaction, value: 20) - insert(:to_address, address: address, transaction: transaction) - insert(:receipt, transaction: transaction, status: 1) - RefreshBalance.perform() - assert Repo.one(Credit).value == Decimal.new(20) - end - end - - test "refreshes debit balances" do - with_mock Exq, enqueue: fn _, _, _, [type] -> RefreshBalance.perform(type) end do - address = insert(:address) - transaction = insert(:transaction, value: 20) - insert(:from_address, address: address, transaction: transaction) - insert(:receipt, transaction: transaction, status: 1) - RefreshBalance.perform() - assert Repo.one(Debit).value == Decimal.new(20) - end - end - end - - describe "perform/1" do - test "refreshes credit balances" do - address = insert(:address) - transaction = insert(:transaction, value: 20) - insert(:to_address, address: address, transaction: transaction) - insert(:receipt, transaction: transaction, status: 1) - RefreshBalance.perform("credit") - assert Repo.one(Credit).value == Decimal.new(20) - end - - test "refreshes debit balances" do - address = insert(:address) - transaction = insert(:transaction, value: 20) - insert(:from_address, address: address, transaction: transaction) - insert(:receipt, transaction: transaction, status: 1) - RefreshBalance.perform("debit") - assert Repo.one(Debit).value == Decimal.new(20) - end - end -end diff --git a/apps/explorer/test/support/factories/chain/block_transaction_factory.ex b/apps/explorer/test/support/factories/chain/block_transaction_factory.ex deleted file mode 100644 index 2489b3a6d4..0000000000 --- a/apps/explorer/test/support/factories/chain/block_transaction_factory.ex +++ /dev/null @@ -1,9 +0,0 @@ -defmodule Explorer.Chain.BlockTransactionFactory do - defmacro __using__(_opts) do - quote do - def block_transaction_factory do - %Explorer.Chain.BlockTransaction{} - end - end - end -end diff --git a/apps/explorer/test/support/factories/chain/from_address_factory.ex b/apps/explorer/test/support/factories/chain/from_address_factory.ex deleted file mode 100644 index 3631126aec..0000000000 --- a/apps/explorer/test/support/factories/chain/from_address_factory.ex +++ /dev/null @@ -1,9 +0,0 @@ -defmodule Explorer.Chain.FromAddressFactory do - defmacro __using__(_opts) do - quote do - def from_address_factory do - %Explorer.Chain.FromAddress{} - end - end - end -end diff --git a/apps/explorer/test/support/factories/chain/to_address_factory.ex b/apps/explorer/test/support/factories/chain/to_address_factory.ex deleted file mode 100644 index 06643f4132..0000000000 --- a/apps/explorer/test/support/factories/chain/to_address_factory.ex +++ /dev/null @@ -1,9 +0,0 @@ -defmodule Explorer.Chain.ToAddressFactory do - defmacro __using__(_opts) do - quote do - def to_address_factory do - %Explorer.Chain.ToAddress{} - end - end - end -end diff --git a/apps/explorer/test/support/factories/chain/transaction_factory.ex b/apps/explorer/test/support/factories/chain/transaction_factory.ex index a10f5c3dc5..8f4bd5ff2a 100644 --- a/apps/explorer/test/support/factories/chain/transaction_factory.ex +++ b/apps/explorer/test/support/factories/chain/transaction_factory.ex @@ -22,16 +22,6 @@ defmodule Explorer.Chain.TransactionFactory do from_address_id: insert(:address).id } end - - def with_block(transaction, block \\ nil) do - block = block || insert(:block) - insert(:block_transaction, %{block_id: block.id, transaction_id: transaction.id}) - 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/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index fe715c6d77..7f5d43bd2d 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -3,11 +3,8 @@ defmodule Explorer.Factory do use ExMachina.Ecto, repo: Explorer.Repo use Explorer.Chain.AddressFactory use Explorer.Chain.BlockFactory - use Explorer.Chain.BlockTransactionFactory - use Explorer.Chain.FromAddressFactory use Explorer.Chain.InternalTransactionFactory use Explorer.Chain.LogFactory use Explorer.Chain.ReceiptFactory - use Explorer.Chain.ToAddressFactory use Explorer.Chain.TransactionFactory end diff --git a/apps/explorer_web/config/config.exs b/apps/explorer_web/config/config.exs index f774845edc..903badbbfa 100644 --- a/apps/explorer_web/config/config.exs +++ b/apps/explorer_web/config/config.exs @@ -24,8 +24,6 @@ config :ex_cldr, locales: ["en"], gettext: ExplorerWeb.Gettext -config :exq_ui, server: false - # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{Mix.env()}.exs" diff --git a/apps/explorer_web/lib/explorer_web/router.ex b/apps/explorer_web/lib/explorer_web/router.ex index a127242e6d..d025c9a0fd 100644 --- a/apps/explorer_web/lib/explorer_web/router.ex +++ b/apps/explorer_web/lib/explorer_web/router.ex @@ -22,23 +22,6 @@ defmodule ExplorerWeb.Router do plug(SetLocale, gettext: ExplorerWeb.Gettext, default_locale: "en") end - pipeline :exq do - plug(:accepts, ["html"]) - plug(:fetch_session) - plug(:fetch_flash) - - plug(:put_secure_browser_headers, %{ - "content-security-policy" => "\ - default-src 'self';\ - script-src 'self' 'unsafe-inline';\ - font-src 'self' fonts.gstatic.com;\ - style-src 'self' 'unsafe-inline' fonts.googleapis.com;\ - " - }) - - plug(ExqUi.RouterPlug, namespace: "exq") - end - pipeline :jasmine do if Mix.env() != :prod, do: plug(Jasmine, js_files: ["js/test.js"], css_files: ["css/test.css"]) end @@ -47,11 +30,6 @@ defmodule ExplorerWeb.Router do plug(:accepts, ["json"]) end - scope "/exq", ExqUi do - pipe_through(:exq) - forward("/", RouterPlug.Router, :index) - end - scope "/", ExplorerWeb do pipe_through(:browser) pipe_through(:jasmine) diff --git a/apps/explorer_web/mix.exs b/apps/explorer_web/mix.exs index bcfde155e4..25cfb27f61 100644 --- a/apps/explorer_web/mix.exs +++ b/apps/explorer_web/mix.exs @@ -46,9 +46,8 @@ defmodule ExplorerWeb.Mixfile do defp elixirc_paths, do: ["lib"] # Specifies extra applications to start per environment - defp extra_applications(:prod), do: [:phoenix_pubsub_redis, :exq, :exq_ui | extra_applications()] + defp extra_applications(:prod), do: [:phoenix_pubsub_redis | extra_applications()] - defp extra_applications(:dev), do: [:exq, :exq_ui | extra_applications()] defp extra_applications(_), do: extra_applications() defp extra_applications, @@ -61,8 +60,7 @@ defmodule ExplorerWeb.Mixfile do :crontab, :set_locale, :logger, - :runtime_tools, - :new_relixir + :runtime_tools ] # Specifies your project dependencies. @@ -90,7 +88,6 @@ defmodule ExplorerWeb.Mixfile do {:junit_formatter, ">= 0.0.0", only: [:test], runtime: false}, {:math, "~> 0.3.0"}, {:mock, "~> 0.3.0", only: [:test], runtime: false}, - {:new_relixir, "~> 0.4"}, {:phoenix, "~> 1.3.0"}, {:phoenix_ecto, "~> 3.2"}, {:phoenix_html, "~> 2.10"}, diff --git a/apps/explorer_web/test/explorer_web/controllers/address_transaction_from_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/address_transaction_from_controller_test.exs index 20822fe276..41171f6912 100644 --- a/apps/explorer_web/test/explorer_web/controllers/address_transaction_from_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/address_transaction_from_controller_test.exs @@ -13,10 +13,9 @@ defmodule ExplorerWeb.AddressTransactionFromControllerTest do test "returns transactions from this address", %{conn: conn} do address = insert(:address) hash = "0xsnacks" - transaction = insert(:transaction, hash: hash, from_address_id: address.id) - insert(:receipt, transaction: transaction) block = insert(:block) - insert(:block_transaction, transaction: transaction, block: block) + transaction = insert(:transaction, block_id: block.id, from_address_id: address.id, hash: hash) + insert(:receipt, transaction: transaction) conn = get(conn, address_transaction_from_path(ExplorerWeb.Endpoint, :index, :en, address.hash)) @@ -32,14 +31,10 @@ defmodule ExplorerWeb.AddressTransactionFromControllerTest do end test "does not return transactions to this address", %{conn: conn} do - transaction = insert(:transaction, hash: "0xsnacks") - insert(:receipt, transaction: transaction) block = insert(:block) - insert(:block_transaction, transaction: transaction, block: block) + transaction = insert(:transaction, block_id: block.id, hash: "0xsnacks") + insert(:receipt, transaction: transaction) address = insert(:address) - other_address = insert(:address) - insert(:to_address, transaction: transaction, address: address) - insert(:from_address, transaction: transaction, address: other_address) conn = get(conn, address_transaction_from_path(ExplorerWeb.Endpoint, :index, :en, address.hash)) @@ -48,12 +43,9 @@ defmodule ExplorerWeb.AddressTransactionFromControllerTest do end test "does not return related transactions without a receipt", %{conn: conn} do - transaction = insert(:transaction) block = insert(:block) - insert(:block_transaction, transaction: transaction, block: block) + insert(:transaction, block_id: block.id) address = insert(:address) - insert(:to_address, transaction: transaction, address: address) - insert(:from_address, transaction: transaction, address: address) conn = get(conn, address_transaction_from_path(ExplorerWeb.Endpoint, :index, :en, address.hash)) @@ -62,12 +54,10 @@ defmodule ExplorerWeb.AddressTransactionFromControllerTest do end test "does not return related transactions without a from address", %{conn: conn} do - transaction = insert(:transaction) - insert(:receipt, transaction: transaction) block = insert(:block) - insert(:block_transaction, transaction: transaction, block: block) + transaction = insert(:transaction, block_id: block.id) + insert(:receipt, transaction: transaction) address = insert(:address) - insert(:to_address, transaction: transaction, address: address) conn = get(conn, address_transaction_from_path(ExplorerWeb.Endpoint, :index, :en, address.hash)) @@ -76,12 +66,10 @@ defmodule ExplorerWeb.AddressTransactionFromControllerTest do end test "does not return related transactions without a to address", %{conn: conn} do - transaction = insert(:transaction) - insert(:receipt, transaction: transaction) block = insert(:block) - insert(:block_transaction, transaction: transaction, block: block) + transaction = insert(:transaction, block_id: block.id) + insert(:receipt, transaction: transaction) address = insert(:address) - insert(:from_address, transaction: transaction, address: address) conn = get(conn, address_transaction_from_path(ExplorerWeb.Endpoint, :index, :en, address.hash)) diff --git a/apps/explorer_web/test/explorer_web/controllers/address_transaction_to_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/address_transaction_to_controller_test.exs index e813d99dfe..629ae744e4 100644 --- a/apps/explorer_web/test/explorer_web/controllers/address_transaction_to_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/address_transaction_to_controller_test.exs @@ -13,10 +13,8 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do test "returns transactions to this address", %{conn: conn} do address = insert(:address) hash = "0xsnacks" - transaction = insert(:transaction, hash: hash, to_address_id: address.id) + transaction = insert(:transaction, block_id: insert(:block).id, hash: hash, to_address_id: address.id) insert(:receipt, transaction: transaction) - block = insert(:block) - insert(:block_transaction, transaction: transaction, block: block) conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address.hash)) @@ -35,12 +33,7 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do test "does not return transactions from this address", %{conn: conn} do transaction = insert(:transaction, hash: "0xsnacks") insert(:receipt, transaction: transaction) - block = insert(:block) - insert(:block_transaction, transaction: transaction, block: block) address = insert(:address) - other_address = insert(:address) - insert(:to_address, transaction: transaction, address: other_address) - insert(:from_address, transaction: transaction, address: address) conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address.hash)) @@ -49,12 +42,9 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do end test "does not return related transactions without a receipt", %{conn: conn} do - transaction = insert(:transaction) - block = insert(:block) - insert(:block_transaction, transaction: transaction, block: block) address = insert(:address) - insert(:to_address, transaction: transaction, address: address) - insert(:from_address, transaction: transaction, address: address) + block = insert(:block) + insert(:transaction, block_id: block.id, from_address_id: address.id, to_address_id: address.id) conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address.hash)) @@ -65,10 +55,7 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do test "does not return related transactions without a from address", %{conn: conn} do transaction = insert(:transaction) insert(:receipt, transaction: transaction) - block = insert(:block) - insert(:block_transaction, transaction: transaction, block: block) address = insert(:address) - insert(:to_address, transaction: transaction, address: address) conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address.hash)) @@ -77,12 +64,10 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do end test "does not return related transactions without a to address", %{conn: conn} do - transaction = insert(:transaction) - insert(:receipt, transaction: transaction) - block = insert(:block) - insert(:block_transaction, transaction: transaction, block: block) address = insert(:address) - insert(:from_address, transaction: transaction, address: address) + block = insert(:block) + transaction = insert(:transaction, block_id: block.id, from_address_id: address.id) + insert(:receipt, transaction: transaction) conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address.hash)) diff --git a/apps/explorer_web/test/explorer_web/controllers/block_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/block_controller_test.exs index 69983660c9..3a50d0440c 100644 --- a/apps/explorer_web/test/explorer_web/controllers/block_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/block_controller_test.exs @@ -25,11 +25,10 @@ defmodule ExplorerWeb.BlockControllerTest do test "returns a block with two transactions", %{conn: conn} do block = insert(:block) - transaction = insert(:transaction) - insert(:block_transaction, block: block, transaction: transaction) - other_transaction = insert(:transaction) - insert(:block_transaction, block: block, transaction: other_transaction) + insert_list(2, :transaction, block_id: block.id) + conn = get(conn, "/en/blocks") + assert conn.assigns.blocks.entries |> Enum.count() == 1 end end diff --git a/apps/explorer_web/test/explorer_web/controllers/block_transaction_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/block_transaction_controller_test.exs index 32e9fd9ab7..3a5d6b321d 100644 --- a/apps/explorer_web/test/explorer_web/controllers/block_transaction_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/block_transaction_controller_test.exs @@ -18,13 +18,9 @@ defmodule ExplorerWeb.BlockTransactionControllerTest do test "returns transactions for the block", %{conn: conn} do hash = "0xsnacks" - transaction = insert(:transaction, hash: hash) - insert(:receipt, transaction: transaction) block = insert(:block) - insert(:block_transaction, transaction: transaction, block: block) - address = insert(:address) - insert(:to_address, transaction: transaction, address: address) - insert(:from_address, transaction: transaction, address: address) + transaction = insert(:transaction, block_id: block.id, hash: hash) + insert(:receipt, transaction: transaction) conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block.number)) @@ -49,9 +45,8 @@ defmodule ExplorerWeb.BlockTransactionControllerTest do end test "does not return related transactions without a receipt", %{conn: conn} do - transaction = insert(:transaction) block = insert(:block) - insert(:block_transaction, transaction: transaction, block: block) + insert(:transaction, block_id: block.id) conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block.number)) @@ -59,10 +54,9 @@ defmodule ExplorerWeb.BlockTransactionControllerTest do end test "does not return related transactions without a from address", %{conn: conn} do - transaction = insert(:transaction, from_address_id: nil) - insert(:receipt, transaction: transaction) block = insert(:block) - insert(:block_transaction, transaction: transaction, block: block) + transaction = insert(:transaction, block_id: block.id, from_address_id: nil) + insert(:receipt, transaction: transaction) conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block.number)) @@ -70,10 +64,9 @@ defmodule ExplorerWeb.BlockTransactionControllerTest do end test "does not return related transactions without a to address", %{conn: conn} do - transaction = insert(:transaction, to_address_id: nil) - insert(:receipt, transaction: transaction) block = insert(:block) - insert(:block_transaction, transaction: transaction, block: block) + transaction = insert(:transaction, block_id: block.id, to_address_id: nil) + insert(:receipt, transaction: transaction) conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block.number)) diff --git a/apps/explorer_web/test/explorer_web/controllers/chain_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/chain_controller_test.exs index 73fd9506b3..3698dd2e66 100644 --- a/apps/explorer_web/test/explorer_web/controllers/chain_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/chain_controller_test.exs @@ -36,9 +36,9 @@ defmodule ExplorerWeb.ChainControllerTest do test "only returns transactions with an associated block", %{conn: conn} do block = insert(:block, number: 33) - insert(:transaction, id: 10, hash: "0xDECAFBAD") |> with_block(block) - + insert(:transaction, block_id: block.id, id: 10, hash: "0xDECAFBAD") insert(:transaction, id: 30) + conn = get(conn, "/en") transaction_ids = @@ -52,7 +52,7 @@ defmodule ExplorerWeb.ChainControllerTest do test "returns a transaction", %{conn: conn} do block = insert(:block, number: 33) - insert(:transaction, hash: "0xDECAFBAD") |> with_block(block) + insert(:transaction, block_id: block.id, hash: "0xDECAFBAD") conn = get(conn, "/en") @@ -69,7 +69,7 @@ defmodule ExplorerWeb.ChainControllerTest do end test "finds a transaction by hash", %{conn: conn} do - transaction = insert(:transaction) |> with_block() + transaction = insert(:transaction, block_id: insert(:block).id) conn = get(conn, "/en/search?q=#{transaction.hash}") assert redirected_to(conn) == transaction_path(conn, :show, "en", transaction.hash) diff --git a/apps/explorer_web/test/explorer_web/controllers/pending_transaction_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/pending_transaction_controller_test.exs index d1f1433a6e..b507f839b3 100644 --- a/apps/explorer_web/test/explorer_web/controllers/pending_transaction_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/pending_transaction_controller_test.exs @@ -5,13 +5,9 @@ defmodule ExplorerWeb.PendingTransactionControllerTest do describe "GET index/2" do test "returns no transactions that have a receipt", %{conn: conn} do - transaction = insert(:transaction) block = insert(:block) - address = insert(:address) + transaction = insert(:transaction, block_id: block.id) insert(:receipt, transaction: transaction) - insert(:block_transaction, transaction: transaction, block: block) - insert(:to_address, transaction: transaction, address: address) - insert(:from_address, transaction: transaction, address: address) conn = get(conn, pending_transaction_path(ExplorerWeb.Endpoint, :index, :en)) @@ -22,13 +18,9 @@ defmodule ExplorerWeb.PendingTransactionControllerTest do end test "does not count transactions that have a receipt", %{conn: conn} do - transaction = insert(:transaction) block = insert(:block) - address = insert(:address) + transaction = insert(:transaction, block_id: block.id) insert(:receipt, transaction: transaction) - insert(:block_transaction, transaction: transaction, block: block) - insert(:to_address, transaction: transaction, address: address) - insert(:from_address, transaction: transaction, address: address) conn = get(conn, pending_transaction_path(ExplorerWeb.Endpoint, :index, :en)) @@ -39,10 +31,7 @@ defmodule ExplorerWeb.PendingTransactionControllerTest do end test "returns pending transactions", %{conn: conn} do - address = insert(:address) transaction = insert(:transaction) - insert(:to_address, transaction: transaction, address: address) - insert(:from_address, transaction: transaction, address: address) conn = get(conn, pending_transaction_path(ExplorerWeb.Endpoint, :index, :en)) @@ -50,10 +39,7 @@ defmodule ExplorerWeb.PendingTransactionControllerTest do end test "returns a count of pending transactions", %{conn: conn} do - address = insert(:address) - transaction = insert(:transaction) - insert(:to_address, transaction: transaction, address: address) - insert(:from_address, transaction: transaction, address: address) + insert(:transaction) conn = get(conn, pending_transaction_path(ExplorerWeb.Endpoint, :index, :en)) @@ -61,10 +47,7 @@ defmodule ExplorerWeb.PendingTransactionControllerTest do end test "paginates transactions using the last seen transaction", %{conn: conn} do - address = insert(:address) transaction = insert(:transaction) - insert(:to_address, transaction: transaction, address: address) - insert(:from_address, transaction: transaction, address: address) conn = get( diff --git a/apps/explorer_web/test/explorer_web/controllers/transaction_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/transaction_controller_test.exs index fd134f49fd..08be4aea3c 100644 --- a/apps/explorer_web/test/explorer_web/controllers/transaction_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/transaction_controller_test.exs @@ -5,19 +5,19 @@ defmodule ExplorerWeb.TransactionControllerTest do describe "GET index/2" do test "returns a transaction with a receipt", %{conn: conn} do - transaction = insert(:transaction) block = insert(:block) + transaction = insert(:transaction, block_id: block.id) insert(:receipt, transaction: transaction) - insert(:block_transaction, transaction: transaction, block: block) + conn = get(conn, "/en/transactions") + assert List.first(conn.assigns.transactions.entries).id == transaction.id end test "returns a count of transactions", %{conn: conn} do - transaction = insert(:transaction) block = insert(:block) + transaction = insert(:transaction, block_id: block.id) insert(:receipt, transaction: transaction) - insert(:block_transaction, transaction: transaction, block: block) conn = get(conn, "/en/transactions") @@ -25,8 +25,10 @@ defmodule ExplorerWeb.TransactionControllerTest do end test "returns no pending transactions", %{conn: conn} do - insert(:transaction) |> with_block() + insert(:transaction, block_id: insert(:block).id) + conn = get(conn, "/en/transactions") + assert conn.assigns.transactions.entries == [] end @@ -37,11 +39,12 @@ defmodule ExplorerWeb.TransactionControllerTest do end test "paginates transactions using the last seen transaction", %{conn: conn} do - transaction = insert(:transaction) block = insert(:block) + transaction = insert(:transaction, block_id: block.id) insert(:receipt, transaction: transaction) - insert(:block_transaction, transaction: transaction, block: block) + conn = get(conn, "/en/transactions", last_seen: transaction.id) + assert conn.assigns.transactions.entries == [] end @@ -69,7 +72,7 @@ defmodule ExplorerWeb.TransactionControllerTest do conn: conn } do block = insert(:block, %{number: 777}) - transaction = insert(:transaction, hash: "0x8") |> with_block(block) + transaction = insert(:transaction, block_id: block.id, hash: "0x8") conn = get(conn, "/en/transactions/0x8") diff --git a/apps/explorer_web/test/explorer_web/features/contributor_browsing_test.exs b/apps/explorer_web/test/explorer_web/features/contributor_browsing_test.exs index afcaef03f4..bf18d8225a 100644 --- a/apps/explorer_web/test/explorer_web/features/contributor_browsing_test.exs +++ b/apps/explorer_web/test/explorer_web/features/contributor_browsing_test.exs @@ -55,7 +55,6 @@ defmodule ExplorerWeb.UserListTest do test "views blocks", %{session: session} do insert_list(4, :block, %{ - number: 1, timestamp: Timex.now() |> Timex.shift(hours: -1), gas_used: 10 }) @@ -72,10 +71,10 @@ defmodule ExplorerWeb.UserListTest do gas_limit: 5_030_101 }) - transaction = insert(:transaction, hash: "0xfaschtnacht") |> with_block(fifth_block) + transaction = insert(:transaction, block_id: fifth_block.id, hash: "0xfaschtnacht") - insert(:transaction, hash: "0xpaczki") |> with_block(fifth_block) - insert(:transaction) |> with_block(fifth_block) + insert(:transaction, block_id: fifth_block.id, hash: "0xpaczki") + insert(:transaction, block_id: fifth_block.id) insert(:receipt, transaction: transaction) Credit.refresh() @@ -84,7 +83,6 @@ defmodule ExplorerWeb.UserListTest do session |> visit("/en") |> assert_has(css(".blocks__title", text: "Blocks")) - |> assert_has(css(".blocks__column--height", count: 5, text: "1")) |> assert_has(css(".blocks__column--transactions-count", count: 5)) |> assert_has(css(".blocks__column--transactions-count", count: 1, text: "3")) |> assert_has(css(".blocks__column--age", count: 5, text: "1 hour ago")) @@ -113,8 +111,8 @@ defmodule ExplorerWeb.UserListTest do gas_used: 123_987 }) - for _ <- 0..3, do: insert(:transaction) |> with_block(block) - insert(:transaction, hash: "0xC001", gas: 5891) |> with_block + insert_list(4, :transaction, block_id: block.id) + insert(:transaction, block_id: insert(:block).id, hash: "0xC001", gas: 5891) lincoln = insert(:address, hash: "0xlincoln") taft = insert(:address, hash: "0xhowardtaft") @@ -122,6 +120,7 @@ defmodule ExplorerWeb.UserListTest do transaction = insert( :transaction, + block_id: block.id, hash: "0xSk8", value: Explorer.Chain.Wei.from(Decimal.new(5656), :ether), gas: Decimal.new(1_230_000_000_000_123_123), @@ -134,8 +133,6 @@ defmodule ExplorerWeb.UserListTest do to_address_id: lincoln.id ) - insert(:block_transaction, block: block, transaction: transaction) - receipt = insert(:receipt, transaction: transaction, status: 1) insert(:log, address_id: lincoln.id, receipt: receipt) @@ -143,13 +140,12 @@ defmodule ExplorerWeb.UserListTest do txn_from_lincoln = insert( :transaction, + block_id: block.id, hash: "0xrazerscooter", from_address_id: lincoln.id, to_address_id: taft.id ) - insert(:block_transaction, block: block, transaction: txn_from_lincoln) - insert(:receipt, transaction: txn_from_lincoln) internal = insert(:internal_transaction, transaction_id: transaction.id) @@ -187,7 +183,7 @@ defmodule ExplorerWeb.UserListTest do |> assert_has(css(".transaction__item", text: "0xlincoln")) |> assert_has(css(".transaction__item", text: "0xhowardtaft")) |> assert_has(css(".transaction__item", text: "block confirmations")) - |> assert_has(css(".transaction__item", text: "48 years ago")) + |> assert_has(css(".transaction__item", text: "49 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)) diff --git a/apps/explorer_web/test/explorer_web/features/exq_test.exs b/apps/explorer_web/test/explorer_web/features/exq_test.exs deleted file mode 100644 index 45990690c9..0000000000 --- a/apps/explorer_web/test/explorer_web/features/exq_test.exs +++ /dev/null @@ -1,11 +0,0 @@ -defmodule ExplorerWeb.ExqTest do - use ExplorerWeb.FeatureCase, async: true - - import Wallaby.Query, only: [css: 2] - - test "views the exq dashboard", %{session: session} do - session - |> visit("/exq") - |> assert_has(css(".navbar-brand", text: "Exq")) - end -end diff --git a/config/config.exs b/config/config.exs index 583d0ed94e..883acdfde1 100644 --- a/config/config.exs +++ b/config/config.exs @@ -14,8 +14,6 @@ config :logger, :console, format: "$time $metadata[$level] $message\n", metadata: [:request_id] -config :new_relixir, active: false - # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{Mix.env()}.exs" diff --git a/config/dev.exs b/config/dev.exs index e472324ced..eee3d06365 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -2,3 +2,4 @@ use Mix.Config # Do not include metadata nor timestamps in development logs config :logger, :console, format: "[$level] $message\n" +config :logger, level: :debug diff --git a/config/prod.exs b/config/prod.exs index 9f7c564a19..6ca4472984 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -2,9 +2,3 @@ use Mix.Config # Do not print debug messages in production config :logger, level: :info - -# Configure New Relic -config :new_relixir, - application_name: System.get_env("NEW_RELIC_APP_NAME"), - license_key: System.get_env("NEW_RELIC_LICENSE_KEY"), - active: true diff --git a/mix.lock b/mix.lock index d078125e12..593c78da23 100644 --- a/mix.lock +++ b/mix.lock @@ -10,7 +10,7 @@ "credo": {:hex, :credo, "0.9.1", "f021affa11b32a94dc2e807a6472ce0914289c9132f99644a97fc84432b202a1", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, "crontab": {:hex, :crontab, "1.1.2", "4784a50987b4a19af07a908f98e8a308b00f9c93efc5a7892155dc10cd8fc7d9", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 2.1", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, "db_connection": {:hex, :db_connection, "1.1.3", "89b30ca1ef0a3b469b1c779579590688561d586694a3ce8792985d4d7e575a61", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, - "decimal": {:hex, :decimal, "1.4.1", "ad9e501edf7322f122f7fc151cce7c2a0c9ada96f2b0155b8a09a795c2029770", [:mix], [], "hexpm"}, + "decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [:mix], [], "hexpm"}, "dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], [], "hexpm"}, "earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [:mix], [], "hexpm"}, "ecto": {:hex, :ecto, "2.2.8", "a4463c0928b970f2cee722cd29aaac154e866a15882c5737e0038bbfcf03ec2c", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, @@ -24,8 +24,6 @@ "exactor": {:hex, :exactor, "2.2.4", "5efb4ddeb2c48d9a1d7c9b465a6fffdd82300eb9618ece5d34c3334d5d7245b1", [:mix], [], "hexpm"}, "excoveralls": {:hex, :excoveralls, "0.8.1", "0bbf67f22c7dbf7503981d21a5eef5db8bbc3cb86e70d3798e8c802c74fa5e27", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, "exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"}, - "exq": {:hex, :exq, "0.9.1", "77ee12c117411ddddd9810aec714eaa704fb8f327949551d9efaea19606122c3", [:mix], [{:poison, ">= 1.2.0 or ~> 2.0", [hex: :poison, repo: "hexpm", optional: false]}, {:redix, ">= 0.5.0", [hex: :redix, repo: "hexpm", optional: false]}, {:uuid, ">= 1.1.0", [hex: :uuid, repo: "hexpm", optional: false]}], "hexpm"}, - "exq_ui": {:hex, :exq_ui, "0.9.0", "e97e9fa9009f30d2926b51a166e40a3a521e83f61f3f333fede8335b2aa57f09", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: false]}, {:exq, "~> 0.9", [hex: :exq, repo: "hexpm", optional: false]}, {:plug, ">= 0.8.1 and < 2.0.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "exvcr": {:hex, :exvcr, "0.10.0", "5150808404d9f48dbda636f70f7f8fefd93e2433cd39f695f810e73b3a9d1736", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:httpoison, "~> 0.13", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.0", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"}, "file_system": {:hex, :file_system, "0.2.4", "f0bdda195c0e46e987333e986452ec523aed21d784189144f647c43eaf307064", [:mix], [], "hexpm"}, "floki": {:hex, :floki, "0.20.1", "4ee125a81605f5920189ac6afbee8dbf4de7808319e726f0d57781e660185980", [:mix], [{:html_entities, "~> 0.4.0", [hex: :html_entities, repo: "hexpm", optional: false]}, {:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"}, @@ -36,6 +34,7 @@ "html_entities": {:hex, :html_entities, "0.4.0", "f2fee876858cf6aaa9db608820a3209e45a087c5177332799592142b50e89a6b", [:mix], [], "hexpm"}, "httpoison": {:hex, :httpoison, "1.1.0", "497949fb62924432f64a45269d20e6f61ecf35084ffa270917afcdb7cd4d8061", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, "idna": {:hex, :idna, "5.1.1", "cbc3b2fa1645113267cc59c760bafa64b2ea0334635ef06dbac8801e42f7279c", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, + "jason": {:hex, :jason, "1.0.0", "0f7cfa9bdb23fed721ec05419bcee2b2c21a77e926bce0deda029b5adc716fe2", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, "jiffy": {:hex, :jiffy, "0.15.1", "be83b09388da1a6c7e798207c9d6a1c4d71bb95fcc387d37d35861788f49ab97", [:rebar3], [], "hexpm"}, "jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm"}, "junit_formatter": {:hex, :junit_formatter, "2.1.0", "ff03d2bbe9a67041f2488d8e72180ddcf4dff9e9c8a39b79eac9828fcb9e9bbf", [:mix], [], "hexpm"}, @@ -47,7 +46,6 @@ "mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], [], "hexpm"}, "mock": {:hex, :mock, "0.3.1", "994f00150f79a0ea50dc9d86134cd9ebd0d177ad60bd04d1e46336cdfdb98ff9", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"}, "mox": {:hex, :mox, "0.3.2", "3b9b8364fd4f28628139de701d97c636b27a8f925f57a8d5a1b85fbd620dad3a", [:mix], [], "hexpm"}, - "new_relixir": {:hex, :new_relixir, "0.4.1", "55907ef2b968cf9c9278c58e43d1d68f756d21101fa552639c566c5536280915", [:mix], [{:ecto, "~> 2.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm"}, "parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"}, "phoenix": {:hex, :phoenix, "1.3.0", "1c01124caa1b4a7af46f2050ff11b267baa3edb441b45dbf243e979cd4c5891b", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_ecto": {:hex, :phoenix_ecto, "3.3.0", "702f6e164512853d29f9d20763493f2b3bcfcb44f118af2bc37bb95d0801b480", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, @@ -59,7 +57,6 @@ "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"}, "postgrex": {:hex, :postgrex, "0.13.5", "3d931aba29363e1443da167a4b12f06dcd171103c424de15e5f3fc2ba3e6d9c5", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"}, - "quantum": {:hex, :quantum, "2.2.1", "a8f2ef38b68121833651617ed0c01b348582542dee11730d553acdc57a7d0f7c", [:mix], [{:calendar, "~> 0.17", [hex: :calendar, repo: "hexpm", optional: true]}, {:crontab, "~> 1.1", [hex: :crontab, repo: "hexpm", optional: false]}, {:gen_stage, "~> 0.12.2", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:timex, "~> 3.1.13", [hex: :timex, repo: "hexpm", optional: true]}], "hexpm"}, "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"}, "react_phoenix": {:hex, :react_phoenix, "0.5.1", "962e1e46ce0ad6e0c9f007ee5f602a6c3758c3a808ce990134b6ed155d60b46d", [:mix], [{:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, "redix": {:hex, :redix, "0.6.1", "20986b0e02f02b13e6f53c79a1ae70aa83147488c408f40275ec261f5bb0a6d0", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"}, @@ -70,7 +67,7 @@ "set_locale": {:git, "https://github.com/minifast/set_locale.git", "da9ae029642bc0fbd9212c2aaf86c0adca70c084", [branch: "master"]}, "sobelow": {:hex, :sobelow, "0.6.9", "da695ec58398a424841f0e5042f9e7bcf5dcb1e5d54bfe82637ed00dcbe75cef", [:mix], [], "hexpm"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"}, - "timex": {:hex, :timex, "3.1.24", "d198ae9783ac807721cca0c5535384ebdf99da4976be8cefb9665a9262a1e9e3", [:mix], [{:combine, "~> 0.7", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"}, + "timex": {:hex, :timex, "3.1.25", "6002dae5432f749d1c93e2cd103eb73cecb53e50d2c885349e8e4146fc96bd44", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"}, "timex_ecto": {:hex, :timex_ecto, "3.2.1", "461140751026e1ca03298fab628f78ab189e78784175f5e301eefa034ee530aa", [:mix], [{:ecto, "~> 2.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm"}, "tzdata": {:hex, :tzdata, "0.5.16", "13424d3afc76c68ff607f2df966c0ab4f3258859bbe3c979c9ed1606135e7352", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"}, From 5a725265f6845077bbee40627c8a5fb045918729 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Mon, 23 Apr 2018 16:33:36 -0500 Subject: [PATCH 02/77] WIP Hash primary keys + Everything compiles + Explorer tests pass + ExplorerWeb tests pass + Hashes are an Ecto.Type - Indexers are still broke, but docs are bulding up --- .credo.exs | 5 +- .formatter.exs | 2 +- apps/explorer/config/config.exs | 12 +- .../lib/backfill_transaction_receipt_ids.ex | 14 - apps/explorer/lib/explorer/application.ex | 11 +- apps/explorer/lib/explorer/chain.ex | 685 ++++++++++++------ apps/explorer/lib/explorer/chain/address.ex | 38 +- apps/explorer/lib/explorer/chain/block.ex | 78 +- apps/explorer/lib/explorer/chain/credit.ex | 4 +- apps/explorer/lib/explorer/chain/debit.ex | 4 +- apps/explorer/lib/explorer/chain/hash.ex | 275 ++++++- apps/explorer/lib/explorer/chain/hash/full.ex | 160 ++++ .../lib/explorer/chain/hash/truncated.ex | 161 ++++ .../explorer/chain/internal_transaction.ex | 53 +- apps/explorer/lib/explorer/chain/log.ex | 10 +- apps/explorer/lib/explorer/chain/receipt.ex | 14 +- .../explorer/lib/explorer/chain/statistics.ex | 42 +- .../lib/explorer/chain/transaction.ex | 81 ++- .../lib/explorer/ethereum/ethereum.ex | 19 - apps/explorer/lib/explorer/ethereum/live.ex | 14 - apps/explorer/lib/explorer/ethereum/test.ex | 9 - .../lib/explorer/ethereumex_extensions.ex | 14 - apps/explorer/lib/explorer/indexer.ex | 10 +- .../lib/explorer/indexer/block_fetcher.ex | 197 ++--- .../explorer/lib/explorer/indexer/sequence.ex | 72 +- .../lib/explorer/indexer/supervisor.ex | 7 + .../lib/explorer/{eth.ex => jsonrpc.ex} | 331 +++++---- apps/explorer/lib/explorer/jsonrpc/block.ex | 131 ++++ apps/explorer/lib/explorer/jsonrpc/blocks.ex | 33 + apps/explorer/lib/explorer/jsonrpc/receipt.ex | 48 ++ .../explorer/lib/explorer/jsonrpc/receipts.ex | 26 + .../lib/explorer/jsonrpc/transaction.ex | 84 +++ .../lib/explorer/jsonrpc/transactions.ex | 21 + apps/explorer/lib/giant_address_migrator.ex | 45 -- apps/explorer/mix.exs | 4 +- .../20180117221921_create_address.exs | 12 +- .../20180117221922_create_blocks.exs | 35 +- .../20180117221923_create_transactions.exs | 64 +- .../20180212214442_create_receipts.exs | 25 +- .../migrations/20180212222309_create_logs.exs | 43 +- ...221001948_create_internal_transactions.exs | 41 +- ..._create_credit_debit_materialized_view.exs | 90 +-- .../test/explorer/chain/address_test.exs | 6 - .../test/explorer/chain/block_test.exs | 30 +- .../test/explorer/chain/credit_test.exs | 14 +- .../test/explorer/chain/debit_test.exs | 14 +- .../test/explorer/chain/hash/full_test.exs | 5 + .../explorer/chain/hash/truncated_test.exs | 5 + .../test/explorer/chain/hash_test.exs | 5 + .../chain/internal_transaction_test.exs | 22 +- .../explorer/chain/statistics/server_test.exs | 8 - .../test/explorer/chain/statistics_test.exs | 6 +- .../test/explorer/chain/transaction_test.exs | 2 +- apps/explorer/test/explorer/chain_test.exs | 326 ++------- .../test/explorer/ethereum/ethereum_test.exs | 18 - .../factories/chain/address_factory.ex | 11 - .../support/factories/chain/block_factory.ex | 21 - .../chain/internal_transaction_factory.ex | 20 - .../support/factories/chain/log_factory.ex | 18 - .../factories/chain/receipt_factory.ex | 14 - .../factories/chain/transaction_factory.ex | 27 - apps/explorer/test/support/factory.ex | 137 +++- apps/explorer_web/lib/explorer_web/chain.ex | 41 +- .../controllers/address_controller.ex | 11 +- .../address_transaction_from_controller.ex | 33 +- .../address_transaction_to_controller.ex | 33 +- .../controllers/chain_controller.ex | 6 +- .../pending_transaction_controller.ex | 47 +- .../controllers/transaction_controller.ex | 123 ++-- .../controllers/transaction_log_controller.ex | 54 +- .../address_transaction_from/index.html.eex | 16 +- .../address_transaction_to/index.html.eex | 16 +- .../templates/block/show.html.eex | 11 +- .../block_transaction/index.html.eex | 16 +- .../pending_transaction/index.html.eex | 63 +- .../templates/transaction/index.html.eex | 37 +- .../views/address_transaction_from_view.ex | 4 + .../views/address_transaction_to_view.ex | 4 + .../views/block_transaction_view.ex | 4 + .../lib/explorer_web/views/block_view.ex | 12 + .../views/pending_transaction_view.ex | 19 +- .../explorer_web/views/transaction_view.ex | 24 +- apps/explorer_web/lib/phoenix/html/safe.ex | 8 + apps/explorer_web/lib/phoenix/param.ex | 25 + apps/explorer_web/mix.exs | 2 +- apps/explorer_web/priv/gettext/default.pot | 37 +- .../priv/gettext/en/LC_MESSAGES/default.po | 37 +- .../test/explorer_web/chain_test.exs | 12 +- .../controllers/address_controller_test.exs | 6 +- ...dress_transaction_from_controller_test.exs | 13 +- ...address_transaction_to_controller_test.exs | 28 +- .../controllers/block_controller_test.exs | 28 +- .../block_transaction_controller_test.exs | 25 +- .../controllers/chain_controller_test.exs | 30 +- .../pending_transaction_controller_test.exs | 33 +- .../transaction_controller_test.exs | 59 +- .../transaction_log_controller_test.exs | 6 +- .../features/contributor_browsing_test.exs | 169 ++--- .../views/transaction_view_test.exs | 18 +- mix.lock | 2 +- 100 files changed, 2978 insertions(+), 1867 deletions(-) delete mode 100644 apps/explorer/lib/backfill_transaction_receipt_ids.ex create mode 100644 apps/explorer/lib/explorer/chain/hash/full.ex create mode 100644 apps/explorer/lib/explorer/chain/hash/truncated.ex delete mode 100644 apps/explorer/lib/explorer/ethereum/ethereum.ex delete mode 100644 apps/explorer/lib/explorer/ethereum/live.ex delete mode 100644 apps/explorer/lib/explorer/ethereum/test.ex delete mode 100644 apps/explorer/lib/explorer/ethereumex_extensions.ex rename apps/explorer/lib/explorer/{eth.ex => jsonrpc.ex} (65%) create mode 100644 apps/explorer/lib/explorer/jsonrpc/block.ex create mode 100644 apps/explorer/lib/explorer/jsonrpc/blocks.ex create mode 100644 apps/explorer/lib/explorer/jsonrpc/receipt.ex create mode 100644 apps/explorer/lib/explorer/jsonrpc/receipts.ex create mode 100644 apps/explorer/lib/explorer/jsonrpc/transaction.ex create mode 100644 apps/explorer/lib/explorer/jsonrpc/transactions.ex delete mode 100644 apps/explorer/lib/giant_address_migrator.ex create mode 100644 apps/explorer/test/explorer/chain/hash/full_test.exs create mode 100644 apps/explorer/test/explorer/chain/hash/truncated_test.exs create mode 100644 apps/explorer/test/explorer/chain/hash_test.exs delete mode 100644 apps/explorer/test/explorer/ethereum/ethereum_test.exs delete mode 100644 apps/explorer/test/support/factories/chain/address_factory.ex delete mode 100644 apps/explorer/test/support/factories/chain/block_factory.ex delete mode 100644 apps/explorer/test/support/factories/chain/internal_transaction_factory.ex delete mode 100644 apps/explorer/test/support/factories/chain/log_factory.ex delete mode 100644 apps/explorer/test/support/factories/chain/receipt_factory.ex delete mode 100644 apps/explorer/test/support/factories/chain/transaction_factory.ex create mode 100644 apps/explorer_web/lib/phoenix/html/safe.ex create mode 100644 apps/explorer_web/lib/phoenix/param.ex diff --git a/.credo.exs b/.credo.exs index f238df2905..da7e5d1501 100644 --- a/.credo.exs +++ b/.credo.exs @@ -26,6 +26,8 @@ ~r"/_build/", ~r"/deps/", ~r"/node_modules/", + # `There are spaces around operators most of the time, but not here` disagrees with `mix format` + ~r"/apps/explorer/lib/explorer/chain/hash.ex", ~r"/apps/explorer_web/lib/explorer_web.ex" ] }, @@ -63,7 +65,8 @@ # You can customize the priority of any check # Priority values are: `low, normal, high, higher` # - {Credo.Check.Design.AliasUsage, excluded_lastnames: ~w(DateTime Number Repo Time), priority: :low}, + {Credo.Check.Design.AliasUsage, + excluded_lastnames: ~w(DateTime Full Number Repo Time Truncated), priority: :low}, # For some checks, you can also set other parameters # diff --git a/.formatter.exs b/.formatter.exs index e1266c5057..2d2aa09553 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -3,7 +3,7 @@ ".credo.exs", ".formatter.exs", "apps/*/mix.exs", - "apps/*/{config,lib,test}/**/*.{ex,exs}", + "apps/*/{config,lib,priv,test}/**/*.{ex,exs}", "mix.exs", "{config}/**/*.{ex,exs}" ], diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index 2af039f34f..d61f2d1674 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -7,11 +7,6 @@ use Mix.Config url = "https://sokol.poa.network" -config :explorer, :eth_client, - http: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :eth]], - trace_url: "https://sokol-trace.poa.network", - url: url - config :ethereumex, url: url, http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :eth]] @@ -19,10 +14,17 @@ config :ethereumex, # General application configuration config :explorer, ecto_repos: [Explorer.Repo] +config :explorer, Explorer.JSONRPC, + http: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :eth]], + trace_url: "https://sokol-trace.poa.network", + url: url + config :explorer, :ethereum, backend: Explorer.Ethereum.Live config :explorer, Explorer.Integrations.EctoLogger, query_time_ms_threshold: 2_000 +config :explorer, Explorer.Repo, migration_timestamps: [type: :utc_datetime] + # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{Mix.env()}.exs" diff --git a/apps/explorer/lib/backfill_transaction_receipt_ids.ex b/apps/explorer/lib/backfill_transaction_receipt_ids.ex deleted file mode 100644 index 746f043a08..0000000000 --- a/apps/explorer/lib/backfill_transaction_receipt_ids.ex +++ /dev/null @@ -1,14 +0,0 @@ -defmodule BackfillTransactionReceiptIds do - @moduledoc "Backfills transactions with receipt_id values" - alias Explorer.Repo - - def run do - query = """ - UPDATE transactions SET (receipt_id) = ( - SELECT id FROM receipts WHERE receipts.transaction_id = transactions.id - ); - """ - - {:ok, _result} = Repo.query(query, []) - end -end diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index 9dd4767d55..2fb30ee090 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -7,8 +7,11 @@ defmodule Explorer.Application do import Supervisor.Spec, only: [supervisor: 3] - # See https://hexdocs.pm/elixir/Application.html - # for more information on OTP Applications + # Functions + + ## Application callbacks + + @impl Application def start(_type, _args) do # See https://hexdocs.pm/elixir/Supervisor.html # for other strategies and supported options @@ -16,12 +19,14 @@ defmodule Explorer.Application do Supervisor.start_link(children(Mix.env()), opts) end + ## Private Functions + defp children(:test), do: children() defp children(_) do children() ++ [ - Explorer.ETH, + Explorer.JSONRPC, supervisor(Task.Supervisor, [[name: Explorer.TaskSupervisor]], id: Explorer.TaskSupervisor), Explorer.Indexer, Explorer.Chain.Statistics.Server, diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 560188fbdf..2ea6f712b1 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -6,7 +6,7 @@ defmodule Explorer.Chain do import Ecto.Query, only: [from: 2, order_by: 2, preload: 2, where: 2, where: 3] alias Ecto.Multi - alias Explorer.Chain.{Address, Block, InternalTransaction, Log, Receipt, Transaction, Wei} + alias Explorer.Chain.{Address, Block, Hash, InternalTransaction, Log, Receipt, Transaction, Wei} alias Explorer.Repo # Types @@ -33,6 +33,8 @@ defmodule Explorer.Chain do """ @type pagination :: map() + @typep after_hash_option :: {:after_hash, Hash.t()} + @typep inserted_after_option :: {:inserted_after, DateTime.t()} @typep necessity_by_association_option :: {:necessity_by_association, necessity_by_association} @typep pagination_option :: {:pagination, pagination} @@ -56,7 +58,7 @@ defmodule Explorer.Chain do @spec block_to_transactions(Block.t(), [necessity_by_association_option | pagination_option]) :: %Scrivener.Page{ entries: [Transaction.t()] } - def block_to_transactions(%Block{id: block_id}, options \\ []) when is_list(options) do + def block_to_transactions(%Block{hash: block_hash}, options \\ []) when is_list(options) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) pagination = Keyword.get(options, :pagination, %{}) @@ -64,8 +66,8 @@ defmodule Explorer.Chain do from( transaction in Transaction, inner_join: block in assoc(transaction, :block), - where: block.id == ^block_id, - order_by: [desc: transaction.inserted_at] + where: block.hash == ^block_hash, + order_by: [desc: transaction.inserted_at, desc: transaction.hash] ) query @@ -77,14 +79,14 @@ defmodule Explorer.Chain do Counts the number of `t:Explorer.Chain.Transaction.t/0` in the `block`. """ @spec block_to_transaction_count(Block.t()) :: non_neg_integer() - def block_to_transaction_count(%Block{id: block_id}) do + def block_to_transaction_count(%Block{hash: block_hash}) do query = from( transaction in Transaction, - where: transaction.block_id == ^block_id + where: transaction.block_hash == ^block_hash ) - Repo.aggregate(query, :count, :id) + Repo.aggregate(query, :count, :hash) end @doc """ @@ -100,13 +102,25 @@ defmodule Explorer.Chain do @doc """ Creates an address. - ## Examples - - iex> Explorer.Addresses.create_address(%{field: value}) - {:ok, %Address{}} - - iex> Explorer.Addresses.create_address(%{field: bad_value}) - {:error, %Ecto.Changeset{}} + iex> {:ok, %Explorer.Chain.Address{hash: hash}} = Explorer.Chain.create_address( + ...> %{hash: "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"} + ...> ) + ...> to_string(hash) + "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" + + A `String.t/0` value for `Explorer.Chain.Addres.t/0` `hash` must have 40 hexadecimal characters after the `0x` prefix + to prevent short- and long-hash transcription errors. + + iex> {:error, %Ecto.Changeset{errors: errors}} = Explorer.Chain.create_address( + ...> %{hash: "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0"} + ...> ) + ...> errors + [hash: {"is invalid", [type: Explorer.Chain.Hash.Truncated, validation: :cast]}] + iex> {:error, %Ecto.Changeset{errors: errors}} = Explorer.Chain.create_address( + ...> %{hash: "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0ba"} + ...> ) + ...> errors + [hash: {"is invalid", [type: Explorer.Chain.Hash.Truncated, validation: :cast]}] """ @spec create_address(map()) :: {:ok, Address.t()} | {:error, Ecto.Changeset.t()} @@ -116,38 +130,6 @@ defmodule Explorer.Chain do |> Repo.insert() end - @doc """ - Ensures that an `t:Explorer.Address.t/0` exists with the given `hash`. - - If a `t:Explorer.Address.t/0` with `hash` already exists, it is returned - - iex> Explorer.Addresses.ensure_hash_address(existing_hash) - {:ok, %Address{}} - - If a `t:Explorer.Address.t/0` does not exist with `hash`, it is created and returned - - iex> Explorer.Addresses.ensure_hash_address(new_hash) - {:ok, %Address{}} - - There is a chance of a race condition when interacting with the database: the `t:Explorer.Address.t/0` may not exist - when first checked, then already exist when it is tried to be created because another connection creates the addres, - then another process deletes the address after this process's connection see it was created, but before it can be - retrieved. In scenario, the address may be not found as only one retry is attempted to prevent infinite loops. - - iex> Explorer.Addresses.ensure_hash_address(flicker_hash) - {:error, :not_found} - - """ - @spec ensure_hash_address(Address.hash()) :: {:ok, Address.t()} | {:error, :not_found} - def ensure_hash_address(hash) when is_binary(hash) do - with {:error, :not_found} <- hash_to_address(hash), - {:error, _} <- create_address(%{hash: hash}) do - # assume race condition occurred and someone else created the address between the first - # hash_to_address and create_address - hash_to_address(hash) - end - end - @doc """ `t:Explorer.Chain.Transaction/0`s from `address`. @@ -162,8 +144,7 @@ defmodule Explorer.Chain do @spec from_address_to_transactions(Address.t(), [ necessity_by_association_option | pagination_option ]) :: %Scrivener.Page{entries: [Transaction.t()]} - def from_address_to_transactions(address = %Address{}, options \\ []) - when is_list(options) do + def from_address_to_transactions(address = %Address{}, options \\ []) when is_list(options) do address_to_transactions(address, Keyword.put(options, :direction, :from)) end @@ -189,20 +170,30 @@ defmodule Explorer.Chain do Returns `{:ok, %Explorer.Chain.Address{}}` if found - iex> hash_to_address("0x0addressaddressaddressaddressaddressaddr") - {:ok, %Explorer.Chain.Address{}} + iex> {:ok, %Explorer.Chain.Address{hash: hash}} = Explorer.Chain.create_address( + ...> %{hash: "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"} + ...> ) + iex> {:ok, %Explorer.Chain.Address{hash: found_hash}} = Explorer.Chain.hash_to_address(hash) + iex> found_hash == hash + true Returns `{:error, :not_found}` if not found - iex> hash_to_address("0x1addressaddressaddressaddressaddressaddr") + iex> {:ok, hash} = Explorer.Chain.string_to_address_hash("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed") + iex> Explorer.Chain.hash_to_address(hash) {:error, :not_found} """ - @spec hash_to_address(Address.hash()) :: {:ok, Address.t()} | {:error, :not_found} - def hash_to_address(hash) do - Address - |> where_hash(hash) - |> preload([:credit, :debit]) + @spec hash_to_address(Hash.Truncated.t()) :: {:ok, Address.t()} | {:error, :not_found} + def hash_to_address(%Hash{byte_count: unquote(Hash.Truncated.byte_count())} = hash) do + query = + from( + address in Address, + where: address.hash == ^hash, + preload: [:credit, :debit] + ) + + query |> Repo.one() |> case do nil -> {:error, :not_found} @@ -210,17 +201,91 @@ defmodule Explorer.Chain do end end + @doc """ + Converts the `t:t/0` to string representation shown to users. + + iex> Explorer.Chain.hash_to_iodata( + ...> %Explorer.Chain.Hash{ + ...> byte_count: 32, + ...> bytes: <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: + ...> big-integer-size(32)-unit(8)>> + ...> } + ...> ) + [ + "0x", + ['9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b'] + ] + + Always pads number, so that it is a valid format for casting. + + iex> Explorer.Chain.hash_to_iodata( + ...> %Explorer.Chain.Hash{ + ...> byte_count: 32, + ...> bytes: <<0x1234567890abcdef :: big-integer-size(32)-unit(8)>> + ...> } + ...> ) + [ + "0x", + [ + [ + [ + [ + [['000', 48, 48, 48], '000', 48, 48, 48], + ['000', 48, 48, 48], + '000', + 48, + 48, + 48 + ], + [['000', 48, 48, 48], '000', 48, 48, 48], + ['000', 48, 48, 48], + '000', + 48, + 48, + 48 + ], + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 48, + 97, + 98, + 99, + 100, + 101, + 102 + ] + ] + ] + + """ + @spec hash_to_iodata(Hash.t()) :: iodata() + def hash_to_iodata(hash) do + Hash.to_iodata(hash) + end + @doc """ Converts `t:Explorer.Chain.Transaction.t/0` `hash` to the `t:Explorer.Chain.Transaction.t/0` with that `hash`. Returns `{:ok, %Explorer.Chain.Transaction{}}` if found - iex> hash_to_transaction("0x0addressaddressaddressaddressaddressaddr") - {:ok, %Explorer.Chain.Transaction{}} + iex> %Transaction{hash: hash} = insert(:transaction) + iex> {:ok, %Explorer.Chain.Transaction{hash: found_hash}} = Explorer.Chain.hash_to_transaction(hash) + iex> found_hash == hash + true Returns `{:error, :not_found}` if not found - iex> hash_to_transaction("0x1addressaddressaddressaddressaddressaddr") + iex> {:ok, hash} = Explorer.Chain.string_to_transaction_hash( + ...> "0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b" + ...> ) + iex> Explorer.Chain.hash_to_transaction(hash) {:error, :not_found} ## Options @@ -229,14 +294,15 @@ defmodule Explorer.Chain do `:required`, and the `t:Explorer.Chain.Transaction.t/0` has no associated record for that association, then the `t:Explorer.Chain.Transaction.t/0` will not be included in the page `entries`. """ - @spec hash_to_transaction(Transaction.hash(), [necessity_by_association_option]) :: + @spec hash_to_transaction(Hash.Full.t(), [necessity_by_association_option]) :: {:ok, Transaction.t()} | {:error, :not_found} - def hash_to_transaction(hash, options \\ []) when is_list(options) do + def hash_to_transaction(%Hash{byte_count: unquote(Hash.Full.byte_count())} = hash, options \\ []) + when is_list(options) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) Transaction + |> where(hash: ^hash) |> join_associations(necessity_by_association) - |> where_hash(hash) |> Repo.one() |> case do nil -> {:error, :not_found} @@ -245,43 +311,29 @@ defmodule Explorer.Chain do end @doc """ - Converts `t:Explorer.Address.t/0` `id` to the `t:Explorer.Address.t/0` with that `id`. - - Returns `{:ok, %Explorer.Address{}}` if found - - iex> id_to_address(123) - {:ok, %Address{}} + Bulk insert tree of resource from a list of blocks - Returns `{:error, :not_found}` if not found - - iex> id_to_address(456) - {:error, :not_found} - - """ - @spec id_to_address(id :: non_neg_integer()) :: {:ok, Address.t()} | {:error, :not_found} - def id_to_address(id) do - Address - |> Repo.get(id) - |> case do - nil -> - {:error, :not_found} + ## Tree - address -> - {:ok, Repo.preload(address, [:credit, :debit])} - end - end + * `t:Explorer.Chain.Block.t/0`s + * `t:Explorer.Chain.Transaction.t/0` + * `t.Explorer.Chain.InternalTransaction.t/0` + * `t.Explorer.Chain.Receipt.t/0` - @doc """ - TODO """ - def import_blocks(raw_blocks, internal_transactions, receipts) do - {blocks, transactions} = extract_blocks(raw_blocks) - + def insert(%{ + blocks_params: blocks_params, + internal_transactions_params: internal_transactions_params, + receipts_params: receipts_params, + transactions_params: transactions_params + }) + when is_list(blocks_params) and is_list(internal_transactions_params) and is_list(receipts_params) and + is_list(transactions_params) do Multi.new() - |> Multi.run(:blocks, &insert_blocks(&1, blocks)) - |> Multi.run(:transactions, &insert_transactions(&1, transactions)) - |> Multi.run(:internal, &insert_internal(&1, internal_transactions)) - |> Multi.run(:receipts, &insert_receipts(&1, receipts)) + |> Multi.run(:blocks, &insert_blocks(&1, blocks_params)) + |> Multi.run(:transactions, &insert_transactions(&1, transactions_params)) + |> Multi.run(:internal, &insert_internal(&1, internal_transactions_params)) + |> Multi.run(:receipts, &insert_receipts(&1, receipts_params)) |> Multi.run(:logs, &insert_logs(&1)) |> Repo.transaction() end @@ -291,22 +343,28 @@ defmodule Explorer.Chain do end @doc """ - The last `t:Explorer.Chain.Transaction.t/0` `id`. + Finds block with greatest number. + + iex> insert(:block, number: 2) + iex> insert(:block, number: 1) + iex> {:ok, %Explorer.Chain.Block{number: number}} = Explorer.Chain.max_numbered_block() + iex> number + 2 + + If there are no blocks `{:error, :not_found}` is returned. + + iex> Explorer.Chain.max_numbered_block() + {:error, :not_found} + """ - @spec last_transaction_id([{:pending, boolean()}]) :: non_neg_integer() - def last_transaction_id(options \\ []) when is_list(options) do - query = - from( - t in Transaction, - select: t.id, - order_by: [desc: t.id], - limit: 1 - ) + @spec max_numbered_block() :: {:ok, Block.t()} | {:error, :not_found} + def max_numbered_block do + query = from(block in Block, order_by: [desc: block.number], limit: 1) - query - |> where_pending(options) - |> Repo.one() - |> Kernel.||(0) + case Repo.one(query) do + nil -> {:error, :not_found} + block -> {:ok, block} + end end @doc """ @@ -385,6 +443,224 @@ defmodule Explorer.Chain do Repo.one(from(r in Receipt, select: count(r.id))) end + @doc """ + Returns the list of collated transactions that occurred recently (10). + + iex> 2 |> insert_list(:transaction) |> validate() + iex> insert(:transaction) # unvalidated transaction + iex> 8 |> insert_list(:transaction) |> validate() + iex> recent_collated_transactions = Explorer.Chain.recent_collated_transactions() + iex> length(recent_collated_transactions) + 10 + iex> Enum.all?(recent_collated_transactions, fn %Explorer.Chain.Transaction{block_hash: block_hash} -> + ...> !is_nil(block_hash) + ...> end) + true + + A `t:Explorer.Chain.Transaction.t/0` `hash` can be supplied to the `:after_hash` option, then only transactions in + after the transaction (with a greater index) in the same block or in a later block (with a greater number) will be + returned. This can be used to generate paging for collated transaction. + + iex> first_block = insert(:block, number: 1) + iex> first_transaction_in_first_block = insert(:transaction, block_hash: first_block.hash, index: 0) + iex> second_transaction_in_first_block = insert(:transaction, block_hash: first_block.hash, index: 1) + iex> second_block = insert(:block, number: 2) + iex> first_transaction_in_second_block = insert(:transaction, block_hash: second_block.hash, index: 0) + iex> after_first_transaciton_in_first_block = Explorer.Chain.recent_collated_transactions( + ...> after_hash: first_transaction_in_first_block.hash + ...> ) + iex> length(after_first_transaciton_in_first_block) + 2 + iex> after_second_transaciton_in_first_block = Explorer.Chain.recent_collated_transactions( + ...> after_hash: second_transaction_in_first_block.hash + ...> ) + iex> length(after_second_transaciton_in_first_block) + 1 + iex> after_first_transaciton_in_second_block = Explorer.Chain.recent_collated_transactions( + ...> after_hash: first_transaction_in_second_block.hash + ...> ) + iex> length(after_first_transaciton_in_second_block) + 0 + + When there are no collated transactions, an empty list is returned. + + iex> insert(:transaction) + iex> Explorer.Chain.recent_collated_transactions() + [] + + Using an unvalidated transaction's hash for `:after_hash` will also yield an empty list. + + iex> %Explorer.Chain.Transaction{hash: hash} = insert(:transaction) + iex> insert(:transaction) + iex> Explorer.Chain.recent_collated_transactions(after_hash: hash) + [] + + ## Options + + * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is + `:required`, and the `t:Explorer.Chain.InternalTransaction.t/0` has no associated record for that association, + then the `t:Explorer.Chain.InternalTransaction.t/0` will not be included in the list. + + """ + @spec recent_collated_transactions([after_hash_option | necessity_by_association_option]) :: [Transaction.t()] + def recent_collated_transactions(options \\ []) when is_list(options) do + necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + + query = + from( + transaction in Transaction, + inner_join: block in assoc(transaction, :block), + order_by: [desc: block.number, desc: transaction.index], + limit: 10 + ) + + query + |> after_hash(options) + |> join_associations(necessity_by_association) + |> Repo.all() + end + + @doc """ + Return the list of pending transactions that occurred recently (10). + + iex> 2 |> insert_list(:transaction) + iex> :transaction |> insert() |> validate() + iex> 8 |> insert_list(:transaction) + iex> recent_pending_transactions = Explorer.Chain.recent_pending_transactions() + iex> length(recent_pending_transactions) + 10 + iex> Enum.all?(recent_pending_transactions, fn %Explorer.Chain.Transaction{block_hash: block_hash} -> + ...> is_nil(block_hash) + ...> end) + true + + A `t:Explorer.Chain.Transaction.t/0` `inserted_at` can be supplied to the `:inserted_after` option, then only pending + transactions inserted after that transaction will be returned. This can be used to generate paging for pending + transactions. + + iex> {:ok, first_inserted_at, 0} = DateTime.from_iso8601("2015-01-23T23:50:07Z") + iex> insert(:transaction, inserted_at: first_inserted_at) + iex> {:ok, second_inserted_at, 0} = DateTime.from_iso8601("2016-01-23T23:50:07Z") + iex> insert(:transaction, inserted_at: second_inserted_at) + iex> after_first_transaction = Explorer.Chain.recent_pending_transactions(inserted_after: first_inserted_at) + iex> length(after_first_transaction) + 1 + iex> after_second_transaction = Explorer.Chain.recent_pending_transactions(inserted_after: second_inserted_at) + iex> length(after_second_transaction) + 0 + + When there are no pending transaction and a collated transaction's inserted_at is used, an empty list is returned + + iex> {:ok, first_inserted_at, 0} = DateTime.from_iso8601("2015-01-23T23:50:07Z") + iex> :transaction |> insert(inserted_at: first_inserted_at) |> validate() + iex> {:ok, second_inserted_at, 0} = DateTime.from_iso8601("2016-01-23T23:50:07Z") + iex> :transaction |> insert(inserted_at: second_inserted_at) |> validate() + iex> Explorer.Chain.recent_pending_transactions(after_inserted_at: first_inserted_at) + [] + + ## Options + + * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is + `:required`, and the `t:Explorer.Chain.InternalTransaction.t/0` has no associated record for that association, + then the `t:Explorer.Chain.InternalTransaction.t/0` will not be included in the list. + + """ + @spec recent_pending_transactions([inserted_after_option | necessity_by_association_option]) :: [Transaction.t()] + def recent_pending_transactions(options \\ []) when is_list(options) do + necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + + query = + from( + transaction in Transaction, + where: is_nil(transaction.block_hash), + order_by: [ + desc: transaction.inserted_at, + # arbitary tie-breaker when inserted at is the same. hash is random distribution, but using it keeps order + # consistent at least + desc: transaction.hash + ], + limit: 10 + ) + + query + |> inserted_after(options) + |> join_associations(necessity_by_association) + |> Repo.all() + end + + @doc """ + The `string` must start with `0x`, then is converted to an integer and then to `t:Explorer.Chain.Hash.Truncated.t/0`. + + iex> Explorer.Chain.string_to_address_hash("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed") + { + :ok, + %Explorer.Chain.Hash{ + byte_count: 20, + bytes: <<0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed :: big-integer-size(20)-unit(8)>> + } + } + + `String.t` format must always have 40 hexadecimal digits after the `0x` base prefix. + + iex> Explorer.Chain.string_to_address_hash("0x0") + :error + + """ + @spec string_to_address_hash(String.t()) :: {:ok, Hash.Truncated.t()} | :error + def string_to_address_hash(string) when is_binary(string) do + Hash.Truncated.cast(string) + end + + @doc """ + The `string` must start with `0x`, then is converted to an integer and then to `t:Explorer.Chain.Hash.t/0`. + + iex> Explorer.Chain.string_to_block_hash( + ...> "0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b" + ...> ) + { + :ok, + %Explorer.Chain.Hash{ + byte_count: 32, + bytes: <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: big-integer-size(32)-unit(8)>> + } + } + + `String.t` format must always have 64 hexadecimal digits after the `0x` base prefix. + + iex> Explorer.Chain.string_to_block_hash("0x0") + :error + + """ + @spec string_to_block_hash(String.t()) :: {:ok, Hash.t()} | :error + def string_to_block_hash(string) when is_binary(string) do + Hash.Full.cast(string) + end + + @doc """ + The `string` must start with `0x`, then is converted to an integer and then to `t:Explorer.Chain.Hash.t/0`. + + iex> Explorer.Chain.string_to_transaction_hash( + ...> "0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b" + ...> ) + { + :ok, + %Explorer.Chain.Hash{ + byte_count: 32, + bytes: <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: big-integer-size(32)-unit(8)>> + } + } + + `String.t` format must always have 64 hexadecimal digits after the `0x` base prefix. + + iex> Explorer.Chain.string_to_transaction_hash("0x0") + :error + + """ + @spec string_to_transaction_hash(String.t()) :: {:ok, Hash.t()} | :error + def string_to_transaction_hash(string) when is_binary(string) do + Hash.Full.cast(string) + end + @doc """ `t:Explorer.Chain.Transaction/0`s to `address`. @@ -406,22 +682,46 @@ defmodule Explorer.Chain do @doc """ Count of `t:Explorer.Chain.Transaction.t/0`. + With no options or an explicit `pending: nil`, both collated and pending transactions will be counted. + + iex> insert(:transaction) + iex> :transaction |> insert() |> validate() + iex> Explorer.Chain.transaction_count() + 2 + iex> Explorer.Chain.transaction_count(pending: nil) + 2 + + To count only collated transactions, pass `pending: false`. + + iex> 2 |> insert_list(:transaction) + iex> 3 |> insert_list(:transaction) |> validate() + iex> Explorer.Chain.transaction_count(pending: false) + 3 + + To count only pending transactions, pass `pending: true`. + + iex> 2 |> insert_list(:transaction) + iex> 3 |> insert_list(:transaction) |> validate() + iex> Explorer.Chain.transaction_count(pending: true) + 2 + ## Options * `:pending` + * `nil` - count all transactions * `true` - only count pending transactions - * `false` - count all transactions + * `false` - only count collated transactions """ @spec transaction_count([{:pending, boolean()}]) :: non_neg_integer() def transaction_count(options \\ []) when is_list(options) do Transaction |> where_pending(options) - |> Repo.aggregate(:count, :id) + |> Repo.aggregate(:count, :hash) end @doc """ - `t:Explorer.Chain.InternalTransaction/0`s in `t:Explorer.Chain.Transaction.t/0` with `hash` + `t:Explorer.Chain.InternalTransaction/0`s in `t:Explorer.Chain.Transaction.t/0` with `hash`. ## Options @@ -430,9 +730,12 @@ defmodule Explorer.Chain do then the `t:Explorer.Chain.InternalTransaction.t/0` will not be included in the list. """ - @spec transaction_hash_to_internal_transactions(Transaction.hash()) :: [InternalTransaction.t()] - def transaction_hash_to_internal_transactions(hash, options \\ []) - when is_binary(hash) and is_list(options) do + @spec transaction_hash_to_internal_transactions(Hash.Full.t()) :: [InternalTransaction.t()] + def transaction_hash_to_internal_transactions( + %Hash{byte_count: unquote(Hash.Full.byte_count())} = hash, + options \\ [] + ) + when is_list(options) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) InternalTransaction @@ -441,34 +744,6 @@ defmodule Explorer.Chain do |> Repo.all() end - @doc """ - Returns the list of transactions that occurred recently (10) before `t:Explorer.Chain.Transaction.t/0` `id`. - - ## Examples - - iex> Explorer.Chain.list_transactions_before_id(id) - [%Explorer.Chain.Transaction{}, ...] - - ## Options - - * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is - `:required`, and the `t:Explorer.Chain.InternalTransaction.t/0` has no associated record for that association, - then the `t:Explorer.Chain.InternalTransaction.t/0` will not be included in the list. - - """ - @spec transactions_recently_before_id(id :: non_neg_integer, [necessity_by_association_option]) :: [ - Transaction.t() - ] - def transactions_recently_before_id(id, options \\ []) when is_list(options) do - necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) - - Transaction - |> join_associations(necessity_by_association) - |> recently_before_id(id) - |> where_pending(options) - |> Repo.all() - end - @doc """ Finds all `t:Explorer.Chain.Log.t/0`s for `t:Explorer.Chain.Transaction.t/0`. @@ -512,25 +787,6 @@ defmodule Explorer.Chain do def transaction_to_status(%Transaction{receipt: %Receipt{status: 0}}), do: :failed - @doc """ - Updates `balance` of `t:Explorer.Address.t/0` with `hash`. - - If `t:Explorer.Address.t/0` with `hash` does not already exist, it is created first. - """ - @spec update_balance(Address.hash(), Address.balance()) :: - {:ok, Address.t()} | {:error, Ecto.Changeset.t()} | {:error, reason :: term} - def update_balance(hash, balance) when is_binary(hash) do - changes = %{ - balance: balance - } - - with {:ok, address} <- ensure_hash_address(hash) do - address - |> Address.balance_changeset(changes) - |> Repo.update() - end - end - @doc """ The `t:Explorer.Chain.Transaction.t/0` or `t:Explorer.Chain.InternalTransaction.t/0` `value` of the `transaction` in `unit`. @@ -547,12 +803,15 @@ defmodule Explorer.Chain do ## Private Functions - defp address_id_to_transactions(address_id, named_arguments) - when is_integer(address_id) and is_list(named_arguments) do + defp address_hash_to_transaction( + %Hash{byte_count: unquote(Hash.Truncated.byte_count())} = address_hash, + named_arguments + ) + when is_list(named_arguments) do field = case Keyword.fetch!(named_arguments, :direction) do - :to -> :to_address_id - :from -> :from_address_id + :to -> :to_address_hash + :from -> :from_address_hash end necessity_by_association = Keyword.get(named_arguments, :necessity_by_association, %{}) @@ -561,35 +820,42 @@ defmodule Explorer.Chain do Transaction |> join_associations(necessity_by_association) |> chronologically() - |> where([t], field(t, ^field) == ^address_id) + |> where([t], field(t, ^field) == ^address_hash) |> Repo.paginate(pagination) end - defp address_to_transactions(%Address{id: address_id}, options) when is_list(options) do - address_id_to_transactions(address_id, options) - end - - defp chronologically(query) do - from(q in query, order_by: [desc: q.inserted_at, desc: q.id]) + defp address_to_transactions(%Address{hash: address_hash}, options) when is_list(options) do + address_hash_to_transaction(address_hash, options) end - defp extract_blocks(raw_blocks) do - timestamps = timestamps() + defp after_hash(query, options) do + case Keyword.fetch(options, :after_hash) do + {:ok, hash} -> + from( + transaction in query, + inner_join: block in assoc(transaction, :block), + join: hash_transaction in Transaction, + on: hash_transaction.hash == ^hash, + inner_join: hash_block in assoc(hash_transaction, :block), + where: + block.number > hash_block.number or + (block.number == hash_block.number and transaction.index > hash_transaction.index) + ) - {blocks, transactions} = - Enum.reduce(raw_blocks, {[], []}, fn raw_block, {blocks_acc, trans_acc} -> - {:ok, block, transactions} = Block.extract(raw_block, timestamps) - {[block | blocks_acc], trans_acc ++ transactions} - end) + :error -> + query + end + end - {Enum.reverse(blocks), transactions} + defp chronologically(query) do + from(q in query, order_by: [desc: q.inserted_at, desc: q.hash]) end - defp for_parent_transaction(query, hash) when is_binary(hash) do + defp for_parent_transaction(query, %Hash{byte_count: unquote(Hash.Full.byte_count())} = hash) do from( child in query, inner_join: transaction in assoc(child, :transaction), - where: fragment("lower(?)", transaction.hash) == ^String.downcase(hash) + where: transaction.hash == ^hash ) end @@ -684,6 +950,16 @@ defmodule Explorer.Chain do {:ok, inserted} end + defp inserted_after(query, options) do + case Keyword.fetch(options, :inserted_after) do + {:ok, inserted_after} -> + from(transaction in query, where: ^inserted_after < transaction.inserted_at) + + :error -> + query + end + end + defp join_association(query, association, necessity) when is_atom(association) do case necessity do :optional -> @@ -700,23 +976,13 @@ defmodule Explorer.Chain do end) end - defp recently_before_id(query, id) do - from( - q in query, - where: q.id < ^id, - order_by: [desc: q.id], - limit: 10 - ) - end - defp timestamps do now = Ecto.DateTime.utc() %{inserted_at: now, updated_at: now} end - defp transaction_hash_to_logs(transaction_hash, options) - when is_binary(transaction_hash) and is_list(options) do - lower_transaction_hash = String.downcase(transaction_hash) + defp transaction_hash_to_logs(%Hash{byte_count: unquote(Hash.Full.byte_count())} = transaction_hash, options) + when is_list(options) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) pagination = Keyword.get(options, :pagination, %{}) @@ -724,7 +990,7 @@ defmodule Explorer.Chain do from( log in Log, join: transaction in assoc(log, :transaction), - where: fragment("lower(?)", transaction.hash) == ^lower_transaction_hash, + where: transaction.hash == ^transaction_hash, order_by: [asc: :index] ) @@ -733,29 +999,18 @@ defmodule Explorer.Chain do |> Repo.paginate(pagination) end - defp where_hash(query, hash) do - from( - q in query, - where: fragment("lower(?)", q.hash) == ^String.downcase(hash) - ) - end - defp where_pending(query, options) when is_list(options) do - pending = Keyword.get(options, :pending, false) + pending = Keyword.get(options, :pending) - where_pending(query, pending) - end + case pending do + false -> + from(transaction in query, where: not is_nil(transaction.block_hash)) - defp where_pending(query, false), do: query + true -> + from(transaction in query, where: is_nil(transaction.block_hash)) - defp where_pending(query, true) do - from( - transaction in query, - where: - fragment( - "NOT EXISTS (SELECT true FROM receipts WHERE receipts.transaction_id = ?)", - transaction.id - ) - ) + nil -> + query + end end end diff --git a/apps/explorer/lib/explorer/chain/address.ex b/apps/explorer/lib/explorer/chain/address.ex index 3cc6d7c30d..5eaaa68194 100644 --- a/apps/explorer/lib/explorer/chain/address.ex +++ b/apps/explorer/lib/explorer/chain/address.ex @@ -5,7 +5,14 @@ defmodule Explorer.Chain.Address do use Explorer.Schema - alias Explorer.Chain.{Credit, Debit} + alias Explorer.Chain.{Credit, Debit, Hash} + + # Constants + + @optional_attrs ~w()a + @required_attrs ~w(hash)a + + # Types @typedoc """ Hash of the public key for this address. @@ -17,6 +24,7 @@ defmodule Explorer.Chain.Address do * `balance_updated_at` - the last time `balance` was recalculated * `credit` - accumulation of all credits to the address `hash` * `debit` - accumulation of all debits to the address `hash` + * `hash` - the hash of the address's public key * `inserted_at` - when this address was inserted * `updated_at` when this address was last updated """ @@ -25,15 +33,15 @@ defmodule Explorer.Chain.Address do balance_updated_at: DateTime.t(), credit: Ecto.Association.NotLoaded.t() | Credit.t() | nil, debit: Ecto.Association.NotLoaded.t() | Debit.t() | nil, - hash: hash(), + hash: Hash.Truncated.t(), inserted_at: DateTime.t(), updated_at: DateTime.t() } + @primary_key {:hash, Hash.Truncated, autogenerate: false} schema "addresses" do field(:balance, :decimal) field(:balance_updated_at, Timex.Ecto.DateTime) - field(:hash, :string) timestamps() @@ -41,26 +49,32 @@ defmodule Explorer.Chain.Address do has_one(:debit, Debit) end - @required_attrs ~w(hash)a - @optional_attrs ~w()a + # Functions + + def balance_changeset(%__MODULE__{} = address, attrs) do + address + |> cast(attrs, [:balance]) + |> validate_required([:balance]) + |> put_balance_updated_at() + end def changeset(%__MODULE__{} = address, attrs) do address |> cast(attrs, @required_attrs, @optional_attrs) |> validate_required(@required_attrs) - |> update_change(:hash, &String.downcase/1) |> unique_constraint(:hash) end - def balance_changeset(%__MODULE__{} = address, attrs) do - address - |> cast(attrs, [:balance]) - |> validate_required([:balance]) - |> put_balance_updated_at() - end + ## Private Functions defp put_balance_updated_at(changeset) do changeset |> put_change(:balance_updated_at, Timex.now()) end + + defimpl String.Chars do + def to_string(%@for{hash: hash}) do + @protocol.to_string(hash) + end + end end diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index 22e2dd84e3..693ee4d929 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -7,8 +7,12 @@ defmodule Explorer.Chain.Block do use Explorer.Schema - alias Ecto.Changeset - alias Explorer.Chain.{Gas, Hash, Transaction} + alias Explorer.Chain.{Address, Gas, Hash, Transaction} + + # Constants + + @required_attrs ~w(difficulty gas_limit gas_used hash miner_hash nonce number parent_hash size timestamp + total_difficulty)a # Types @@ -47,7 +51,8 @@ defmodule Explorer.Chain.Block do gas_limit: Gas.t(), gas_used: Gas.t(), hash: Hash.t(), - miner: Address.hash(), + miner: %Ecto.Association.NotLoaded{} | Address.t(), + miner_hash: Hash.Truncated.t(), nonce: Hash.t(), number: block_number(), parent_hash: Hash.t(), @@ -57,84 +62,33 @@ defmodule Explorer.Chain.Block do transactions: %Ecto.Association.NotLoaded{} | [Transaction.t()] } + # Schema + + @primary_key {:hash, Hash.Full, autogenerate: false} schema "blocks" do field(:difficulty, :decimal) field(:gas_limit, :integer) field(:gas_used, :integer) - field(:hash, :string) - field(:miner, :string) field(:nonce, :string) field(:number, :integer) - field(:parent_hash, :string) field(:size, :integer) field(:timestamp, Timex.Ecto.DateTime) field(:total_difficulty, :decimal) timestamps() + belongs_to(:miner, Address, foreign_key: :miner_hash, references: :hash, type: Hash.Truncated) + belongs_to(:parent, __MODULE__, foreign_key: :parent_hash, references: :hash, type: Hash.Full) has_many(:transactions, Transaction) end - @required_attrs ~w(difficulty gas_limit gas_used hash miner nonce number parent_hash size timestamp total_difficulty)a + # Functions - @doc false def changeset(%__MODULE__{} = block, attrs) do block |> cast(attrs, @required_attrs) |> validate_required(@required_attrs) - |> update_change(:hash, &String.downcase/1) - |> unique_constraint(:hash) - end - - @doc false - def extract(raw_block, %{} = timestamps) do - raw_block - |> extract_block(timestamps) - |> extract_transactions(raw_block["transactions"], timestamps) - end - - def null, do: %__MODULE__{number: -1, timestamp: :calendar.universal_time()} - - def latest(query) do - query |> order_by(desc: :number) - end - - ## Private Functions - - defp extract_block(raw_block, %{} = timestamps) do - attrs = %{ - hash: raw_block["hash"], - number: raw_block["number"], - gas_used: raw_block["gasUsed"], - timestamp: raw_block["timestamp"], - parent_hash: raw_block["parentHash"], - miner: raw_block["miner"], - difficulty: raw_block["difficulty"], - total_difficulty: raw_block["totalDifficulty"], - size: raw_block["size"], - gas_limit: raw_block["gasLimit"], - nonce: raw_block["nonce"] || "0" - } - - case changeset(%__MODULE__{}, attrs) do - %Changeset{valid?: true, changes: changes} -> {:ok, Map.merge(changes, timestamps)} - %Changeset{valid?: false, errors: errors} -> {:error, {:block, errors}} - end - end - - defp extract_transactions({:ok, block_changes}, raw_transactions, %{} = timestamps) do - raw_transactions - |> Enum.map(&Transaction.decode(&1, block_changes.number, timestamps)) - |> Enum.reduce_while({:ok, block_changes, []}, fn - {:ok, trans_changes}, {:ok, block, acc} -> - {:cont, {:ok, block, [trans_changes | acc]}} - - {:error, reason}, _ -> - {:halt, {:error, {:transaction, reason}}} - end) - end - - defp extract_transactions({:error, reason}, _transactions, _timestamps) do - {:error, reason} + |> foreign_key_constraint(:parent_hash) + |> unique_constraint(:hash, name: :blocks_pkey) end end diff --git a/apps/explorer/lib/explorer/chain/credit.ex b/apps/explorer/lib/explorer/chain/credit.ex index ede5471c92..a620d992d4 100644 --- a/apps/explorer/lib/explorer/chain/credit.ex +++ b/apps/explorer/lib/explorer/chain/credit.ex @@ -6,7 +6,7 @@ defmodule Explorer.Chain.Credit do use Explorer.Schema alias Ecto.Adapters.SQL - alias Explorer.Chain.Address + alias Explorer.Chain.{Address, Hash} alias Explorer.Repo @primary_key false @@ -16,7 +16,7 @@ defmodule Explorer.Chain.Credit do timestamps() - belongs_to(:address, Address, primary_key: true) + belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Truncated) end def refresh do diff --git a/apps/explorer/lib/explorer/chain/debit.ex b/apps/explorer/lib/explorer/chain/debit.ex index de6c7f6ed4..6d43bcb33b 100644 --- a/apps/explorer/lib/explorer/chain/debit.ex +++ b/apps/explorer/lib/explorer/chain/debit.ex @@ -6,7 +6,7 @@ defmodule Explorer.Chain.Debit do use Explorer.Schema alias Ecto.Adapters.SQL - alias Explorer.Chain.Address + alias Explorer.Chain.{Address, Hash} alias Explorer.Repo @primary_key false @@ -16,7 +16,7 @@ defmodule Explorer.Chain.Debit do timestamps() - belongs_to(:address, Address, primary_key: true) + belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Truncated) end def refresh do diff --git a/apps/explorer/lib/explorer/chain/hash.ex b/apps/explorer/lib/explorer/chain/hash.ex index 6ad8fefd86..faedc666e8 100644 --- a/apps/explorer/lib/explorer/chain/hash.ex +++ b/apps/explorer/lib/explorer/chain/hash.ex @@ -1,10 +1,279 @@ defmodule Explorer.Chain.Hash do @moduledoc """ - Hash used throughout Ethereum chains. + A [KECCAK-256](https://en.wikipedia.org/wiki/SHA-3) hash. """ + import Bitwise + + # Constants + + @bits_per_byte 8 + @hexadecimal_digits_per_byte 2 + @max_byte_count 32 + + # Struct + + defstruct ~w(byte_count bytes)a + + # Types + @typedoc """ - [KECCAK-256](https://en.wikipedia.org/wiki/SHA-3) hash as a string. + A full [KECCAK-256](https://en.wikipedia.org/wiki/SHA-3) hash is #{@max_byte_count}, but it can also be truncated to + fewer bytes. + """ + @type byte_count :: 1..unquote(@max_byte_count) + + @typedoc """ + A module that implements this behaviour's callbacks + """ + @type t :: %__MODULE__{ + byte_count: byte_count, + bytes: <<_::_*8>> + } + + # Callbacks + + @callback byte_count() :: byte_count() + + # Functions + + @doc """ + Number of bits in a byte + """ + def bits_per_byte, do: 8 + + @doc """ + How many hexadecimal digits are used to represent a byte + """ + def hexadecimal_digits_per_byte, do: 2 + + ## Ecto.Type callbacks + + @doc """ + Casts `term` to `t:t/0` using `c:byte_count/0` in `module` + """ + @spec cast(module(), term()) :: {:ok, t()} | :error + def cast(callback_module, term) when is_atom(callback_module) do + byte_count = callback_module.byte_count() + + case term do + %__MODULE__{byte_count: ^byte_count, bytes: <<_::big-integer-size(byte_count)-unit(@bits_per_byte)>>} = cast -> + {:ok, cast} + + <<"0x", hexadecimal_digits::binary>> -> + cast_hexadecimal_digits(hexadecimal_digits, byte_count) + + integer when is_integer(integer) -> + cast_integer(integer, byte_count) + + _ -> + :error + end + end + + @doc """ + Dumps the `t` `bytes` to `:binary` (`bytea`) format used in database. + """ + @spec dump(module(), term()) :: {:ok, binary} | :error + def dump(callback_module, term) when is_atom(callback_module) do + byte_count = callback_module.byte_count() + + case term do + # ensure inconsistent `t` with a different `byte_count` from the `callback_module` isn't dumped to the database, + # in case `%__MODULE__{}` is set in a field value directly + %__MODULE__{byte_count: ^byte_count, bytes: <<_::big-integer-size(byte_count)-unit(@bits_per_byte)>> = bytes} -> + {:ok, bytes} + + _ -> + :error + end + end + + @doc """ + Loads the binary hash from the database into `t:t/0` if it has `c:byte_count/0` bytes from `callback_module`. """ - @type t :: String.t() + @spec load(module(), term()) :: {:ok, t} | :error + def load(callback_module, term) do + byte_count = callback_module.byte_count() + + case term do + # ensure that only hashes of `byte_count` that matches `callback_module` can be loaded back from database to + # prevent using `Ecto.Type` with wrong byte_count on a database column + <<_::big-integer-size(byte_count)-unit(@bits_per_byte)>> -> + {:ok, %__MODULE__{byte_count: byte_count, bytes: term}} + + _ -> + :error + end + end + + @doc """ + Converts the `t:t/0` to the integer version of the hash + + iex> Explorer.Chain.Hash.to_integer( + ...> %Explorer.Chain.Hash{ + ...> byte_count: 32, + ...> bytes: <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: + ...> big-integer-size(32)-unit(8)>> + ...> } + ...> ) + 0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b + iex> Explorer.Chain.Hash.to_integer( + ...> %Explorer.Chain.Hash{ + ...> byte_count: 20, + ...> bytes: <<0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed :: big-integer-size(20)-unit(8)>> + ...> } + ...> ) + 0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed + + """ + @spec to_integer(t()) :: pos_integer() + def to_integer(%__MODULE__{byte_count: byte_count, bytes: bytes}) do + <> = bytes + + integer + end + + @doc """ + Converts the `t:t/0` to string representation shown to users. + + iex> Explorer.Chain.Hash.to_iodata( + ...> %Explorer.Chain.Hash{ + ...> byte_count: 32, + ...> bytes: <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: + ...> big-integer-size(32)-unit(8)>> + ...> } + ...> ) + [ + "0x", + ['9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b'] + ] + + Always pads number, so that it is a valid format for casting. + + iex> Explorer.Chain.Hash.to_iodata( + ...> %Explorer.Chain.Hash{ + ...> byte_count: 32, + ...> bytes: <<0x1234567890abcdef :: big-integer-size(32)-unit(8)>> + ...> } + ...> ) + [ + "0x", + [ + [ + [ + [ + [['000', 48, 48, 48], '000', 48, 48, 48], + ['000', 48, 48, 48], + '000', + 48, + 48, + 48 + ], + [['000', 48, 48, 48], '000', 48, 48, 48], + ['000', 48, 48, 48], + '000', + 48, + 48, + 48 + ], + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 48, + 97, + 98, + 99, + 100, + 101, + 102 + ] + ] + ] + + """ + @spec to_iodata(t) :: iodata() + def to_iodata(%__MODULE__{byte_count: byte_count} = hash) do + integer = to_integer(hash) + hexadecimal_digit_count = byte_count_to_hexadecimal_digit_count(byte_count) + unprefixed = :io_lib.format('~#{hexadecimal_digit_count}.16.0b', [integer]) + + ["0x", unprefixed] + end + + @doc """ + Converts the `t:t/0` to string representation shown to users. + + iex> Explorer.Chain.Hash.to_string( + ...> %Explorer.Chain.Hash{ + ...> byte_count: 32, + ...> bytes: <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: + ...> big-integer-size(32)-unit(8)>> + ...> } + ...> ) + "0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b" + + Always pads number, so that it is a valid format for casting. + + iex> Explorer.Chain.Hash.to_string( + ...> %Explorer.Chain.Hash{ + ...> byte_count: 32, + ...> bytes: <<0x1234567890abcdef :: big-integer-size(32)-unit(8)>> + ...> } + ...> ) + "0x0000000000000000000000000000000000000000000000001234567890abcdef" + + """ + @spec to_string(t) :: String.t() + def to_string(%__MODULE__{} = hash) do + hash + |> to_iodata() + |> IO.iodata_to_binary() + end + + ## Private Functions + + defp byte_count_to_hexadecimal_digit_count(byte_count) do + byte_count * @hexadecimal_digits_per_byte + end + + defp byte_count_to_max_integer(byte_count) do + (1 <<< (byte_count * @bits_per_byte + 1)) - 1 + end + + defp cast_hexadecimal_digits(hexadecimal_digits, byte_count) when is_binary(hexadecimal_digits) do + hexadecimal_digit_count = byte_count_to_hexadecimal_digit_count(byte_count) + + with ^hexadecimal_digit_count <- String.length(hexadecimal_digits), + {integer, ""} <- Integer.parse(hexadecimal_digits, 16) do + cast_integer(integer, byte_count) + else + _ -> :error + end + end + + defp cast_integer(integer, byte_count) when is_integer(integer) do + max_integer = byte_count_to_max_integer(byte_count) + + case integer do + in_range when 0 <= in_range and in_range <= max_integer -> + {:ok, + %__MODULE__{byte_count: byte_count, bytes: <>}} + + _ -> + :error + end + end + + defimpl String.Chars do + def to_string(hash) do + @for.to_string(hash) + end + end end diff --git a/apps/explorer/lib/explorer/chain/hash/full.ex b/apps/explorer/lib/explorer/chain/hash/full.ex new file mode 100644 index 0000000000..ba906e41c5 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/hash/full.ex @@ -0,0 +1,160 @@ +defmodule Explorer.Chain.Hash.Full do + @moduledoc """ + A 32-byte [KECCAK-256](https://en.wikipedia.org/wiki/SHA-3) hash. + """ + + alias Explorer.Chain.Hash + + @behaviour Ecto.Type + @behaviour Hash + + # Constants + + @byte_count 32 + @hexadecimal_digit_count Hash.hexadecimal_digits_per_byte() * @byte_count + + # Types + + @typedoc """ + A #{@byte_count}-byte hash of the `t:Explorer.Chain.Block.t/0` or `t:Explorer.Chain.Transaction.t/0`. + """ + @type t :: %Hash{byte_count: unquote(@byte_count), bytes: <<_::unquote(@byte_count * Hash.bits_per_byte())>>} + + # Functions + + ## Ecto.Type callbacks + + @doc """ + Casts `term` to `t:t/0` + + If the `term` is already in `t:t/0`, then it is returned + + iex> Explorer.Chain.Hash.Full.cast( + ...> %Explorer.Chain.Hash{ + ...> byte_count: 32, + ...> bytes: <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: + ...> big-integer-size(32)-unit(8)>> + ...> } + ...> ) + { + :ok, + %Explorer.Chain.Hash{ + byte_count: 32, + bytes: <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: big-integer-size(32)-unit(8)>> + } + } + + If the `term` is an `non_neg_integer`, then it is converted to `t:t/0` + + iex> Explorer.Chain.Hash.Full.cast(0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b) + { + :ok, + %Explorer.Chain.Hash{ + byte_count: 32, + bytes: <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: big-integer-size(32)-unit(8)>> + } + } + + If the `non_neg_integer` is too large, then `:error` is returned. + + iex> Explorer.Chain.Hash.Full.cast(0xffff_9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8) + :error + + If the `term` is a `String.t` that starts with `0x`, then is converted to an integer and then to `t:t/0`. + + iex> Explorer.Chain.Hash.Full.cast("0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b") + { + :ok, + %Explorer.Chain.Hash{ + byte_count: 32, + bytes: <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: big-integer-size(32)-unit(8)>> + } + } + + While `non_neg_integers` don't have to be the correct width (because zero padding it difficult with numbers), + `String.t` format must always have #{@hexadecimal_digit_count} hexadecimal digits after the `0x` base prefix. + + iex> Explorer.Chain.Hash.Full.cast("0x0") + :error + + """ + @impl Ecto.Type + @spec cast(term()) :: {:ok, t()} | :error + def cast(term) do + Hash.cast(__MODULE__, term) + end + + @doc """ + Dumps the binary hash to `:binary` (`bytea`) format used in database. + + If the field from the struct is `t:t/0`, then it succeeds. + + iex> Explorer.Chain.Hash.Full.dump( + ...> %Explorer.Chain.Hash{ + ...> byte_count: 32, + ...> bytes: <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: + ...> big-integer-size(32)-unit(8)>> + ...> } + ...> ) + {:ok, <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: big-integer-size(32)-unit(8)>>} + + If the field from the struct is an incorrect format such as `t:Explorer.Chain.Address.Hash.t/0`, `:error` is returned. + + iex> Explorer.Chain.Hash.Full.dump( + ...> %Explorer.Chain.Hash{ + ...> byte_count: 20, + ...> bytes: <<0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed :: big-integer-size(20)-unit(8)>> + ...> } + ...> ) + :error + + """ + @impl Ecto.Type + @spec dump(term()) :: {:ok, binary} | :error + def dump(term) do + Hash.dump(__MODULE__, term) + end + + @doc """ + Loads the binary hash from the database. + + If the binary hash is the correct format, it is returned + + iex> Explorer.Chain.Hash.Full.load( + ...> <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: big-integer-size(32)-unit(8)>> + ...> ) + { + :ok, + %Explorer.Chain.Hash{ + byte_count: 32, + bytes: <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: big-integer-size(32)-unit(8)>> + } + } + + If the binary hash is an incorrect format, such as if an `Explorer.Chain.Address.Hash` field is loaded, `:error` is + returned + + iex> Explorer.Chain.Hash.Full.load( + ...> <<0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed :: big-integer-size(20)-unit(8)>> + ...> ) + :error + + """ + @impl Ecto.Type + @spec load(term()) :: {:ok, t()} | :error + def load(term) do + Hash.load(__MODULE__, term) + end + + @doc """ + The underlying database type: `binary`. `binary` is used because no Postgres integer type is 32 bytes long. + """ + @impl Ecto.Type + @spec type() :: :binary + def type, do: :binary + + ## Explorer.Chain.Hash callbacks + + @impl Hash + def byte_count, do: @byte_count +end diff --git a/apps/explorer/lib/explorer/chain/hash/truncated.ex b/apps/explorer/lib/explorer/chain/hash/truncated.ex new file mode 100644 index 0000000000..877d8010f8 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/hash/truncated.ex @@ -0,0 +1,161 @@ +defmodule Explorer.Chain.Hash.Truncated do + @moduledoc """ + The address (40 (hex) characters / 160 bits / 20 bytes) is derived from the public key (128 (hex) characters / + 512 bits / 64 bytes) which is derived from the private key (64 (hex) characters / 256 bits / 32 bytes). + + The address is actually the last 40 characters of the keccak-256 hash of the public key with `0x` appended. + """ + + alias Explorer.Chain.Hash + + @behaviour Ecto.Type + @behaviour Hash + + # Constants + + @byte_count 20 + @hexadecimal_digit_count Hash.hexadecimal_digits_per_byte() * @byte_count + + # Types + + @typedoc """ + A #{@byte_count}-byte hash of the address public key. + """ + @type t :: %Hash{byte_count: unquote(@byte_count), bytes: <<_::unquote(@byte_count * Hash.bits_per_byte())>>} + + # Functions + + ## Ecto.Type callbacks + + @doc """ + Casts `term` to `t:t/0`. + + If the `term` is already in `t:t/0`, then it is returned + + iex> Explorer.Chain.Hash.Truncated.cast( + ...> %Explorer.Chain.Hash{ + ...> byte_count: 20, + ...> bytes: <<0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed :: big-integer-size(20)-unit(8)>> + ...> } + ...> ) + { + :ok, + %Explorer.Chain.Hash{ + byte_count: 20, + bytes: <<0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed :: big-integer-size(20)-unit(8)>> + } + } + + If the `term` is an `non_neg_integer`, then it is converted to `t:t/0` + + iex> Explorer.Chain.Hash.Truncated.cast(0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed) + { + :ok, + %Explorer.Chain.Hash{ + byte_count: 20, + bytes: <<0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed :: big-integer-size(20)-unit(8)>> + } + } + + If the `non_neg_integer` is too large, then `:error` is returned. + + iex> Explorer.Chain.Hash.Truncated.cast(0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b) + :error + + If the `term` is a `String.t` that starts with `0x`, then is converted to an integer and then to `t:t/0`. + + iex> Explorer.Chain.Hash.Truncated.cast("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed") + { + :ok, + %Explorer.Chain.Hash{ + byte_count: 20, + bytes: <<0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed :: big-integer-size(20)-unit(8)>> + } + } + + While `non_neg_integers` don't have to be the correct width (because zero padding it difficult with numbers), + `String.t` format must always have #{@hexadecimal_digit_count} digits after the `0x` base prefix. + + iex> Explorer.Chain.Hash.Truncated.cast("0x0") + :error + + """ + @impl Ecto.Type + @spec cast(term()) :: {:ok, t()} :: :error + def cast(term) do + Hash.cast(__MODULE__, term) + end + + @doc """ + Dumps the binary hash to `:binary` (`bytea`) format used in database. + + If the field from the struct is `t:t/0`, then it succeeds + + iex> Explorer.Chain.Hash.Truncated.dump( + ...> %Explorer.Chain.Hash{ + ...> byte_count: 20, + ...> bytes: <<0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed :: big-integer-size(20)-unit(8)>> + ...> } + ...> ) + {:ok, <<0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed :: big-integer-size(20)-unit(8)>>} + + If the field from the struct is an incorrect format such as `t:Explorer.Chain.Hash.t/0`, `:error` is returned + + iex> Explorer.Chain.Hash.Truncated.dump( + ...> %Explorer.Chain.Hash{ + ...> byte_count: 32, + ...> bytes: <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: + ...> big-integer-size(32)-unit(8)>> + ...> } + ...> ) + :error + + """ + @impl Ecto.Type + @spec dump(term()) :: {:ok, binary} | :error + def dump(term) do + Hash.dump(__MODULE__, term) + end + + @doc """ + Loads the binary hash from the database. + + If the binary hash is the correct format, it is returned. + + iex> Explorer.Chain.Hash.Truncated.load( + ...> <<0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed :: big-integer-size(20)-unit(8)>> + ...> ) + { + :ok, + %Explorer.Chain.Hash{ + byte_count: 20, + bytes: <<0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed :: big-integer-size(20)-unit(8)>> + } + } + + If the binary hash is an incorrect format, such as if an `Explorer.Chain.Hash` field is loaded, `:error` is returned. + + iex> Explorer.Chain.Hash.Truncated.load( + ...> <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: big-integer-size(32)-unit(8)>> + ...> ) + :error + + """ + @impl Ecto.Type + @spec load(term()) :: {:ok, t} | :error + def load(term) do + Hash.load(__MODULE__, term) + end + + @doc """ + The underlying database type: `binary`. `binary` is used because no Postgres integer type is 20 bytes long. + """ + @impl Ecto.Type + @spec type() :: :binary + def type, do: :binary + + ## Explorer.Chain.Hash callbacks + + @impl Hash + def byte_count, do: @byte_count +end diff --git a/apps/explorer/lib/explorer/chain/internal_transaction.ex b/apps/explorer/lib/explorer/chain/internal_transaction.ex index af839f7008..74c11a1c00 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction.ex @@ -3,7 +3,7 @@ defmodule Explorer.Chain.InternalTransaction do use Explorer.Schema - alias Explorer.Chain.{Address, Gas, Transaction, Wei} + alias Explorer.Chain.{Address, Gas, Hash, Transaction, Wei} @typedoc """ * `"call"` @@ -18,7 +18,6 @@ defmodule Explorer.Chain.InternalTransaction do * `call_type` - the type of call * `from_address` - the source of the `value` * `from_address_hash` - hash of the source of the `value` - * `from_address_id` - foreign key for `from_address` * `gas` - the amount of gas allowed * `gas_used` - the amount of gas used * `index` - the index of this internal transaction inside the `transaction` @@ -26,7 +25,6 @@ defmodule Explorer.Chain.InternalTransaction do * `output` - output bytes from the call * `to_address` - the sink of the `value` * `to_address_hash` - hash of the sink of the `value` - * `to_address_id` - foreign key for `to_address` * `trace_address` - list of traces * `transaction` - transaction in which this transaction occured * `transaction_id` - foreign key for `transaction` @@ -35,58 +33,69 @@ defmodule Explorer.Chain.InternalTransaction do @type t :: %__MODULE__{ call_type: call_type, from_address: %Ecto.Association.NotLoaded{} | Address.t(), - from_address_hash: Address.hash(), - from_address_id: non_neg_integer(), + from_address_hash: Hash.Truncated.t(), gas: Gas.t(), gas_used: Gas.t(), index: non_neg_integer(), input: String.t(), output: String.t(), to_address: %Ecto.Association.NotLoaded{} | Address.t(), - to_address_hash: Address.hash(), - to_address_id: non_neg_integer(), + to_address_hash: Hash.Truncated.t(), trace_address: [non_neg_integer()], transaction: %Ecto.Association.NotLoaded{} | Transaction.t(), - transaction_id: non_neg_integer(), + transaction_hash: Explorer.Chain.Hash.t(), value: Wei.t() } schema "internal_transactions" do field(:call_type, :string) - field(:from_address_hash, :string) field(:gas, :decimal) field(:gas_used, :decimal) field(:index, :integer) field(:input, :string) field(:output, :string) - field(:to_address_hash, :string) field(:trace_address, {:array, :integer}) field(:value, :decimal) timestamps() - belongs_to(:from_address, Address) - belongs_to(:to_address, Address) - belongs_to(:transaction, Transaction) + belongs_to( + :from_address, + Address, + foreign_key: :from_address_hash, + references: :hash, + type: Hash.Truncated + ) + + belongs_to( + :to_address, + Address, + foreign_key: :to_address_hash, + references: :hash, + type: Hash.Truncated + ) + + belongs_to(:transaction, Transaction, foreign_key: :transaction_hash, references: :hash, type: Hash.Full) 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) + @required_attrs ~w(call_type from_address_hash gas gas_used index to_address_hash trace_address transaction_hash + value)a + @allowed_attrs @optional_attrs ++ @required_attrs def changeset(%__MODULE__{} = internal_transaction, attrs \\ %{}) do internal_transaction - |> cast(attrs, @required_attrs ++ @optional_attrs) + |> cast(attrs, @allowed_attrs) |> validate_required(@required_attrs) - |> foreign_key_constraint(:transaction_id) - |> foreign_key_constraint(:to_address_id) - |> foreign_key_constraint(:from_address_id) - |> unique_constraint(:transaction_id, name: :internal_transactions_transaction_id_index_index) + |> foreign_key_constraint(:from_address_hash) + |> foreign_key_constraint(:to_address_hash) + |> foreign_key_constraint(:transaction_hash) + |> unique_constraint(:transaction_hash) end - def extract(trace, transaction_id, %{} = timestamps) do + def extract(trace, transaction_hash, %{} = timestamps) do %{ - transaction_id: transaction_id, + transaction_hash: transaction_hash, index: 0, call_type: trace["action"]["callType"] || trace["type"], to_address_hash: to_address(trace), diff --git a/apps/explorer/lib/explorer/chain/log.ex b/apps/explorer/lib/explorer/chain/log.ex index 6b81c2139f..2e608c1e90 100644 --- a/apps/explorer/lib/explorer/chain/log.ex +++ b/apps/explorer/lib/explorer/chain/log.ex @@ -3,12 +3,10 @@ defmodule Explorer.Chain.Log do use Explorer.Schema - alias Explorer.Chain.{Address, Receipt} + alias Explorer.Chain.{Address, Hash, Receipt} - @required_attrs ~w(address_id data index type)a - @optional_attrs ~w( - first_topic second_topic third_topic fourth_topic - )a + @required_attrs ~w(address_hash data index type)a + @optional_attrs ~w(first_topic second_topic third_topic fourth_topic)a schema "logs" do field(:data, :string) @@ -21,7 +19,7 @@ defmodule Explorer.Chain.Log do timestamps() - belongs_to(:address, Address) + belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Truncated) belongs_to(:receipt, Receipt) has_one(:transaction, through: [:receipt, :transaction]) end diff --git a/apps/explorer/lib/explorer/chain/receipt.ex b/apps/explorer/lib/explorer/chain/receipt.ex index 700a97daf6..08bc7857fc 100644 --- a/apps/explorer/lib/explorer/chain/receipt.ex +++ b/apps/explorer/lib/explorer/chain/receipt.ex @@ -3,9 +3,9 @@ defmodule Explorer.Chain.Receipt do use Explorer.Schema - alias Explorer.Chain.{Log, Transaction} + alias Explorer.Chain.{Hash, Log, Transaction} - @optional_attrs ~w(transaction_id)a + @optional_attrs ~w(transaction_hash)a @required_attrs ~w(cumulative_gas_used gas_used status index)a @allowed_attrs @optional_attrs ++ @required_attrs @@ -15,7 +15,7 @@ defmodule Explorer.Chain.Receipt do field(:status, :integer) field(:index, :integer) - belongs_to(:transaction, Transaction) + belongs_to(:transaction, Transaction, foreign_key: :transaction_hash, references: :hash, type: Hash.Full) has_many(:logs, Log) timestamps() @@ -29,18 +29,18 @@ defmodule Explorer.Chain.Receipt do |> cast_assoc(:transaction) |> cast_assoc(:logs) |> validate_required(@required_attrs) - |> foreign_key_constraint(:transaction_id) - |> unique_constraint(:transaction_id) + |> foreign_key_constraint(:transaction_hash) + |> unique_constraint(:transaction_hash) end - def extract(raw_receipt, transaction_id, %{} = timestamps) do + def extract(raw_receipt, transaction_hash, %{} = timestamps) do logs = raw_receipt |> Map.fetch!("logs") |> Enum.map(&extract_log(&1, timestamps)) receipt = %{ - transaction_id: transaction_id, + transaction_hash: transaction_hash, index: raw_receipt["transactionIndex"], cumulative_gas_used: raw_receipt["cumulativeGasUsed"], gas_used: raw_receipt["gasUsed"], diff --git a/apps/explorer/lib/explorer/chain/statistics.ex b/apps/explorer/lib/explorer/chain/statistics.ex index 5fccdd65d7..b757b6d754 100644 --- a/apps/explorer/lib/explorer/chain/statistics.ex +++ b/apps/explorer/lib/explorer/chain/statistics.ex @@ -6,8 +6,8 @@ defmodule Explorer.Chain.Statistics do import Ecto.Query alias Ecto.Adapters.SQL + alias Explorer.{Chain, Repo} alias Explorer.Chain.{Block, Transaction} - alias Explorer.Repo alias Timex.Duration # Constants @@ -23,9 +23,9 @@ defmodule Explorer.Chain.Statistics do """ @transaction_count_query """ - SELECT count(transactions.id) + SELECT count(transactions.hash) FROM transactions - JOIN blocks ON blocks.id = transactions.block_id + JOIN blocks ON blocks.hash = transactions.block_hash WHERE blocks.timestamp > NOW() - interval '1 day' """ @@ -33,7 +33,7 @@ defmodule Explorer.Chain.Statistics do SELECT COUNT(missing_number) FROM generate_series(0, $1, 1) AS missing_number LEFT JOIN blocks ON missing_number = blocks.number - WHERE blocks.id IS NULL + WHERE blocks.hash IS NULL """ @lag_query """ @@ -47,13 +47,13 @@ defmodule Explorer.Chain.Statistics do """ @block_velocity_query """ - SELECT count(blocks.id) + SELECT count(blocks.hash) FROM blocks WHERE blocks.inserted_at > NOW() - interval '1 minute' """ @transaction_velocity_query """ - SELECT count(transactions.id) + SELECT count(transactions.hash) FROM transactions WHERE transactions.inserted_at > NOW() - interval '1 minute' """ @@ -130,21 +130,33 @@ defmodule Explorer.Chain.Statistics do limit: 5 ) - last_block = Block |> Block.latest() |> limit(1) |> Repo.one() - latest_block = last_block || Block.null() - %__MODULE__{ - number: latest_block.number, - timestamp: latest_block.timestamp, average_time: query_duration(@average_time_query), - transaction_count: query_value(@transaction_count_query), - skipped_blocks: query_value(@skipped_blocks_query, [latest_block.number]), - lag: query_duration(@lag_query), block_velocity: query_value(@block_velocity_query), - transaction_velocity: query_value(@transaction_velocity_query), blocks: Repo.all(blocks), + lag: query_duration(@lag_query), + transaction_count: query_value(@transaction_count_query), + transaction_velocity: query_value(@transaction_velocity_query), transactions: Repo.all(transactions) } + |> put_max_numbered_block() + end + + ## Private Functions + + defp put_max_numbered_block(state) do + case Chain.max_numbered_block() do + {:ok, %Block{number: number, timestamp: timestamp}} -> + %__MODULE__{ + state + | number: number, + skipped_blocks: query_value(@skipped_blocks_query, [number]), + timestamp: timestamp + } + + {:error, :not_found} -> + state + end end defp query_value(query, args \\ []) do diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index 473ba07c19..afa820bf9d 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -8,10 +8,8 @@ defmodule Explorer.Chain.Transaction do # Constants - @required_attrs ~w(hash value gas gas_price input nonce public_key r s - standard_v transaction_index v)a - - @optional_attrs ~w(to_address_id from_address_id)a + @optional_attrs ~w(block_hash from_address_hash index to_address_hash)a + @required_attrs ~w(gas gas_price hash input nonce public_key r s standard_v v value)a # Types @@ -77,13 +75,14 @@ defmodule Explorer.Chain.Transaction do @type wei_per_gas :: non_neg_integer() @typedoc """ - * `block` - the block in which this transaction was mined/validated - * `block_id` - `block` foreign key + * `block` - the block in which this transaction was mined/validated. `nil` when transaction is pending. + * `block_hash` - `block` foreign key. `nil` when transaction is pending. * `from_address` - the source of `value` - * `from_address_id` - foreign key of `from_address` + * `from_address_hash` - foreign key of `from_address` * `gas` - Gas provided by the sender * `gas_price` - How much the sender is willing to pay for `gas` * `hash` - hash of contents of this transaction + * `index` - index of this transaction in `block`. `nil` when transaction is pending. * `input`- data sent along with the transaction * `internal_transactions` - transactions (value transfers) created while executing contract used for this transaction * `nonce` - the number of transaction made by the sender prior to this one @@ -94,19 +93,19 @@ defmodule Explorer.Chain.Transaction do the X coordinate of a point R, modulo the curve order n. * `standard_v` - The standardized V field of the signature * `to_address` - sink of `value` - * `to_address_id` - `to_address` foreign key - * `transaction_index` - index of this transaction in `block` + * `to_address_hash` - `to_address` foreign key * `v` - The V field of the signature. * `value` - wei transferred from `from_address` to `to_address` """ @type t :: %__MODULE__{ - block: %Ecto.Association.NotLoaded{} | Block.t(), - block_id: non_neg_integer, + block: %Ecto.Association.NotLoaded{} | Block.t() | nil, + block_hash: Hash.t() | nil, from_address: %Ecto.Association.NotLoaded{} | Address.t(), - from_address_id: non_neg_integer(), + from_address_hash: Hash.Truncated.t(), gas: Gas.t(), gas_price: wei_per_gas, hash: Hash.t(), + index: non_neg_integer() | nil, input: String.t(), internal_transactions: %Ecto.Association.NotLoaded{} | [InternalTransaction.t()], nonce: non_neg_integer(), @@ -116,50 +115,67 @@ defmodule Explorer.Chain.Transaction do s: s(), standard_v: standard_v(), to_address: %Ecto.Association.NotLoaded{} | Address.t(), - to_address_id: non_neg_integer(), - transaction_index: non_neg_integer(), + to_address_hash: Hash.Truncated.t(), v: v(), value: Wei.t() } # Schema + @primary_key {:hash, Hash.Full, autogenerate: false} schema "transactions" do field(:gas, :decimal) field(:gas_price, :decimal) - field(:hash, :string) + field(:index, :integer) field(:input, :string) field(:nonce, :integer) field(:public_key, :string) field(:r, :string) field(:s, :string) field(:standard_v, :string) - field(:transaction_index, :string) field(:v, :string) field(:value, :decimal) timestamps() - belongs_to(:block, Block) - belongs_to(:from_address, Address) - has_many(:internal_transactions, InternalTransaction) - has_one(:receipt, Receipt) - belongs_to(:to_address, Address) + belongs_to(:block, Block, foreign_key: :block_hash, references: :hash, type: Hash.Full) + + belongs_to( + :from_address, + Address, + foreign_key: :from_address_hash, + references: :hash, + type: Hash.Truncated + ) + + has_many(:internal_transactions, InternalTransaction, foreign_key: :transaction_hash) + has_one(:receipt, Receipt, foreign_key: :transaction_hash) + + belongs_to( + :to_address, + Address, + foreign_key: :to_address_hash, + references: :hash, + type: Hash.Truncated + ) end + # Functions + @doc false def changeset(%__MODULE__{} = transaction, attrs \\ %{}) do transaction |> cast(attrs, @required_attrs ++ @optional_attrs) |> validate_required(@required_attrs) - |> foreign_key_constraint(:block_id) - |> update_change(:hash, &String.downcase/1) + |> validate_collated() + |> foreign_key_constraint(:block_hash) |> unique_constraint(:hash) end def decode(raw_transaction, block_number, %{} = timestamps) do attrs = %{ hash: raw_transaction["hash"], + index: raw_transaction["transactionIndex"], value: raw_transaction["value"], gas: raw_transaction["gas"], gas_price: raw_transaction["gasPrice"], @@ -169,7 +185,6 @@ defmodule Explorer.Chain.Transaction do r: raw_transaction["r"], s: raw_transaction["s"], standard_v: raw_transaction["standardV"], - transaction_index: raw_transaction["transactionIndex"], v: raw_transaction["v"] } @@ -186,4 +201,22 @@ defmodule Explorer.Chain.Transaction do end def null, do: %__MODULE__{} + + ## Private Functions + + defp validate_collated(%Changeset{} = changeset) do + case {Changeset.get_field(changeset, :block_hash), Changeset.get_field(changeset, :index)} do + {nil, nil} -> + changeset + + {_block_hash, nil} -> + Changeset.add_error(changeset, :index, "can't be blank when transaction is collated into a block") + + {nil, _index} -> + Changeset.add_error(changeset, :index, "can't be set when the transaction is pending") + + _ -> + changeset + end + end end diff --git a/apps/explorer/lib/explorer/ethereum/ethereum.ex b/apps/explorer/lib/explorer/ethereum/ethereum.ex deleted file mode 100644 index 6b4e4f6cd9..0000000000 --- a/apps/explorer/lib/explorer/ethereum/ethereum.ex +++ /dev/null @@ -1,19 +0,0 @@ -defmodule Explorer.Ethereum do - @client Application.get_env(:explorer, :ethereum)[:backend] - - defmodule API do - @moduledoc false - @callback download_balance(String.t()) :: String.t() - end - - defdelegate download_balance(hash), to: @client - - def decode_integer_field(hex) do - {"0x", base_16} = String.split_at(hex, 2) - String.to_integer(base_16, 16) - end - - def decode_time_field(field) do - field |> decode_integer_field() |> Timex.from_unix() - end -end diff --git a/apps/explorer/lib/explorer/ethereum/live.ex b/apps/explorer/lib/explorer/ethereum/live.ex deleted file mode 100644 index 08eb02f8d6..0000000000 --- a/apps/explorer/lib/explorer/ethereum/live.ex +++ /dev/null @@ -1,14 +0,0 @@ -defmodule Explorer.Ethereum.Live do - @moduledoc """ - An implementation for Ethereum that uses the actual node. - """ - - @behaviour Explorer.Ethereum.API - - import Ethereumex.HttpClient, only: [eth_get_balance: 1] - - def download_balance(hash) do - {:ok, result} = eth_get_balance(hash) - result - end -end diff --git a/apps/explorer/lib/explorer/ethereum/test.ex b/apps/explorer/lib/explorer/ethereum/test.ex deleted file mode 100644 index 743e240e59..0000000000 --- a/apps/explorer/lib/explorer/ethereum/test.ex +++ /dev/null @@ -1,9 +0,0 @@ -defmodule Explorer.Ethereum.Test do - @moduledoc """ - An interface for the Ethereum node that does not hit the network - """ - @behaviour Explorer.Ethereum.API - def download_balance(_hash) do - "0x15d231fca629c7c0" - end -end diff --git a/apps/explorer/lib/explorer/ethereumex_extensions.ex b/apps/explorer/lib/explorer/ethereumex_extensions.ex deleted file mode 100644 index 7f80869f8b..0000000000 --- a/apps/explorer/lib/explorer/ethereumex_extensions.ex +++ /dev/null @@ -1,14 +0,0 @@ -defmodule Explorer.EthereumexExtensions do - @moduledoc """ - Downloads the trace for a Transaction from a node. - """ - - alias Ethereumex.HttpClient - - @dialyzer {:nowarn_function, trace_transaction: 1} - def trace_transaction(hash) do - params = [hash, ["trace"]] - {:ok, trace} = HttpClient.request("trace_replayTransaction", params, []) - trace - end -end diff --git a/apps/explorer/lib/explorer/indexer.ex b/apps/explorer/lib/explorer/indexer.ex index 1b1ad71400..95c05da187 100644 --- a/apps/explorer/lib/explorer/indexer.ex +++ b/apps/explorer/lib/explorer/indexer.ex @@ -1,11 +1,13 @@ defmodule Explorer.Indexer do @moduledoc """ - TODO + Indexers an Ethereum-based chain using JSONRPC. """ alias Explorer.Chain alias Explorer.Chain.Block + # Functions + def child_spec(opts) do %{ id: __MODULE__, @@ -16,9 +18,6 @@ defmodule Explorer.Indexer do } end - @doc """ - TODO - """ def last_indexed_block_number do case Chain.get_latest_block() do %Block{number: num} -> num @@ -26,9 +25,6 @@ defmodule Explorer.Indexer do end end - @doc """ - TODO - """ def next_block_number do case last_indexed_block_number() do 0 -> 0 diff --git a/apps/explorer/lib/explorer/indexer/block_fetcher.ex b/apps/explorer/lib/explorer/indexer/block_fetcher.ex index 717da65048..46eb0a5ab0 100644 --- a/apps/explorer/lib/explorer/indexer/block_fetcher.ex +++ b/apps/explorer/lib/explorer/indexer/block_fetcher.ex @@ -6,63 +6,42 @@ defmodule Explorer.Indexer.BlockFetcher do - after gensis index transition to RT index """ + use GenServer require Logger - alias Explorer.{Chain, ETH, Indexer} + alias Explorer.{Chain, Indexer, JSONRPC} alias Explorer.Indexer.Sequence + alias Explorer.JSONRPC.Transactions + + # Struct defstruct ~w(current_block genesis_task subscription_id)a - @batch_size 50 - @blocks_concurrency 10 + # Constants - @receipts_batch_size 250 - @receipts_concurrency 10 + # 50 + @batch_size 1 + @blocks_concurrency 10 @internal_batch_size 50 @internal_concurrency 4 @polling_interval 20_000 - @doc """ - Ensures missing block number ranges are chunked into fetchable batches. - """ - def missing_block_numbers do - {count, missing_ranges} = Chain.missing_block_numbers() - - chunked_ranges = - Enum.flat_map(missing_ranges, fn - {start, ending} when ending - start <= @batch_size -> - [{start, ending}] - - {start, ending} -> - start - |> Stream.iterate(&(&1 + @batch_size)) - |> Enum.reduce_while([], fn - chunk_start, acc when chunk_start + @batch_size >= ending -> - {:halt, [{chunk_start, ending} | acc]} - - chunk_start, acc -> - {:cont, [{chunk_start, chunk_start + @batch_size - 1} | acc]} - end) - |> Enum.reverse() - end) + @receipts_batch_size 250 + @receipts_concurrency 10 - {count, chunked_ranges} - end + # Functions def start_link(opts) do GenServer.start_link(__MODULE__, opts, name: __MODULE__) end - def init(_opts) do - send(self(), :index) - :timer.send_interval(15_000, self(), :debug_count) + ## GenServer callbacks - {:ok, %__MODULE__{current_block: 0, genesis_task: nil, subscription_id: nil}} - end + @impl GenServer def handle_info(:index, state) do {count, missing_ranges} = missing_block_numbers() @@ -83,11 +62,11 @@ defmodule Explorer.Indexer.BlockFetcher do def handle_info(:poll, %__MODULE__{subscription_id: subscription_id} = state) do Process.send_after(self(), :poll, @polling_interval) - with {:ok, blocks} when length(blocks) > 0 <- ETH.check_for_updates(subscription_id) do + with {:ok, blocks} when length(blocks) > 0 <- JSONRPC.check_for_updates(subscription_id) do Logger.debug(fn -> "Processing #{length(blocks)} new block(s)" end) # TODO do something with the new blocks - ETH.fetch_blocks_by_hash(blocks) + JSONRPC.fetch_blocks_by_hash(blocks) end {:noreply, state} @@ -96,7 +75,7 @@ defmodule Explorer.Indexer.BlockFetcher do def handle_info({:DOWN, _ref, :process, pid, :normal}, %__MODULE__{genesis_task: pid} = state) do Logger.info(fn -> "Finished index from genesis" end) - {:ok, subscription_id} = ETH.listen_for_new_blocks() + {:ok, subscription_id} = JSONRPC.listen_for_new_blocks() send(self(), :poll) @@ -120,34 +99,16 @@ defmodule Explorer.Indexer.BlockFetcher do {:noreply, state} end - defp stream_import(missing_ranges, current_block) do - {:ok, seq} = Sequence.start_link(missing_ranges, current_block, @batch_size) - - seq - |> Sequence.build_stream() - |> Task.async_stream( - fn {block_start, block_end} = range -> - with {:ok, next, blocks, range} <- ETH.fetch_blocks_by_range(block_start, block_end), - :ok <- cap_seq(seq, next, range), - transaction_hashes <- collect_transaction_hashes(blocks), - {:ok, receipts} <- fetch_transaction_receipts(transaction_hashes), - {:ok, internals} <- fetch_internal_transactions(transaction_hashes) do - import_blocks(blocks, internals, receipts, seq, range) - else - {:error, reason} -> - Logger.debug(fn -> - "failed to fetch blocks #{inspect(range)}: #{inspect(reason)}. Retrying" - end) + @impl GenServer + def init(_opts) do + send(self(), :index) + :timer.send_interval(15_000, self(), :debug_count) - :ok = Sequence.inject_range(seq, range) - end - end, - max_concurrency: @blocks_concurrency, - timeout: :infinity - ) - |> Enum.each(fn {:ok, :ok} -> :ok end) + {:ok, %__MODULE__{current_block: 0, genesis_task: nil, subscription_id: nil}} end + ## Private Functions + defp cap_seq(seq, :end_of_chain, {_block_start, block_end}) do Logger.info("Reached end of blockchain #{inspect(block_end)}") :ok = Sequence.cap(seq) @@ -158,40 +119,52 @@ defmodule Explorer.Indexer.BlockFetcher do :ok end - defp fetch_transaction_receipts([]), do: {:ok, %{}} + defp fetch_internal_transactions([]), do: {:ok, %{}} - defp fetch_transaction_receipts(hashes) do - Logger.debug(fn -> "fetching #{length(hashes)} transaction receipts" end) - stream_opts = [max_concurrency: @receipts_concurrency, timeout: :infinity] + defp fetch_internal_transactions(hashes) do + Logger.debug(fn -> "fetching #{length(hashes)} internal transactions" end) + stream_opts = [max_concurrency: @internal_concurrency, timeout: :infinity] hashes - |> Enum.chunk_every(@receipts_batch_size) - |> Task.async_stream(Ð.fetch_transaction_receipts(&1), stream_opts) + |> Enum.chunk_every(@internal_batch_size) + |> Task.async_stream(&JSONRPC.fetch_internal_transactions(&1), stream_opts) |> Enum.reduce_while({:ok, %{}}, fn - {:ok, {:ok, receipts}}, {:ok, acc} -> {:cont, {:ok, Map.merge(acc, receipts)}} + {:ok, {:ok, trans}}, {:ok, acc} -> {:cont, {:ok, Map.merge(acc, trans)}} {:ok, {:error, reason}}, {:ok, _acc} -> {:halt, {:error, reason}} {:error, reason}, {:ok, _acc} -> {:halt, {:error, reason}} end) end - defp fetch_internal_transactions([]), do: {:ok, %{}} + defp fetch_transaction_receipts([]), do: {:ok, []} - defp fetch_internal_transactions(hashes) do - Logger.debug(fn -> "fetching #{length(hashes)} internal transactions" end) - stream_opts = [max_concurrency: @internal_concurrency, timeout: :infinity] + defp fetch_transaction_receipts(hashes) do + Logger.debug(fn -> "fetching #{length(hashes)} transaction receipts" end) + stream_opts = [max_concurrency: @receipts_concurrency, timeout: :infinity] hashes - |> Enum.chunk_every(@internal_batch_size) - |> Task.async_stream(Ð.fetch_internal_transactions(&1), stream_opts) - |> Enum.reduce_while({:ok, %{}}, fn - {:ok, {:ok, trans}}, {:ok, acc} -> {:cont, {:ok, Map.merge(acc, trans)}} + |> Enum.chunk_every(@receipts_batch_size) + |> Task.async_stream(&JSONRPC.fetch_transaction_receipts(&1), stream_opts) + |> Enum.reduce_while({:ok, []}, fn + {:ok, {:ok, receipt_params}}, {:ok, acc} -> {:cont, {:ok, acc ++ receipt_params}} {:ok, {:error, reason}}, {:ok, _acc} -> {:halt, {:error, reason}} {:error, reason}, {:ok, _acc} -> {:halt, {:error, reason}} end) end - defp import_blocks(blocks, internal_transactions, receipts, seq, range) do - case Chain.import_blocks(blocks, internal_transactions, receipts) do + defp insert(%{ + blocks_params: blocks_params, + internal_transactions_params: internal_transactions_params, + range: range, + receipts_params: receipt_params, + seq: seq, + transactions_params: transactions_params + }) do + case Chain.insert(%{ + blocks_params: blocks_params, + internal_transactions_params: internal_transactions_params, + receipts_params: receipt_params, + transactions_params: transactions_params + }) do {:ok, _results} -> :ok @@ -204,9 +177,65 @@ defmodule Explorer.Indexer.BlockFetcher do end end - defp collect_transaction_hashes(raw_blocks) do - Enum.flat_map(raw_blocks, fn %{"transactions" => transactions} -> - Enum.map(transactions, fn %{"hash" => hash} -> hash end) - end) + defp missing_block_numbers do + {count, missing_ranges} = Chain.missing_block_numbers() + + chunked_ranges = + Enum.flat_map(missing_ranges, fn + {start, ending} when ending - start <= @batch_size -> + [{start, ending}] + + {start, ending} -> + start + |> Stream.iterate(&(&1 + @batch_size)) + |> Enum.reduce_while([], fn + chunk_start, acc when chunk_start + @batch_size >= ending -> + {:halt, [{chunk_start, ending} | acc]} + + chunk_start, acc -> + {:cont, [{chunk_start, chunk_start + @batch_size - 1} | acc]} + end) + |> Enum.reverse() + end) + + {count, chunked_ranges} + end + + defp stream_import(missing_ranges, current_block) do + {:ok, seq} = Sequence.start_link(missing_ranges, current_block, @batch_size) + + seq + |> Sequence.build_stream() + |> Task.async_stream( + fn {block_start, block_end} = range -> + with {:ok, value} <- JSONRPC.fetch_blocks_by_range(block_start, block_end), + # `mix format` bug made the line too long when pattern combined into above line + %{next: next, blocks_params: blocks_params, range: range, transactions_params: transactions_params} = + value, + :ok <- cap_seq(seq, next, range), + transaction_hashes <- Transactions.params_to_hashes(transactions_params), + {:ok, receipts_params} <- fetch_transaction_receipts(transaction_hashes), + {:ok, internal_transactions_params} <- fetch_internal_transactions(transaction_hashes) do + insert(%{ + blocks_params: blocks_params, + internal_transactions_params: internal_transactions_params, + range: range, + receipts_params: receipts_params, + seq: seq, + transactions_params: transactions_params + }) + else + {:error, reason} -> + Logger.debug(fn -> + "failed to fetch blocks #{inspect(range)}: #{inspect(reason)}. Retrying" + end) + + :ok = Sequence.inject_range(seq, range) + end + end, + max_concurrency: @blocks_concurrency, + timeout: :infinity + ) + |> Enum.each(fn {:ok, :ok} -> :ok end) end end diff --git a/apps/explorer/lib/explorer/indexer/sequence.ex b/apps/explorer/lib/explorer/indexer/sequence.ex index e63ed843db..60da351423 100644 --- a/apps/explorer/lib/explorer/indexer/sequence.ex +++ b/apps/explorer/lib/explorer/indexer/sequence.ex @@ -3,33 +3,31 @@ defmodule Explorer.Indexer.Sequence do use Agent + # Struct + defstruct ~w(current mode queue step)a + # Types + @type range :: {pos_integer(), pos_integer()} - @doc """ - Stars a process for managing a block sequence. - """ - @spec start_link([range()], pos_integer(), pos_integer()) :: Agent.on_start() - def start_link(initial_ranges, range_start, step) do - Agent.start_link(fn -> - %__MODULE__{ - current: range_start, - step: step, - mode: :infinite, - queue: :queue.from_list(initial_ranges) - } - end) - end + # Functions @doc """ - Adds a range of block numbers to the sequence. + Builds an enumerable stream using a sequencer agent. """ - @spec inject_range(pid(), range()) :: :ok - def inject_range(sequencer, {_first, _last} = range) when is_pid(sequencer) do - Agent.update(sequencer, fn state -> - %__MODULE__{state | queue: :queue.in(range, state.queue)} - end) + @spec build_stream(pid()) :: Enumerable.t() + def build_stream(sequencer) when is_pid(sequencer) do + Stream.resource( + fn -> sequencer end, + fn seq -> + case pop(seq) do + :halt -> {:halt, seq} + range -> {[range], seq} + end + end, + fn seq -> seq end + ) end @doc """ @@ -43,20 +41,13 @@ defmodule Explorer.Indexer.Sequence do end @doc """ - Builds an enumerable stream using a sequencer agent. + Adds a range of block numbers to the sequence. """ - @spec build_stream(pid()) :: Enumerable.t() - def build_stream(sequencer) when is_pid(sequencer) do - Stream.resource( - fn -> sequencer end, - fn seq -> - case pop(seq) do - :halt -> {:halt, seq} - range -> {[range], seq} - end - end, - fn seq -> seq end - ) + @spec inject_range(pid(), range()) :: :ok + def inject_range(sequencer, {_first, _last} = range) when is_pid(sequencer) do + Agent.update(sequencer, fn state -> + %__MODULE__{state | queue: :queue.in(range, state.queue)} + end) end @doc """ @@ -77,4 +68,19 @@ defmodule Explorer.Indexer.Sequence do end end) end + + @doc """ + Stars a process for managing a block sequence. + """ + @spec start_link([range()], pos_integer(), pos_integer()) :: Agent.on_start() + def start_link(initial_ranges, range_start, step) do + Agent.start_link(fn -> + %__MODULE__{ + current: range_start, + step: step, + mode: :infinite, + queue: :queue.from_list(initial_ranges) + } + end) + end end diff --git a/apps/explorer/lib/explorer/indexer/supervisor.ex b/apps/explorer/lib/explorer/indexer/supervisor.ex index daa1652d13..686df6f0ad 100644 --- a/apps/explorer/lib/explorer/indexer/supervisor.ex +++ b/apps/explorer/lib/explorer/indexer/supervisor.ex @@ -3,12 +3,19 @@ defmodule Explorer.Indexer.Supervisor do Supervising the fetchers for the `Explorer.Indexer` """ + use Supervisor + alias Explorer.Indexer.BlockFetcher + # Functions + def start_link(opts) do Supervisor.start_link(__MODULE__, opts) end + ## Supervisor callbacks + + @impl Supervisor def init(_opts) do children = [ {BlockFetcher, []} diff --git a/apps/explorer/lib/explorer/eth.ex b/apps/explorer/lib/explorer/jsonrpc.ex similarity index 65% rename from apps/explorer/lib/explorer/eth.ex rename to apps/explorer/lib/explorer/jsonrpc.ex index 48aff34fda..7eab0b21a1 100644 --- a/apps/explorer/lib/explorer/eth.ex +++ b/apps/explorer/lib/explorer/jsonrpc.ex @@ -1,43 +1,72 @@ -defmodule Explorer.ETH do +defmodule Explorer.JSONRPC do @moduledoc """ Ethereum JSONRPC client. ## Configuration - Configuration for parity URLs can be provided with the - following mix config: + Configuration for parity URLs can be provided with the following mix config: - config :explorer, :eth_client, + config :explorer, Explorer.JSONRPC, url: "https://sokol.poa.network", trace_url: "https://sokol-trace.poa.network", http: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :eth]] - Note: the tracing node URL is provided separately from `:url`, via - `:trace_url`. The trace URL and is used for `fetch_internal_transactions`, - which is only a supported method on tracing nodes. The `:http` option is - passed directly to the HTTP library (`HTTPoison`), which forwards the - options down to `:hackney`. + Note: the tracing node URL is provided separately from `:url`, via `:trace_url`. The trace URL and is used for + `fetch_internal_transactions`, which is only a supported method on tracing nodes. The `:http` option is passed + directly to the HTTP library (`HTTPoison`), which forwards the options down to `:hackney`. """ + require Logger - def child_spec(_opts) do - :hackney_pool.child_spec(:eth, recv_timeout: 60_000, timeout: 60_000, max_connections: 1000) - end + alias Explorer.JSONRPC.{Blocks, Receipts, Transactions} - @doc """ - Creates a filter subscription that can be polled for retreiving new blocks. + # Types + + @typedoc """ + Truncated 20-byte [KECCAK-256](https://en.wikipedia.org/wiki/SHA-3) hash encoded as a hexadecimal number in a + `String.t`. """ - def listen_for_new_blocks do - id = DateTime.utc_now() |> DateTime.to_unix() + @type address :: String.t() - request = %{ - "id" => id, - "jsonrpc" => "2.0", - "method" => "eth_newBlockFilter", - "params" => [] - } + @typedoc """ + Binary data encoded as a single hexadecimal number in a `String.t` + """ + @type data :: String.t() - json_rpc(request, config(:url)) + @typedoc """ + A full 32-byte [KECCAK-256](https://en.wikipedia.org/wiki/SHA-3) hash encoded as a hexadecimal number in a `String.t` + + ## Example + + "0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331" + + """ + @type hash :: String.t() + + @typedoc """ + 8 byte [KECCAK-256](https://en.wikipedia.org/wiki/SHA-3) hash of the proof-of-work. + """ + @type nonce :: String.t() + + @typedoc """ + A number encoded as a hexadecimal number in a `String.t` + + ## Example + + "0x1b4" + + """ + @type quantity :: String.t() + + @typedoc """ + Unix timestamp encoded as a hexadecimal number in a `String.t` + """ + @type timestamp :: String.t() + + # Functions + + def child_spec(_opts) do + :hackney_pool.child_spec(:eth, recv_timeout: 60_000, timeout: 60_000, max_connections: 1000) end @doc """ @@ -73,13 +102,28 @@ defmodule Explorer.ETH do json_rpc(batched_requests, config(:url)) end - def decode_int(hex) do - {"0x", base_16} = String.split_at(hex, 2) - String.to_integer(base_16, 16) + @doc """ + Fetches blocks by block number range. + """ + def fetch_blocks_by_range(block_start, block_end) do + block_start + |> build_batch_get_block_by_number(block_end) + |> json_rpc(config(:url)) + |> handle_get_block_by_number(block_start, block_end) end - def decode_time(field) do - field |> decode_int() |> Timex.from_unix() + def fetch_internal_transactions(hashes) when is_list(hashes) do + hashes + |> Enum.map(fn hash -> + %{ + "id" => hash, + "jsonrpc" => "2.0", + "method" => "trace_replayTransaction", + "params" => [hash, ["trace"]] + } + end) + |> json_rpc(config(:trace_url)) + |> handle_internal_transactions() end def fetch_transaction_receipts(hashes) when is_list(hashes) do @@ -96,61 +140,62 @@ defmodule Explorer.ETH do |> handle_receipts() end - defp handle_receipts({:ok, results}) do - results_map = - Enum.into(results, %{}, fn %{"id" => hash, "result" => receipt} -> - {hash, - Map.merge(receipt, %{ - "transactionHash" => String.downcase(receipt["transactionHash"]), - "transactionIndex" => decode_int(receipt["transactionIndex"]), - "cumulativeGasUsed" => decode_int(receipt["cumulativeGasUsed"]), - "gasUsed" => decode_int(receipt["gasUsed"]), - "status" => decode_int(receipt["status"]), - "logs" => - Enum.map(receipt["logs"], fn log -> - Map.merge(log, %{"logIndex" => decode_int(log["logIndex"])}) - end) - })} - end) + @doc """ + Creates a filter subscription that can be polled for retreiving new blocks. + """ + def listen_for_new_blocks do + id = DateTime.utc_now() |> DateTime.to_unix() - {:ok, results_map} + request = %{ + "id" => id, + "jsonrpc" => "2.0", + "method" => "eth_newBlockFilter", + "params" => [] + } + + json_rpc(request, config(:url)) end - defp handle_receipts({:error, reason}) do - {:error, reason} + @doc """ + Converts `t:nonce/0` to `t:non_neg_integer/0` + """ + def nonce_to_integer(nonce) do + hexadecimal_to_integer(nonce) end - def fetch_internal_transactions(hashes) when is_list(hashes) do - hashes - |> Enum.map(fn hash -> - %{ - "id" => hash, - "jsonrpc" => "2.0", - "method" => "trace_replayTransaction", - "params" => [hash, ["trace"]] - } - end) - |> json_rpc(config(:trace_url)) - |> handle_internal_transactions() + @doc """ + Converts `t:quantity/0` to `t:non_neg_integer/0`. + """ + def quantity_to_integer(quantity) do + hexadecimal_to_integer(quantity) end - defp handle_internal_transactions({:ok, results}) do - results_map = - Enum.into(results, %{}, fn - %{"error" => error} -> - throw({:error, error}) + @doc """ + Converts `t:timestamp/0` to `t:DateTime.t/0` + """ + def timestamp_to_datetime(timestamp) do + timestamp + |> hexadecimal_to_integer() + |> Timex.from_unix() + end - %{"id" => hash, "result" => %{"trace" => traces}} -> - {hash, Enum.map(traces, &decode_trace(&1))} - end) + ## Private Functions - {:ok, results_map} - catch - {:error, reason} -> {:error, reason} + defp build_batch_get_block_by_number(block_start, block_end) do + for current <- block_start..block_end do + %{ + "id" => current, + "jsonrpc" => "2.0", + "method" => "eth_getBlockByNumber", + "params" => [int_to_hash_string(current), true] + } + end end - defp handle_internal_transactions({:error, reason}) do - {:error, reason} + defp config(key) do + :explorer + |> Application.fetch_env!(__MODULE__) + |> Keyword.fetch!(key) end defp decode_trace(%{"action" => action} = trace) do @@ -158,38 +203,29 @@ defmodule Explorer.ETH do |> Map.merge(%{ "action" => Map.merge(action, %{ - "value" => decode_int(action["value"]), - "gas" => decode_int(action["gas"]) + "value" => quantity_to_integer(action["value"]), + "gas" => quantity_to_integer(action["gas"]) }) }) |> put_gas_used() end - defp put_gas_used(%{"error" => _} = trace), do: trace + defp encode_json(data), do: Jason.encode_to_iodata!(data) - defp put_gas_used(%{"result" => %{"gasUsed" => gas}} = trace) do - put_in(trace, ["result", "gasUsed"], decode_int(gas)) - end + defp decode_json(body, posted_payload) do + Jason.decode!(body) + rescue + Jason.DecodeError -> + Logger.error(""" + failed to decode json payload: - @doc """ - Fetches blocks by block number range. - """ - def fetch_blocks_by_range(block_start, block_end) do - block_start - |> build_batch_get_block_by_number(block_end) - |> json_rpc(config(:url)) - |> handle_get_block_by_number(block_start, block_end) - end + #{inspect(body)} - defp build_batch_get_block_by_number(block_start, block_end) do - for current <- block_start..block_end do - %{ - "id" => current, - "jsonrpc" => "2.0", - "method" => "eth_getBlockByNumber", - "params" => [int_to_hash_string(current), true] - } - end + #{inspect(posted_payload)} + + """) + + raise("bad jason") end defp handle_get_block_by_number({:ok, results}, block_start, block_end) do @@ -199,52 +235,54 @@ defmodule Explorer.ETH do %{"result" => %{} = block}, {blocks, next} -> {[block | blocks], next} end) - {:ok, next, decode_blocks(blocks), {block_start, block_end}} + elixir_blocks = Blocks.to_elixir(blocks) + elixir_transactions = Blocks.elixir_to_transactions(elixir_blocks) + blocks_params = Blocks.elixir_to_params(elixir_blocks) + transactions_params = Transactions.elixir_to_params(elixir_transactions) + + {:ok, + %{ + next: next, + blocks_params: blocks_params, + range: {block_start, block_end}, + transactions_params: transactions_params + }} end defp handle_get_block_by_number({:error, reason}, block_start, block_end) do {:error, reason, {block_start, block_end}} end - defp decode_blocks(blocks) do - Enum.map(blocks, fn block -> - Map.merge(block, %{ - "hash" => String.downcase(block["hash"]), - "number" => decode_int(block["number"]), - "gasUsed" => decode_int(block["gasUsed"]), - "timestamp" => decode_time(block["timestamp"]), - "difficulty" => decode_int(block["difficulty"]), - "totalDifficulty" => decode_int(block["totalDifficulty"]), - "size" => decode_int(block["size"]), - "gasLimit" => decode_int(block["gasLimit"]), - "transactions" => decode_transactions(block["transactions"]) - }) - end) + defp handle_internal_transactions({:ok, results}) do + results_map = + Enum.into(results, %{}, fn + %{"error" => error} -> + throw({:error, error}) + + %{"id" => hash, "result" => %{"trace" => traces}} -> + {hash, Enum.map(traces, &decode_trace(&1))} + end) + + {:ok, results_map} + catch + {:error, reason} -> {:error, reason} end - defp decode_transactions(transactions) do - Enum.map(transactions, fn transaction -> - Map.merge(transaction, %{ - "hash" => String.downcase(transaction["hash"]), - "value" => decode_int(transaction["value"]), - "gas" => decode_int(transaction["gas"]), - "gasPrice" => decode_int(transaction["gasPrice"]), - "nonce" => decode_int(transaction["nonce"]) - }) - end) + defp handle_internal_transactions({:error, reason}) do + {:error, reason} end - defp json_rpc(payload, url) do - json = encode_json(payload) - headers = [{"Content-Type", "application/json"}] + defp handle_receipts({:ok, results}) do + results_params = + results + |> Receipts.to_elixir() + |> Receipts.elixir_to_params() - case HTTPoison.post(url, json, headers, config(:http)) do - {:ok, %HTTPoison.Response{body: body, status_code: code}} -> - body |> decode_json(payload) |> handle_response(code) + {:ok, results_params} + end - {:error, %HTTPoison.Error{reason: reason}} -> - {:error, reason} - end + defp handle_receipts({:error, reason}) do + {:error, reason} end defp handle_response(resp, 200) do @@ -259,29 +297,28 @@ defmodule Explorer.ETH do {:error, resp} end - defp config(key) do - :explorer - |> Application.fetch_env!(:eth_client) - |> Keyword.fetch!(key) + defp hexadecimal_to_integer("0x" <> hexadecimal_digits) do + String.to_integer(hexadecimal_digits, 16) end - defp encode_json(data), do: Jason.encode_to_iodata!(data) + defp int_to_hash_string(number), do: "0x" <> Integer.to_string(number, 16) - defp decode_json(body, posted_payload) do - Jason.decode!(body) - rescue - Jason.DecodeError -> - Logger.error(""" - failed to decode json payload: + defp json_rpc(payload, url) do + json = encode_json(payload) + headers = [{"Content-Type", "application/json"}] - #{inspect(body)} + case HTTPoison.post(url, json, headers, config(:http)) do + {:ok, %HTTPoison.Response{body: body, status_code: code}} -> + body |> decode_json(payload) |> handle_response(code) - #{inspect(posted_payload)} + {:error, %HTTPoison.Error{reason: reason}} -> + {:error, reason} + end + end - """) + defp put_gas_used(%{"error" => _} = trace), do: trace - raise("bad jason") + defp put_gas_used(%{"result" => %{"gasUsed" => gas}} = trace) do + put_in(trace, ["result", "gasUsed"], quantity_to_integer(gas)) end - - defp int_to_hash_string(number), do: "0x" <> Integer.to_string(number, 16) end diff --git a/apps/explorer/lib/explorer/jsonrpc/block.ex b/apps/explorer/lib/explorer/jsonrpc/block.ex new file mode 100644 index 0000000000..7ec4b53c3b --- /dev/null +++ b/apps/explorer/lib/explorer/jsonrpc/block.ex @@ -0,0 +1,131 @@ +defmodule Explorer.JSONRPC.Block do + @moduledoc """ + Block format as returned by [`eth_getBlockByHash`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbyhash) + and [`eth_getBlockByNumber`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbynumber). + """ + + import Explorer.JSONRPC, only: [nonce_to_integer: 1, quantity_to_integer: 1, timestamp_to_datetime: 1] + + alias Explorer.JSONRPC + alias Explorer.JSONRPC.Transactions + + # Types + + @type elixir :: %{String.t() => non_neg_integer | DateTime.t() | String.t() | nil} + + @typedoc """ + * `"author"` - `t:Explorer.JSONRPC.address/0` that created the block. Aliased by `"miner"`. + * `"difficulty"` - `t:Explorer.JSONRPC.quantity/0`` of the difficulty for this block. + * `"extraData"` - the extra `t:Explorer.JSONRPC.data/0`` field of this block. + * `"gasLimit" - maximum gas `t:Explorer.JSONRPC.quantity/0`` in this block. + * `"gasUsed" - the total `t:Explorer.JSONRPC.quantity/0`` of gas used by all transactions in this block. + * `"hash"` - the `t:Explorer.JSONRPC.hash/0` of the block. + * `"logsBloom"` - `t:Explorer.JSONRPC.data/0`` for the [Bloom filter](https://en.wikipedia.org/wiki/Bloom_filter) for + the logs of the block. `nil` when block is pending. + * `"miner"` - `t:Explorer.JSONRPC.address/0` of the beneficiary to whom the mining rewards were given. Aliased by + `"author"`. + * `"nonce"` - `t:Explorer.JSONRPC.nonce/0`. `nil` when its pending block. + * `"number"` - the block number `t:Explorer.JSONRPC.quantity/0`. `nil` when block is pending. + * `"parentHash" - the `t:Explorer.JSONRPC.hash/0` of the parent block. + * `"receiptsRoot"` - `t:Explorer.JSONRPC.hash/0` of the root of the receipts. + [trie](https://github.com/ethereum/wiki/wiki/Patricia-Tree) of the block. + * `"sealFields"` - UNKNOWN + * `"sha3Uncles"` - `t:Explorer.JSONRPC.hash/0` of the + [uncles](https://bitcoin.stackexchange.com/questions/39329/in-ethereum-what-is-an-uncle-block) data in the block. + * `"signature"` - UNKNOWN + * `"size"` - `t:Explorer.JSONRPC.quantity/0`` of bytes in this block + * `"stateRoot" - `t:Explorer.JSONRPC.hash/0` of the root of the final state + [trie](https://github.com/ethereum/wiki/wiki/Patricia-Tree) of the block. + * `"step"` - UNKNOWN + * `"timestamp"`: the unix timestamp as a `t:Explorer.JSONRPC.quantity/0`` for when the block was collated. + * `"totalDifficulty" - `t:Explorer.JSONRPC.quantity/0`` of the total difficulty of the chain until this block. + * `"transactions"` - `t:list/0` of `t:Explorer.JSONRPC.Transaction.t/0`. + * `"transactionsRoot" - `t:Explorer.JSONRPC.hash/0` of the root of the transaction + [trie](https://github.com/ethereum/wiki/wiki/Patricia-Tree) of the block. + * `uncles`: `t:list/0` of + [uncles](https://bitcoin.stackexchange.com/questions/39329/in-ethereum-what-is-an-uncle-block) + `t:Explorer.JSONRPC.hash/0`. + """ + @type t :: %{String.t() => JSONRPC.data() | JSONRPC.hash() | JSONRPC.quantity() | nil} + + @spec elixir_to_params(elixir) :: map + def elixir_to_params( + %{ + "author" => miner_hash, + "difficulty" => difficulty, + "gasLimit" => gas_limit, + "gasUsed" => gas_used, + "hash" => hash, + "miner" => miner_hash, + "number" => number, + "parentHash" => parent_hash, + "size" => size, + "timestamp" => timestamp, + "totalDifficulty" => total_difficulty + } = elixir + ) do + %{ + difficulty: difficulty, + gas_limit: gas_limit, + gas_used: gas_used, + hash: hash, + miner_hash: miner_hash, + number: number, + parent_hash: parent_hash, + size: size, + timestamp: timestamp, + total_difficulty: total_difficulty + } + |> Map.put(:nonce, Map.get(elixir, "nonce", 0)) + end + + @doc """ + Get `t:Explorer.JSONRPC.Transactions.elixir/0` from `t:elixir/0` + """ + @spec elixir_to_transactions(elixir) :: Transactions.elixir() + def elixir_to_transactions(%{"transactions" => transactions}), do: transactions + + @doc """ + Decodes the stringly typed numerical fields to `t:non_neg_integer/0` and the timestamps to `t:DateTime.t/0` + """ + def to_elixir(block) when is_map(block) do + Enum.into(block, %{}, &entry_to_elixir/1) + end + + def elixir_to_explorer_chain_block_params(elixir) when is_map(elixir) do + Enum.into(elixir, %{}, &elixir_to_explorer_chain_block_param/1) + end + + ## Private Functions + + defp elixir_to_explorer_chain_block_param({"difficulty", difficulty}) when is_integer(difficulty), + do: {:difficulty, difficulty} + + defp elixir_to_explorer_chain_block_param({"gasUsed", gas_used}) when is_integer(gas_used), do: {:gas_used, gas_used} + defp elixir_to_explorer_chain_block_param({"hash", hash}), do: {:hash, hash} + defp elixir_to_explorer_chain_block_param({"number", number}) when is_integer(number), do: {:number, number} + + defp entry_to_elixir({key, quantity}) when key in ~w(difficulty gasLimit gasUsed number size totalDifficulty) do + {key, quantity_to_integer(quantity)} + end + + # double check that no new keys are being missed by requiring explicit match for passthrough + # `t:Explorer.JSONRPC.address/0` and `t:Explorer.JSONRPC.hash/0` pass through as `Explorer.Chain` can verify correct + # hash format + defp entry_to_elixir({key, _} = entry) + when key in ~w(author extraData hash logsBloom miner parentHash receiptsRoot sealFields sha3Uncles signature + stateRoot step transactionsRoot uncles), + do: entry + + defp entry_to_elixir({"nonce" = key, nonce}) do + {key, nonce_to_integer(nonce)} + end + + defp entry_to_elixir({"timestamp" = key, timestamp}) do + {key, timestamp_to_datetime(timestamp)} + end + + defp entry_to_elixir({"transactions" = key, transactions}) do + {key, Transactions.to_elixir(transactions)} + end +end diff --git a/apps/explorer/lib/explorer/jsonrpc/blocks.ex b/apps/explorer/lib/explorer/jsonrpc/blocks.ex new file mode 100644 index 0000000000..c26cd57dc3 --- /dev/null +++ b/apps/explorer/lib/explorer/jsonrpc/blocks.ex @@ -0,0 +1,33 @@ +defmodule Explorer.JSONRPC.Blocks do + @moduledoc """ + Blocks format as returned by [`eth_getBlockByHash`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbyhash) + and [`eth_getBlockByNumber`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbynumber) from batch requests. + """ + + alias Explorer.JSONRPC.{Block, Transactions} + + # Types + + @type elixir :: [Block.elixir()] + @type t :: [Block.t()] + + # Functions + + @spec elixir_to_params(elixir) :: [map] + def elixir_to_params(elixir) when is_list(elixir) do + Enum.map(elixir, &Block.elixir_to_params/1) + end + + @spec elixir_to_transactions(t) :: Transactions.elixir() + def elixir_to_transactions(elixir) when is_list(elixir) do + Enum.flat_map(elixir, &Block.elixir_to_transactions/1) + end + + @doc """ + Decodes the stringly typed numerical fields to `t:non_neg_integer/0` and the timestamps to `t:DateTime.t/0` + """ + @spec to_elixir(t) :: elixir + def to_elixir(blocks) when is_list(blocks) do + Enum.map(blocks, &Block.to_elixir/1) + end +end diff --git a/apps/explorer/lib/explorer/jsonrpc/receipt.ex b/apps/explorer/lib/explorer/jsonrpc/receipt.ex new file mode 100644 index 0000000000..60caae417b --- /dev/null +++ b/apps/explorer/lib/explorer/jsonrpc/receipt.ex @@ -0,0 +1,48 @@ +defmodule Explorer.JSONRPC.Receipt do + @moduledoc """ + Receipts format as returned by + [`eth_getTransactionReceipt`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionreceipt). + """ + + alias Explorer.JSONRPC + + # Types + + @type elixir :: %{String.t() => nil} + + @typedoc """ + * `"contractAddress"` - The contract `t:Explorer.JSONRPC.address/0` created, if the transaction was a contract + creation, otherwise `nil`. + * `"blockHash"` - `t:Explorer.JSONRPC.hash/0` of the block where `"transactionHash"` was in. + * `"blockNumber"` - The block number `t:Explorer.JSONRPC.quanity/0`. + * `"cumulativeGasUsed"` - `t:Explorer.JSONRPC.quantity/0` of gas used when this transaction was executed in the block. + * `"gasUsed"` - `t:Explorer.JSONRPC.quantity/0` of gas used by this specific transaction alone. + * `"logs"` - `t:list/0` of log objects, which this transaction generated. + * `"logsBloom"` - `t:Explorer.JSONRPC.data/0` of 256 Bytes for + [Bloom filter](https://en.wikipedia.org/wiki/Bloom_filter) for light clients to quickly retrieve related logs. + * `"root"` - `t:Explorer.JSONRPC.hash/0` of post-transaction stateroot (pre-Byzantium) + * `"status"` - `t:Explorer.JSONRPC.quantity/0` of either 1 (success) or 0 (failure) (post-Byzantium) + * `"transactionHash" - `t:Explorer.JSONRPC.hash/0` the transaction. + * `"transactionIndex"` - `t:Explorer.JSONRPC.quantity/0` for the transaction index in the block. + """ + @type t :: %{String.t() => JSONRPC.address() | JSONRPC.data() | JSONRPC.hash() | JSONRPC.quantity() | list | nil} + + # Functions + + @spec elixir_to_params(elixir) :: [map] + def elixir_to_params(elixir) when is_list(elixir) do + raise "BOOM" + end + + @spec to_elixir(t) :: elixir + def to_elixir(receipt) when is_map(receipt) do + Enum.into(receipt, %{}, &entry_to_elixir/1) + end + + ## Private Functions + + # double check that no new keys are being missed by requiring explicit match for passthrough + # `t:Explorer.JSONRPC.address/0` and `t:Explorer.JSONRPC.hash/0` pass through as `Explorer.Chain` can verify correct + # hash format + defp entry_to_elixir({key, _} = entry) when key in ~w(foo), do: entry +end diff --git a/apps/explorer/lib/explorer/jsonrpc/receipts.ex b/apps/explorer/lib/explorer/jsonrpc/receipts.ex new file mode 100644 index 0000000000..63e9b6506f --- /dev/null +++ b/apps/explorer/lib/explorer/jsonrpc/receipts.ex @@ -0,0 +1,26 @@ +defmodule Explorer.JSONRPC.Receipts do + @moduledoc """ + Receipts format as returned by + [`eth_getTransactionReceipt`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionreceipt) from batch + requests. + """ + + alias Explorer.JSONRPC.Receipt + + # Types + + @type elixir :: [Receipt.elixir()] + @type t :: [Receipt.t()] + + # Functions + + @spec elixir_to_params(elixir) :: [map] + def elixir_to_params(elixir) when is_list(elixir) do + Enum.map(elixir, &Receipt.elixir_to_params/1) + end + + @spec to_elixir(t) :: elixir + def to_elixir(receipts) when is_list(receipts) do + Enum.map(receipts, &Receipt.to_elixir/1) + end +end diff --git a/apps/explorer/lib/explorer/jsonrpc/transaction.ex b/apps/explorer/lib/explorer/jsonrpc/transaction.ex new file mode 100644 index 0000000000..10ea7289a6 --- /dev/null +++ b/apps/explorer/lib/explorer/jsonrpc/transaction.ex @@ -0,0 +1,84 @@ +defmodule Explorer.JSONRPC.Transaction do + @moduledoc """ + Transaction format included in the return of + [`eth_getBlockByHash`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbyhash) + and [`eth_getBlockByNumber`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbynumber) and returned by + [`eth_getTransactionByHash`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionbyhash), + [`eth_getTransactionByBlockHashAndIndex`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionbyblockhashandindex), + and [`eth_getTransactionByBlockNumberAndIndex`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionbyblocknumberandindex) + """ + + import Explorer.JSONRPC, only: [quantity_to_integer: 1] + + alias Explorer.JSONRPC + + # Types + + @typedoc """ + * `"blockHash"` - `t:Explorer.JSONRPC.hash/0` of the block this transaction is in. `nil` when transaction is pending. + * `"blockNumber"` - `t:Explorer.JSONRPC.quantity/0` for the block number this transaction is in. `nil` when + transaction is pending. + * `"from"` - `t:Explorer.JSONRPC.address/0` of the sender. + * `"gas"` - `t:Explorer.JSONRPC.quantity/0` of gas provided by the sender. This is the max gas that may be used. + `gas * gasPrice` is the max fee in wei that the sender is willing to pay for the transaction to be executed. + * `"gasPrice"` - `t:Explorer.JSONRPC.quantity/0` of wei to pay per unit of gas used. + * `"hash"` - `t:Explorer.JSONRPC.hash/0` of the transaction + * `"input"` - `t:Explorer.JSONRPC.data/0` sent along with the transaction, such as input to the contract. + * `"nonce"` - `t:Explorer.JSONRPC.quantity/0` of transactions made by the sender prior to this one. + * `"to"` - `t:Explorer.JSONRPC.address/0` of the receiver. `nil` when it is a contract creation transaction. + * `"transactionIndex"` - `t:Explorer.JSONRPC.quantity/0` for the index of the transaction in the block. `nil` when + transaction is pending. + * `"value"` - `t:Explorer.JSONRPC.quantity/0` of wei transfered + """ + @type t :: %{String.t() => JSONRPC.address() | JSONRPC.hash() | JSONRPC.quantity() | nil} + + # Functions + + def elixir_to_params(%{ + "gas" => gas, + "gasPrice" => gas_price, + "hash" => hash, + "input" => input, + "nonce" => nonce, + "publicKey" => public_key, + "r" => r, + "s" => s, + "standardV" => standard_v, + "transactionIndex" => index, + "v" => v, + "value" => value + }) do + %{ + gas: gas, + gas_price: gas_price, + hash: hash, + index: index, + input: input, + nonce: nonce, + public_key: public_key, + r: r, + s: s, + standard_v: standard_v, + v: v, + value: value + } + end + + def params_to_hash(%{"hash" => hash}), do: hash + + @doc """ + Decodes the stringly typed numerical fields to `t:non_neg_integer/0`. + """ + def to_elixir(transaction) when is_map(transaction) do + Enum.into(transaction, %{}, &to_elixir/1) + end + + # double check that no new keys are being missed by requiring explicit match for passthrough + # `t:Explorer.JSONRPC.address/0` and `t:Explorer.JSONRPC.hash/0` pass through as `Explorer.Chain` can verify correct + # hash format + def to_elixir({key, value}) when key in ~w(blockHash from hash input jsonrpc), do: {key, value} + + def to_elixir({key, quantity}) when key in ~w(blockNumber gas gasPrice nonce transactionIndex value) do + {key, quantity_to_integer(quantity)} + end +end diff --git a/apps/explorer/lib/explorer/jsonrpc/transactions.ex b/apps/explorer/lib/explorer/jsonrpc/transactions.ex new file mode 100644 index 0000000000..5802ef4309 --- /dev/null +++ b/apps/explorer/lib/explorer/jsonrpc/transactions.ex @@ -0,0 +1,21 @@ +defmodule Explorer.JSONRPC.Transactions do + @moduledoc """ + List of transactions format as included in return from + [`eth_getBlockByHash`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbyhash) and + [`eth_getBlockByNumber`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbynumber). + """ + + alias Explorer.JSONRPC.Transaction + + def elixir_to_params(elixir) when is_list(elixir) do + Enum.map(elixir, &Transaction.elixir_to_params/1) + end + + def params_to_hashes(params) when is_list(params) do + Enum.map(params, &Transaction.params_to_hash/1) + end + + def to_elixir(transactions) when is_list(transactions) do + Enum.map(transactions, &Transaction.to_elixir/1) + end +end diff --git a/apps/explorer/lib/giant_address_migrator.ex b/apps/explorer/lib/giant_address_migrator.ex deleted file mode 100644 index d9e62c333b..0000000000 --- a/apps/explorer/lib/giant_address_migrator.ex +++ /dev/null @@ -1,45 +0,0 @@ -defmodule GiantAddressMigrator do - @moduledoc "Migrate away from Address join tables." - - require Logger - - alias Explorer.Repo - - def migrate do - for n <- 1..20 do - chunk_size = 500_000 - lower = n * chunk_size - chunk_size - upper = n * chunk_size - - Logger.info("fetching results between #{lower} and #{upper}") - work_on_transactions_between_ids(lower, upper) - end - end - - def work_on_transactions_between_ids(lower, upper) do - query = """ - select transactions.id, from_addresses.address_id as from_address_id, to_addresses.address_id as to_address_id - FROM transactions - inner join from_addresses on from_addresses.transaction_id = id - inner join to_addresses on to_addresses.transaction_id = id - where transactions.id >= #{lower} AND transactions.id < #{upper} - ; - """ - - {:ok, result} = Repo.query(query, []) - Logger.info("got em!") - - result.rows - |> Enum.each(&sweet_update/1) - end - - def sweet_update([transaction_id, from_address_id, to_address_id]) do - query = """ - UPDATE transactions SET from_address_id = $1, to_address_id = $2 WHERE id = $3 - """ - - {:ok, _status} = Repo.query(query, [from_address_id, to_address_id, transaction_id]) - end - - def sweet_update(_), do: nil -end diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index dcaca72e58..68dcf2b4fd 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -68,7 +68,7 @@ defmodule Explorer.Mixfile do defp deps do [ {:bypass, "~> 0.8", only: :test}, - {:credo, "0.9.1", only: [:dev, :test], runtime: false}, + {:credo, "0.9.2", only: [:dev, :test], runtime: false}, {:crontab, "~> 1.1"}, {:dialyxir, "~> 0.5", only: [:dev, :test], runtime: false}, {:ethereumex, "~> 0.3"}, @@ -103,7 +103,7 @@ defmodule Explorer.Mixfile do compile: "compile --warnings-as-errors", "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], "ecto.reset": ["ecto.drop", "ecto.setup"], - test: ["ecto.create --quiet", "ecto.migrate", "test"] + test: ["ecto.drop", "ecto.create --quiet", "ecto.migrate", "test"] ] end diff --git a/apps/explorer/priv/repo/migrations/20180117221921_create_address.exs b/apps/explorer/priv/repo/migrations/20180117221921_create_address.exs index ec1a86392a..22c8cbebef 100644 --- a/apps/explorer/priv/repo/migrations/20180117221921_create_address.exs +++ b/apps/explorer/priv/repo/migrations/20180117221921_create_address.exs @@ -2,14 +2,12 @@ defmodule Explorer.Repo.Migrations.CreateAddress do use Ecto.Migration def change do - create table(:addresses) do - add :balance, :numeric, precision: 100 - add :balance_updated_at, :utc_datetime - add :hash, :string, null: false + create table(:addresses, primary_key: false) do + add(:balance, :numeric, precision: 100) + add(:balance_updated_at, :utc_datetime) + add(:hash, :bytea, null: false, primary_key: true) - timestamps null: false + timestamps(null: false) end - - create unique_index(:addresses, [:hash]) end end diff --git a/apps/explorer/priv/repo/migrations/20180117221922_create_blocks.exs b/apps/explorer/priv/repo/migrations/20180117221922_create_blocks.exs index 16344a40a5..732560c7e6 100644 --- a/apps/explorer/priv/repo/migrations/20180117221922_create_blocks.exs +++ b/apps/explorer/priv/repo/migrations/20180117221922_create_blocks.exs @@ -2,24 +2,27 @@ defmodule Explorer.Repo.Migrations.CreateBlocks do use Ecto.Migration def change do - create table(:blocks) do - add :difficulty, :numeric, precision: 50 - add :gas_limit, :integer, null: false - add :gas_used, :integer, null: false - add :hash, :string, null: false - add :miner, :string, null: false - add :nonce, :string, null: false - add :number, :bigint, null: false - add :parent_hash, :string, null: false - add :size, :integer, null: false - add :timestamp, :utc_datetime, null: false - add :total_difficulty, :numeric, precision: 50 + create table(:blocks, primary_key: false) do + add(:difficulty, :numeric, precision: 50) + add(:gas_limit, :integer, null: false) + add(:gas_used, :integer, null: false) + add(:hash, :bytea, null: false, primary_key: true) + add(:miner_hash, references(:addresses, column: :hash, type: :bytea), null: false) + add(:nonce, :string, null: false) + add(:number, :bigint, null: false) - timestamps null: false + # not a foreign key to allow skipped blocks + add(:parent_hash, :bytea, null: false) + + add(:size, :integer, null: false) + add(:timestamp, :utc_datetime, null: false) + add(:total_difficulty, :numeric, precision: 50) + + timestamps(null: false) end - create index(:blocks, [:timestamp]) - create unique_index(:blocks, [:hash]) - create unique_index(:blocks, [:number]) + create(index(:blocks, [:timestamp])) + create(unique_index(:blocks, [:parent_hash])) + create(unique_index(:blocks, [:number])) end end diff --git a/apps/explorer/priv/repo/migrations/20180117221923_create_transactions.exs b/apps/explorer/priv/repo/migrations/20180117221923_create_transactions.exs index 7ff1a27cf5..c32ed01fd1 100644 --- a/apps/explorer/priv/repo/migrations/20180117221923_create_transactions.exs +++ b/apps/explorer/priv/repo/migrations/20180117221923_create_transactions.exs @@ -2,36 +2,50 @@ defmodule Explorer.Repo.Migrations.CreateTransactions do use Ecto.Migration def change do - create table(:transactions) do + create table(:transactions, primary_key: false) do # Fields - add :gas, :numeric, precision: 100, null: false - add :gas_price, :numeric, precision: 100, null: false - add :hash, :string, null: false - add :input, :text, null: false - add :nonce, :integer, null: false - add :public_key, :string, null: false - add :r, :string, null: false - add :s, :string, null: false - add :standard_v, :string, null: false - add :transaction_index, :string, null: false - add :v, :string, null: false - add :value, :numeric, precision: 100, null: false - - timestamps null: false + + add(:gas, :numeric, precision: 100, null: false) + add(:gas_price, :numeric, precision: 100, null: false) + add(:hash, :bytea, null: false, primary_key: true) + + # `null` when a pending transaction + add(:index, :integer, null: true) + + add(:input, :text, null: false) + add(:nonce, :integer, null: false) + add(:public_key, :string, null: false) + add(:r, :string, null: false) + add(:s, :string, null: false) + add(:standard_v, :string, null: false) + add(:v, :string, null: false) + add(:value, :numeric, precision: 100, null: false) + + timestamps(null: false) # Foreign Keys - # null when a pending transaction - add :block_id, references(:blocks, on_delete: :delete_all), null: true - add :from_address_id, references(:addresses, on_delete: :delete_all) - add :to_address_id, references(:addresses, on_delete: :delete_all) + # `null` when a pending transaction + add(:block_hash, references(:blocks, column: :hash, on_delete: :delete_all, type: :bytea), null: true) + + add(:from_address_hash, references(:addresses, column: :hash, on_delete: :delete_all, type: :bytea), null: false) + # `null` when it is a contract creation transaction + add(:to_address_hash, references(:addresses, column: :hash, on_delete: :delete_all, type: :bytea), null: true) end - create index(:transactions, :block_id) - create index(:transactions, :from_address_id) - create unique_index(:transactions, [:hash]) - create index(:transactions, :inserted_at) - create index(:transactions, :to_address_id) - create index(:transactions, :updated_at) + # Foreign Key indexes + + create(index(:transactions, :block_hash)) + create(index(:transactions, :from_address_hash)) + create(index(:transactions, :to_address_hash)) + + # Search indexes + + create(index(:transactions, :inserted_at)) + create(index(:transactions, :updated_at)) + + # Unique indexes + + create(unique_index(:transactions, [:block_hash, :index])) end end diff --git a/apps/explorer/priv/repo/migrations/20180212214442_create_receipts.exs b/apps/explorer/priv/repo/migrations/20180212214442_create_receipts.exs index c72436f342..581722fce9 100644 --- a/apps/explorer/priv/repo/migrations/20180212214442_create_receipts.exs +++ b/apps/explorer/priv/repo/migrations/20180212214442_create_receipts.exs @@ -3,21 +3,26 @@ defmodule Explorer.Repo.Migrations.CreateReceipts do def change do create table(:receipts) do - add :cumulative_gas_used, :numeric, precision: 100, null: false - add :gas_used, :numeric, precision: 100, null: false - add :index, :integer, null: false - add :status, :integer, null: false + add(:cumulative_gas_used, :numeric, precision: 100, null: false) + add(:gas_used, :numeric, precision: 100, null: false) + add(:index, :integer, null: false) + add(:status, :integer, null: false) - timestamps null: false + timestamps(null: false) # Foreign keys - add :receipt_id, references(:receipts, on_delete: :delete_all), null: true - add :transaction_id, references(:transactions, on_delete: :delete_all), null: false + add(:receipt_id, references(:receipts, on_delete: :delete_all), null: true) + + add( + :transaction_hash, + references(:transactions, column: :hash, on_delete: :delete_all, type: :bytea), + null: false + ) end - create index(:receipts, :index) - create index(:receipts, :status) - create unique_index(:receipts, :transaction_id) + create(index(:receipts, :index)) + create(index(:receipts, :status)) + create(unique_index(:receipts, :transaction_hash)) end end diff --git a/apps/explorer/priv/repo/migrations/20180212222309_create_logs.exs b/apps/explorer/priv/repo/migrations/20180212222309_create_logs.exs index 1aa014f577..ae5e61d8fe 100644 --- a/apps/explorer/priv/repo/migrations/20180212222309_create_logs.exs +++ b/apps/explorer/priv/repo/migrations/20180212222309_create_logs.exs @@ -3,30 +3,35 @@ defmodule Explorer.Repo.Migrations.CreateLogs do def change do create table(:logs) do - add :data, :text, null: false - add :index, :integer, null: false - add :type, :string, null: false - add :first_topic, :string, null: true - add :second_topic, :string, null: true - add :third_topic, :string, null: true - add :fourth_topic, :string, null: true + add(:data, :text, null: false) + add(:index, :integer, null: false) + add(:type, :string, null: false) + add(:first_topic, :string, null: true) + add(:second_topic, :string, null: true) + add(:third_topic, :string, null: true) + add(:fourth_topic, :string, null: true) - timestamps null: false + timestamps(null: false) # Foreign Keys - # TODO used in views, but not in indexer - add :address_id, references(:addresses, on_delete: :delete_all), null: true - add :receipt_id, references(:receipts, on_delete: :delete_all), null: false + add(:address_hash, references(:addresses, column: :hash, on_delete: :delete_all, type: :bytea), null: true) + add(:receipt_id, references(:receipts, on_delete: :delete_all), null: false) end - create index(:logs, :address_id) - create index(:logs, :index) - create index(:logs, :type) - create index(:logs, :first_topic) - create index(:logs, :second_topic) - create index(:logs, :third_topic) - create index(:logs, :fourth_topic) - create unique_index(:logs, [:receipt_id, :index]) + # Foreign Key indexes + + create(index(:logs, :address_hash)) + create(index(:logs, :receipt_id)) + + # Search indexes + + create(index(:logs, :index)) + create(index(:logs, :type)) + create(index(:logs, :first_topic)) + create(index(:logs, :second_topic)) + create(index(:logs, :third_topic)) + create(index(:logs, :fourth_topic)) + create(unique_index(:logs, [:receipt_id, :index])) end end diff --git a/apps/explorer/priv/repo/migrations/20180221001948_create_internal_transactions.exs b/apps/explorer/priv/repo/migrations/20180221001948_create_internal_transactions.exs index 4abb6c6077..70cedbd48e 100644 --- a/apps/explorer/priv/repo/migrations/20180221001948_create_internal_transactions.exs +++ b/apps/explorer/priv/repo/migrations/20180221001948_create_internal_transactions.exs @@ -3,30 +3,33 @@ defmodule Explorer.Repo.Migrations.CreateInternalTransactions do def change do create table(:internal_transactions) do - add :call_type, :string, null: false - add :from_address_hash, :string - add :gas, :numeric, precision: 100, null: false - add :gas_used, :numeric, precision: 100, null: false - add :index, :integer, null: false - add :input, :text - add :output, :text - add :to_address_hash, :string - add :trace_address, {:array, :integer}, null: false - add :value, :numeric, precision: 100, null: false + add(:call_type, :string, null: false) + add(:gas, :numeric, precision: 100, null: false) + add(:gas_used, :numeric, precision: 100, null: false) + add(:index, :integer, null: false) + add(:input, :text) + add(:output, :text) + add(:trace_address, {:array, :integer}, null: false) + add(:value, :numeric, precision: 100, null: false) - timestamps null: false + timestamps(null: false) # Foreign keys - # TODO used in views, but not in indexer - add :from_address_id, references(:addresses, on_delete: :delete_all), null: true - # TODO used in views, but not in indexer - add :to_address_id, references(:addresses, on_delete: :delete_all), null: true - add :transaction_id, references(:transactions, on_delete: :delete_all), null: false + add(:from_address_hash, references(:addresses, column: :hash, type: :bytea)) + add(:to_address_hash, references(:addresses, column: :hash, type: :bytea)) + + add( + :transaction_hash, + references(:transactions, column: :hash, on_delete: :delete_all, type: :bytea), + null: false + ) end - create index(:internal_transactions, :transaction_id) - create index(:internal_transactions, :to_address_hash) - create index(:internal_transactions, :from_address_hash) + # Foreign Key indexes + + create(index(:internal_transactions, :from_address_hash)) + create(index(:internal_transactions, :to_address_hash)) + create(index(:internal_transactions, :transaction_hash)) end end diff --git a/apps/explorer/priv/repo/migrations/20180224004300_create_credit_debit_materialized_view.exs b/apps/explorer/priv/repo/migrations/20180224004300_create_credit_debit_materialized_view.exs index 1cb1350201..093a9c2cca 100644 --- a/apps/explorer/priv/repo/migrations/20180224004300_create_credit_debit_materialized_view.exs +++ b/apps/explorer/priv/repo/migrations/20180224004300_create_credit_debit_materialized_view.exs @@ -1,87 +1,49 @@ defmodule Explorer.Repo.Migrations.UpdateCreditDebitMaterializedView do use Ecto.Migration - def up do - execute "DROP MATERIALIZED VIEW IF EXISTS credits;" - execute "DROP MATERIALIZED VIEW IF EXISTS debits;" - - execute """ - CREATE MATERIALIZED VIEW credits AS - SELECT addresses.id AS address_id, - COALESCE(SUM(transactions.value), 0) AS value, - COUNT(transactions.to_address_id) AS count, - COALESCE(MIN(transactions.inserted_at), NOW()) AS inserted_at, - COALESCE(MAX(transactions.inserted_at), NOW()) AS updated_at - FROM addresses - INNER JOIN transactions ON transactions.to_address_id = addresses.id - INNER JOIN receipts ON receipts.transaction_id = transactions.id AND receipts.status = 1 - GROUP BY addresses.id - ; - """ - - execute """ - CREATE MATERIALIZED VIEW debits AS - SELECT addresses.id AS address_id, - COALESCE(SUM(transactions.value), 0) AS value, - COUNT(transactions.from_address_id) AS count, - COALESCE(MIN(transactions.inserted_at), NOW()) AS inserted_at, - COALESCE(MAX(transactions.inserted_at), NOW()) AS updated_at - FROM addresses - INNER JOIN transactions ON transactions.from_address_id = addresses.id - INNER JOIN receipts ON receipts.transaction_id = transactions.id AND receipts.status = 1 - GROUP BY addresses.id - ; - """ - - create unique_index(:credits, :address_id) - create index(:credits, :inserted_at) - create index(:credits, :updated_at) - - create unique_index(:debits, :address_id) - create index(:debits, :inserted_at) - create index(:debits, :updated_at) + def down do + execute("DROP MATERIALIZED VIEW IF EXISTS credits;") + execute("DROP MATERIALIZED VIEW IF EXISTS debits;") end - def down do - execute "DROP MATERIALIZED VIEW IF EXISTS credits;" - execute "DROP MATERIALIZED VIEW IF EXISTS debits;" + def up do + execute("DROP MATERIALIZED VIEW IF EXISTS credits;") + execute("DROP MATERIALIZED VIEW IF EXISTS debits;") - execute """ + execute(""" CREATE MATERIALIZED VIEW credits AS - SELECT addresses.id AS address_id, + SELECT addresses.hash AS address_hash, COALESCE(SUM(transactions.value), 0) AS value, - COUNT(to_addresses.address_id) AS count, + COUNT(transactions.to_address_hash) AS count, COALESCE(MIN(transactions.inserted_at), NOW()) AS inserted_at, COALESCE(MAX(transactions.inserted_at), NOW()) AS updated_at FROM addresses - INNER JOIN to_addresses ON to_addresses.address_id = addresses.id - INNER JOIN transactions ON transactions.id = to_addresses.transaction_id - INNER JOIN receipts ON receipts.transaction_id = transactions.id AND receipts.status = 1 - GROUP BY addresses.id + INNER JOIN transactions ON transactions.to_address_hash = addresses.hash + INNER JOIN receipts ON receipts.transaction_hash = transactions.hash AND receipts.status = 1 + GROUP BY addresses.hash ; - """ + """) - execute """ + execute(""" CREATE MATERIALIZED VIEW debits AS - SELECT addresses.id AS address_id, + SELECT addresses.hash AS address_hash, COALESCE(SUM(transactions.value), 0) AS value, - COUNT(from_addresses.address_id) AS count, + COUNT(transactions.from_address_hash) AS count, COALESCE(MIN(transactions.inserted_at), NOW()) AS inserted_at, COALESCE(MAX(transactions.inserted_at), NOW()) AS updated_at FROM addresses - INNER JOIN from_addresses ON from_addresses.address_id = addresses.id - INNER JOIN transactions ON transactions.id = from_addresses.transaction_id - INNER JOIN receipts ON receipts.transaction_id = transactions.id AND receipts.status = 1 - GROUP BY addresses.id + INNER JOIN transactions ON transactions.from_address_hash = addresses.hash + INNER JOIN receipts ON receipts.transaction_hash = transactions.hash AND receipts.status = 1 + GROUP BY addresses.hash ; - """ + """) - create unique_index(:credits, :address_id) - create index(:credits, :inserted_at) - create index(:credits, :updated_at) + create(unique_index(:credits, :address_hash)) + create(index(:credits, :inserted_at)) + create(index(:credits, :updated_at)) - create unique_index(:debits, :address_id) - create index(:debits, :inserted_at) - create index(:debits, :updated_at) + create(unique_index(:debits, :address_hash)) + create(index(:debits, :inserted_at)) + create(index(:debits, :updated_at)) end end diff --git a/apps/explorer/test/explorer/chain/address_test.exs b/apps/explorer/test/explorer/chain/address_test.exs index a916db7a3a..2a631664f4 100644 --- a/apps/explorer/test/explorer/chain/address_test.exs +++ b/apps/explorer/test/explorer/chain/address_test.exs @@ -14,12 +14,6 @@ defmodule Explorer.Chain.AddressTest do changeset = Address.changeset(%Address{}, %{dog: "woodstock"}) refute changeset.valid? end - - test "it downcases hashes on the way in" do - params = params_for(:address, hash: "0xALLCAPS") - changeset = Address.changeset(%Address{}, params) - assert Ecto.Changeset.get_change(changeset, :hash) == "0xallcaps" - end end describe "balance_changeset/2" do diff --git a/apps/explorer/test/explorer/chain/block_test.exs b/apps/explorer/test/explorer/chain/block_test.exs index da09d4000d..00812e6183 100644 --- a/apps/explorer/test/explorer/chain/block_test.exs +++ b/apps/explorer/test/explorer/chain/block_test.exs @@ -1,8 +1,6 @@ defmodule Explorer.Chain.BlockTest do use Explorer.DataCase - import Ecto.Query, only: [order_by: 2] - alias Explorer.Chain.Block describe "changeset/2" do @@ -17,36 +15,26 @@ defmodule Explorer.Chain.BlockTest do end test "with duplicate information" do - insert(:block, hash: "0x0") + %Block{hash: hash} = insert(:block) - {:error, changeset} = %Block{} |> Block.changeset(params_for(:block, hash: "0x0")) |> Repo.insert() + {:error, changeset} = %Block{} |> Block.changeset(params_for(:block, hash: hash)) |> Repo.insert() refute changeset.valid? assert changeset.errors == [hash: {"has already been taken", []}] end test "rejects duplicate blocks with mixed case" do - insert(:block, hash: "0xa") + insert(:block, hash: "0xef95f2f1ed3ca60b048b4bf67cde2195961e0bba6f70bcbea9a2c4e133e34b46") - {:error, changeset} = %Block{} |> Block.changeset(params_for(:block, hash: "0xA")) |> Repo.insert() + {:error, changeset} = + %Block{} + |> Block.changeset( + params_for(:block, hash: "0xeF95f2f1ed3ca60b048b4bf67cde2195961e0bba6f70bcbea9a2c4e133e34b46") + ) + |> Repo.insert() refute changeset.valid? assert changeset.errors == [hash: {"has already been taken", []}] end end - - describe "null/0" do - test "returns a block with a number of 0" do - assert Block.null().number === -1 - end - end - - describe "latest/1" do - test "returns the blocks sorted by number" do - insert(:block, number: 1) - insert(:block, number: 5) - - assert Block |> Block.latest() |> Repo.all() == Block |> order_by(desc: :number) |> Repo.all() - end - end end diff --git a/apps/explorer/test/explorer/chain/credit_test.exs b/apps/explorer/test/explorer/chain/credit_test.exs index 1fb1fb059f..5866f344d0 100644 --- a/apps/explorer/test/explorer/chain/credit_test.exs +++ b/apps/explorer/test/explorer/chain/credit_test.exs @@ -17,7 +17,7 @@ defmodule Explorer.Chain.CreditTest do test "returns a credit when there is an address with a receipt" do recipient = insert(:address) sender = insert(:address) - transaction = insert(:transaction, to_address_id: recipient.id, from_address_id: sender.id) + transaction = insert(:transaction, to_address_hash: recipient.hash, from_address_hash: sender.hash) insert(:receipt, transaction: transaction, status: 1) Credit.refresh() credits = Credit |> Repo.all() @@ -28,12 +28,12 @@ defmodule Explorer.Chain.CreditTest do recipient = insert(:address) sender = insert(:address) - transaction = insert(:transaction, value: 21, to_address_id: recipient.id, from_address_id: sender.id) + transaction = insert(:transaction, value: 21, to_address_hash: recipient.hash, from_address_hash: sender.hash) insert(:receipt, transaction: transaction, status: 1) - address_id = sender.id + address_hash = sender.hash Credit.refresh() - credit = Credit |> where(address_id: ^address_id) |> Repo.one() + credit = Credit |> where(address_hash: ^address_hash) |> Repo.one() assert credit == nil end @@ -41,12 +41,12 @@ defmodule Explorer.Chain.CreditTest do recipient = insert(:address) sender = insert(:address) - transaction = insert(:transaction, value: 21, to_address_id: recipient.id, from_address_id: sender.id) + transaction = insert(:transaction, value: 21, to_address_hash: recipient.hash, from_address_hash: sender.hash) insert(:receipt, transaction: transaction, status: 1) - address_id = recipient.id + address_hash = recipient.hash Credit.refresh() - credit = Credit |> where(address_id: ^address_id) |> Repo.one() + credit = Credit |> where(address_hash: ^address_hash) |> Repo.one() assert credit.value == Decimal.new(21) end end diff --git a/apps/explorer/test/explorer/chain/debit_test.exs b/apps/explorer/test/explorer/chain/debit_test.exs index 7d89e85ab9..3653cd27e3 100644 --- a/apps/explorer/test/explorer/chain/debit_test.exs +++ b/apps/explorer/test/explorer/chain/debit_test.exs @@ -17,7 +17,7 @@ defmodule Explorer.Chain.DebitTest do test "returns a debit when there is an address with a receipt" do recipient = insert(:address) sender = insert(:address) - transaction = insert(:transaction, to_address_id: recipient.id, from_address_id: sender.id) + transaction = insert(:transaction, to_address_hash: recipient.hash, from_address_hash: sender.hash) insert(:receipt, transaction: transaction, status: 1) Debit.refresh() debits = Debit |> Repo.all() @@ -28,12 +28,12 @@ defmodule Explorer.Chain.DebitTest do recipient = insert(:address) sender = insert(:address) - transaction = insert(:transaction, value: 21, to_address_id: recipient.id, from_address_id: sender.id) + transaction = insert(:transaction, value: 21, to_address_hash: recipient.hash, from_address_hash: sender.hash) insert(:receipt, transaction: transaction, status: 1) - address_id = sender.id + address_hash = sender.hash Debit.refresh() - debit = Debit |> where(address_id: ^address_id) |> Repo.one() + debit = Debit |> where(address_hash: ^address_hash) |> Repo.one() assert debit.value == Decimal.new(21) end @@ -41,12 +41,12 @@ defmodule Explorer.Chain.DebitTest do recipient = insert(:address) sender = insert(:address) - transaction = insert(:transaction, value: 21, to_address_id: recipient.id, from_address_id: sender.id) + transaction = insert(:transaction, value: 21, to_address_hash: recipient.hash, from_address_hash: sender.hash) insert(:receipt, transaction: transaction, status: 1) - address_id = recipient.id + address_hash = recipient.hash Debit.refresh() - debit = Debit |> where(address_id: ^address_id) |> Repo.one() + debit = Debit |> where(address_hash: ^address_hash) |> Repo.one() assert debit == nil end end diff --git a/apps/explorer/test/explorer/chain/hash/full_test.exs b/apps/explorer/test/explorer/chain/hash/full_test.exs new file mode 100644 index 0000000000..bcaa0bc3eb --- /dev/null +++ b/apps/explorer/test/explorer/chain/hash/full_test.exs @@ -0,0 +1,5 @@ +defmodule Explorer.Chain.Hash.FullTest do + use ExUnit.Case, async: true + + doctest Explorer.Chain.Hash.Full +end diff --git a/apps/explorer/test/explorer/chain/hash/truncated_test.exs b/apps/explorer/test/explorer/chain/hash/truncated_test.exs new file mode 100644 index 0000000000..4cfac3b5b7 --- /dev/null +++ b/apps/explorer/test/explorer/chain/hash/truncated_test.exs @@ -0,0 +1,5 @@ +defmodule Explorer.Chain.Hash.TruncatedTest do + use ExUnit.Case, async: true + + doctest Explorer.Chain.Hash.Truncated +end diff --git a/apps/explorer/test/explorer/chain/hash_test.exs b/apps/explorer/test/explorer/chain/hash_test.exs new file mode 100644 index 0000000000..7565e9d067 --- /dev/null +++ b/apps/explorer/test/explorer/chain/hash_test.exs @@ -0,0 +1,5 @@ +defmodule Explorer.Chain.HashTest do + use ExUnit.Case, async: true + + doctest Explorer.Chain.Hash +end diff --git a/apps/explorer/test/explorer/chain/internal_transaction_test.exs b/apps/explorer/test/explorer/chain/internal_transaction_test.exs index ce7acf640a..aff00cbc63 100644 --- a/apps/explorer/test/explorer/chain/internal_transaction_test.exs +++ b/apps/explorer/test/explorer/chain/internal_transaction_test.exs @@ -9,17 +9,17 @@ defmodule Explorer.Chain.InternalTransactionTest do changeset = InternalTransaction.changeset(%InternalTransaction{}, %{ - transaction_id: transaction.id, - index: 0, call_type: "call", - trace_address: [0, 1], - value: 100, + from_address_hash: "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", gas: 100, gas_used: 100, + index: 0, input: "pintos", output: "refried", - to_address_id: 1, - from_address_id: 2 + to_address_hash: "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", + trace_address: [0, 1], + transaction_hash: transaction.hash, + value: 100 }) assert changeset.valid? @@ -35,15 +35,15 @@ defmodule Explorer.Chain.InternalTransactionTest do changeset = InternalTransaction.changeset(%InternalTransaction{}, %{ - transaction: transaction, - index: 0, call_type: "call", - trace_address: [0, 1], - value: 100, gas: 100, gas_used: 100, + index: 0, input: "thin-mints", - output: "munchos" + output: "munchos", + trace_address: [0, 1], + transaction: transaction, + value: 100 }) assert Repo.insert(changeset) diff --git a/apps/explorer/test/explorer/chain/statistics/server_test.exs b/apps/explorer/test/explorer/chain/statistics/server_test.exs index fb1ad654be..84f1e40cf6 100644 --- a/apps/explorer/test/explorer/chain/statistics/server_test.exs +++ b/apps/explorer/test/explorer/chain/statistics/server_test.exs @@ -24,14 +24,6 @@ defmodule Explorer.Chain.Statistics.ServerTest do end end - describe "fetch/0" do - test "fetches the chain when not started" do - original = Statistics.fetch() - - assert Server.fetch() == original - end - end - describe "handle_info/2" do test "returns the original chain when sent a :refresh message" do original = Statistics.fetch() diff --git a/apps/explorer/test/explorer/chain/statistics_test.exs b/apps/explorer/test/explorer/chain/statistics_test.exs index a4f96e9a61..ffd28b2100 100644 --- a/apps/explorer/test/explorer/chain/statistics_test.exs +++ b/apps/explorer/test/explorer/chain/statistics_test.exs @@ -47,8 +47,8 @@ defmodule Explorer.Chain.StatisticsTest do last_week = Timex.shift(time, days: -8) block = insert(:block, timestamp: time) old_block = insert(:block, timestamp: last_week) - insert(:transaction, block_id: block.id) - insert(:transaction, block_id: old_block.id) + insert(:transaction, block_hash: block.hash) + insert(:transaction, block_hash: old_block.hash) assert %Statistics{transaction_count: 1} = Statistics.fetch() end @@ -98,7 +98,7 @@ defmodule Explorer.Chain.StatisticsTest do test "returns the last five transactions with blocks" do block = insert(:block) - insert_list(6, :transaction, block_id: block.id) + insert_list(6, :transaction, block_hash: block.hash) statistics = Statistics.fetch() diff --git a/apps/explorer/test/explorer/chain/transaction_test.exs b/apps/explorer/test/explorer/chain/transaction_test.exs index d1f3e15257..6cd33dbd82 100644 --- a/apps/explorer/test/explorer/chain/transaction_test.exs +++ b/apps/explorer/test/explorer/chain/transaction_test.exs @@ -7,7 +7,7 @@ defmodule Explorer.Chain.TransactionTest do test "with valid attributes" do changeset = Transaction.changeset(%Transaction{}, %{ - hash: "0x0", + hash: "0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b", value: 1, gas: 21000, gas_price: 10000, diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 48d6797e3f..2cf0a52ed2 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -1,14 +1,12 @@ defmodule Explorer.ChainTest do use Explorer.DataCase - alias Explorer.{Chain, Repo} + import Explorer.Factory + alias Explorer.{Chain, Repo} alias Explorer.Chain.{Address, Block, InternalTransaction, Log, Receipt, Transaction} - # Constants - - @invalid_attrs %{hash: nil} - @valid_attrs %{hash: "some hash"} + doctest Explorer.Chain # Tests @@ -16,7 +14,7 @@ defmodule Explorer.ChainTest do test "without transactions" do block = insert(:block) - assert Repo.aggregate(Transaction, :count, :id) == 0 + assert Repo.aggregate(Transaction, :count, :hash) == 0 assert %Scrivener.Page{ entries: [], @@ -27,25 +25,25 @@ defmodule Explorer.ChainTest do test "with transactions" do block = insert(:block) - %Transaction{id: transaction_id} = insert(:transaction, block_id: block.id) + %Transaction{hash: transaction_hash} = insert(:transaction, block_hash: block.hash) assert %Scrivener.Page{ - entries: [%Transaction{id: ^transaction_id}], + entries: [%Transaction{hash: ^transaction_hash}], page_number: 1, total_entries: 1 } = Chain.block_to_transactions(block) end test "with transaction with receipt required without receipt does not return transaction" do - block = %Block{id: block_id} = insert(:block) + block = %Block{hash: block_hash} = insert(:block) - %Transaction{id: transaction_id_with_receipt} = insert(:transaction, block_id: block_id) - insert(:receipt, transaction_id: transaction_id_with_receipt) + %Transaction{hash: transaction_hash_with_receipt} = insert(:transaction, block_hash: block_hash) + insert(:receipt, transaction_hash: transaction_hash_with_receipt) - %Transaction{id: transaction_id_without_receipt} = insert(:transaction, block_id: block_id) + %Transaction{hash: transaction_hash_without_receipt} = insert(:transaction, block_hash: block_hash) assert %Scrivener.Page{ - entries: [%Transaction{id: ^transaction_id_with_receipt, receipt: %Receipt{}}], + entries: [%Transaction{hash: ^transaction_hash_with_receipt, receipt: %Receipt{}}], page_number: 1, total_entries: 1 } = @@ -66,24 +64,24 @@ defmodule Explorer.ChainTest do assert length(transactions) == 2 - transaction_by_id = - Enum.into(transactions, %{}, fn transaction = %Transaction{id: id} -> - {id, transaction} + transaction_by_hash = + Enum.into(transactions, %{}, fn transaction = %Transaction{hash: hash} -> + {hash, transaction} end) - assert %Transaction{receipt: %Receipt{}} = transaction_by_id[transaction_id_with_receipt] - assert %Transaction{receipt: nil} = transaction_by_id[transaction_id_without_receipt] + assert %Transaction{receipt: %Receipt{}} = transaction_by_hash[transaction_hash_with_receipt] + assert %Transaction{receipt: nil} = transaction_by_hash[transaction_hash_without_receipt] end test "with transactions can be paginated" do block = insert(:block) - transactions = insert_list(2, :transaction, block_id: block.id) + transactions = insert_list(2, :transaction, block_hash: block.hash) - [%Transaction{id: first_transaction_id}, %Transaction{id: second_transaction_id}] = transactions + [%Transaction{hash: first_transaction_hash}, %Transaction{hash: second_transaction_hash}] = transactions assert %Scrivener.Page{ - entries: [%Transaction{id: ^second_transaction_id}], + entries: [%Transaction{hash: ^second_transaction_hash}], page_number: 1, page_size: 1, total_entries: 2, @@ -91,7 +89,7 @@ defmodule Explorer.ChainTest do } = Chain.block_to_transactions(block, pagination: %{page_size: 1}) assert %Scrivener.Page{ - entries: [%Transaction{id: ^first_transaction_id}], + entries: [%Transaction{hash: ^first_transaction_hash}], page_number: 2, page_size: 1, total_entries: 2, @@ -109,7 +107,7 @@ defmodule Explorer.ChainTest do test "with transactions" do block = insert(:block) - insert(:transaction, block_id: block.id) + insert(:transaction, block_hash: block.hash) assert Chain.block_to_transaction_count(block) == 1 end @@ -134,39 +132,6 @@ defmodule Explorer.ChainTest do end end - describe "create_address/1" do - test "with valid data creates a address" do - assert {:ok, %Address{} = address} = Chain.create_address(@valid_attrs) - assert address.hash == "some hash" - end - - test "with invalid data returns error changeset" do - assert {:error, %Ecto.Changeset{}} = Chain.create_address(@invalid_attrs) - end - end - - describe "ensure_hash_address/1" do - test "creates a new address when one does not exist" do - Chain.ensure_hash_address("0xFreshPrince") - - assert {:ok, _} = Chain.hash_to_address("0xfreshprince") - end - - test "when the address already exists doesn't insert a new address" do - insert(:address, %{hash: "bigmouthbillybass"}) - - before = Repo.aggregate(Address, :count, :id) - - assert {:ok, _} = Chain.ensure_hash_address("bigmouthbillybass") - - assert Repo.aggregate(Address, :count, :id) == before - end - - test "when there is no hash it blows up" do - assert {:error, :not_found} = Chain.ensure_hash_address("") - end - end - describe "gas_price/2" do test ":wei unit" do assert Chain.gas_price(%Transaction{gas_price: Decimal.new(1)}, :wei) == Decimal.new(1) @@ -189,7 +154,7 @@ defmodule Explorer.ChainTest do test "without transactions" do address = insert(:address) - assert Repo.aggregate(Transaction, :count, :id) == 0 + assert Repo.aggregate(Transaction, :count, :hash) == 0 assert %Scrivener.Page{ entries: [], @@ -199,27 +164,27 @@ defmodule Explorer.ChainTest do end test "with transactions" do - %Transaction{from_address_id: from_address_id, id: transaction_id} = insert(:transaction) - address = Repo.get!(Address, from_address_id) + %Transaction{from_address_hash: from_address_hash, hash: transaction_hash} = insert(:transaction) + address = Repo.get!(Address, from_address_hash) assert %Scrivener.Page{ - entries: [%Transaction{id: ^transaction_id}], + entries: [%Transaction{hash: ^transaction_hash}], page_number: 1, total_entries: 1 } = Chain.from_address_to_transactions(address) end test "with transactions with receipt required without receipt does not return transaction" do - address = %Address{id: from_address_id} = insert(:address) + address = %Address{hash: from_address_hash} = insert(:address) - %Transaction{id: transaction_id_with_receipt} = insert(:transaction, from_address_id: from_address_id) + %Transaction{hash: transaction_hash_with_receipt} = insert(:transaction, from_address_hash: from_address_hash) - insert(:receipt, transaction_id: transaction_id_with_receipt) + insert(:receipt, transaction_hash: transaction_hash_with_receipt) - %Transaction{id: transaction_id_without_receipt} = insert(:transaction, from_address_id: from_address_id) + %Transaction{hash: transaction_hash_without_receipt} = insert(:transaction, from_address_hash: from_address_hash) assert %Scrivener.Page{ - entries: [%Transaction{id: ^transaction_id_with_receipt, receipt: %Receipt{}}], + entries: [%Transaction{hash: ^transaction_hash_with_receipt, receipt: %Receipt{}}], page_number: 1, total_entries: 1 } = @@ -240,23 +205,23 @@ defmodule Explorer.ChainTest do assert length(transactions) == 2 - transaction_by_id = - Enum.into(transactions, %{}, fn transaction = %Transaction{id: id} -> - {id, transaction} + transaction_by_hash = + Enum.into(transactions, %{}, fn transaction = %Transaction{hash: hash} -> + {hash, transaction} end) - assert %Transaction{receipt: %Receipt{}} = transaction_by_id[transaction_id_with_receipt] - assert %Transaction{receipt: nil} = transaction_by_id[transaction_id_without_receipt] + assert %Transaction{receipt: %Receipt{}} = transaction_by_hash[transaction_hash_with_receipt] + assert %Transaction{receipt: nil} = transaction_by_hash[transaction_hash_without_receipt] end test "with transactions can be paginated" do - adddress = %Address{id: from_address_id} = insert(:address) - transactions = insert_list(2, :transaction, from_address_id: from_address_id) + adddress = %Address{hash: from_address_hash} = insert(:address) + transactions = insert_list(2, :transaction, from_address_hash: from_address_hash) - [%Transaction{id: oldest_transaction_id}, %Transaction{id: newest_transaction_id}] = transactions + [%Transaction{hash: oldest_transaction_hash}, %Transaction{hash: newest_transaction_hash}] = transactions assert %Scrivener.Page{ - entries: [%Transaction{id: ^newest_transaction_id}], + entries: [%Transaction{hash: ^newest_transaction_hash}], page_number: 1, page_size: 1, total_entries: 2, @@ -264,7 +229,7 @@ defmodule Explorer.ChainTest do } = Chain.from_address_to_transactions(adddress, pagination: %{page_size: 1}) assert %Scrivener.Page{ - entries: [%Transaction{id: ^oldest_transaction_id}], + entries: [%Transaction{hash: ^oldest_transaction_hash}], page_number: 2, page_size: 1, total_entries: 2, @@ -273,35 +238,11 @@ defmodule Explorer.ChainTest do end end - describe "hash_to_address/1" do - test "without address returns {:error, :not_found}" do - assert {:error, :not_found} = Chain.hash_to_address("unknown") - end - - test "with address returns {:ok, address}" do - hash = "0xandesmints" - %Address{id: address_id} = insert(:address, hash: hash) - - assert {:ok, %Address{id: ^address_id}} = Chain.hash_to_address(hash) - end - end - describe "hash_to_transaction/2" do - test "without transaction returns {:error, :not_found}" do - assert {:error, :not_found} = Chain.hash_to_transaction("unknown") - end - - test "with transaction returns {:ok, transaction}" do - hash = "0xandesmints" - %Transaction{id: transaction_id} = insert(:transaction, hash: hash) - - assert {:ok, %Transaction{id: ^transaction_id}} = Chain.hash_to_transaction(hash) - end - test "with transaction with receipt required without receipt returns {:error, :not_found}" do - %Transaction{hash: hash_with_receipt, id: transaction_id_with_receipt} = insert(:transaction) + %Transaction{hash: hash_with_receipt} = insert(:transaction) - insert(:receipt, transaction_id: transaction_id_with_receipt) + insert(:receipt, transaction_hash: hash_with_receipt) %Transaction{hash: hash_without_receipt} = insert(:transaction) @@ -325,40 +266,6 @@ defmodule Explorer.ChainTest do end end - describe "id_to_address/1" do - test "returns the address with given id" do - %Address{id: id} = insert(:address) - - assert {:ok, %Address{id: ^id}} = Chain.id_to_address(id) - end - end - - describe "last_transaction_id/1" do - test "without transactions returns 0" do - assert Chain.last_transaction_id() == 0 - end - - test "with transaction returns last created transaction's id" do - insert(:transaction) - %Transaction{id: id} = insert(:transaction) - - assert Chain.last_transaction_id() == id - end - - test "with transaction with pending: true returns last pending transaction id, not the last transaction" do - %Transaction{id: pending_transaction_id} = insert(:transaction) - - %Transaction{id: transaction_id} = insert(:transaction) - insert(:receipt, transaction_id: transaction_id) - - assert pending_transaction_id < transaction_id - - assert Chain.last_transaction_id(pending: true) == pending_transaction_id - assert Chain.last_transaction_id(pending: false) == transaction_id - assert Chain.last_transaction_id() == transaction_id - end - end - describe "list_blocks/2" do test "without blocks" do assert %Scrivener.Page{ @@ -370,10 +277,10 @@ defmodule Explorer.ChainTest do end test "with blocks" do - %Block{id: id} = insert(:block) + %Block{hash: hash} = insert(:block) assert %Scrivener.Page{ - entries: [%Block{id: ^id}], + entries: [%Block{hash: ^hash}], page_number: 1, total_entries: 1 } = Chain.list_blocks() @@ -433,7 +340,7 @@ defmodule Explorer.ChainTest do test "without transactions" do address = insert(:address) - assert Repo.aggregate(Transaction, :count, :id) == 0 + assert Repo.aggregate(Transaction, :count, :hash) == 0 assert %Scrivener.Page{ entries: [], @@ -443,27 +350,27 @@ defmodule Explorer.ChainTest do end test "with transactions" do - %Transaction{to_address_id: to_address_id, id: transaction_id} = insert(:transaction) - address = Repo.get!(Address, to_address_id) + %Transaction{to_address_hash: to_address_hash, hash: transaction_hash} = insert(:transaction) + address = Repo.get!(Address, to_address_hash) assert %Scrivener.Page{ - entries: [%Transaction{id: ^transaction_id}], + entries: [%Transaction{hash: ^transaction_hash}], page_number: 1, total_entries: 1 } = Chain.to_address_to_transactions(address) end test "with transactions with receipt required without receipt does not return transaction" do - address = %Address{id: to_address_id} = insert(:address) + address = %Address{hash: to_address_hash} = insert(:address) - %Transaction{id: transaction_id_with_receipt} = insert(:transaction, to_address_id: to_address_id) + %Transaction{hash: transaction_hash_with_receipt} = insert(:transaction, to_address_hash: to_address_hash) - insert(:receipt, transaction_id: transaction_id_with_receipt) + insert(:receipt, transaction_hash: transaction_hash_with_receipt) - %Transaction{id: transaction_id_without_receipt} = insert(:transaction, to_address_id: to_address_id) + %Transaction{hash: transaction_hash_without_receipt} = insert(:transaction, to_address_hash: to_address_hash) assert %Scrivener.Page{ - entries: [%Transaction{id: ^transaction_id_with_receipt, receipt: %Receipt{}}], + entries: [%Transaction{hash: ^transaction_hash_with_receipt, receipt: %Receipt{}}], page_number: 1, total_entries: 1 } = @@ -484,23 +391,23 @@ defmodule Explorer.ChainTest do assert length(transactions) == 2 - transaction_by_id = - Enum.into(transactions, %{}, fn transaction = %Transaction{id: id} -> - {id, transaction} + transaction_by_hash = + Enum.into(transactions, %{}, fn transaction = %Transaction{hash: hash} -> + {hash, transaction} end) - assert %Transaction{receipt: %Receipt{}} = transaction_by_id[transaction_id_with_receipt] - assert %Transaction{receipt: nil} = transaction_by_id[transaction_id_without_receipt] + assert %Transaction{receipt: %Receipt{}} = transaction_by_hash[transaction_hash_with_receipt] + assert %Transaction{receipt: nil} = transaction_by_hash[transaction_hash_without_receipt] end test "with transactions can be paginated" do - adddress = %Address{id: to_address_id} = insert(:address) - transactions = insert_list(2, :transaction, to_address_id: to_address_id) + adddress = %Address{hash: to_address_hash} = insert(:address) + transactions = insert_list(2, :transaction, to_address_hash: to_address_hash) - [%Transaction{id: oldest_transaction_id}, %Transaction{id: newest_transaction_id}] = transactions + [%Transaction{hash: oldest_transaction_hash}, %Transaction{hash: newest_transaction_hash}] = transactions assert %Scrivener.Page{ - entries: [%Transaction{id: ^newest_transaction_id}], + entries: [%Transaction{hash: ^newest_transaction_hash}], page_number: 1, page_size: 1, total_entries: 2, @@ -508,7 +415,7 @@ defmodule Explorer.ChainTest do } = Chain.to_address_to_transactions(adddress, pagination: %{page_size: 1}) assert %Scrivener.Page{ - entries: [%Transaction{id: ^oldest_transaction_id}], + entries: [%Transaction{hash: ^oldest_transaction_hash}], page_number: 2, page_size: 1, total_entries: 2, @@ -517,33 +424,12 @@ defmodule Explorer.ChainTest do end end - describe "transaction_count/0" do - test "without transactions" do - assert Chain.transaction_count() == 0 - end - - test "with transactions" do - count = 2 - insert_list(count, :transaction) - - assert Chain.transaction_count() == count - end - - test "with transaction pending: true counts only pending transactions" do - insert(:transaction) - - %Transaction{id: transaction_id} = insert(:transaction) - insert(:receipt, transaction_id: transaction_id) - - assert Chain.transaction_count(pending: true) == 1 - assert Chain.transaction_count(pending: false) == 2 - assert Chain.transaction_count() == 2 - end - end - describe "transaction_hash_to_internal_transactions/1" do test "without transaction" do - assert Chain.transaction_hash_to_internal_transactions("unknown") == [] + {:ok, hash} = + Chain.string_to_transaction_hash("0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b") + + assert Chain.transaction_hash_to_internal_transactions(hash) == [] end test "with transaction without internal transactions" do @@ -554,7 +440,7 @@ defmodule Explorer.ChainTest do test "with transaction with internal transactions returns all internal transactions for a given transaction hash" do transaction = insert(:transaction) - internal_transaction = insert(:internal_transaction, transaction_id: transaction.id) + internal_transaction = insert(:internal_transaction, transaction_hash: transaction.hash) result = hd(Chain.transaction_hash_to_internal_transactions(transaction.hash)) @@ -562,8 +448,8 @@ defmodule Explorer.ChainTest do end test "with transaction with internal transactions loads associations with in necessity_by_assocation" do - %Transaction{hash: hash, id: transaction_id} = insert(:transaction) - insert(:internal_transaction, transaction_id: transaction_id) + %Transaction{hash: hash} = insert(:transaction) + insert(:internal_transaction, transaction_hash: hash) assert [ %InternalTransaction{ @@ -591,40 +477,6 @@ defmodule Explorer.ChainTest do end end - describe "transactions_recently_before_id" do - test "returns at most 10 transactions" do - count = 12 - - assert 10 < count - - transactions = insert_list(count, :transaction) - %Transaction{id: last_transaction_id} = List.last(transactions) - - recent_transactions = Chain.transactions_recently_before_id(last_transaction_id) - - assert length(recent_transactions) == 10 - end - - test "with pending: true returns only pending transactions" do - count = 12 - - transactions = insert_list(count, :transaction) - %Transaction{id: last_transaction_id} = List.last(transactions) - - transactions - |> Enum.take(3) - |> Enum.each(fn %Transaction{id: id} -> - insert(:receipt, transaction_id: id) - end) - - assert length(Chain.transactions_recently_before_id(last_transaction_id, pending: true)) == 8 - - assert length(Chain.transactions_recently_before_id(last_transaction_id, pending: false)) == 10 - - assert length(Chain.transactions_recently_before_id(last_transaction_id)) == 10 - end - end - describe "transaction_to_logs/2" do test "without logs" do transaction = insert(:transaction) @@ -639,7 +491,7 @@ defmodule Explorer.ChainTest do test "with logs" do transaction = insert(:transaction) - %Receipt{id: receipt_id} = insert(:receipt, transaction_id: transaction.id) + %Receipt{id: receipt_id} = insert(:receipt, transaction_hash: transaction.hash) %Log{id: id} = insert(:log, receipt_id: receipt_id) assert %Scrivener.Page{ @@ -652,7 +504,7 @@ defmodule Explorer.ChainTest do test "with logs can be paginated" do transaction = insert(:transaction) - %Receipt{id: receipt_id} = insert(:receipt, transaction_id: transaction.id) + %Receipt{id: receipt_id} = insert(:receipt, transaction_hash: transaction.hash) logs = insert_list(2, :log, receipt_id: receipt_id) [%Log{id: first_log_id}, %Log{id: second_log_id}] = logs @@ -676,7 +528,7 @@ defmodule Explorer.ChainTest do test "with logs necessity_by_association loads associations" do transaction = insert(:transaction) - %Receipt{id: receipt_id} = insert(:receipt, transaction_id: transaction.id) + %Receipt{id: receipt_id} = insert(:receipt, transaction_hash: transaction.hash) insert(:log, receipt_id: receipt_id) assert %Scrivener.Page{ @@ -715,38 +567,6 @@ defmodule Explorer.ChainTest do end end - describe "update_balance/2" do - test "updates the balance" do - hash = "0xwarheads" - insert(:address, hash: hash) - - Chain.update_balance(hash, 5) - - expected_balance = Decimal.new(5) - - assert {:ok, %Address{balance: ^expected_balance}} = Chain.hash_to_address(hash) - end - - test "updates the balance timestamp" do - hash = "0xtwizzlers" - insert(:address, hash: hash) - - Chain.update_balance(hash, 88) - - assert {:ok, %Address{balance_updated_at: balance_updated_at}} = Chain.hash_to_address("0xtwizzlers") - - refute is_nil(balance_updated_at) - end - - test "creates an address if one does not exist" do - Chain.update_balance("0xtwizzlers", 88) - - expected_balance = Decimal.new(88) - - assert {:ok, %Address{balance: ^expected_balance}} = Chain.hash_to_address("0xtwizzlers") - end - end - describe "value/2" do test "with InternalTransaction.t with :wei" do assert Chain.value(%InternalTransaction{value: Decimal.new(1)}, :wei) == Decimal.new(1) diff --git a/apps/explorer/test/explorer/ethereum/ethereum_test.exs b/apps/explorer/test/explorer/ethereum/ethereum_test.exs deleted file mode 100644 index 2117bb2682..0000000000 --- a/apps/explorer/test/explorer/ethereum/ethereum_test.exs +++ /dev/null @@ -1,18 +0,0 @@ -defmodule Explorer.EthereumTest do - use Explorer.DataCase - - alias Explorer.Ethereum - - describe "decode_integer_field/1" do - test "returns the integer value of a hex value" do - assert(Ethereum.decode_integer_field("0x7f2fb") == 520_955) - end - end - - describe "decode_time_field/1" do - test "returns the date value of a hex value" do - the_seventies = Timex.parse!("1970-01-01T00:00:18-00:00", "{ISO:Extended}") - assert(Ethereum.decode_time_field("0x12") == the_seventies) - end - end -end diff --git a/apps/explorer/test/support/factories/chain/address_factory.ex b/apps/explorer/test/support/factories/chain/address_factory.ex deleted file mode 100644 index e26810329f..0000000000 --- a/apps/explorer/test/support/factories/chain/address_factory.ex +++ /dev/null @@ -1,11 +0,0 @@ -defmodule Explorer.Chain.AddressFactory do - defmacro __using__(_opts) do - quote do - def address_factory do - %Explorer.Chain.Address{ - hash: String.pad_trailing(sequence("0x"), 42, "address") - } - end - end - end -end diff --git a/apps/explorer/test/support/factories/chain/block_factory.ex b/apps/explorer/test/support/factories/chain/block_factory.ex deleted file mode 100644 index 9ef458c079..0000000000 --- a/apps/explorer/test/support/factories/chain/block_factory.ex +++ /dev/null @@ -1,21 +0,0 @@ -defmodule Explorer.Chain.BlockFactory do - defmacro __using__(_opts) do - quote do - def block_factory do - %Explorer.Chain.Block{ - number: sequence(""), - hash: sequence("0x"), - parent_hash: sequence("0x"), - nonce: sequence(""), - miner: sequence("0x"), - difficulty: Enum.random(1..100_000), - total_difficulty: Enum.random(1..100_000), - size: Enum.random(1..100_000), - gas_limit: Enum.random(1..100_000), - gas_used: Enum.random(1..100_000), - timestamp: DateTime.utc_now() - } - end - end - end -end diff --git a/apps/explorer/test/support/factories/chain/internal_transaction_factory.ex b/apps/explorer/test/support/factories/chain/internal_transaction_factory.ex deleted file mode 100644 index 2edf5129db..0000000000 --- a/apps/explorer/test/support/factories/chain/internal_transaction_factory.ex +++ /dev/null @@ -1,20 +0,0 @@ -defmodule Explorer.Chain.InternalTransactionFactory do - defmacro __using__(_opts) do - quote do - def internal_transaction_factory do - %Explorer.Chain.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, - 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 diff --git a/apps/explorer/test/support/factories/chain/log_factory.ex b/apps/explorer/test/support/factories/chain/log_factory.ex deleted file mode 100644 index 537f59c533..0000000000 --- a/apps/explorer/test/support/factories/chain/log_factory.ex +++ /dev/null @@ -1,18 +0,0 @@ -defmodule Explorer.Chain.LogFactory do - defmacro __using__(_opts) do - quote do - def log_factory do - %Explorer.Chain.Log{ - address_id: insert(:address).id, - data: sequence("0x"), - first_topic: nil, - fourth_topic: nil, - index: sequence(""), - second_topic: nil, - third_topic: nil, - type: sequence("0x") - } - end - end - end -end diff --git a/apps/explorer/test/support/factories/chain/receipt_factory.ex b/apps/explorer/test/support/factories/chain/receipt_factory.ex deleted file mode 100644 index 3cb513c1ac..0000000000 --- a/apps/explorer/test/support/factories/chain/receipt_factory.ex +++ /dev/null @@ -1,14 +0,0 @@ -defmodule Explorer.Chain.ReceiptFactory do - defmacro __using__(_opts) do - quote do - def receipt_factory do - %Explorer.Chain.Receipt{ - cumulative_gas_used: Enum.random(21_000..100_000), - gas_used: Enum.random(21_000..100_000), - status: Enum.random(0..1), - index: sequence("") - } - end - end - end -end diff --git a/apps/explorer/test/support/factories/chain/transaction_factory.ex b/apps/explorer/test/support/factories/chain/transaction_factory.ex deleted file mode 100644 index 8f4bd5ff2a..0000000000 --- a/apps/explorer/test/support/factories/chain/transaction_factory.ex +++ /dev/null @@ -1,27 +0,0 @@ -defmodule Explorer.Chain.TransactionFactory do - defmacro __using__(_opts) do - quote do - alias Explorer.Chain.{Address, BlockTransaction, Transaction} - alias Explorer.Repo - - def transaction_factory do - %Transaction{ - hash: String.pad_trailing(sequence("0x"), 43, "action"), - value: Enum.random(1..100_000), - gas: Enum.random(21_000..100_000), - gas_price: Enum.random(10..99) * 1_000_000_00, - input: sequence("0x"), - nonce: Enum.random(1..1_000), - public_key: sequence("0x"), - r: sequence("0x"), - s: sequence("0x"), - standard_v: sequence("0x"), - transaction_index: sequence("0x"), - v: sequence("0x"), - to_address_id: insert(:address).id, - from_address_id: insert(:address).id - } - end - end - end -end diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index 7f5d43bd2d..b37f528a60 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -1,10 +1,133 @@ defmodule Explorer.Factory do - @dialyzer {:nowarn_function, fields_for: 1} use ExMachina.Ecto, repo: Explorer.Repo - use Explorer.Chain.AddressFactory - use Explorer.Chain.BlockFactory - use Explorer.Chain.InternalTransactionFactory - use Explorer.Chain.LogFactory - use Explorer.Chain.ReceiptFactory - use Explorer.Chain.TransactionFactory + + alias Explorer.Chain.{Address, Block, Hash, InternalTransaction, Log, Receipt, Transaction} + alias Explorer.Repo + + @dialyzer {:nowarn_function, fields_for: 1} + + def address_factory do + %Address{ + hash: address_hash() + } + end + + def address_hash do + {:ok, address_hash} = + "address_hash" + |> sequence(& &1) + |> Hash.Truncated.cast() + + address_hash + end + + def block_factory do + %Block{ + number: sequence("block_number", & &1), + hash: block_hash(), + parent_hash: block_hash(), + nonce: sequence("block_nonce"), + miner_hash: insert(:address).hash, + difficulty: Enum.random(1..100_000), + total_difficulty: Enum.random(1..100_000), + size: Enum.random(1..100_000), + gas_limit: Enum.random(1..100_000), + gas_used: Enum.random(1..100_000), + timestamp: DateTime.utc_now() + } + end + + def block_hash do + {:ok, block_hash} = + "block_hash" + |> sequence(& &1) + |> Hash.Full.cast() + + block_hash + end + + def internal_transaction_factory do + %InternalTransaction{ + call_type: Enum.random(["call", "creates", "calldelegate"]), + from_address_hash: insert(:address).hash, + gas: Enum.random(1..100_000), + gas_used: Enum.random(1..100_000), + index: Enum.random(0..9), + input: sequence("0x"), + output: sequence("0x"), + to_address_hash: insert(:address).hash, + trace_address: [Enum.random(0..4), Enum.random(0..4)], + value: Enum.random(1..100_000) + } + end + + def log_factory do + %Log{ + address_hash: insert(:address).hash, + data: sequence("0x"), + first_topic: nil, + fourth_topic: nil, + index: sequence(""), + second_topic: nil, + third_topic: nil, + type: sequence("0x") + } + end + + def receipt_factory do + %Receipt{ + cumulative_gas_used: Enum.random(21_000..100_000), + gas_used: Enum.random(21_000..100_000), + status: Enum.random(0..1), + index: sequence("") + } + end + + def transaction_factory do + %Transaction{ + from_address_hash: insert(:address).hash, + gas: Enum.random(21_000..100_000), + gas_price: Enum.random(10..99) * 1_000_000_00, + hash: transaction_hash(), + input: sequence("0x"), + nonce: Enum.random(1..1_000), + public_key: sequence("0x"), + r: sequence("0x"), + s: sequence("0x"), + standard_v: sequence("0x"), + to_address_hash: insert(:address).hash, + v: sequence("0x"), + value: Enum.random(1..100_000) + } + end + + def transaction_hash do + {:ok, transaction_hash} = + "transaction_hash" + |> sequence(& &1) + |> Hash.Full.cast() + + transaction_hash + end + + @doc """ + Validates the pending `transaction`(s) by add it to a `t:Explorer.Chain.Block.t/0` and giving it a `receipt` + """ + + def validate(transactions) when is_list(transactions) do + Enum.map(transactions, &validate/1) + end + + def validate(%Transaction{hash: hash} = transaction) do + block = insert(:block) + + block_transaction = + transaction + |> Explorer.Chain.Transaction.changeset(%{block_hash: block.hash, index: 0}) + |> Repo.update!() + + insert(:receipt, transaction_hash: hash) + + Repo.preload(block_transaction, [:block, :receipt]) + end end diff --git a/apps/explorer_web/lib/explorer_web/chain.ex b/apps/explorer_web/lib/explorer_web/chain.ex index b513852c75..b89bc3cde9 100644 --- a/apps/explorer_web/lib/explorer_web/chain.ex +++ b/apps/explorer_web/lib/explorer_web/chain.ex @@ -3,17 +3,26 @@ defmodule ExplorerWeb.Chain do Converts the `param` to the corresponding resource that uses that format of param. """ - import Explorer.Chain, only: [hash_to_address: 1, hash_to_transaction: 1, number_to_block: 1] + import Explorer.Chain, + only: [ + hash_to_address: 1, + hash_to_transaction: 1, + number_to_block: 1, + string_to_address_hash: 1, + string_to_transaction_hash: 1 + ] + + # Functions @spec from_param(String.t()) :: {:ok, Address.t() | Transaction.t() | Block.t()} | {:error, :not_found} def from_param(param) - def from_param(hash) when byte_size(hash) > 42 do - hash_to_transaction(hash) - end - - def from_param(hash) when byte_size(hash) == 42 do - hash_to_address(hash) + def from_param("0x" <> number_string = param) do + case String.length(number_string) do + 40 -> address_from_param(param) + 64 -> transaction_from_param(param) + _ -> {:error, :not_found} + end end def from_param(formatted_number) when is_binary(formatted_number) do @@ -29,4 +38,22 @@ defmodule ExplorerWeb.Chain do _ -> {:error, :invalid} end end + + ## Private Functions + + defp address_from_param(param) do + with {:ok, hash} <- string_to_address_hash(param) do + hash_to_address(hash) + else + :error -> {:error, :not_found} + end + end + + defp transaction_from_param(param) do + with {:ok, hash} <- string_to_transaction_hash(param) do + hash_to_transaction(hash) + else + :error -> {:error, :not_found} + end + end end diff --git a/apps/explorer_web/lib/explorer_web/controllers/address_controller.ex b/apps/explorer_web/lib/explorer_web/controllers/address_controller.ex index 88d413627b..9eeaa628fc 100644 --- a/apps/explorer_web/lib/explorer_web/controllers/address_controller.ex +++ b/apps/explorer_web/lib/explorer_web/controllers/address_controller.ex @@ -3,11 +3,12 @@ defmodule ExplorerWeb.AddressController do alias Explorer.Chain - def show(conn, %{"id" => hash}) do - hash - |> Chain.hash_to_address() - |> case do - {:ok, address} -> render(conn, "show.html", address: address) + def show(conn, %{"id" => string}) do + with {:ok, hash} <- Chain.string_to_address_hash(string), + {:ok, address} <- Chain.hash_to_address(hash) do + render(conn, "show.html", address: address) + else + :error -> not_found(conn) {:error, :not_found} -> not_found(conn) end end diff --git a/apps/explorer_web/lib/explorer_web/controllers/address_transaction_from_controller.ex b/apps/explorer_web/lib/explorer_web/controllers/address_transaction_from_controller.ex index 75123b4062..dc47ba76d4 100644 --- a/apps/explorer_web/lib/explorer_web/controllers/address_transaction_from_controller.ex +++ b/apps/explorer_web/lib/explorer_web/controllers/address_transaction_from_controller.ex @@ -7,22 +7,25 @@ defmodule ExplorerWeb.AddressTransactionFromController do alias Explorer.Chain - def index(conn, %{"address_id" => from_address_hash} = params) do - case Chain.hash_to_address(from_address_hash) do - {:ok, from_address} -> - page = - Chain.from_address_to_transactions( - from_address, - necessity_by_association: %{ - block: :required, - from_address: :optional, - to_address: :optional, - receipt: :required - }, - pagination: params - ) + def index(conn, %{"address_id" => from_address_hash_string} = params) do + with {:ok, from_address_hash} <- Chain.string_to_address_hash(from_address_hash_string), + {:ok, from_address} <- Chain.hash_to_address(from_address_hash) do + page = + Chain.from_address_to_transactions( + from_address, + necessity_by_association: %{ + block: :required, + from_address: :optional, + to_address: :optional, + receipt: :required + }, + pagination: params + ) - render(conn, "index.html", page: page) + render(conn, "index.html", page: page) + else + :error -> + not_found(conn) {:error, :not_found} -> not_found(conn) diff --git a/apps/explorer_web/lib/explorer_web/controllers/address_transaction_to_controller.ex b/apps/explorer_web/lib/explorer_web/controllers/address_transaction_to_controller.ex index 50168f32a1..16367422e4 100644 --- a/apps/explorer_web/lib/explorer_web/controllers/address_transaction_to_controller.ex +++ b/apps/explorer_web/lib/explorer_web/controllers/address_transaction_to_controller.ex @@ -7,22 +7,25 @@ defmodule ExplorerWeb.AddressTransactionToController do alias Explorer.Chain - def index(conn, %{"address_id" => to_address_hash} = params) do - case Chain.hash_to_address(to_address_hash) do - {:ok, to_address} -> - page = - Chain.to_address_to_transactions( - to_address, - necessity_by_association: %{ - block: :required, - from_address: :optional, - to_address: :optional, - receipt: :required - }, - pagination: params - ) + def index(conn, %{"address_id" => to_address_hash_string} = params) do + with {:ok, to_address_hash} <- Chain.string_to_address_hash(to_address_hash_string), + {:ok, to_address} <- Chain.hash_to_address(to_address_hash) do + page = + Chain.to_address_to_transactions( + to_address, + necessity_by_association: %{ + block: :required, + from_address: :optional, + to_address: :optional, + receipt: :required + }, + pagination: params + ) - render(conn, "index.html", page: page) + render(conn, "index.html", page: page) + else + :error -> + not_found(conn) {:error, :not_found} -> not_found(conn) diff --git a/apps/explorer_web/lib/explorer_web/controllers/chain_controller.ex b/apps/explorer_web/lib/explorer_web/controllers/chain_controller.ex index 8d3943730b..8cccf77cbf 100644 --- a/apps/explorer_web/lib/explorer_web/controllers/chain_controller.ex +++ b/apps/explorer_web/lib/explorer_web/controllers/chain_controller.ex @@ -22,11 +22,11 @@ defmodule ExplorerWeb.ChainController do end defp redirect_search_results(conn, %Address{} = item) do - redirect(conn, to: address_path(conn, :show, Gettext.get_locale(), item.hash)) + redirect(conn, to: address_path(conn, :show, Gettext.get_locale(), item)) end defp redirect_search_results(conn, %Block{} = item) do - redirect(conn, to: block_path(conn, :show, Gettext.get_locale(), item.number)) + redirect(conn, to: block_path(conn, :show, Gettext.get_locale(), item)) end defp redirect_search_results(conn, %Transaction{} = item) do @@ -37,7 +37,7 @@ defmodule ExplorerWeb.ChainController do conn, :show, Gettext.get_locale(), - item.hash + item ) ) end diff --git a/apps/explorer_web/lib/explorer_web/controllers/pending_transaction_controller.ex b/apps/explorer_web/lib/explorer_web/controllers/pending_transaction_controller.ex index 64ef95a2e2..d9fa89a8c3 100644 --- a/apps/explorer_web/lib/explorer_web/controllers/pending_transaction_controller.ex +++ b/apps/explorer_web/lib/explorer_web/controllers/pending_transaction_controller.ex @@ -2,45 +2,36 @@ defmodule ExplorerWeb.PendingTransactionController do use ExplorerWeb, :controller alias Explorer.Chain - alias Explorer.Chain.Transaction - def index(conn, %{"last_seen" => last_seen_id} = _) do - total = Chain.transaction_count(pending: true) - - transactions = - Chain.transactions_recently_before_id( - last_seen_id, - necessity_by_association: %{from_address: :optional, to_address: :optional}, - pending: true - ) + def index(conn, params) do + with %{"last_seen_pending_inserted_at" => last_seen_pending_inserted_at_string} <- params, + {:ok, last_seen_pending_inserted_at} = Timex.parse(last_seen_pending_inserted_at_string, "{ISO:Extended:Z}") do + do_index(conn, inserted_after: last_seen_pending_inserted_at) + else + _ -> do_index(conn) + end + end - last_seen_transaction_id = - case transactions do - [] -> - nil + ## Private Functions - _ -> - transactions - |> Stream.map(fn %Transaction{id: id} -> id end) - |> Enum.max() - end + defp do_index(conn, options \\ []) when is_list(options) do + full_options = Keyword.merge([necessity_by_association: %{from_address: :optional, to_address: :optional}], options) + transactions = Chain.recent_pending_transactions(full_options) + last_seen_pending_inserted_at = last_seen_pending_inserted_at(transactions) + transaction_count = Chain.transaction_count(pending: true) render( conn, "index.html", - last_seen_transaction_id: last_seen_transaction_id, - transaction_count: total, + last_seen_pending_inserted_at: last_seen_pending_inserted_at, + transaction_count: transaction_count, transactions: transactions ) end - def index(conn, params) do - last_seen = - [pending: true] - |> Chain.last_transaction_id() - |> Kernel.+(1) - |> Integer.to_string() + defp last_seen_pending_inserted_at([]), do: nil - index(conn, Map.put(params, "last_seen", last_seen)) + defp last_seen_pending_inserted_at(transactions) do + List.last(transactions).inserted_at end end diff --git a/apps/explorer_web/lib/explorer_web/controllers/transaction_controller.ex b/apps/explorer_web/lib/explorer_web/controllers/transaction_controller.ex index 9e1d54a87a..dded10efd9 100644 --- a/apps/explorer_web/lib/explorer_web/controllers/transaction_controller.ex +++ b/apps/explorer_web/lib/explorer_web/controllers/transaction_controller.ex @@ -2,73 +2,84 @@ defmodule ExplorerWeb.TransactionController do use ExplorerWeb, :controller alias Explorer.Chain - alias Explorer.Chain.Transaction - def index(conn, %{"last_seen" => last_seen_id}) do - total = Chain.transaction_count() + def index(conn, params) do + with %{"last_seen_collated_hash" => last_seen_collated_hash_string} <- params, + {:ok, last_seen_collated_hash} <- Chain.string_to_transaction_hash(last_seen_collated_hash_string) do + do_index(conn, after_hash: last_seen_collated_hash) + else + _ -> do_index(conn) + end + end - entries = - Chain.transactions_recently_before_id( - last_seen_id, - necessity_by_association: %{ - block: :required, - from_address: :optional, - to_address: :optional, - receipt: :required - } + def show(conn, %{"id" => hash_string}) do + with {:ok, hash} <- Chain.string_to_transaction_hash(hash_string), + {:ok, transaction} <- + Chain.hash_to_transaction( + hash, + necessity_by_association: %{ + block: :optional, + from_address: :optional, + to_address: :optional, + receipt: :optional + } + ) do + internal_transactions = + Chain.transaction_hash_to_internal_transactions( + transaction.hash, + necessity_by_association: %{from_address: :required, to_address: :required} + ) + + max_block_number = Chain.max_block_number() + + render( + conn, + "show.html", + internal_transactions: internal_transactions, + max_block_number: max_block_number, + transaction: transaction ) + else + :error -> + not_found(conn) - last = List.last(entries) || Transaction.null() + {:error, :not_found} -> + not_found(conn) + end + end + + ## Private Functions + + defp do_index(conn, options \\ []) when is_list(options) do + full_options = + Keyword.merge( + [ + necessity_by_association: %{ + block: :required, + from_address: :optional, + to_address: :optional, + receipt: :required + } + ], + options + ) + + transactions = Chain.recent_collated_transactions(full_options) + last_seen_collated_hash = last_seen_collated_hash(transactions) + transaction_count = Chain.transaction_count() render( conn, "index.html", - transactions: %{ - entries: entries, - total_entries: total, - last_seen: last.id - } + last_seen_collated_hash: last_seen_collated_hash, + transaction_count: transaction_count, + transactions: transactions ) end - def index(conn, params) do - last_seen = - Chain.last_transaction_id() - |> Kernel.+(1) - |> Integer.to_string() - - index(conn, Map.put(params, "last_seen", last_seen)) - end - - def show(conn, params) do - case Chain.hash_to_transaction( - params["id"], - necessity_by_association: %{ - block: :optional, - from_address: :optional, - to_address: :optional, - receipt: :optional - } - ) do - {:ok, transaction} -> - internal_transactions = - Chain.transaction_hash_to_internal_transactions( - transaction.hash, - necessity_by_association: %{from_address: :required, to_address: :required} - ) + defp last_seen_collated_hash([]), do: nil - max_block_number = Chain.max_block_number() - - render( - conn, - "show.html", - internal_transactions: internal_transactions, - max_block_number: max_block_number, - transaction: transaction - ) - - {:error, :not_found} -> - not_found(conn) - end + defp last_seen_collated_hash(transactions) do + List.last(transactions).hash end end diff --git a/apps/explorer_web/lib/explorer_web/controllers/transaction_log_controller.ex b/apps/explorer_web/lib/explorer_web/controllers/transaction_log_controller.ex index ac014a13ed..2f1efd35ae 100644 --- a/apps/explorer_web/lib/explorer_web/controllers/transaction_log_controller.ex +++ b/apps/explorer_web/lib/explorer_web/controllers/transaction_log_controller.ex @@ -3,33 +3,37 @@ defmodule ExplorerWeb.TransactionLogController do alias Explorer.Chain - def index(conn, %{"transaction_id" => transaction_hash} = params) do - case Chain.hash_to_transaction( - transaction_hash, - necessity_by_association: %{ - block: :optional, - from_address: :required, - receipt: :optional, - to_address: :required - } - ) do - {:ok, transaction} -> - logs = - Chain.transaction_to_logs( - transaction, - necessity_by_association: %{address: :optional}, - pagination: params - ) + def index(conn, %{"transaction_id" => transaction_hash_string} = params) do + with {:ok, transaction_hash} <- Chain.string_to_transaction_hash(transaction_hash_string), + {:ok, transaction} <- + Chain.hash_to_transaction( + transaction_hash, + necessity_by_association: %{ + block: :optional, + from_address: :required, + receipt: :optional, + to_address: :required + } + ) do + logs = + Chain.transaction_to_logs( + transaction, + necessity_by_association: %{address: :optional}, + pagination: params + ) - max_block_number = Chain.max_block_number() + max_block_number = Chain.max_block_number() - render( - conn, - "index.html", - logs: logs, - max_block_number: max_block_number, - transaction: transaction - ) + render( + conn, + "index.html", + logs: logs, + max_block_number: max_block_number, + transaction: transaction + ) + else + :error -> + not_found(conn) {:error, :not_found} -> not_found(conn) diff --git a/apps/explorer_web/lib/explorer_web/templates/address_transaction_from/index.html.eex b/apps/explorer_web/lib/explorer_web/templates/address_transaction_from/index.html.eex index 58cee67c1d..721506cca9 100644 --- a/apps/explorer_web/lib/explorer_web/templates/address_transaction_from/index.html.eex +++ b/apps/explorer_web/lib/explorer_web/templates/address_transaction_from/index.html.eex @@ -64,17 +64,17 @@
<%= link( - transaction.hash, + hash(transaction), class: "transactions__link transactions__link--truncated transactions__link--long-hash", - to: transaction_path(@conn, :show, @conn.assigns.locale, transaction.hash) + to: transaction_path(@conn, :show, @conn.assigns.locale, transaction) ) %>
<%= link( - transaction.block.number, + block(transaction), class: "transactions__link", - to: block_path(@conn, :show, @conn.assigns.locale, transaction.block.number) + to: block_path(@conn, :show, @conn.assigns.locale, transaction.block) ) %> @@ -83,18 +83,18 @@
<%= link( - transaction.from_address.hash, + from_address(transaction), class: "transactions__link transactions__link--truncated transactions__link--hash", - to: address_path(@conn, :show, @conn.assigns.locale, transaction.from_address.hash) + to: address_path(@conn, :show, @conn.assigns.locale, transaction.from_address) ) %>
<%= link( - transaction.to_address.hash, + to_address(transaction), class: "transactions__link transactions__link--truncated transactions__link--hash", - to: address_path(@conn, :show, @conn.assigns.locale, transaction.to_address.hash) + to: address_path(@conn, :show, @conn.assigns.locale, transaction.to_address) ) %>
diff --git a/apps/explorer_web/lib/explorer_web/templates/address_transaction_to/index.html.eex b/apps/explorer_web/lib/explorer_web/templates/address_transaction_to/index.html.eex index 413c12b9ff..f5b28dc853 100644 --- a/apps/explorer_web/lib/explorer_web/templates/address_transaction_to/index.html.eex +++ b/apps/explorer_web/lib/explorer_web/templates/address_transaction_to/index.html.eex @@ -63,17 +63,17 @@
<%= link( - transaction.hash, + hash(transaction), class: "transactions__link transactions__link--truncated transactions__link--long-hash", - to: transaction_path(@conn, :show, @conn.assigns.locale, transaction.hash) + to: transaction_path(@conn, :show, @conn.assigns.locale, transaction) ) %>
<%= link( - transaction.block.number, + block(transaction), class: "transactions__link", - to: block_path(@conn, :show, @conn.assigns.locale, transaction.block.number) + to: block_path(@conn, :show, @conn.assigns.locale, transaction.block) ) %> @@ -82,18 +82,18 @@
<%= link( - transaction.from_address.hash, + from_address(transaction), class: "transactions__link transactions__link--truncated transactions__link--hash", - to: address_path(@conn, :show, @conn.assigns.locale, transaction.from_address.hash) + to: address_path(@conn, :show, @conn.assigns.locale, transaction.from_address) ) %>
<%= link( - transaction.to_address.hash, + to_address(transaction), class: "transactions__link transactions__link--truncated transactions__link--hash", - to: address_path(@conn, :show, @conn.assigns.locale, transaction.to_address.hash) + to: address_path(@conn, :show, @conn.assigns.locale, transaction.to_address) ) %>
diff --git a/apps/explorer_web/lib/explorer_web/templates/block/show.html.eex b/apps/explorer_web/lib/explorer_web/templates/block/show.html.eex index 89d17a6d23..a86049a72a 100644 --- a/apps/explorer_web/lib/explorer_web/templates/block/show.html.eex +++ b/apps/explorer_web/lib/explorer_web/templates/block/show.html.eex @@ -38,20 +38,23 @@
<%= gettext "Hash" %>
-
<%= @block.hash %>
+ <% hash = hash(@block) %> +
<%= hash %>
<%= gettext "Parent Hash" %>
-
+ <% parent_hash = parent_hash(@block) %> +
<%= link( - @block.parent_hash, + parent_hash, class: "block__link", to: block_path(@conn, :show, @conn.assigns.locale, @block.number - 1) ) %>
<%= gettext "Miner" %>
-
<%= @block.miner %>
+ <% miner_hash = miner_hash(@block) %> +
<%= miner_hash %>
<%= gettext "Difficulty" %>
diff --git a/apps/explorer_web/lib/explorer_web/templates/block_transaction/index.html.eex b/apps/explorer_web/lib/explorer_web/templates/block_transaction/index.html.eex index b1e10015b1..9e3463c72f 100644 --- a/apps/explorer_web/lib/explorer_web/templates/block_transaction/index.html.eex +++ b/apps/explorer_web/lib/explorer_web/templates/block_transaction/index.html.eex @@ -55,16 +55,16 @@
<%= link( - transaction.hash, + hash(transaction), class: "transactions__link transactions__link--truncated transactions__link--long-hash", - to: transaction_path(@conn, :show, @conn.assigns.locale, transaction.hash) + to: transaction_path(@conn, :show, @conn.assigns.locale, transaction) ) %>
<%= link( - transaction.block.number, + block(transaction), class: "transactions__link", - to: block_path(@conn, :show, @conn.assigns.locale, transaction.block.number) + to: block_path(@conn, :show, @conn.assigns.locale, transaction.block) ) %> @@ -73,17 +73,17 @@
<%= link( - transaction.from_address.hash, + from_address(transaction), class: "transactions__link transactions__link--truncated transactions__link--hash", - to: address_path(@conn, :show, @conn.assigns.locale, transaction.from_address.hash) + to: address_path(@conn, :show, @conn.assigns.locale, transaction.from_address) ) %>
<%= link( - transaction.to_address.hash, + to_address(transaction), class: "transactions__link transactions__link--truncated transactions__link--hash", - to: address_path(@conn, :show, @conn.assigns.locale, transaction.to_address.hash) + to: address_path(@conn, :show, @conn.assigns.locale, transaction.to_address) ) %>
diff --git a/apps/explorer_web/lib/explorer_web/templates/pending_transaction/index.html.eex b/apps/explorer_web/lib/explorer_web/templates/pending_transaction/index.html.eex index c046ac0c02..6c4de97cc7 100644 --- a/apps/explorer_web/lib/explorer_web/templates/pending_transaction/index.html.eex +++ b/apps/explorer_web/lib/explorer_web/templates/pending_transaction/index.html.eex @@ -46,35 +46,25 @@ <%= link( - transaction.hash, + hash(transaction), class: "transactions__link transactions__link--truncated transactions__link--long-hash", - to: transaction_path(@conn, :show, @conn.assigns.locale, transaction.hash) + to: transaction_path(@conn, :show, @conn.assigns.locale, transaction) ) %> <%= last_seen(transaction) %> - - <% to_address_hash = to_address_hash(transaction) %> - <%= if to_address_hash do %> - <%= link( - to_address_hash, - class: "transactions__link transactions__link--truncated transactions__link--hash", - to: address_path(@conn, :show, @conn.assigns.locale, to_address_hash) - ) %> - <% else %> - <%= gettext "Pending" %> - <% end %> - - <% from_address_hash = from_address_hash(transaction) %> - <%= if from_address_hash do %> - <%= link( - from_address_hash, - class: "transactions__link transactions__link--truncated transactions__link--hash", - to: address_path(@conn, :show, @conn.assigns.locale, from_address_hash) - ) %> - <% else %> - <%= gettext "Pending" %> - <% end %> + <%= link( + from_address(transaction), + class: "transactions__link transactions__link--truncated transactions__link--hash", + to: address_path(@conn, :show, @conn.assigns.locale, transaction.from_address) + ) %> + + + <%= link( + to_address(transaction), + class: "transactions__link transactions__link--truncated transactions__link--hash", + to: address_path(@conn, :show, @conn.assigns.locale, transaction.to_address) + ) %> <%= value(transaction) %> <%= gettext "Ether" %> @@ -83,16 +73,21 @@ <% end %> - <%= link( - gettext("Next Page"), - class: "transactions__link transactions__link--next-page", - to: pending_transaction_path( - @conn, - :index, - @conn.assigns.locale, - %{"last_seen" => @last_seen_transaction_id} - ) - ) %> + <%= if @last_seen_pending_inserted_at do %> + <%= link( + gettext("Next Page"), + class: "transactions__link transactions__link--next-page", + to: pending_transaction_path( + @conn, + :index, + @conn.assigns.locale, + %{ + "last_seen_inserted_at" => + Timex.format!(@last_seen_pending_inserted_at, "{ISO:Extended:Z}") + } + ) + ) %> + <% end %>
diff --git a/apps/explorer_web/lib/explorer_web/templates/transaction/index.html.eex b/apps/explorer_web/lib/explorer_web/templates/transaction/index.html.eex index 5bc6ea3ab4..450fa4f4b5 100644 --- a/apps/explorer_web/lib/explorer_web/templates/transaction/index.html.eex +++ b/apps/explorer_web/lib/explorer_web/templates/transaction/index.html.eex @@ -2,7 +2,7 @@

- <%= gettext("Showing %{count} Transactions", count: @transactions.total_entries) %> + <%= gettext("Showing %{count} Transactions", count: @transaction_count) %>

@@ -40,7 +40,7 @@ - <%= for transaction <- @transactions.entries do %> + <%= for transaction <- @transactions do %>
@@ -48,17 +48,17 @@
<%= link( - transaction.hash, + hash(transaction), class: "transactions__link transactions__link--truncated transactions__link--long-hash", - to: transaction_path(@conn, :show, @conn.assigns.locale, transaction.hash) + to: transaction_path(@conn, :show, @conn.assigns.locale, transaction) ) %>
<%= link( - transaction.block.number, + block(transaction), class: "transactions__link", - to: block_path(@conn, :show, @conn.assigns.locale, transaction.block.number) + to: block_path(@conn, :show, @conn.assigns.locale, transaction.block) ) %> @@ -67,17 +67,17 @@
<%= link( - transaction.from_address.hash, + from_address(transaction), class: "transactions__link transactions__link--truncated transactions__link--hash", - to: address_path(@conn, :show, @conn.assigns.locale, transaction.from_address.hash) + to: address_path(@conn, :show, @conn.assigns.locale, transaction.from_address) ) %>
<%= link( - transaction.to_address.hash, + to_address(transaction), class: "transactions__link transactions__link--truncated transactions__link--hash", - to: address_path(@conn, :show, @conn.assigns.locale, transaction.to_address.hash) + to: address_path(@conn, :show, @conn.assigns.locale, transaction.to_address) ) %>
@@ -87,11 +87,18 @@ <% end %> - <%= link( - gettext("Next Page"), - class: "transactions__link transactions__link--next-page", - to: transaction_path(@conn, :index, @conn.assigns.locale, %{"last_seen" => @transactions.last_seen}) - ) %> + <%= if @last_seen_collated_hash do %> + <%= link( + gettext("Next Page"), + class: "transactions__link transactions__link--next-page", + to: transaction_path( + @conn, + :index, + @conn.assigns.locale, + %{"last_seen_collated_hash" => to_string(@last_seen_collated_hash)} + ) + ) %> + <% end %>
diff --git a/apps/explorer_web/lib/explorer_web/views/address_transaction_from_view.ex b/apps/explorer_web/lib/explorer_web/views/address_transaction_from_view.ex index c76c3e82d7..990482cc86 100644 --- a/apps/explorer_web/lib/explorer_web/views/address_transaction_from_view.ex +++ b/apps/explorer_web/lib/explorer_web/views/address_transaction_from_view.ex @@ -3,6 +3,10 @@ defmodule ExplorerWeb.AddressTransactionFromView do alias ExplorerWeb.TransactionView + defdelegate block(transaction), to: TransactionView + defdelegate from_address(transaction), to: TransactionView + defdelegate hash(transaction), to: TransactionView defdelegate status(transacton), to: TransactionView + defdelegate to_address(transaction), to: TransactionView defdelegate value(transaction), to: TransactionView end diff --git a/apps/explorer_web/lib/explorer_web/views/address_transaction_to_view.ex b/apps/explorer_web/lib/explorer_web/views/address_transaction_to_view.ex index ec1b88dc15..6d4898a70d 100644 --- a/apps/explorer_web/lib/explorer_web/views/address_transaction_to_view.ex +++ b/apps/explorer_web/lib/explorer_web/views/address_transaction_to_view.ex @@ -3,6 +3,10 @@ defmodule ExplorerWeb.AddressTransactionToView do alias ExplorerWeb.TransactionView + defdelegate block(transaction), to: TransactionView + defdelegate from_address(transaction), to: TransactionView + defdelegate hash(transaction), to: TransactionView defdelegate status(transacton), to: TransactionView + defdelegate to_address(transaction), to: TransactionView defdelegate value(transaction), to: TransactionView end diff --git a/apps/explorer_web/lib/explorer_web/views/block_transaction_view.ex b/apps/explorer_web/lib/explorer_web/views/block_transaction_view.ex index 7af46a8d31..6f77917150 100644 --- a/apps/explorer_web/lib/explorer_web/views/block_transaction_view.ex +++ b/apps/explorer_web/lib/explorer_web/views/block_transaction_view.ex @@ -5,6 +5,10 @@ defmodule ExplorerWeb.BlockTransactionView do # Functions + defdelegate from_address(transaction), to: TransactionView + defdelegate block(transaction), to: TransactionView + defdelegate hash(transaction), to: TransactionView defdelegate status(transacton), to: TransactionView + defdelegate to_address(transaction), to: TransactionView defdelegate value(transaction), to: TransactionView end diff --git a/apps/explorer_web/lib/explorer_web/views/block_view.ex b/apps/explorer_web/lib/explorer_web/views/block_view.ex index e3dcb8aefa..1afeee9e69 100644 --- a/apps/explorer_web/lib/explorer_web/views/block_view.ex +++ b/apps/explorer_web/lib/explorer_web/views/block_view.ex @@ -14,4 +14,16 @@ defmodule ExplorerWeb.BlockView do def formatted_timestamp(%Block{timestamp: timestamp}) do Timex.format!(timestamp, "%b-%d-%Y %H:%M:%S %p %Z", :strftime) end + + def hash(%Block{hash: hash}) do + to_string(hash) + end + + def miner_hash(%Block{miner_hash: miner_hash}) do + to_string(miner_hash) + end + + def parent_hash(%Block{parent_hash: parent_hash}) do + to_string(parent_hash) + end end diff --git a/apps/explorer_web/lib/explorer_web/views/pending_transaction_view.ex b/apps/explorer_web/lib/explorer_web/views/pending_transaction_view.ex index c5201aa5fa..2401d9070f 100644 --- a/apps/explorer_web/lib/explorer_web/views/pending_transaction_view.ex +++ b/apps/explorer_web/lib/explorer_web/views/pending_transaction_view.ex @@ -1,28 +1,15 @@ defmodule ExplorerWeb.PendingTransactionView do use ExplorerWeb, :view - alias Explorer.Chain.{Address, Transaction} alias ExplorerWeb.TransactionView @dialyzer :no_match # Functions - def from_address_hash(%Transaction{from_address: from_address}) do - case from_address do - %Address{hash: hash} -> hash - _ -> nil - end - end - + defdelegate from_address(transaction), to: TransactionView + defdelegate hash(transaction), to: TransactionView defdelegate last_seen(transaction), to: TransactionView - - def to_address_hash(%Transaction{to_address: to_address}) do - case to_address do - %Address{hash: hash} -> hash - _ -> nil - end - end - + defdelegate to_address(transaction), to: TransactionView defdelegate value(transaction), to: TransactionView end diff --git a/apps/explorer_web/lib/explorer_web/views/transaction_view.ex b/apps/explorer_web/lib/explorer_web/views/transaction_view.ex index a4fda9db28..1ac2273a36 100644 --- a/apps/explorer_web/lib/explorer_web/views/transaction_view.ex +++ b/apps/explorer_web/lib/explorer_web/views/transaction_view.ex @@ -3,11 +3,18 @@ defmodule ExplorerWeb.TransactionView do alias Cldr.Number alias Explorer.Chain - alias Explorer.Chain.{Block, InternalTransaction, Transaction} + alias Explorer.Chain.{Address, Block, InternalTransaction, Transaction} alias ExplorerWeb.BlockView # Functions + def block(%Transaction{block: block}) do + case block do + nil -> gettext("Pending") + _ -> to_string(block.number) + end + end + def confirmations(%Transaction{block: block}, named_arguments) when is_list(named_arguments) do case block do nil -> 0 @@ -44,6 +51,10 @@ defmodule ExplorerWeb.TransactionView do end end + def from_address(%Transaction{from_address: %Address{hash: hash}}) do + to_string(hash) + end + def gas(%type{gas: gas}) when type in [InternalTransaction, Transaction] do Cldr.Number.to_string!(gas) end @@ -54,6 +65,10 @@ defmodule ExplorerWeb.TransactionView do |> Cldr.Number.to_string!() end + def hash(%Transaction{hash: hash}) do + to_string(hash) + end + def last_seen(%Transaction{updated_at: updated_at}) do Timex.from_now(updated_at) end @@ -73,6 +88,13 @@ defmodule ExplorerWeb.TransactionView do end end + def to_address(%Transaction{to_address: to_address}) do + case to_address do + nil -> "Contract Creation" + _ -> to_string(to_address) + end + end + def value(transaction) do transaction |> Chain.value(:ether) diff --git a/apps/explorer_web/lib/phoenix/html/safe.ex b/apps/explorer_web/lib/phoenix/html/safe.ex new file mode 100644 index 0000000000..c49af83cf1 --- /dev/null +++ b/apps/explorer_web/lib/phoenix/html/safe.ex @@ -0,0 +1,8 @@ +alias Explorer.Chain +alias Explorer.Chain.Hash + +defimpl Phoenix.HTML.Safe, for: Hash do + def to_iodata(hash) do + Chain.hash_to_iodata(hash) + end +end diff --git a/apps/explorer_web/lib/phoenix/param.ex b/apps/explorer_web/lib/phoenix/param.ex new file mode 100644 index 0000000000..bde64a36fe --- /dev/null +++ b/apps/explorer_web/lib/phoenix/param.ex @@ -0,0 +1,25 @@ +alias Explorer.Chain.{Address, Block, Hash, Transaction} + +defimpl Phoenix.Param, for: Address do + def to_param(%@for{hash: hash}) do + @protocol.to_param(hash) + end +end + +defimpl Phoenix.Param, for: Block do + def to_param(%@for{number: number}) do + to_string(number) + end +end + +defimpl Phoenix.Param, for: Hash do + def to_param(hash) do + to_string(hash) + end +end + +defimpl Phoenix.Param, for: Transaction do + def to_param(%@for{hash: hash}) do + @protocol.to_param(hash) + end +end diff --git a/apps/explorer_web/mix.exs b/apps/explorer_web/mix.exs index 25cfb27f61..771a0199a1 100644 --- a/apps/explorer_web/mix.exs +++ b/apps/explorer_web/mix.exs @@ -69,7 +69,7 @@ defmodule ExplorerWeb.Mixfile do defp deps do [ {:cowboy, "~> 1.0"}, - {:credo, "0.9.1", only: [:dev, :test], runtime: false}, + {:credo, "0.9.2", only: [:dev, :test], runtime: false}, {:crontab, "~> 1.1"}, {:dialyxir, "~> 0.5", only: [:dev, :test], runtime: false}, {:ex_cldr_numbers, "~> 1.0"}, diff --git a/apps/explorer_web/priv/gettext/default.pot b/apps/explorer_web/priv/gettext/default.pot index 04b06f9b23..49d16a91d4 100644 --- a/apps/explorer_web/priv/gettext/default.pot +++ b/apps/explorer_web/priv/gettext/default.pot @@ -28,7 +28,7 @@ msgid "Copyright %{year} POA" msgstr "" #: lib/explorer_web/templates/block/index.html.eex:32 -#: lib/explorer_web/templates/block/show.html.eex:77 +#: lib/explorer_web/templates/block/show.html.eex:80 #: lib/explorer_web/templates/chain/show.html.eex:67 msgid "Gas Used" msgstr "" @@ -80,22 +80,22 @@ msgstr "" msgid "Block #%{number} Details" msgstr "" -#: lib/explorer_web/templates/block/show.html.eex:57 +#: lib/explorer_web/templates/block/show.html.eex:60 msgid "Difficulty" msgstr "" #: lib/explorer_web/templates/block/index.html.eex:33 -#: lib/explorer_web/templates/block/show.html.eex:85 +#: lib/explorer_web/templates/block/show.html.eex:88 #: lib/explorer_web/templates/transaction/overview.html.eex:93 #: lib/explorer_web/templates/transaction/show.html.eex:29 msgid "Gas Limit" msgstr "" -#: lib/explorer_web/templates/block/show.html.eex:53 +#: lib/explorer_web/templates/block/show.html.eex:55 msgid "Miner" msgstr "" -#: lib/explorer_web/templates/block/show.html.eex:89 +#: lib/explorer_web/templates/block/show.html.eex:92 #: lib/explorer_web/templates/transaction/overview.html.eex:75 msgid "Nonce" msgstr "" @@ -104,11 +104,11 @@ msgstr "" msgid "Number" msgstr "" -#: lib/explorer_web/templates/block/show.html.eex:44 +#: lib/explorer_web/templates/block/show.html.eex:45 msgid "Parent Hash" msgstr "" -#: lib/explorer_web/templates/block/show.html.eex:73 +#: lib/explorer_web/templates/block/show.html.eex:76 msgid "Size" msgstr "" @@ -116,7 +116,7 @@ msgstr "" msgid "Timestamp" msgstr "" -#: lib/explorer_web/templates/block/show.html.eex:67 +#: lib/explorer_web/templates/block/show.html.eex:70 msgid "Total Difficulty" msgstr "" @@ -179,7 +179,7 @@ msgstr "" msgid "Overview" msgstr "" -#: lib/explorer_web/views/transaction_view.ex:72 +#: lib/explorer_web/views/transaction_view.ex:87 msgid "Success" msgstr "" @@ -229,15 +229,14 @@ msgid "Showing %{count} Transactions" msgstr "" #: lib/explorer_web/templates/pending_transaction/index.html.eex:19 -#: lib/explorer_web/templates/pending_transaction/index.html.eex:64 -#: lib/explorer_web/templates/pending_transaction/index.html.eex:76 #: lib/explorer_web/templates/transaction/index.html.eex:19 #: lib/explorer_web/templates/transaction/overview.html.eex:56 #: lib/explorer_web/templates/transaction/overview.html.eex:70 -#: lib/explorer_web/views/transaction_view.ex:20 -#: lib/explorer_web/views/transaction_view.ex:35 +#: lib/explorer_web/views/transaction_view.ex:13 +#: lib/explorer_web/views/transaction_view.ex:27 #: lib/explorer_web/views/transaction_view.ex:42 -#: lib/explorer_web/views/transaction_view.ex:71 +#: lib/explorer_web/views/transaction_view.ex:49 +#: lib/explorer_web/views/transaction_view.ex:86 msgid "Pending" msgstr "" @@ -295,16 +294,16 @@ msgstr "" msgid "TPM" msgstr "" -#: lib/explorer_web/templates/pending_transaction/index.html.eex:87 -#: lib/explorer_web/templates/transaction/index.html.eex:91 +#: lib/explorer_web/templates/pending_transaction/index.html.eex:78 +#: lib/explorer_web/templates/transaction/index.html.eex:92 msgid "Next Page" msgstr "" -#: lib/explorer_web/views/transaction_view.ex:69 +#: lib/explorer_web/views/transaction_view.ex:84 msgid "Failed" msgstr "" -#: lib/explorer_web/views/transaction_view.ex:70 +#: lib/explorer_web/views/transaction_view.ex:85 msgid "Out of Gas" msgstr "" @@ -330,7 +329,7 @@ msgstr "" #: lib/explorer_web/templates/address_transaction_to/index.html.eex:101 #: lib/explorer_web/templates/block_transaction/index.html.eex:90 #: lib/explorer_web/templates/chain/show.html.eex:128 -#: lib/explorer_web/templates/pending_transaction/index.html.eex:80 +#: lib/explorer_web/templates/pending_transaction/index.html.eex:70 #: lib/explorer_web/templates/transaction/index.html.eex:84 #: lib/explorer_web/templates/transaction/overview.html.eex:44 #: lib/explorer_web/templates/transaction/show.html.eex:28 diff --git a/apps/explorer_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/explorer_web/priv/gettext/en/LC_MESSAGES/default.po index 0d0d084240..efb407b15a 100644 --- a/apps/explorer_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/explorer_web/priv/gettext/en/LC_MESSAGES/default.po @@ -40,7 +40,7 @@ msgid "Copyright %{year} POA" msgstr "%{year} POA Network Ltd. All rights reserved" #: lib/explorer_web/templates/block/index.html.eex:32 -#: lib/explorer_web/templates/block/show.html.eex:77 +#: lib/explorer_web/templates/block/show.html.eex:80 #: lib/explorer_web/templates/chain/show.html.eex:67 msgid "Gas Used" msgstr "Gas Used" @@ -92,22 +92,22 @@ msgstr "Value" msgid "Block #%{number} Details" msgstr "Block #%{number} Details" -#: lib/explorer_web/templates/block/show.html.eex:57 +#: lib/explorer_web/templates/block/show.html.eex:60 msgid "Difficulty" msgstr "Difficulty" #: lib/explorer_web/templates/block/index.html.eex:33 -#: lib/explorer_web/templates/block/show.html.eex:85 +#: lib/explorer_web/templates/block/show.html.eex:88 #: lib/explorer_web/templates/transaction/overview.html.eex:93 #: lib/explorer_web/templates/transaction/show.html.eex:29 msgid "Gas Limit" msgstr "Gas Limit" -#: lib/explorer_web/templates/block/show.html.eex:53 +#: lib/explorer_web/templates/block/show.html.eex:55 msgid "Miner" msgstr "Validator" -#: lib/explorer_web/templates/block/show.html.eex:89 +#: lib/explorer_web/templates/block/show.html.eex:92 #: lib/explorer_web/templates/transaction/overview.html.eex:75 msgid "Nonce" msgstr "Nonce" @@ -116,11 +116,11 @@ msgstr "Nonce" msgid "Number" msgstr "Height" -#: lib/explorer_web/templates/block/show.html.eex:44 +#: lib/explorer_web/templates/block/show.html.eex:45 msgid "Parent Hash" msgstr "Parent Hash" -#: lib/explorer_web/templates/block/show.html.eex:73 +#: lib/explorer_web/templates/block/show.html.eex:76 msgid "Size" msgstr "Size" @@ -128,7 +128,7 @@ msgstr "Size" msgid "Timestamp" msgstr "Timestamp" -#: lib/explorer_web/templates/block/show.html.eex:67 +#: lib/explorer_web/templates/block/show.html.eex:70 msgid "Total Difficulty" msgstr "Total Difficulty" @@ -191,7 +191,7 @@ msgstr "From" msgid "Overview" msgstr "Overview" -#: lib/explorer_web/views/transaction_view.ex:72 +#: lib/explorer_web/views/transaction_view.ex:87 msgid "Success" msgstr "Success" @@ -241,15 +241,14 @@ msgid "Showing %{count} Transactions" msgstr "Showing %{count} Transactions" #: lib/explorer_web/templates/pending_transaction/index.html.eex:19 -#: lib/explorer_web/templates/pending_transaction/index.html.eex:64 -#: lib/explorer_web/templates/pending_transaction/index.html.eex:76 #: lib/explorer_web/templates/transaction/index.html.eex:19 #: lib/explorer_web/templates/transaction/overview.html.eex:56 #: lib/explorer_web/templates/transaction/overview.html.eex:70 -#: lib/explorer_web/views/transaction_view.ex:20 -#: lib/explorer_web/views/transaction_view.ex:35 +#: lib/explorer_web/views/transaction_view.ex:13 +#: lib/explorer_web/views/transaction_view.ex:27 #: lib/explorer_web/views/transaction_view.ex:42 -#: lib/explorer_web/views/transaction_view.ex:71 +#: lib/explorer_web/views/transaction_view.ex:49 +#: lib/explorer_web/views/transaction_view.ex:86 msgid "Pending" msgstr "Pending" @@ -307,16 +306,16 @@ msgstr "" msgid "TPM" msgstr "" -#: lib/explorer_web/templates/pending_transaction/index.html.eex:87 -#: lib/explorer_web/templates/transaction/index.html.eex:91 +#: lib/explorer_web/templates/pending_transaction/index.html.eex:78 +#: lib/explorer_web/templates/transaction/index.html.eex:92 msgid "Next Page" msgstr "" -#: lib/explorer_web/views/transaction_view.ex:69 +#: lib/explorer_web/views/transaction_view.ex:84 msgid "Failed" msgstr "" -#: lib/explorer_web/views/transaction_view.ex:70 +#: lib/explorer_web/views/transaction_view.ex:85 msgid "Out of Gas" msgstr "" @@ -342,7 +341,7 @@ msgstr "" #: lib/explorer_web/templates/address_transaction_to/index.html.eex:101 #: lib/explorer_web/templates/block_transaction/index.html.eex:90 #: lib/explorer_web/templates/chain/show.html.eex:128 -#: lib/explorer_web/templates/pending_transaction/index.html.eex:80 +#: lib/explorer_web/templates/pending_transaction/index.html.eex:70 #: lib/explorer_web/templates/transaction/index.html.eex:84 #: lib/explorer_web/templates/transaction/overview.html.eex:44 #: lib/explorer_web/templates/transaction/show.html.eex:28 diff --git a/apps/explorer_web/test/explorer_web/chain_test.exs b/apps/explorer_web/test/explorer_web/chain_test.exs index 88a0d14d25..680d51772c 100644 --- a/apps/explorer_web/test/explorer_web/chain_test.exs +++ b/apps/explorer_web/test/explorer_web/chain_test.exs @@ -14,16 +14,16 @@ defmodule ExplorerWeb.ChainTest do |> Chain.from_param() end - test "finds a transaction by hash" do - %Transaction{hash: hash} = insert(:transaction) + test "finds a transaction by hash string" do + transaction = %Transaction{hash: hash} = insert(:transaction) - assert {:ok, %Transaction{hash: ^hash}} = Chain.from_param(hash) + assert {:ok, %Transaction{hash: ^hash}} = transaction |> Phoenix.Param.to_param() |> Chain.from_param() end - test "finds an address by hash" do - %Address{hash: hash} = insert(:address) + test "finds an address by hash string" do + address = %Address{hash: hash} = insert(:address) - assert {:ok, %Address{hash: ^hash}} = Chain.from_param(hash) + assert {:ok, %Address{hash: ^hash}} = address |> Phoenix.Param.to_param() |> Chain.from_param() end test "returns {:error, :not_found} when garbage is passed in" do diff --git a/apps/explorer_web/test/explorer_web/controllers/address_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/address_controller_test.exs index c85deddc6c..17a97daca9 100644 --- a/apps/explorer_web/test/explorer_web/controllers/address_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/address_controller_test.exs @@ -11,13 +11,13 @@ defmodule ExplorerWeb.AddressControllerTest do end test "with address returns an address", %{conn: conn} do - address = insert(:address, hash: "0x9") + address = insert(:address, hash: "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed") Credit.refresh() Debit.refresh() - conn = get(conn, "/en/addresses/0x9") + conn = get(conn, "/en/addresses/0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed") - assert conn.assigns.address.id == address.id + assert conn.assigns.address.hash == address.hash end end end diff --git a/apps/explorer_web/test/explorer_web/controllers/address_transaction_from_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/address_transaction_from_controller_test.exs index 41171f6912..0b1f78ec60 100644 --- a/apps/explorer_web/test/explorer_web/controllers/address_transaction_from_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/address_transaction_from_controller_test.exs @@ -12,9 +12,8 @@ defmodule ExplorerWeb.AddressTransactionFromControllerTest do test "returns transactions from this address", %{conn: conn} do address = insert(:address) - hash = "0xsnacks" block = insert(:block) - transaction = insert(:transaction, block_id: block.id, from_address_id: address.id, hash: hash) + transaction = insert(:transaction, block_hash: block.hash, from_address_hash: address.hash, index: 0) insert(:receipt, transaction: transaction) conn = get(conn, address_transaction_from_path(ExplorerWeb.Endpoint, :index, :en, address.hash)) @@ -26,13 +25,13 @@ defmodule ExplorerWeb.AddressTransactionFromControllerTest do assert length(transaction_hash_divs) == 1 assert List.first(transaction_hash_divs) |> Floki.attribute("href") == [ - "/en/transactions/#{hash}" + "/en/transactions/#{Phoenix.Param.to_param(transaction)}" ] end test "does not return transactions to this address", %{conn: conn} do block = insert(:block) - transaction = insert(:transaction, block_id: block.id, hash: "0xsnacks") + transaction = insert(:transaction, block_hash: block.hash, index: 0) insert(:receipt, transaction: transaction) address = insert(:address) @@ -44,7 +43,7 @@ defmodule ExplorerWeb.AddressTransactionFromControllerTest do test "does not return related transactions without a receipt", %{conn: conn} do block = insert(:block) - insert(:transaction, block_id: block.id) + insert(:transaction, block_hash: block.hash, index: 0) address = insert(:address) conn = get(conn, address_transaction_from_path(ExplorerWeb.Endpoint, :index, :en, address.hash)) @@ -55,7 +54,7 @@ defmodule ExplorerWeb.AddressTransactionFromControllerTest do test "does not return related transactions without a from address", %{conn: conn} do block = insert(:block) - transaction = insert(:transaction, block_id: block.id) + transaction = insert(:transaction, block_hash: block.hash, index: 0) insert(:receipt, transaction: transaction) address = insert(:address) @@ -67,7 +66,7 @@ defmodule ExplorerWeb.AddressTransactionFromControllerTest do test "does not return related transactions without a to address", %{conn: conn} do block = insert(:block) - transaction = insert(:transaction, block_id: block.id) + transaction = insert(:transaction, block_hash: block.hash, index: 0) insert(:receipt, transaction: transaction) address = insert(:address) diff --git a/apps/explorer_web/test/explorer_web/controllers/address_transaction_to_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/address_transaction_to_controller_test.exs index 629ae744e4..770c91472b 100644 --- a/apps/explorer_web/test/explorer_web/controllers/address_transaction_to_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/address_transaction_to_controller_test.exs @@ -12,11 +12,10 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do test "returns transactions to this address", %{conn: conn} do address = insert(:address) - hash = "0xsnacks" - transaction = insert(:transaction, block_id: insert(:block).id, hash: hash, to_address_id: address.id) + transaction = insert(:transaction, block_hash: insert(:block).hash, index: 0, to_address_hash: address.hash) insert(:receipt, transaction: transaction) - conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address.hash)) + conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address)) assert html = html_response(conn, 200) assert html |> Floki.find("tbody tr") |> length == 1 @@ -26,16 +25,16 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do assert length(transaction_hash_divs) == 1 assert List.first(transaction_hash_divs) |> Floki.attribute("href") == [ - "/en/transactions/#{hash}" + "/en/transactions/#{Phoenix.Param.to_param(transaction)}" ] end test "does not return transactions from this address", %{conn: conn} do - transaction = insert(:transaction, hash: "0xsnacks") + transaction = insert(:transaction) insert(:receipt, transaction: transaction) address = insert(:address) - conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address.hash)) + conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address)) assert html = html_response(conn, 200) assert html |> Floki.find("tbody tr") |> length == 0 @@ -44,9 +43,16 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do test "does not return related transactions without a receipt", %{conn: conn} do address = insert(:address) block = insert(:block) - insert(:transaction, block_id: block.id, from_address_id: address.id, to_address_id: address.id) - conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address.hash)) + insert( + :transaction, + block_hash: block.hash, + from_address_hash: address.hash, + index: 0, + to_address_hash: address.hash + ) + + conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address)) assert html = html_response(conn, 200) assert html |> Floki.find("tbody tr") |> length == 0 @@ -57,7 +63,7 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do insert(:receipt, transaction: transaction) address = insert(:address) - conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address.hash)) + conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address)) assert html = html_response(conn, 200) assert html |> Floki.find("tbody tr") |> length == 0 @@ -66,10 +72,10 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do test "does not return related transactions without a to address", %{conn: conn} do address = insert(:address) block = insert(:block) - transaction = insert(:transaction, block_id: block.id, from_address_id: address.id) + transaction = insert(:transaction, block_hash: block.hash, from_address_hash: address.hash, index: 0) insert(:receipt, transaction: transaction) - conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address.hash)) + conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address)) assert html = html_response(conn, 200) assert html |> Floki.find("tbody tr") |> length == 0 diff --git a/apps/explorer_web/test/explorer_web/controllers/block_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/block_controller_test.exs index 3a50d0440c..a7ee4b3e70 100644 --- a/apps/explorer_web/test/explorer_web/controllers/block_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/block_controller_test.exs @@ -1,6 +1,10 @@ defmodule ExplorerWeb.BlockControllerTest do use ExplorerWeb.ConnCase + alias Explorer.Chain.Block + + @locale "en" + describe "GET show/2" do test "without block", %{conn: conn} do conn = get(conn, "/en/blocks/3") @@ -9,25 +13,33 @@ defmodule ExplorerWeb.BlockControllerTest do end test "with block returns a block", %{conn: conn} do - block = insert(:block, number: 3) - conn = get(conn, "/en/blocks/3") - assert conn.assigns.block.id == block.id + block = insert(:block) + + conn = get(conn, block_path(conn, :show, @locale, block)) + + assert conn.assigns.block.hash == block.hash end end describe "GET index/2" do test "returns all blocks", %{conn: conn} do - block_ids = insert_list(4, :block) |> Enum.map(fn block -> block.number end) |> Enum.reverse() + block_ids = + 4 + |> insert_list(:block) + |> Stream.map(fn block -> block.number end) + |> Enum.reverse() + + conn = get(conn, block_path(conn, :index, @locale)) - conn = get(conn, "/en/blocks") assert conn.assigns.blocks |> Enum.map(fn block -> block.number end) == block_ids end test "returns a block with two transactions", %{conn: conn} do - block = insert(:block) - insert_list(2, :transaction, block_id: block.id) + %Block{hash: hash} = insert(:block) + + Enum.map(0..1, fn index -> insert(:transaction, block_hash: hash, index: index) end) - conn = get(conn, "/en/blocks") + conn = get(conn, block_path(conn, :index, @locale)) assert conn.assigns.blocks.entries |> Enum.count() == 1 end diff --git a/apps/explorer_web/test/explorer_web/controllers/block_transaction_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/block_transaction_controller_test.exs index 3a5d6b321d..8753906451 100644 --- a/apps/explorer_web/test/explorer_web/controllers/block_transaction_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/block_transaction_controller_test.exs @@ -17,9 +17,8 @@ defmodule ExplorerWeb.BlockTransactionControllerTest do end test "returns transactions for the block", %{conn: conn} do - hash = "0xsnacks" block = insert(:block) - transaction = insert(:transaction, block_id: block.id, hash: hash) + transaction = insert(:transaction, block_hash: block.hash) insert(:receipt, transaction: transaction) conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block.number)) @@ -31,7 +30,7 @@ defmodule ExplorerWeb.BlockTransactionControllerTest do assert length(transaction_hash_divs) == 1 assert List.first(transaction_hash_divs) |> Floki.attribute("href") == [ - "/en/transactions/#{hash}" + "/en/transactions/#{Phoenix.Param.to_param(transaction)}" ] end @@ -39,36 +38,26 @@ defmodule ExplorerWeb.BlockTransactionControllerTest do insert(:transaction) block = insert(:block) - conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block.number)) + conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block)) refute html_response(conn, 200) =~ ~r/transactions__row/ end test "does not return related transactions without a receipt", %{conn: conn} do block = insert(:block) - insert(:transaction, block_id: block.id) + insert(:transaction, block_hash: block.hash, index: 0) - conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block.number)) - - refute html_response(conn, 200) =~ ~r/transactions__row/ - end - - test "does not return related transactions without a from address", %{conn: conn} do - block = insert(:block) - transaction = insert(:transaction, block_id: block.id, from_address_id: nil) - insert(:receipt, transaction: transaction) - - conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block.number)) + conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block)) refute html_response(conn, 200) =~ ~r/transactions__row/ end test "does not return related transactions without a to address", %{conn: conn} do block = insert(:block) - transaction = insert(:transaction, block_id: block.id, to_address_id: nil) + transaction = insert(:transaction, block_hash: block.hash, index: 0, to_address_hash: nil) insert(:receipt, transaction: transaction) - conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block.number)) + conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block)) refute html_response(conn, 200) =~ ~r/transactions__row/ end diff --git a/apps/explorer_web/test/explorer_web/controllers/chain_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/chain_controller_test.exs index 3698dd2e66..cb51907a46 100644 --- a/apps/explorer_web/test/explorer_web/controllers/chain_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/chain_controller_test.exs @@ -36,27 +36,25 @@ defmodule ExplorerWeb.ChainControllerTest do test "only returns transactions with an associated block", %{conn: conn} do block = insert(:block, number: 33) - insert(:transaction, block_id: block.id, id: 10, hash: "0xDECAFBAD") - insert(:transaction, id: 30) + associated = insert(:transaction, block_hash: block.hash, index: 0) + unassociated = insert(:transaction) conn = get(conn, "/en") - transaction_ids = - conn.assigns.chain.transactions - |> Enum.map(fn transaction -> transaction.id end) + transaction_hashes = Enum.map(conn.assigns.chain.transactions, fn transaction -> transaction.hash end) - assert(Enum.member?(transaction_ids, 10)) - refute(Enum.member?(transaction_ids, 30)) + assert(Enum.member?(transaction_hashes, associated.hash)) + refute(Enum.member?(transaction_hashes, unassociated.hash)) end test "returns a transaction", %{conn: conn} do block = insert(:block, number: 33) - insert(:transaction, block_id: block.id, hash: "0xDECAFBAD") + transaction = insert(:transaction, block_hash: block.hash, index: 0) conn = get(conn, "/en") - assert(List.first(conn.assigns.chain.transactions).hash == "0xDECAFBAD") + assert(List.first(conn.assigns.chain.transactions).hash == transaction.hash) end end @@ -69,24 +67,24 @@ defmodule ExplorerWeb.ChainControllerTest do end test "finds a transaction by hash", %{conn: conn} do - transaction = insert(:transaction, block_id: insert(:block).id) - conn = get(conn, "/en/search?q=#{transaction.hash}") + transaction = insert(:transaction, block_hash: insert(:block).hash) + conn = get(conn, "/en/search?q=#{to_string(transaction.hash)}") - assert redirected_to(conn) == transaction_path(conn, :show, "en", transaction.hash) + assert redirected_to(conn) == transaction_path(conn, :show, "en", transaction) end test "finds an address by hash", %{conn: conn} do address = insert(:address) - conn = get(conn, "en/search?q=#{address.hash}") + conn = get(conn, "en/search?q=#{to_string(address.hash)}") - assert redirected_to(conn) == address_path(conn, :show, "en", address.hash) + assert redirected_to(conn) == address_path(conn, :show, "en", address) end test "finds an address by hash when there are extra spaces", %{conn: conn} do address = insert(:address) - conn = get(conn, "en/search?q=#{address.hash} ") + conn = get(conn, "en/search?q=#{to_string(address.hash)} ") - assert redirected_to(conn) == address_path(conn, :show, "en", address.hash) + assert redirected_to(conn) == address_path(conn, :show, "en", address) end test "redirects to 404 when it finds nothing", %{conn: conn} do diff --git a/apps/explorer_web/test/explorer_web/controllers/pending_transaction_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/pending_transaction_controller_test.exs index b507f839b3..e589d4a4a3 100644 --- a/apps/explorer_web/test/explorer_web/controllers/pending_transaction_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/pending_transaction_controller_test.exs @@ -4,10 +4,9 @@ defmodule ExplorerWeb.PendingTransactionControllerTest do import ExplorerWeb.Router.Helpers, only: [pending_transaction_path: 3] describe "GET index/2" do - test "returns no transactions that have a receipt", %{conn: conn} do + test "returns no transactions that are in a block", %{conn: conn} do block = insert(:block) - transaction = insert(:transaction, block_id: block.id) - insert(:receipt, transaction: transaction) + insert(:transaction, block_hash: block.hash, index: 0) conn = get(conn, pending_transaction_path(ExplorerWeb.Endpoint, :index, :en)) @@ -19,7 +18,7 @@ defmodule ExplorerWeb.PendingTransactionControllerTest do test "does not count transactions that have a receipt", %{conn: conn} do block = insert(:block) - transaction = insert(:transaction, block_id: block.id) + transaction = insert(:transaction, block_hash: block.hash, index: 0) insert(:receipt, transaction: transaction) conn = get(conn, pending_transaction_path(ExplorerWeb.Endpoint, :index, :en)) @@ -35,7 +34,7 @@ defmodule ExplorerWeb.PendingTransactionControllerTest do conn = get(conn, pending_transaction_path(ExplorerWeb.Endpoint, :index, :en)) - assert html_response(conn, 200) =~ transaction.hash + assert html_response(conn, 200) =~ to_string(transaction.hash) end test "returns a count of pending transactions", %{conn: conn} do @@ -47,16 +46,30 @@ defmodule ExplorerWeb.PendingTransactionControllerTest do end test "paginates transactions using the last seen transaction", %{conn: conn} do - transaction = insert(:transaction) + {:ok, first_inserted_at, 0} = DateTime.from_iso8601("2015-01-23T23:50:07Z") + insert(:transaction, inserted_at: first_inserted_at) + {:ok, second_inserted_at, 0} = DateTime.from_iso8601("2016-01-23T23:50:07Z") + insert(:transaction, inserted_at: second_inserted_at) - conn = + first_response_conn = get( conn, pending_transaction_path(ExplorerWeb.Endpoint, :index, :en), - last_seen: transaction.id + last_seen_pending_inserted_at: Timex.format!(first_inserted_at, "{ISO:Extended:Z}") ) - refute html_response(conn, 200) =~ ~r/transactions__row/ + assert first_html = html_response(first_response_conn, 200) + assert first_html |> Floki.find("table.transactions__table tbody tr") |> Enum.count() == 1 + + second_response_conn = + get( + conn, + pending_transaction_path(ExplorerWeb.Endpoint, :index, :en), + last_seen_pending_inserted_at: Timex.format!(second_inserted_at, "{ISO:Extended:Z}") + ) + + assert second_html = html_response(second_response_conn, 200) + assert second_html |> Floki.find("table.transactions__table tbody tr") |> Enum.count() == 0 end test "sends back an estimate of the number of transactions", %{conn: conn} do @@ -67,7 +80,7 @@ defmodule ExplorerWeb.PendingTransactionControllerTest do end test "works when there are no transactions", %{conn: conn} do - conn = get(conn, pending_transaction_path(ExplorerWeb.Endpoint, :index, :en)) + conn = get(conn, pending_transaction_path(conn, :index, :en)) assert html = html_response(conn, 200) diff --git a/apps/explorer_web/test/explorer_web/controllers/transaction_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/transaction_controller_test.exs index 08be4aea3c..23d15f0a14 100644 --- a/apps/explorer_web/test/explorer_web/controllers/transaction_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/transaction_controller_test.exs @@ -5,59 +5,70 @@ defmodule ExplorerWeb.TransactionControllerTest do describe "GET index/2" do test "returns a transaction with a receipt", %{conn: conn} do - block = insert(:block) - transaction = insert(:transaction, block_id: block.id) - insert(:receipt, transaction: transaction) + transaction = + :transaction + |> insert() + |> validate() conn = get(conn, "/en/transactions") - assert List.first(conn.assigns.transactions.entries).id == transaction.id + assert List.first(conn.assigns.transactions).hash == transaction.hash end test "returns a count of transactions", %{conn: conn} do block = insert(:block) - transaction = insert(:transaction, block_id: block.id) + transaction = insert(:transaction, block_hash: block.hash) insert(:receipt, transaction: transaction) conn = get(conn, "/en/transactions") - assert length(conn.assigns.transactions.entries) === 1 + assert length(conn.assigns.transactions) == 1 end test "returns no pending transactions", %{conn: conn} do - insert(:transaction, block_id: insert(:block).id) + insert(:transaction) conn = get(conn, "/en/transactions") - assert conn.assigns.transactions.entries == [] + assert conn.assigns.transactions == [] end test "only returns transactions that have a receipt", %{conn: conn} do insert(:transaction) + conn = get(conn, "/en/transactions") - assert length(conn.assigns.transactions.entries) === 0 + + assert length(conn.assigns.transactions) == 0 end test "paginates transactions using the last seen transaction", %{conn: conn} do block = insert(:block) - transaction = insert(:transaction, block_id: block.id) + transaction = insert(:transaction, block_hash: block.hash, index: 0) insert(:receipt, transaction: transaction) - conn = get(conn, "/en/transactions", last_seen: transaction.id) + conn = + get( + conn, + "/en/transactions", + last_seen_collated_hash: to_string(transaction.hash) + ) - assert conn.assigns.transactions.entries == [] + assert conn.assigns.transactions == [] end - test "sends back an estimate of the number of transactions", %{conn: conn} do + test "sends back the number of transactions", %{conn: conn} do insert(:transaction) + conn = get(conn, "/en/transactions") - refute conn.assigns.transactions.total_entries == nil + + refute conn.assigns.transaction_count == nil end test "works when there are no transactions", %{conn: conn} do conn = get(conn, "/en/transactions") - assert conn.assigns.transactions.total_entries == 0 - assert conn.assigns.transactions.entries == [] + + assert conn.assigns.transaction_count == 0 + assert conn.assigns.transactions == [] end end @@ -72,33 +83,33 @@ defmodule ExplorerWeb.TransactionControllerTest do conn: conn } do block = insert(:block, %{number: 777}) - transaction = insert(:transaction, block_id: block.id, hash: "0x8") + transaction = insert(:transaction, block_hash: block.hash, index: 0) - conn = get(conn, "/en/transactions/0x8") + conn = get(conn, transaction_path(conn, :show, :en, transaction)) assert html = html_response(conn, 200) - assert html |> Floki.find("div.transaction__header h3") |> Floki.text() == transaction.hash + assert html |> Floki.find("div.transaction__header h3") |> Floki.text() == to_string(transaction.hash) assert html |> Floki.find("span.transaction__item--primary a") |> Floki.text() == to_string(block.number) end test "returns a transaction without associated block data", %{conn: conn} do - transaction = insert(:transaction, hash: "0x8") + transaction = insert(:transaction) - conn = get(conn, "/en/transactions/0x8") + conn = get(conn, transaction_path(conn, :show, :en, transaction)) assert html = html_response(conn, 200) - assert html |> Floki.find("div.transaction__header h3") |> Floki.text() == transaction.hash + assert html |> Floki.find("div.transaction__header h3") |> Floki.text() == to_string(transaction.hash) assert html |> Floki.find("span.transaction__item--primary a") |> Floki.text() == "" end test "returns internal transactions for the transaction", %{conn: conn} do transaction = insert(:transaction) - internal_transaction = insert(:internal_transaction, transaction_id: transaction.id) + internal_transaction = insert(:internal_transaction, transaction_hash: transaction.hash) - path = transaction_path(ExplorerWeb.Endpoint, :show, :en, transaction.hash) + path = transaction_path(ExplorerWeb.Endpoint, :show, :en, transaction) conn = get(conn, path) diff --git a/apps/explorer_web/test/explorer_web/controllers/transaction_log_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/transaction_log_controller_test.exs index a7d7108a65..3176cce68b 100644 --- a/apps/explorer_web/test/explorer_web/controllers/transaction_log_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/transaction_log_controller_test.exs @@ -14,9 +14,9 @@ defmodule ExplorerWeb.TransactionLogControllerTest do transaction = insert(:transaction) receipt = insert(:receipt, transaction: transaction) address = insert(:address) - insert(:log, receipt: receipt, address_id: address.id) + insert(:log, receipt: receipt, address_hash: address.hash) - conn = get(conn, transaction_log_path(ExplorerWeb.Endpoint, :index, :en, transaction.hash)) + conn = get(conn, transaction_log_path(ExplorerWeb.Endpoint, :index, :en, transaction)) first_log = List.first(conn.assigns.logs.entries) assert first_log.receipt_id == receipt.id @@ -24,7 +24,7 @@ defmodule ExplorerWeb.TransactionLogControllerTest do test "assigns no logs when there are none", %{conn: conn} do transaction = insert(:transaction) - path = transaction_log_path(ExplorerWeb.Endpoint, :index, :en, transaction.hash) + path = transaction_log_path(ExplorerWeb.Endpoint, :index, :en, transaction) conn = get(conn, path) diff --git a/apps/explorer_web/test/explorer_web/features/contributor_browsing_test.exs b/apps/explorer_web/test/explorer_web/features/contributor_browsing_test.exs index bf18d8225a..c1dbe2bed2 100644 --- a/apps/explorer_web/test/explorer_web/features/contributor_browsing_test.exs +++ b/apps/explorer_web/test/explorer_web/features/contributor_browsing_test.exs @@ -3,7 +3,8 @@ defmodule ExplorerWeb.UserListTest do import Wallaby.Query, only: [css: 1, css: 2, link: 1] - alias Explorer.Chain.{Credit, Debit} + # alias Explorer.Chain + alias Explorer.Chain.{Address, Block, Credit, Debit, Transaction} @logo css("img.header__logo") @@ -18,39 +19,35 @@ defmodule ExplorerWeb.UserListTest do end test "search for blocks", %{session: session} do - insert(:block, number: 42, miner: "mittens") + %Block{miner_hash: miner_hash} = insert(:block, number: 42) session |> visit("/") |> fill_in(css(".header__cell--search-input"), with: "42") |> send_keys([:enter]) - |> assert_has(css(".block__item", text: "mittens")) + |> assert_has(css(~s|.block__item dd[title="#{miner_hash}"]|)) end test "search for transactions", %{session: session} do - insert(:transaction, hash: "0xdeadbeef000000000000000000000000000000000", input: "socks") + input = "INPUT" + %Transaction{hash: hash} = insert(:transaction, input: input) session |> visit("/") - |> fill_in( - css(".header__cell--search-input"), - with: "0xdeadbeef000000000000000000000000000000000" - ) + |> fill_in(css(".header__cell--search-input"), with: to_string(hash)) |> send_keys([:enter]) - |> assert_has(css(".transaction__item", text: "socks")) + |> assert_has(css(".transaction__item", text: input)) end test "search for address", %{session: session} do - insert(:address, hash: "0xBAADF00D00000000000000000000000000000000") + %Address{hash: hash} = insert(:address) + string = to_string(hash) session |> visit("/") - |> fill_in( - css(".header__cell--search-input"), - with: "0xBAADF00D00000000000000000000000000000000" - ) + |> fill_in(css(".header__cell--search-input"), with: string) |> send_keys([:enter]) - |> assert_has(css(".address__subheading", text: "0xBAADF00D00000000000000000000000000000000")) + |> assert_has(css(".address__subheading", text: string)) end test "views blocks", %{session: session} do @@ -59,22 +56,23 @@ defmodule ExplorerWeb.UserListTest do gas_used: 10 }) + number = 311 + number_string = to_string(number) + fifth_block = insert(:block, %{ - number: 311, - hash: "0xMrCoolBlock", + number: number, timestamp: Timex.now() |> Timex.shift(hours: -1), - miner: "Heathcliff", size: 9_999_999, nonce: "once upon a nonce", gas_used: 1_010_101, gas_limit: 5_030_101 }) - transaction = insert(:transaction, block_id: fifth_block.id, hash: "0xfaschtnacht") + transaction = insert(:transaction, block_hash: fifth_block.hash, index: 0) - insert(:transaction, block_id: fifth_block.id, hash: "0xpaczki") - insert(:transaction, block_id: fifth_block.id) + insert(:transaction, block_hash: fifth_block.hash, index: 1) + insert(:transaction, block_hash: fifth_block.hash, index: 2) insert(:receipt, transaction: transaction) Credit.refresh() @@ -90,17 +88,17 @@ defmodule ExplorerWeb.UserListTest do session |> click(link("Blocks")) - |> assert_has(css(".blocks__column--height", text: "311")) - |> click(link("311")) - |> assert_has(css(".block__item", text: "0xMrCoolBlock")) - |> assert_has(css(".block__item", text: "Heathcliff")) + |> assert_has(css(".blocks__column--height", text: number_string)) + |> click(link(number_string)) + |> assert_has(css(~s|.block__item dd[title="#{fifth_block.hash}"]|)) + |> assert_has(css(~s|.block__item dd[title="#{fifth_block.miner_hash}"]|)) |> assert_has(css(".block__item", text: "9,999,999")) |> assert_has(css(".block__item", text: "1 hour ago")) |> assert_has(css(".block__item", text: "5,030,101")) |> assert_has(css(".block__item", text: "once upon a nonce")) |> assert_has(css(".block__item", text: "1,010,101")) |> click(css(".block__link", text: "Transactions")) - |> assert_has(css(".transactions__link--long-hash", text: "0xfaschtnacht")) + |> assert_has(css(".transactions__link--long-hash", text: to_string(transaction.hash))) end test "views transactions", %{session: session} do @@ -111,98 +109,103 @@ defmodule ExplorerWeb.UserListTest do gas_used: 123_987 }) - insert_list(4, :transaction, block_id: block.id) - insert(:transaction, block_id: insert(:block).id, hash: "0xC001", gas: 5891) + Enum.each(0..3, &insert(:transaction, block_hash: block.hash, index: &1)) + # pending_transaction = insert(:transaction, gas: 5891) - lincoln = insert(:address, hash: "0xlincoln") - taft = insert(:address, hash: "0xhowardtaft") + lincoln = insert(:address) + taft = insert(:address) transaction = insert( :transaction, - block_id: block.id, - hash: "0xSk8", - value: Explorer.Chain.Wei.from(Decimal.new(5656), :ether), + block_hash: block.hash, + from_address_hash: taft.hash, gas: Decimal.new(1_230_000_000_000_123_123), gas_price: Decimal.new(7_890_000_000_898_912_300_045), + index: 4, input: "0x00012", - nonce: 99045, inserted_at: Timex.parse!("1970-01-01T00:00:18-00:00", "{ISO:Extended}"), + nonce: 99045, + to_address_hash: lincoln.hash, updated_at: Timex.parse!("1980-01-01T00:00:18-00:00", "{ISO:Extended}"), - from_address_id: taft.id, - to_address_id: lincoln.id + value: Explorer.Chain.Wei.from(Decimal.new(5656), :ether) ) receipt = insert(:receipt, transaction: transaction, status: 1) - insert(:log, address_id: lincoln.id, receipt: receipt) + insert(:log, address_hash: lincoln.hash, receipt: receipt) # From Lincoln to Taft. - txn_from_lincoln = + transaction_from_lincoln = insert( :transaction, - block_id: block.id, - hash: "0xrazerscooter", - from_address_id: lincoln.id, - to_address_id: taft.id + block_hash: block.hash, + from_address_hash: lincoln.hash, + index: 5, + to_address_hash: taft.hash ) - insert(:receipt, transaction: txn_from_lincoln) + insert(:receipt, transaction: transaction_from_lincoln) - internal = insert(:internal_transaction, transaction_id: transaction.id) + # internal = insert(:internal_transaction, transaction_hash: transaction.hash) Credit.refresh() Debit.refresh() + # transaction_hash_string = to_string(transaction.hash) + session |> visit("/en") |> assert_has(css(".transactions__title", text: "Transactions")) |> assert_has(css(".transactions__column--hash", count: 5)) |> assert_has(css(".transactions__column--value", count: 5)) |> assert_has(css(".transactions__column--age", count: 5, visible: false)) - |> visit("/transactions") - |> click(css(".transactions__tab-link", text: "Pending")) - |> click(css(".transactions__link", text: "0xC001")) - |> assert_has(css(".transaction__item-value--status", text: "Pending")) - |> visit("/transactions") - |> refute_has(css(".transactions__column--block", text: "Pending")) - |> click(link("0xSk8")) - |> assert_has(css(".transaction__subheading", text: "0xSk8")) - |> assert_has(css(".transaction__item", text: "123,987")) - |> assert_has(css(".transaction__item", text: "5,656 POA")) - |> assert_has(css(".transaction__item", text: "Success")) - |> assert_has( - css( - ".transaction__item", - text: "7,890,000,000,898,912,300,045 Wei (7,890,000,000,898.912 Gwei)" - ) - ) - |> assert_has(css(".transaction__item", text: "1,230,000,000,000,123,123 Gas")) - |> assert_has(css(".transaction__item", text: "0x00012")) - |> assert_has(css(".transaction__item", text: "99045")) - |> assert_has(css(".transaction__item", text: "123,987")) - |> assert_has(css(".transaction__item", text: "0xlincoln")) - |> assert_has(css(".transaction__item", text: "0xhowardtaft")) - |> assert_has(css(".transaction__item", text: "block confirmations")) - |> assert_has(css(".transaction__item", text: "49 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")) - |> click(css(".transaction-log__link", text: "0xlincoln")) - |> assert_has(css(".address__subheading", text: "0xlincoln")) - |> click(css(".address__link", text: "Transactions To")) - |> assert_has(css(".transactions__link--long-hash", text: "0xSk8")) - |> click(css(".address__link", text: "Transactions From")) - |> assert_has(css(".transactions__link--long-hash", text: "0xrazerscooter")) + + # |> visit("/transactions") + # |> click(css(".transactions__tab-link", text: "Pending")) + # |> click(css(".transactions__link", text: Chain.transaction_hash_to_string(pending_transaction.hash))) + # |> assert_has(css(".transaction__item-value--status", text: "Pending")) + # |> visit("/transactions") + # |> refute_has(css(".transactions__column--block", text: "Pending")) + # |> click(link(transaction_hash_string)) + # |> assert_has(css(".transaction__subheading", text: transaction_hash_string)) + # |> assert_has(css(".transaction__item", text: "123,987")) + # |> assert_has(css(".transaction__item", text: "5,656 POA")) + # |> assert_has(css(".transaction__item", text: "Success")) + # |> assert_has( + # css( + # ".transaction__item", + # text: "7,890,000,000,898,912,300,045 Wei (7,890,000,000,898.912 Gwei)" + # ) + # ) + # |> assert_has(css(".transaction__item", text: "1,230,000,000,000,123,123 Gas")) + # |> assert_has(css(".transaction__item", text: "0x00012")) + # |> assert_has(css(".transaction__item", text: "99045")) + # |> assert_has(css(".transaction__item", text: "123,987")) + # |> assert_has(css(".transaction__item", text: "0xlincoln")) + # |> assert_has(css(".transaction__item", text: "0xhowardtaft")) + # |> assert_has(css(".transaction__item", text: "block confirmations")) + # |> assert_has(css(".transaction__item", text: "49 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")) + # |> click(css(".transaction-log__link", text: "0xlincoln")) + # |> assert_has(css(".address__subheading", text: "0xlincoln")) + # |> click(css(".address__link", text: "Transactions To")) + # |> assert_has(css(".transactions__link--long-hash", text: "0xSk8")) + # |> click(css(".address__link", text: "Transactions From")) + # |> assert_has( + # css(".transactions__link--long-hash", text: Chain.transaction_hash_to_string(transaction_from_lincoln.hash)) + # ) end test "views addresses", %{session: session} do - insert(:address, hash: "0xthinmints", balance: 500) + address = insert(:address, balance: 500) session - |> visit("/en/addresses/0xthinmints") + |> visit("/en/addresses/#{Phoenix.Param.to_param(address)}") |> assert_has(css(".address__balance", text: "0.0000000000000005")) end end diff --git a/apps/explorer_web/test/explorer_web/views/transaction_view_test.exs b/apps/explorer_web/test/explorer_web/views/transaction_view_test.exs index 809f9bf076..bab1e854a8 100644 --- a/apps/explorer_web/test/explorer_web/views/transaction_view_test.exs +++ b/apps/explorer_web/test/explorer_web/views/transaction_view_test.exs @@ -17,12 +17,12 @@ defmodule ExplorerWeb.TransactionViewTest do test "with receipt with status 0 with gas_used < gas" do gas = 2 - %Transaction{id: id} = insert(:transaction, gas: gas) - insert(:receipt, gas_used: gas - 1, status: 0, transaction_id: id) + %Transaction{hash: hash} = insert(:transaction, gas: gas) + insert(:receipt, gas_used: gas - 1, status: 0, transaction_hash: hash) transaction = Transaction - |> Repo.get!(id) + |> Repo.get!(hash) |> Repo.preload(:receipt) assert TransactionView.formatted_status(transaction) == "Failed" @@ -30,12 +30,12 @@ defmodule ExplorerWeb.TransactionViewTest do test "with receipt with status 0 with gas <= gas_used" do gas = 2 - %Transaction{id: id} = insert(:transaction, gas: gas) - insert(:receipt, gas_used: gas, status: 0, transaction_id: id) + %Transaction{hash: hash} = insert(:transaction, gas: gas) + insert(:receipt, gas_used: gas, status: 0, transaction_hash: hash) transaction = Transaction - |> Repo.get!(id) + |> Repo.get!(hash) |> Repo.preload(:receipt) assert TransactionView.formatted_status(transaction) == "Out of Gas" @@ -43,12 +43,12 @@ defmodule ExplorerWeb.TransactionViewTest do test "with receipt with status 1" do gas = 2 - %Transaction{id: id} = insert(:transaction, gas: gas) - insert(:receipt, gas_used: gas - 1, status: 1, transaction_id: id) + %Transaction{hash: hash} = insert(:transaction, gas: gas) + insert(:receipt, gas_used: gas - 1, status: 1, transaction_hash: hash) transaction = Transaction - |> Repo.get!(id) + |> Repo.get!(hash) |> Repo.preload(:receipt) assert TransactionView.formatted_status(transaction) == "Success" diff --git a/mix.lock b/mix.lock index 593c78da23..e5617502e2 100644 --- a/mix.lock +++ b/mix.lock @@ -7,7 +7,7 @@ "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, "cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"}, - "credo": {:hex, :credo, "0.9.1", "f021affa11b32a94dc2e807a6472ce0914289c9132f99644a97fc84432b202a1", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, + "credo": {:hex, :credo, "0.9.2", "841d316612f568beb22ba310d816353dddf31c2d94aa488ae5a27bb53760d0bf", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, "crontab": {:hex, :crontab, "1.1.2", "4784a50987b4a19af07a908f98e8a308b00f9c93efc5a7892155dc10cd8fc7d9", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 2.1", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, "db_connection": {:hex, :db_connection, "1.1.3", "89b30ca1ef0a3b469b1c779579590688561d586694a3ce8792985d4d7e575a61", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, "decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [:mix], [], "hexpm"}, From 1fb23bf3959fc880e06d59d60d584f93792705ad Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 1 May 2018 06:37:31 -0500 Subject: [PATCH 03/77] Sokol indexer complete to block 2229598 --- apps/explorer/config/dev.exs | 5 +- apps/explorer/lib/explorer/chain.ex | 328 +++++++++++++----- apps/explorer/lib/explorer/chain/address.ex | 8 + apps/explorer/lib/explorer/chain/block.ex | 6 +- .../explorer/chain/internal_transaction.ex | 147 +++++--- .../chain/internal_transaction/call_type.ex | 122 +++++++ .../chain/internal_transaction/type.ex | 120 +++++++ apps/explorer/lib/explorer/chain/log.ex | 8 +- apps/explorer/lib/explorer/chain/receipt.ex | 54 +-- .../lib/explorer/chain/receipt/status.ex | 113 ++++++ .../lib/explorer/chain/transaction.ex | 41 +-- .../lib/explorer/indexer/block_fetcher.ex | 38 +- apps/explorer/lib/explorer/jsonrpc.ex | 139 +++----- apps/explorer/lib/explorer/jsonrpc/block.ex | 194 ++++++++++- apps/explorer/lib/explorer/jsonrpc/blocks.ex | 57 +++ apps/explorer/lib/explorer/jsonrpc/log.ex | 123 +++++++ apps/explorer/lib/explorer/jsonrpc/logs.ex | 25 ++ apps/explorer/lib/explorer/jsonrpc/parity.ex | 49 +++ .../lib/explorer/jsonrpc/parity/trace.ex | 313 +++++++++++++++++ .../explorer/jsonrpc/parity/trace/action.ex | 38 ++ .../explorer/jsonrpc/parity/trace/result.ex | 36 ++ .../lib/explorer/jsonrpc/parity/traces.ex | 17 + apps/explorer/lib/explorer/jsonrpc/receipt.ex | 106 +++++- .../explorer/lib/explorer/jsonrpc/receipts.ex | 44 ++- .../lib/explorer/jsonrpc/transaction.ex | 79 ++++- .../20180117221922_create_blocks.exs | 2 +- .../20180117221923_create_transactions.exs | 10 + .../20180212214442_create_receipts.exs | 8 +- .../migrations/20180212222309_create_logs.exs | 11 +- ...221001948_create_internal_transactions.exs | 43 ++- .../test/explorer/chain/credit_test.exs | 42 ++- .../test/explorer/chain/debit_test.exs | 41 ++- .../internal_transaction/call_type_test.exs | 5 + .../chain/internal_transaction/type_test.exs | 5 + .../explorer/chain/receipt/status_test.exs | 5 + .../test/explorer/chain/receipt_test.exs | 26 +- .../test/explorer/chain/statistics_test.exs | 6 +- apps/explorer/test/explorer/chain_test.exs | 74 ++-- .../test/explorer/jsonrpc/block_test.exs | 5 + .../test/explorer/jsonrpc/blocks_test.exs | 5 + .../test/explorer/jsonrpc/log_test.exs | 5 + .../jsonrpc/parity/trace/action_test.exs | 5 + .../jsonrpc/parity/trace/result_test.exs | 5 + .../explorer/jsonrpc/parity/trace_test.exs | 5 + .../test/explorer/jsonrpc/receipt_test.exs | 5 + .../explorer/jsonrpc/transaction_test.exs | 5 + apps/explorer/test/support/factory.ex | 55 ++- ...dress_transaction_from_controller_test.exs | 8 +- ...address_transaction_to_controller_test.exs | 19 +- .../block_transaction_controller_test.exs | 6 +- .../controllers/chain_controller_test.exs | 3 +- .../pending_transaction_controller_test.exs | 2 +- .../transaction_controller_test.exs | 6 +- .../transaction_log_controller_test.exs | 13 +- .../features/contributor_browsing_test.exs | 11 +- .../views/transaction_view_test.exs | 21 +- 56 files changed, 2202 insertions(+), 470 deletions(-) create mode 100644 apps/explorer/lib/explorer/chain/internal_transaction/call_type.ex create mode 100644 apps/explorer/lib/explorer/chain/internal_transaction/type.ex create mode 100644 apps/explorer/lib/explorer/chain/receipt/status.ex create mode 100644 apps/explorer/lib/explorer/jsonrpc/log.ex create mode 100644 apps/explorer/lib/explorer/jsonrpc/logs.ex create mode 100644 apps/explorer/lib/explorer/jsonrpc/parity.ex create mode 100644 apps/explorer/lib/explorer/jsonrpc/parity/trace.ex create mode 100644 apps/explorer/lib/explorer/jsonrpc/parity/trace/action.ex create mode 100644 apps/explorer/lib/explorer/jsonrpc/parity/trace/result.ex create mode 100644 apps/explorer/lib/explorer/jsonrpc/parity/traces.ex create mode 100644 apps/explorer/test/explorer/chain/internal_transaction/call_type_test.exs create mode 100644 apps/explorer/test/explorer/chain/internal_transaction/type_test.exs create mode 100644 apps/explorer/test/explorer/chain/receipt/status_test.exs create mode 100644 apps/explorer/test/explorer/jsonrpc/block_test.exs create mode 100644 apps/explorer/test/explorer/jsonrpc/blocks_test.exs create mode 100644 apps/explorer/test/explorer/jsonrpc/log_test.exs create mode 100644 apps/explorer/test/explorer/jsonrpc/parity/trace/action_test.exs create mode 100644 apps/explorer/test/explorer/jsonrpc/parity/trace/result_test.exs create mode 100644 apps/explorer/test/explorer/jsonrpc/parity/trace_test.exs create mode 100644 apps/explorer/test/explorer/jsonrpc/receipt_test.exs create mode 100644 apps/explorer/test/explorer/jsonrpc/transaction_test.exs diff --git a/apps/explorer/config/dev.exs b/apps/explorer/config/dev.exs index e50a57226d..289a073dfe 100644 --- a/apps/explorer/config/dev.exs +++ b/apps/explorer/config/dev.exs @@ -6,6 +6,9 @@ config :explorer, Explorer.Repo, database: "explorer_dev", hostname: "localhost", loggers: [], - pool_size: 10 + pool_size: 20, + pool_timeout: 60_000, + # Default value of 15_000 causes timeouts around block 813200 + timeout: 60_000 import_config "dev.secret.exs" diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 2ea6f712b1..9ca09707fb 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -5,7 +5,7 @@ defmodule Explorer.Chain do import Ecto.Query, only: [from: 2, order_by: 2, preload: 2, where: 2, where: 3] - alias Ecto.Multi + alias Ecto.{Changeset, Multi} alias Explorer.Chain.{Address, Block, Hash, InternalTransaction, Log, Receipt, Transaction, Wei} alias Explorer.Repo @@ -37,11 +37,26 @@ defmodule Explorer.Chain do @typep inserted_after_option :: {:inserted_after, DateTime.t()} @typep necessity_by_association_option :: {:necessity_by_association, necessity_by_association} @typep pagination_option :: {:pagination, pagination} + @typep timestamps :: %{inserted_at: DateTime.t(), updated_at: DateTime.t()} + @typep timestamps_option :: {:timestamps, timestamps} # Functions + @doc """ + The number of `t:Explorer.Chain.Block.t/0`. + + iex> insert_list(2, :block) + iex> Explorer.Chain.block_count() + 2 + + When there are no `t:Explorer.Chain.Block.t/0`. + + iex> Explorer.Chain.block_count() + 0 + + """ def block_count do - Repo.one(from(b in Block, select: count(b.id))) + Repo.aggregate(Block, :count, :hash) end @doc """ @@ -311,7 +326,7 @@ defmodule Explorer.Chain do end @doc """ - Bulk insert tree of resource from a list of blocks + Bulk insert tree of resource from a list of blocks. ## Tree @@ -319,27 +334,45 @@ defmodule Explorer.Chain do * `t:Explorer.Chain.Transaction.t/0` * `t.Explorer.Chain.InternalTransaction.t/0` * `t.Explorer.Chain.Receipt.t/0` + * `t.Explorer.Chain.Log.t/0` """ def insert(%{ blocks_params: blocks_params, + logs_params: logs_params, internal_transactions_params: internal_transactions_params, receipts_params: receipts_params, transactions_params: transactions_params }) - when is_list(blocks_params) and is_list(internal_transactions_params) and is_list(receipts_params) and - is_list(transactions_params) do - Multi.new() - |> Multi.run(:blocks, &insert_blocks(&1, blocks_params)) - |> Multi.run(:transactions, &insert_transactions(&1, transactions_params)) - |> Multi.run(:internal, &insert_internal(&1, internal_transactions_params)) - |> Multi.run(:receipts, &insert_receipts(&1, receipts_params)) - |> Multi.run(:logs, &insert_logs(&1)) - |> Repo.transaction() + when is_list(blocks_params) and is_list(internal_transactions_params) and is_list(logs_params) and + is_list(receipts_params) and is_list(transactions_params) do + with {:ok, ecto_schema_module_to_changes_list} <- + ecto_schema_module_to_params_list_to_ecto_schema_module_to_changes_list(%{ + Block => blocks_params, + Log => logs_params, + InternalTransaction => internal_transactions_params, + Receipt => receipts_params, + Transaction => transactions_params + }) do + insert_ecto_schema_module_to_changes_list(ecto_schema_module_to_changes_list) + end end + @doc """ + The number of `t:Explorer.Chain.InternalTransaction.t/0`. + + iex> insert(:internal_transaction, index: 0) + iex> Explorer.Chain.internal_transaction_count() + 1 + + If there are none, the count is `0`. + + iex> Explorer.Chain.internal_transaction_count() + 0 + + """ def internal_transaction_count do - Repo.one(from(t in InternalTransaction, select: count(t.id))) + Repo.aggregate(InternalTransaction, :count, :id) end @doc """ @@ -391,8 +424,24 @@ defmodule Explorer.Chain do |> Repo.paginate(pagination) end + @doc """ + The number of `t:Explorer.Chain.Log.t/0`. + + iex> block = insert(:block) + iex> transaction = insert(:transaction, block_hash: block.hash, index: 0) + iex> receipt = insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) + iex> insert(:log, transaction_hash: receipt.transaction_hash, index: 0) + iex> Explorer.Chain.log_count() + 1 + + When there are no `t:Explorer.Chain.Log.t/0`. + + iex> Explorer.Chain.log_count() + 0 + + """ def log_count do - Repo.one(from(l in Log, select: count(l.id))) + Repo.aggregate(Log, :count, :id) end @doc """ @@ -439,8 +488,23 @@ defmodule Explorer.Chain do end end + @doc """ + The number of `t:Explorer.Chain.Receipt.t/0`. + + iex> block = insert(:block) + iex> transaction = insert(:transaction, block_hash: block.hash, index: 0) + iex> insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) + iex> Explorer.Chain.receipt_count() + 1 + + When there are no `t:Explorer.Chain.Receipt.t/0`. + + iex> Explorer.Chain.receipt_count() + 0 + + """ def receipt_count do - Repo.one(from(r in Receipt, select: count(r.id))) + Repo.aggregate(Receipt, :count, :transaction_hash) end @doc """ @@ -775,17 +839,17 @@ defmodule Explorer.Chain do """ @spec transaction_to_status(Transaction.t()) :: :failed | :pending | :out_of_gas | :success def transaction_to_status(%Transaction{receipt: nil}), do: :pending - def transaction_to_status(%Transaction{receipt: %Receipt{status: 1}}), do: :success + def transaction_to_status(%Transaction{receipt: %Receipt{status: :ok}}), do: :success def transaction_to_status(%Transaction{ gas: gas, - receipt: %Receipt{gas_used: gas_used, status: 0} + receipt: %Receipt{gas_used: gas_used, status: :error} }) when gas_used >= gas do :out_of_gas end - def transaction_to_status(%Transaction{receipt: %Receipt{status: 0}}), do: :failed + def transaction_to_status(%Transaction{receipt: %Receipt{status: :error}}), do: :failed @doc """ The `t:Explorer.Chain.Transaction.t/0` or `t:Explorer.Chain.InternalTransaction.t/0` `value` of the `transaction` in @@ -847,10 +911,64 @@ defmodule Explorer.Chain do end end + @spec changes_list(params :: map, [{:for, module}]) :: {:ok, changes :: map} | {:error, [Changeset.t()]} + defp changes_list(params, named_arguments) when is_list(named_arguments) do + ecto_schema_module = Keyword.fetch!(named_arguments, :for) + struct = ecto_schema_module.__struct__() + + {status, acc} = + params + |> Stream.map(&ecto_schema_module.changeset(struct, &1)) + |> Enum.reduce({:ok, []}, fn + changeset = %Changeset{valid?: false}, {:ok, _} -> {:error, [changeset]} + changeset = %Changeset{valid?: false}, {:error, acc_changesets} -> {:error, [changeset | acc_changesets]} + %Changeset{changes: changes, valid?: true}, {:ok, acc_changes} -> {:ok, [changes | acc_changes]} + %Changeset{valid?: true}, {:error, _} = error -> error + end) + + {status, Enum.reverse(acc)} + end + defp chronologically(query) do from(q in query, order_by: [desc: q.inserted_at, desc: q.hash]) end + defp ecto_schema_module_changes_list_to_address_hash_set({ecto_schema_module, changes_list}) do + Enum.reduce(changes_list, MapSet.new(), fn changes, acc -> + changes + |> ecto_schema_module.changes_to_address_hash_set() + |> MapSet.union(acc) + end) + end + + defp ecto_schema_module_to_changes_list_to_address_hash_set(ecto_schema_module_to_changes_list) do + Enum.reduce(ecto_schema_module_to_changes_list, MapSet.new(), fn ecto_schema_module_changes_list, acc -> + ecto_schema_module_changes_list + |> ecto_schema_module_changes_list_to_address_hash_set() + |> MapSet.union(acc) + end) + end + + defp ecto_schema_module_to_params_list_to_ecto_schema_module_to_changes_list(ecto_schema_module_to_params_list) do + ecto_schema_module_to_params_list + |> Stream.map(fn {ecto_schema_module, params} -> + {ecto_schema_module, changes_list(params, for: ecto_schema_module)} + end) + |> Enum.reduce({:ok, %{}}, fn + {ecto_schema_module, {:ok, changes_list}}, {:ok, ecto_schema_module_to_changes_list} -> + {:ok, Map.put(ecto_schema_module_to_changes_list, ecto_schema_module, changes_list)} + + {_, {:ok, _}}, {:error, _} = error -> + error + + {_, {:error, _} = error}, {:ok, _} -> + error + + {_, {:error, changesets}}, {:error, acc_changesets} -> + {:error, acc_changesets ++ changesets} + end) + end + defp for_parent_transaction(query, %Hash{byte_count: unquote(Hash.Full.byte_count())} = hash) do from( child in query, @@ -859,95 +977,121 @@ defmodule Explorer.Chain do ) end - defp insert_blocks(%{}, blocks) do - {_, inserted_blocks} = - Repo.safe_insert_all( - Block, - blocks, - returning: [:id, :number], - on_conflict: :replace_all, - conflict_target: :number - ) - - {:ok, inserted_blocks} + @spec insert_addresses([map()], [timestamps_option]) :: {:ok, Block.t()} | {:error, [Changeset.t()]} + defp insert_addresses(changes_list, named_arguments) when is_list(changes_list) and is_list(named_arguments) do + timestamps = Keyword.fetch!(named_arguments, :timestamps) + + insert_changes_list( + changes_list, + conflict_target: :hash, + # Do nothing so that pre-existing balance is not overwritten + on_conflict: :nothing, + for: Address, + timestamps: timestamps + ) end - defp insert_internal(%{transactions: transactions}, internal_transactions) do - timestamps = timestamps() + @spec insert_blocks([map()], [timestamps_option]) :: {:ok, Block.t()} | {:error, [Changeset.t()]} + defp insert_blocks(changes_list, named_arguments) when is_list(changes_list) and is_list(named_arguments) do + timestamps = Keyword.fetch!(named_arguments, :timestamps) - internals = - Enum.flat_map(transactions, fn %{hash: hash, id: id} -> - case Map.fetch(internal_transactions, hash) do - {:ok, traces} -> - Enum.map(traces, &InternalTransaction.extract(&1, id, timestamps)) + insert_changes_list( + changes_list, + conflict_target: :number, + on_conflict: :replace_all, + for: Block, + timestamps: timestamps + ) + end - :error -> - [] - end - end) + defp insert_ecto_schema_module_to_changes_list( + %{ + Block => blocks_changes, + Log => logs_changes, + InternalTransaction => internal_transactions_changes, + Receipt => receipts_changes, + Transaction => transactions_changes + } = ecto_schema_module_to_changes_list + ) do + address_hash_set = ecto_schema_module_to_changes_list_to_address_hash_set(ecto_schema_module_to_changes_list) + addresses_changes = Address.hash_set_to_changes_list(address_hash_set) - {_, inserted} = Repo.safe_insert_all(InternalTransaction, internals, on_conflict: :nothing) + timestamps = timestamps() - {:ok, inserted} + Multi.new() + |> Multi.run(:addresses, fn _ -> insert_addresses(addresses_changes, timestamps: timestamps) end) + |> Multi.run(:blocks, fn _ -> insert_blocks(blocks_changes, timestamps: timestamps) end) + |> Multi.run(:transactions, fn _ -> insert_transactions(transactions_changes, timestamps: timestamps) end) + |> Multi.run(:internal_transactions, fn _ -> + insert_internal_transactions(internal_transactions_changes, timestamps: timestamps) + end) + |> Multi.run(:receipts, fn _ -> insert_receipts(receipts_changes, timestamps: timestamps) end) + |> Multi.run(:logs, fn _ -> insert_logs(logs_changes, timestamps: timestamps) end) + |> Repo.transaction() end - defp insert_logs(%{receipts: %{inserted: receipts, logs: logs_map}}) do - logs_to_insert = - Enum.reduce(receipts, [], fn receipt, acc -> - case Map.fetch(logs_map, receipt.transaction_id) do - {:ok, []} -> - acc - - {:ok, [_ | _] = logs} -> - logs = Enum.map(logs, &Map.put(&1, :receipt_id, receipt.id)) - logs ++ acc - end - end) + @spec insert_internal_transactions([map()], [timestamps_option]) :: + {:ok, InternalTransaction.t()} | {:error, [Changeset.t()]} + defp insert_internal_transactions(changes_list, named_arguments) + when is_list(changes_list) and is_list(named_arguments) do + timestamps = Keyword.fetch!(named_arguments, :timestamps) - {_, inserted_logs} = Repo.safe_insert_all(Log, logs_to_insert, returning: [:id]) - {:ok, inserted_logs} + insert_changes_list( + changes_list, + for: InternalTransaction, + timestamps: timestamps + ) end - defp insert_receipts(%{transactions: transactions}, raw_receipts) do - timestamps = timestamps() - - {receipts_to_insert, logs_map} = - Enum.reduce(transactions, {[], %{}}, fn trans, {receipts_acc, logs_acc} -> - case Map.fetch(raw_receipts, trans.hash) do - {:ok, raw_receipt} -> - {receipt, logs} = Receipt.extract(raw_receipt, trans.id, timestamps) - {[receipt | receipts_acc], Map.put(logs_acc, trans.id, logs)} + @spec insert_logs([map()], [timestamps_option]) :: {:ok, Log.t()} | {:error, [Changeset.t()]} + defp insert_logs(changes_list, named_arguments) when is_list(changes_list) and is_list(named_arguments) do + timestamps = Keyword.fetch!(named_arguments, :timestamps) - :error -> - {receipts_acc, logs_acc} - end - end) + insert_changes_list( + changes_list, + conflict_target: [:transaction_hash, :index], + on_conflict: :replace_all, + for: Log, + timestamps: timestamps + ) + end - {_, inserted_receipts} = - Repo.safe_insert_all( - Receipt, - receipts_to_insert, - returning: [:id, :transaction_id] - ) + @spec insert_receipts([map()], [timestamps_option]) :: {:ok, Receipt.t()} | {:error, [Changeset.t()]} + defp insert_receipts(changes_list, named_arguments) when is_list(changes_list) and is_list(named_arguments) do + timestamps = Keyword.fetch!(named_arguments, :timestamps) - {:ok, %{inserted: inserted_receipts, logs: logs_map}} + insert_changes_list( + changes_list, + conflict_target: :transaction_hash, + on_conflict: :replace_all, + for: Receipt, + timestamps: timestamps + ) end - defp insert_transactions(%{blocks: blocks}, transactions) do - blocks_map = for block <- blocks, into: %{}, do: {block.number, block} + defp insert_changes_list(changes_list, options) when is_list(changes_list) do + ecto_schema_module = Keyword.fetch!(options, :for) - transactions = - for transaction <- transactions do - %{id: id} = Map.fetch!(blocks_map, transaction.block_number) - - transaction - |> Map.put(:block_id, id) - |> Map.delete(:block_number) - end + timestamped_changes_list = timestamp_changes_list(changes_list, Keyword.fetch!(options, :timestamps)) + {_, inserted} = Repo.safe_insert_all(ecto_schema_module, timestamped_changes_list, Keyword.delete(options, :for)) + {:ok, inserted} + rescue + e in Postgrex.Error -> + IO.inspect(changes_list, label: "CHANGES_LIST") + raise e + end - {_, inserted} = Repo.safe_insert_all(Transaction, transactions, returning: [:id, :hash]) + @spec insert_transactions([map()], [timestamps_option]) :: {:ok, Transaction.t()} | {:error, [Changeset.t()]} + defp insert_transactions(changes_list, named_arguments) when is_list(changes_list) and is_list(named_arguments) do + timestamps = Keyword.fetch!(named_arguments, :timestamps) - {:ok, inserted} + insert_changes_list( + changes_list, + conflict_target: :hash, + on_conflict: :replace_all, + for: Transaction, + timestamps: timestamps + ) end defp inserted_after(query, options) do @@ -976,6 +1120,14 @@ defmodule Explorer.Chain do end) end + defp timestamp_params(changes, timestamps) when is_map(changes) do + Map.merge(changes, timestamps) + end + + defp timestamp_changes_list(changes_list, timestamps) when is_list(changes_list) do + Enum.map(changes_list, ×tamp_params(&1, timestamps)) + end + defp timestamps do now = Ecto.DateTime.utc() %{inserted_at: now, updated_at: now} diff --git a/apps/explorer/lib/explorer/chain/address.ex b/apps/explorer/lib/explorer/chain/address.ex index 5eaaa68194..0379e0240a 100644 --- a/apps/explorer/lib/explorer/chain/address.ex +++ b/apps/explorer/lib/explorer/chain/address.ex @@ -65,8 +65,16 @@ defmodule Explorer.Chain.Address do |> unique_constraint(:hash) end + def hash_set_to_changes_list(hash_set) do + Enum.map(hash_set, &hash_to_changes/1) + end + ## Private Functions + defp hash_to_changes(%Hash{byte_count: 20} = hash) do + %{hash: hash} + end + defp put_balance_updated_at(changeset) do changeset |> put_change(:balance_updated_at, Timex.now()) diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index 693ee4d929..ace23d40eb 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -69,7 +69,7 @@ defmodule Explorer.Chain.Block do field(:difficulty, :decimal) field(:gas_limit, :integer) field(:gas_used, :integer) - field(:nonce, :string) + field(:nonce, :integer) field(:number, :integer) field(:size, :integer) field(:timestamp, Timex.Ecto.DateTime) @@ -91,4 +91,8 @@ defmodule Explorer.Chain.Block do |> foreign_key_constraint(:parent_hash) |> unique_constraint(:hash, name: :blocks_pkey) end + + def changes_to_address_hash_set(%{miner_hash: miner_hash}) do + MapSet.new([miner_hash]) + end end diff --git a/apps/explorer/lib/explorer/chain/internal_transaction.ex b/apps/explorer/lib/explorer/chain/internal_transaction.ex index 74c11a1c00..1f4906d758 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction.ex @@ -4,61 +4,69 @@ defmodule Explorer.Chain.InternalTransaction do use Explorer.Schema alias Explorer.Chain.{Address, Gas, Hash, Transaction, Wei} + alias Explorer.Chain.InternalTransaction.{CallType, Type} @typedoc """ - * `"call"` - * `"callcode"` - * `"delegatecall"` - * `"none"` - * `"staticcall" - """ - @type call_type :: String.t() - - @typedoc """ - * `call_type` - the type of call + * `call_type` - the type of call. `nil` when `type` is not `:call`. + * `error` - error message when `:call` `type` errors * `from_address` - the source of the `value` * `from_address_hash` - hash of the source of the `value` * `gas` - the amount of gas allowed - * `gas_used` - the amount of gas used + * `gas_used` - the amount of gas used. `nil` when a call errors. * `index` - the index of this internal transaction inside the `transaction` * `input` - input bytes to the call - * `output` - output bytes from the call + * `output` - output bytes from the call. `nil` when a call errors. * `to_address` - the sink of the `value` * `to_address_hash` - hash of the sink of the `value` * `trace_address` - list of traces * `transaction` - transaction in which this transaction occured * `transaction_id` - foreign key for `transaction` + * `type` - type of internal transaction * `value` - value of transfered from `from_address` to `to_address` """ @type t :: %__MODULE__{ - call_type: call_type, + call_type: CallType.t() | nil, + error: String.t(), from_address: %Ecto.Association.NotLoaded{} | Address.t(), from_address_hash: Hash.Truncated.t(), gas: Gas.t(), - gas_used: Gas.t(), + gas_used: Gas.t() | nil, index: non_neg_integer(), input: String.t(), - output: String.t(), + output: String.t() | nil, to_address: %Ecto.Association.NotLoaded{} | Address.t(), to_address_hash: Hash.Truncated.t(), trace_address: [non_neg_integer()], transaction: %Ecto.Association.NotLoaded{} | Transaction.t(), transaction_hash: Explorer.Chain.Hash.t(), + type: Type.t(), value: Wei.t() } schema "internal_transactions" do - field(:call_type, :string) + field(:call_type, CallType) + field(:created_contract_code, :string) + field(:error, :string) field(:gas, :decimal) field(:gas_used, :decimal) field(:index, :integer) + field(:init, :string) field(:input, :string) field(:output, :string) field(:trace_address, {:array, :integer}) + field(:type, Type) field(:value, :decimal) timestamps() + belongs_to( + :created_contract_address, + Address, + foreign_key: :created_contract_address_hash, + references: :hash, + type: Hash.Truncated + ) + belongs_to( :from_address, Address, @@ -78,49 +86,90 @@ defmodule Explorer.Chain.InternalTransaction do belongs_to(:transaction, Transaction, foreign_key: :transaction_hash, references: :hash, type: Hash.Full) end - @optional_attrs ~w(input output) - @required_attrs ~w(call_type from_address_hash gas gas_used index to_address_hash trace_address transaction_hash - value)a - @allowed_attrs @optional_attrs ++ @required_attrs - def changeset(%__MODULE__{} = internal_transaction, attrs \\ %{}) do internal_transaction - |> cast(attrs, @allowed_attrs) - |> validate_required(@required_attrs) + |> cast(attrs, ~w(type)a) + |> type_changeset(attrs) + end + + def changes_to_address_hash_set(changes) do + Enum.reduce(~w(created_contract_address_hash from_address_hash to_address_hash)a, MapSet.new(), fn field, acc -> + case Map.get(changes, field) do + nil -> acc + value -> MapSet.put(acc, value) + end + end) + end + + ## Private Functions + + defp type_changeset(changeset, attrs) do + type = get_field(changeset, :type) + + type_changeset(changeset, attrs, type) + end + + @call_optional_fields ~w(error gas_used output) + @call_required_fields ~w(call_type from_address_hash gas index to_address_hash trace_address transaction_hash value)a + @call_allowed_fields @call_optional_fields ++ @call_required_fields + + defp type_changeset(changeset, attrs, :call) do + changeset + |> cast(attrs, @call_allowed_fields) + |> validate_required(@call_required_fields) + |> validate_call_error_or_result() |> foreign_key_constraint(:from_address_hash) |> foreign_key_constraint(:to_address_hash) |> foreign_key_constraint(:transaction_hash) - |> unique_constraint(:transaction_hash) + |> unique_constraint(:index) + end + + @create_optional_fields ~w(error created_contract_code created_contract_address_hash gas_used) + @create_required_fields ~w(from_address_hash gas index init trace_address transaction_hash value)a + @create_allowed_fields @create_optional_fields ++ @create_required_fields + + defp type_changeset(changeset, attrs, :create) do + changeset + |> cast(attrs, @create_allowed_fields) + |> validate_required(@create_required_fields) + |> validate_create_error_or_result() + |> foreign_key_constraint(:created_contract_address_hash) + |> foreign_key_constraint(:from_address_hash) + |> foreign_key_constraint(:transaction_hash) + |> unique_constraint(:index) end - def extract(trace, transaction_hash, %{} = timestamps) do - %{ - transaction_hash: transaction_hash, - index: 0, - call_type: trace["action"]["callType"] || trace["type"], - to_address_hash: to_address(trace), - from_address_hash: trace |> from_address(), - trace_address: trace["traceAddress"], - value: trace["action"]["value"], - gas: trace["action"]["gas"], - gas_used: gas_used(trace), - input: trace["action"]["input"], - output: trace["result"]["output"], - # error: trace["error"], - inserted_at: Map.fetch!(timestamps, :inserted_at), - updated_at: Map.fetch!(timestamps, :updated_at) - } + defp validate_disallowed(changeset, field, named_arguments) when is_atom(field) do + case get_field(changeset, field) do + nil -> changeset + _ -> add_error(changeset, field, Keyword.get(named_arguments, :message, "can't be present")) + end end - defp from_address(%{"action" => %{"from" => address}}), do: address + defp validate_disallowed(changeset, fields, named_arguments) when is_list(fields) do + Enum.reduce(fields, changeset, fn field, acc_changeset -> + validate_disallowed(acc_changeset, field, named_arguments) + end) + end - defp gas_used(%{"result" => %{"gasUsed" => gas}}), do: gas - defp gas_used(%{"error" => _error}), do: 0 + @call_success_fields ~w(gas_used output)a - defp to_address(%{"action" => %{"to" => address}}) - when not is_nil(address), - do: address + # Validates that :call `type` changeset either has an `error` or both `gas_used` and `output` + defp validate_call_error_or_result(changeset) do + case get_field(changeset, :error) do + nil -> validate_required(changeset, @call_success_fields, message: "can't be blank for successful call") + _ -> validate_disallowed(changeset, @call_success_fields, message: "can't be present for failed call") + end + end + + @create_success_fields ~w(created_contract_code created_contract_address_hash gas_used)a - defp to_address(%{"result" => %{"address" => address}}), do: address - defp to_address(%{"error" => _error}), do: nil + # Validates that :create `type` changeset either has an `:error` or both `:created_contract_code` and + # `:created_contract_address_hash` + defp validate_create_error_or_result(changeset) do + case get_field(changeset, :error) do + nil -> validate_required(changeset, @create_success_fields, message: "can't be blank for successful create") + _ -> validate_disallowed(changeset, @create_success_fields, message: "can't be present for failed create") + end + end end diff --git a/apps/explorer/lib/explorer/chain/internal_transaction/call_type.ex b/apps/explorer/lib/explorer/chain/internal_transaction/call_type.ex new file mode 100644 index 0000000000..b9bdeeb32a --- /dev/null +++ b/apps/explorer/lib/explorer/chain/internal_transaction/call_type.ex @@ -0,0 +1,122 @@ +defmodule Explorer.Chain.InternalTransaction.CallType do + @moduledoc """ + Internal transaction types + """ + + @behaviour Ecto.Type + + # Types + + @typedoc """ + * `:call` - call a function in a contract by jumping into the contract's context + * `:callcode` + * `:delegatecall` - Instead of jumping into the code as with `"call", and using the call's contract's context, use + the current contract's context with the delegated contract's code. There's some good chances for finding bugs + when fuzzing these if the memory layout differs between the current contract and the delegated contract. + * `:staticcall` + """ + @type t :: :call | :callcode | :delegatecall | :staticcall + + # Functions + + ## Ecto.Type callbacks + + @doc """ + Casts `term` to `t:t/0` + + If the `term` is already in `t:t/0`, then it is returned + + iex> Explorer.Chain.InternalTransaction.CallType.cast(:call) + {:ok, :call} + iex> Explorer.Chain.InternalTransaction.CallType.cast(:callcode) + {:ok, :callcode} + iex> Explorer.Chain.InternalTransaction.CallType.cast(:delegatecall) + {:ok, :delegatecall} + iex> Explorer.Chain.InternalTransaction.CallType.cast(:staticcall) + {:ok, :staticcall} + + If `term` is a `String.t`, then it is converted to the corresponding `t:t/0`. + + iex> Explorer.Chain.InternalTransaction.CallType.cast("call") + {:ok, :call} + iex> Explorer.Chain.InternalTransaction.CallType.cast("callcode") + {:ok, :callcode} + iex> Explorer.Chain.InternalTransaction.CallType.cast("delegatecall") + {:ok, :delegatecall} + iex> Explorer.Chain.InternalTransaction.CallType.cast("staticcall") + {:ok, :staticcall} + + Unsupported `String.t` return an `:error`. + + iex> Explorer.Chain.InternalTransaction.CallType.cast("hard-fork") + :error + + """ + @impl Ecto.Type + @spec cast(term()) :: {:ok, t()} | :error + def cast(t) when t in ~w(call callcode delegatecall staticcall)a, do: {:ok, t} + def cast("call"), do: {:ok, :call} + def cast("callcode"), do: {:ok, :callcode} + def cast("delegatecall"), do: {:ok, :delegatecall} + def cast("staticcall"), do: {:ok, :staticcall} + def cast(_), do: :error + + @doc """ + Dumps the `atom` format to `String.t` format used in the database. + + iex> Explorer.Chain.InternalTransaction.CallType.dump(:call) + {:ok, "call"} + iex> Explorer.Chain.InternalTransaction.CallType.dump(:callcode) + {:ok, "callcode"} + iex> Explorer.Chain.InternalTransaction.CallType.dump(:delegatecall) + {:ok, "delegatecall"} + iex> Explorer.Chain.InternalTransaction.CallType.dump(:staticcall) + {:ok, "staticcall"} + + Other atoms return an error + + iex> Explorer.Chain.InternalTransaction.CallType.dump(:other) + :error + + """ + @impl Ecto.Type + @spec dump(term()) :: {:ok, String.t()} | :error + def dump(:call), do: {:ok, "call"} + def dump(:callcode), do: {:ok, "callcode"} + def dump(:delegatecall), do: {:ok, "delegatecall"} + def dump(:staticcall), do: {:ok, "staticcall"} + def dump(_), do: :error + + @doc """ + Loads the `t:String.t/0` from the database. + + iex> Explorer.Chain.InternalTransaction.CallType.load("call") + {:ok, :call} + iex> Explorer.Chain.InternalTransaction.CallType.load("callcode") + {:ok, :callcode} + iex> Explorer.Chain.InternalTransaction.CallType.load("delegatecall") + {:ok, :delegatecall} + iex> Explorer.Chain.InternalTransaction.CallType.load("staticcall") + {:ok, :staticcall} + + Other `t:String.t/0` return `:error` + + iex> Explorer.Chain.InternalTransaction.CallType.load("other") + :error + + """ + @impl Ecto.Type + @spec load(term()) :: {:ok, t()} | :error + def load("call"), do: {:ok, :call} + def load("callcode"), do: {:ok, :callcode} + def load("delegatecall"), do: {:ok, :delegatecall} + def load("staticcall"), do: {:ok, :staticcall} + def load(_), do: :error + + @doc """ + The underlying database type: `:string` + """ + @impl Ecto.Type + @spec type() :: :string + def type, do: :string +end diff --git a/apps/explorer/lib/explorer/chain/internal_transaction/type.ex b/apps/explorer/lib/explorer/chain/internal_transaction/type.ex new file mode 100644 index 0000000000..e71ac51515 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/internal_transaction/type.ex @@ -0,0 +1,120 @@ +defmodule Explorer.Chain.InternalTransaction.Type do + @moduledoc """ + Internal transaction types + """ + + @behaviour Ecto.Type + + # Types + + @typedoc """ + * `:call` + * `:create` + * `:suicide` + * `:reward` + """ + @type t :: :call | :create | :suicide | :reward + + # Functions + + ## Ecto.Type callbacks + + @doc """ + Casts `term` to `t:t/0` + + If the `term` is already in `t:t/0`, then it is returned + + iex> Explorer.Chain.InternalTransaction.Type.cast(:call) + {:ok, :call} + iex> Explorer.Chain.InternalTransaction.Type.cast(:create) + {:ok, :create} + iex> Explorer.Chain.InternalTransaction.Type.cast(:reward) + {:ok, :reward} + iex> Explorer.Chain.InternalTransaction.Type.cast(:suicide) + {:ok, :suicide} + + If `term` is a `String.t`, then it is converted to the corresponding `t:t/0`. + + iex> Explorer.Chain.InternalTransaction.Type.cast("call") + {:ok, :call} + iex> Explorer.Chain.InternalTransaction.Type.cast("create") + {:ok, :create} + iex> Explorer.Chain.InternalTransaction.Type.cast("reward") + {:ok, :reward} + iex> Explorer.Chain.InternalTransaction.Type.cast("suicide") + {:ok, :suicide} + + Unsupported `String.t` return an `:error`. + + iex> Explorer.Chain.InternalTransaction.Type.cast("hard-fork") + :error + + """ + @impl Ecto.Type + @spec cast(term()) :: {:ok, t()} | :error + def cast(t) when t in ~w(call create suicide reward)a, do: {:ok, t} + def cast("call"), do: {:ok, :call} + def cast("create"), do: {:ok, :create} + def cast("reward"), do: {:ok, :reward} + def cast("suicide"), do: {:ok, :suicide} + def cast(_), do: :error + + @doc """ + Dumps the `atom` format to `String.t` format used in the database. + + iex> Explorer.Chain.InternalTransaction.Type.dump(:call) + {:ok, "call"} + iex> Explorer.Chain.InternalTransaction.Type.dump(:create) + {:ok, "create"} + iex> Explorer.Chain.InternalTransaction.Type.dump(:reward) + {:ok, "reward"} + iex> Explorer.Chain.InternalTransaction.Type.dump(:suicide) + {:ok, "suicide"} + + Other atoms return an error + + iex> Explorer.Chain.InternalTransaction.Type.dump(:other) + :error + + """ + @impl Ecto.Type + @spec dump(term()) :: {:ok, String.t()} | :error + def dump(:call), do: {:ok, "call"} + def dump(:create), do: {:ok, "create"} + def dump(:reward), do: {:ok, "reward"} + def dump(:suicide), do: {:ok, "suicide"} + def dump(_), do: :error + + @doc """ + Loads the `t:String.t/0` from the database. + + iex> Explorer.Chain.InternalTransaction.Type.load("call") + {:ok, :call} + iex> Explorer.Chain.InternalTransaction.Type.load("create") + {:ok, :create} + iex> Explorer.Chain.InternalTransaction.Type.load("reward") + {:ok, :reward} + iex> Explorer.Chain.InternalTransaction.Type.load("suicide") + {:ok, :suicide} + + Other `t:String.t/0` return `:error` + + iex> Explorer.Chain.InternalTransaction.Type.load("other") + :error + + """ + @impl Ecto.Type + @spec load(term()) :: {:ok, t()} | :error + def load("call"), do: {:ok, :call} + def load("create"), do: {:ok, :create} + def load("reward"), do: {:ok, :reward} + def load("suicide"), do: {:ok, :suicide} + def load(_), do: :error + + @doc """ + The underlying database type: `:string` + """ + @impl Ecto.Type + @spec type() :: :string + def type, do: :string +end diff --git a/apps/explorer/lib/explorer/chain/log.ex b/apps/explorer/lib/explorer/chain/log.ex index 2e608c1e90..0cd8b5c701 100644 --- a/apps/explorer/lib/explorer/chain/log.ex +++ b/apps/explorer/lib/explorer/chain/log.ex @@ -5,7 +5,7 @@ defmodule Explorer.Chain.Log do alias Explorer.Chain.{Address, Hash, Receipt} - @required_attrs ~w(address_hash data index type)a + @required_attrs ~w(address_hash data index transaction_hash type)a @optional_attrs ~w(first_topic second_topic third_topic fourth_topic)a schema "logs" do @@ -20,7 +20,7 @@ defmodule Explorer.Chain.Log do timestamps() belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Truncated) - belongs_to(:receipt, Receipt) + belongs_to(:receipt, Receipt, foreign_key: :transaction_hash, references: :transaction_hash, type: Hash.Full) has_one(:transaction, through: [:receipt, :transaction]) end @@ -32,4 +32,8 @@ defmodule Explorer.Chain.Log do |> cast_assoc(:receipt) |> validate_required(@required_attrs) end + + def changes_to_address_hash_set(%{address_hash: address_hash}) do + MapSet.new([address_hash]) + end end diff --git a/apps/explorer/lib/explorer/chain/receipt.ex b/apps/explorer/lib/explorer/chain/receipt.ex index 08bc7857fc..c470d7b809 100644 --- a/apps/explorer/lib/explorer/chain/receipt.ex +++ b/apps/explorer/lib/explorer/chain/receipt.ex @@ -4,19 +4,21 @@ defmodule Explorer.Chain.Receipt do use Explorer.Schema alias Explorer.Chain.{Hash, Log, Transaction} + alias Explorer.Chain.Receipt.Status - @optional_attrs ~w(transaction_hash)a - @required_attrs ~w(cumulative_gas_used gas_used status index)a + @optional_attrs ~w()a + @required_attrs ~w(cumulative_gas_used gas_used status transaction_hash transaction_index)a @allowed_attrs @optional_attrs ++ @required_attrs + @primary_key false schema "receipts" do field(:cumulative_gas_used, :decimal) field(:gas_used, :decimal) - field(:status, :integer) - field(:index, :integer) + field(:status, Status) + field(:transaction_index, :integer) belongs_to(:transaction, Transaction, foreign_key: :transaction_hash, references: :hash, type: Hash.Full) - has_many(:logs, Log) + has_many(:logs, Log, foreign_key: :transaction_hash, references: :transaction_hash) timestamps() end @@ -26,50 +28,10 @@ defmodule Explorer.Chain.Receipt do def changeset(%__MODULE__{} = transaction_receipt, attrs \\ %{}) do transaction_receipt |> cast(attrs, @allowed_attrs) - |> cast_assoc(:transaction) - |> cast_assoc(:logs) |> validate_required(@required_attrs) |> foreign_key_constraint(:transaction_hash) |> unique_constraint(:transaction_hash) end - def extract(raw_receipt, transaction_hash, %{} = timestamps) do - logs = - raw_receipt - |> Map.fetch!("logs") - |> Enum.map(&extract_log(&1, timestamps)) - - receipt = %{ - transaction_hash: transaction_hash, - index: raw_receipt["transactionIndex"], - cumulative_gas_used: raw_receipt["cumulativeGasUsed"], - gas_used: raw_receipt["gasUsed"], - status: raw_receipt["status"], - inserted_at: Map.fetch!(timestamps, :inserted_at), - updated_at: Map.fetch!(timestamps, :updated_at) - } - - {receipt, logs} - end - - def null, do: %__MODULE__{} - - ## Private Functions - - defp extract_log(log, %{} = timestamps) do - # address = Address.find_or_create_by_hash(log["address"]) - - %{ - # address_id: 0, # TODO - index: log["logIndex"], - data: log["data"], - type: log["type"], - first_topic: log["topics"] |> Enum.at(0), - second_topic: log["topics"] |> Enum.at(1), - third_topic: log["topics"] |> Enum.at(2), - fourth_topic: log["topics"] |> Enum.at(3), - inserted_at: Map.fetch!(timestamps, :inserted_at), - updated_at: Map.fetch!(timestamps, :updated_at) - } - end + def changes_to_address_hash_set(_), do: MapSet.new() end diff --git a/apps/explorer/lib/explorer/chain/receipt/status.ex b/apps/explorer/lib/explorer/chain/receipt/status.ex new file mode 100644 index 0000000000..cea77792f9 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/receipt/status.ex @@ -0,0 +1,113 @@ +defmodule Explorer.Chain.Receipt.Status do + @moduledoc """ + Post-[Byzantium](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-609.md) status is `0x1` for success and `0x0` + for failure, but instead of keeping track of just an integer and having to remember if its like C boolean (`0` for + `false`, `1` for `true`) or a Posix exit code, let's represent it as native elixir - `:ok` for success and `:error` + for failure. + """ + + @behaviour Ecto.Type + + # Types + + @typedoc """ + * `:ok` - transaction succeeded + * `:error` - transaction failed + """ + @type t :: :ok | :error + + # Functions + + ## Ecto.Type callbacks + + @doc """ + Casts `term` to `t:t/0` + + If the `term` is already in `t:t/0`, then it is returned + + iex> Explorer.Chain.Receipt.Status.cast(:ok) + {:ok, :ok} + iex> Explorer.Chain.Receipt.Status.cast(:error) + {:ok, :error} + + If the `term` is an `non_neg_integer`, then it is converted only if it is `0` or `1`. + + iex> Explorer.Chain.Receipt.Status.cast(0) + {:ok, :error} + iex> Explorer.Chain.Receipt.Status.cast(1) + {:ok, :ok} + iex> Explorer.Chain.Receipt.Status.cast(2) + :error + + If the `term` is in the quantity format used by `Explorer.JSONRPC`, it is converted only if `0x0` or `0x1` + + iex> Explorer.Chain.Receipt.Status.cast("0x0") + {:ok, :error} + iex> Explorer.Chain.Receipt.Status.cast("0x1") + {:ok, :ok} + iex> Explorer.Chain.Receipt.Status.cast("0x2") + :error + + """ + @impl Ecto.Type + @spec cast(term()) :: {:ok, t()} | :error + def cast(:error), do: {:ok, :error} + def cast(:ok), do: {:ok, :ok} + def cast(0), do: {:ok, :error} + def cast(1), do: {:ok, :ok} + def cast("0x0"), do: {:ok, :error} + def cast("0x1"), do: {:ok, :ok} + def cast(_), do: :error + + @doc """ + Dumps the `atom` format to `integer` format used in database. + + iex> Explorer.Chain.Receipt.Status.dump(:ok) + {:ok, 1} + iex> Explorer.Chain.Receipt.Status.dump(:error) + {:ok, 0} + + If the value hasn't been cast first, it can't be dumped. + + iex> Explorer.Chain.Receipt.Status.dump(0) + :error + iex> Explorer.Chain.Receipt.Status.dump(1) + :error + iex> Explorer.Chain.Receipt.Status.dump("0x0") + :error + iex> Explorer.Chain.Receipt.Status.dump("0x1") + :error + + """ + @impl Ecto.Type + @spec dump(term()) :: {:ok, 0 | 1} | :error + def dump(:error), do: {:ok, 0} + def dump(:ok), do: {:ok, 1} + def dump(_), do: :error + + @doc """ + Loads the integer from the database. + + Only loads integers `0` and `1`. + + iex> Explorer.Chain.Receipt.Status.load(0) + {:ok, :error} + iex> Explorer.Chain.Receipt.Status.load(1) + {:ok, :ok} + iex> Explorer.Chain.Receipt.Status.load(2) + :error + + """ + @impl Ecto.Type + @spec load(term()) :: {:ok, t()} | :error + def load(0), do: {:ok, :error} + def load(1), do: {:ok, :ok} + def load(_), do: :error + + @doc """ + The underlying database type: `:integer` + """ + @impl Ecto.Type + @spec type() :: :integer + def type, do: :integer +end diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index afa820bf9d..d68390375a 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -162,46 +162,29 @@ defmodule Explorer.Chain.Transaction do # Functions - @doc false def changeset(%__MODULE__{} = transaction, attrs \\ %{}) do transaction |> cast(attrs, @required_attrs ++ @optional_attrs) |> validate_required(@required_attrs) |> validate_collated() + |> check_constraint( + :index, + message: "cannot be set when block_hash is nil and must be set when block_hash is not nil", + name: :indexed + ) |> foreign_key_constraint(:block_hash) |> unique_constraint(:hash) end - def decode(raw_transaction, block_number, %{} = timestamps) do - attrs = %{ - hash: raw_transaction["hash"], - index: raw_transaction["transactionIndex"], - value: raw_transaction["value"], - gas: raw_transaction["gas"], - gas_price: raw_transaction["gasPrice"], - input: raw_transaction["input"], - nonce: raw_transaction["nonce"], - public_key: raw_transaction["publicKey"], - r: raw_transaction["r"], - s: raw_transaction["s"], - standard_v: raw_transaction["standardV"], - v: raw_transaction["v"] - } - - case changeset(%__MODULE__{}, attrs) do - %Changeset{valid?: true, changes: changes} -> - {:ok, - changes - |> Map.put(:block_number, block_number) - |> Map.merge(timestamps)} - - %Changeset{valid?: false, errors: errors} -> - {:error, errors} - end + def changes_to_address_hash_set(changes) do + Enum.reduce(~w(from_address_hash to_address_hash)a, MapSet.new(), fn field, acc -> + case Map.get(changes, field) do + nil -> acc + value -> MapSet.put(acc, value) + end + end) end - def null, do: %__MODULE__{} - ## Private Functions defp validate_collated(%Changeset{} = changeset) do diff --git a/apps/explorer/lib/explorer/indexer/block_fetcher.ex b/apps/explorer/lib/explorer/indexer/block_fetcher.ex index 46eb0a5ab0..f8f35fe099 100644 --- a/apps/explorer/lib/explorer/indexer/block_fetcher.ex +++ b/apps/explorer/lib/explorer/indexer/block_fetcher.ex @@ -21,17 +21,16 @@ defmodule Explorer.Indexer.BlockFetcher do # Constants - # 50 - @batch_size 1 - @blocks_concurrency 10 + @batch_size 50 + @blocks_concurrency 20 @internal_batch_size 50 - @internal_concurrency 4 + @internal_concurrency 8 @polling_interval 20_000 @receipts_batch_size 250 - @receipts_concurrency 10 + @receipts_concurrency 20 # Functions @@ -119,7 +118,7 @@ defmodule Explorer.Indexer.BlockFetcher do :ok end - defp fetch_internal_transactions([]), do: {:ok, %{}} + defp fetch_internal_transactions([]), do: {:ok, []} defp fetch_internal_transactions(hashes) do Logger.debug(fn -> "fetching #{length(hashes)} internal transactions" end) @@ -128,14 +127,14 @@ defmodule Explorer.Indexer.BlockFetcher do hashes |> Enum.chunk_every(@internal_batch_size) |> Task.async_stream(&JSONRPC.fetch_internal_transactions(&1), stream_opts) - |> Enum.reduce_while({:ok, %{}}, fn - {:ok, {:ok, trans}}, {:ok, acc} -> {:cont, {:ok, Map.merge(acc, trans)}} + |> Enum.reduce_while({:ok, []}, fn + {:ok, {:ok, internal_transactions_params}}, {:ok, acc} -> {:cont, {:ok, acc ++ internal_transactions_params}} {:ok, {:error, reason}}, {:ok, _acc} -> {:halt, {:error, reason}} {:error, reason}, {:ok, _acc} -> {:halt, {:error, reason}} end) end - defp fetch_transaction_receipts([]), do: {:ok, []} + defp fetch_transaction_receipts([]), do: {:ok, %{logs_params: [], receipts_params: []}} defp fetch_transaction_receipts(hashes) do Logger.debug(fn -> "fetching #{length(hashes)} transaction receipts" end) @@ -144,16 +143,24 @@ defmodule Explorer.Indexer.BlockFetcher do hashes |> Enum.chunk_every(@receipts_batch_size) |> Task.async_stream(&JSONRPC.fetch_transaction_receipts(&1), stream_opts) - |> Enum.reduce_while({:ok, []}, fn - {:ok, {:ok, receipt_params}}, {:ok, acc} -> {:cont, {:ok, acc ++ receipt_params}} - {:ok, {:error, reason}}, {:ok, _acc} -> {:halt, {:error, reason}} - {:error, reason}, {:ok, _acc} -> {:halt, {:error, reason}} + |> Enum.reduce_while({:ok, %{logs_params: [], receipts_params: []}}, fn + {:ok, {:ok, %{logs_params: logs_params, receipts_params: receipts_params}}}, + {:ok, %{logs_params: acc_log_params, receipts_params: acc_receipts_params}} -> + {:cont, + {:ok, %{logs_params: acc_log_params ++ logs_params, receipts_params: acc_receipts_params ++ receipts_params}}} + + {:ok, {:error, reason}}, {:ok, _acc} -> + {:halt, {:error, reason}} + + {:error, reason}, {:ok, _acc} -> + {:halt, {:error, reason}} end) end defp insert(%{ blocks_params: blocks_params, internal_transactions_params: internal_transactions_params, + logs_params: log_params, range: range, receipts_params: receipt_params, seq: seq, @@ -162,6 +169,7 @@ defmodule Explorer.Indexer.BlockFetcher do case Chain.insert(%{ blocks_params: blocks_params, internal_transactions_params: internal_transactions_params, + logs_params: log_params, receipts_params: receipt_params, transactions_params: transactions_params }) do @@ -214,11 +222,13 @@ defmodule Explorer.Indexer.BlockFetcher do value, :ok <- cap_seq(seq, next, range), transaction_hashes <- Transactions.params_to_hashes(transactions_params), - {:ok, receipts_params} <- fetch_transaction_receipts(transaction_hashes), + {:ok, %{logs_params: logs_params, receipts_params: receipts_params}} <- + fetch_transaction_receipts(transaction_hashes), {:ok, internal_transactions_params} <- fetch_internal_transactions(transaction_hashes) do insert(%{ blocks_params: blocks_params, internal_transactions_params: internal_transactions_params, + logs_params: logs_params, range: range, receipts_params: receipts_params, seq: seq, diff --git a/apps/explorer/lib/explorer/jsonrpc.ex b/apps/explorer/lib/explorer/jsonrpc.ex index 7eab0b21a1..0d110e05dc 100644 --- a/apps/explorer/lib/explorer/jsonrpc.ex +++ b/apps/explorer/lib/explorer/jsonrpc.ex @@ -18,7 +18,7 @@ defmodule Explorer.JSONRPC do require Logger - alias Explorer.JSONRPC.{Blocks, Receipts, Transactions} + alias Explorer.JSONRPC.{Blocks, Parity, Receipts, Transactions} # Types @@ -83,6 +83,24 @@ defmodule Explorer.JSONRPC do json_rpc(request, config(:url)) end + @doc """ + Fetches configuration for this module under `key` + + Configuration can be set a compile time using `config` + + config :explorer, Explorer.JSONRRPC, key: value + + Configuration can be set a runtime using `Application.put_env/3` + + Application.put_env(:explorer, Explorer.JSONRPC, key: value) + + """ + def config(key) do + :explorer + |> Application.fetch_env!(__MODULE__) + |> Keyword.fetch!(key) + end + @doc """ Fetches blocks by block hashes. @@ -112,32 +130,38 @@ defmodule Explorer.JSONRPC do |> handle_get_block_by_number(block_start, block_end) end + @doc """ + Fetches internal transactions from client-specific API. + """ def fetch_internal_transactions(hashes) when is_list(hashes) do - hashes - |> Enum.map(fn hash -> - %{ - "id" => hash, - "jsonrpc" => "2.0", - "method" => "trace_replayTransaction", - "params" => [hash, ["trace"]] - } - end) - |> json_rpc(config(:trace_url)) - |> handle_internal_transactions() + Parity.fetch_internal_transactions(hashes) end def fetch_transaction_receipts(hashes) when is_list(hashes) do - hashes - |> Enum.map(fn hash -> - %{ - "id" => hash, - "jsonrpc" => "2.0", - "method" => "eth_getTransactionReceipt", - "params" => [hash] - } - end) - |> json_rpc(config(:url)) - |> handle_receipts() + Receipts.fetch(hashes) + end + + @doc """ + 1. POSTs JSON `payload` to `url` + 2. Decodes the response + 3. Handles the response + + ## Returns + + * Handled response + * `{:error, reason}` if POST failes + """ + def json_rpc(payload, url) do + json = encode_json(payload) + headers = [{"Content-Type", "application/json"}] + + case HTTPoison.post(url, json, headers, config(:http)) do + {:ok, %HTTPoison.Response{body: body, status_code: code}} -> + body |> decode_json(payload) |> handle_response(code) + + {:error, %HTTPoison.Error{reason: reason}} -> + {:error, reason} + end end @doc """ @@ -192,24 +216,6 @@ defmodule Explorer.JSONRPC do end end - defp config(key) do - :explorer - |> Application.fetch_env!(__MODULE__) - |> Keyword.fetch!(key) - end - - defp decode_trace(%{"action" => action} = trace) do - trace - |> Map.merge(%{ - "action" => - Map.merge(action, %{ - "value" => quantity_to_integer(action["value"]), - "gas" => quantity_to_integer(action["gas"]) - }) - }) - |> put_gas_used() - end - defp encode_json(data), do: Jason.encode_to_iodata!(data) defp decode_json(body, posted_payload) do @@ -253,38 +259,6 @@ defmodule Explorer.JSONRPC do {:error, reason, {block_start, block_end}} end - defp handle_internal_transactions({:ok, results}) do - results_map = - Enum.into(results, %{}, fn - %{"error" => error} -> - throw({:error, error}) - - %{"id" => hash, "result" => %{"trace" => traces}} -> - {hash, Enum.map(traces, &decode_trace(&1))} - end) - - {:ok, results_map} - catch - {:error, reason} -> {:error, reason} - end - - defp handle_internal_transactions({:error, reason}) do - {:error, reason} - end - - defp handle_receipts({:ok, results}) do - results_params = - results - |> Receipts.to_elixir() - |> Receipts.elixir_to_params() - - {:ok, results_params} - end - - defp handle_receipts({:error, reason}) do - {:error, reason} - end - defp handle_response(resp, 200) do case resp do [%{} | _] = batch_resp -> {:ok, batch_resp} @@ -302,23 +276,4 @@ defmodule Explorer.JSONRPC do end defp int_to_hash_string(number), do: "0x" <> Integer.to_string(number, 16) - - defp json_rpc(payload, url) do - json = encode_json(payload) - headers = [{"Content-Type", "application/json"}] - - case HTTPoison.post(url, json, headers, config(:http)) do - {:ok, %HTTPoison.Response{body: body, status_code: code}} -> - body |> decode_json(payload) |> handle_response(code) - - {:error, %HTTPoison.Error{reason: reason}} -> - {:error, reason} - end - end - - defp put_gas_used(%{"error" => _} = trace), do: trace - - defp put_gas_used(%{"result" => %{"gasUsed" => gas}} = trace) do - put_in(trace, ["result", "gasUsed"], quantity_to_integer(gas)) - end end diff --git a/apps/explorer/lib/explorer/jsonrpc/block.ex b/apps/explorer/lib/explorer/jsonrpc/block.ex index 7ec4b53c3b..533ac6ff1f 100644 --- a/apps/explorer/lib/explorer/jsonrpc/block.ex +++ b/apps/explorer/lib/explorer/jsonrpc/block.ex @@ -48,6 +48,53 @@ defmodule Explorer.JSONRPC.Block do """ @type t :: %{String.t() => JSONRPC.data() | JSONRPC.hash() | JSONRPC.quantity() | nil} + @doc """ + Converts `t:elixir/0` format to params used in `Explorer.Chain`. + + iex> Explorer.JSONRPC.Block.elixir_to_params( + ...> %{ + ...> "author" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> "difficulty" => 340282366920938463463374607431465537093, + ...> "extraData" => "0xd5830108048650617269747986312e32322e31826c69", + ...> "gasLimit" => 6706541, + ...> "gasUsed" => 0, + ...> "hash" => "0x52c867bc0a91e573dc39300143c3bead7408d09d45bdb686749f02684ece72f3", + ...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + ...> "miner" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> "number" => 1, + ...> "parentHash" => "0x5b28c1bfd3a15230c9a46b399cd0f9a6920d432e85381cc6a140b06e8410112f", + ...> "receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + ...> "sealFields" => [ + ...> "0x84120a71ba", + ...> "0xb8417a5887662f09ac4673af5850d28f3ad6550407b9c814ef563a13320f881b55ef03754f48f2dde027ad4a5abcabcc42780d9ebfc645f183e5252507d6a25bc2ec01" + ...> ], + ...> "sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + ...> "signature" => "7a5887662f09ac4673af5850d28f3ad6550407b9c814ef563a13320f881b55ef03754f48f2dde027ad4a5abcabcc42780d9ebfc645f183e5252507d6a25bc2ec01", + ...> "size" => 576, + ...> "stateRoot" => "0xc196ad59d867542ef20b29df5f418d07dc7234f4bc3d25260526620b7958a8fb", + ...> "step" => "302674362", + ...> "timestamp" => Timex.parse!("2017-12-15T21:03:30Z", "{ISO:Extended:Z}"), + ...> "totalDifficulty" => 340282366920938463463374607431465668165, + ...> "transactions" => [], + ...> "transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + ...> "uncles" => [] + ...> } + ...> ) + %{ + difficulty: 340282366920938463463374607431465537093, + gas_limit: 6706541, + gas_used: 0, + hash: "0x52c867bc0a91e573dc39300143c3bead7408d09d45bdb686749f02684ece72f3", + miner_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + nonce: 0, + number: 1, + parent_hash: "0x5b28c1bfd3a15230c9a46b399cd0f9a6920d432e85381cc6a140b06e8410112f", + size: 576, + timestamp: Timex.parse!("2017-12-15T21:03:30Z", "{ISO:Extended:Z}"), + total_difficulty: 340282366920938463463374607431465668165 + } + + """ @spec elixir_to_params(elixir) :: map def elixir_to_params( %{ @@ -81,30 +128,155 @@ defmodule Explorer.JSONRPC.Block do @doc """ Get `t:Explorer.JSONRPC.Transactions.elixir/0` from `t:elixir/0` + + iex> Explorer.JSONRPC.Block.elixir_to_transactions( + ...> %{ + ...> "author" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> "difficulty" => 340282366920938463463374607431768211454, + ...> "extraData" => "0xd5830108048650617269747986312e32322e31826c69", + ...> "gasLimit" => 6926030, + ...> "gasUsed" => 269607, + ...> "hash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", + ...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + ...> "miner" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> "number" => 34, + ...> "parentHash" => "0x106d528393159b93218dd410e2a778f083538098e46f1a44902aa67a164aed0b", + ...> "receiptsRoot" => "0xf45ed4ab910504ffe231230879c86e32b531bb38a398a7c9e266b4a992e12dfb", + ...> "sealFields" => [ + ...> "0x84120a71db", + ...> "0xb8417ad0ecca535f81e1807dac338a57c7ccffd05d3e7f0687944650cd005360a192205df306a68eddfe216e0674c6b113050d90eff9b627c1762d43657308f986f501" + ...> ], + ...> "sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + ...> "signature" => "7ad0ecca535f81e1807dac338a57c7ccffd05d3e7f0687944650cd005360a192205df306a68eddfe216e0674c6b113050d90eff9b627c1762d43657308f986f501", + ...> "size" => 1493, + ...> "stateRoot" => "0x6eaa6281df37b9b010f4779affc25ee059088240547ce86cf7ca7b7acd952d4f", + ...> "step" => "302674395", + ...> "timestamp" => Timex.parse!("2017-12-15T21:06:15Z", "{ISO:Extended:Z}"), + ...> "totalDifficulty" => 11569600475311907757754736652679816646147, + ...> "transactions" => [ + ...> %{ + ...> "blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", + ...> "blockNumber" => 34, + ...> "chainId" => 77, + ...> "condition" => nil, + ...> "creates" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", + ...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> "gas" => 4700000, + ...> "gasPrice" => 100000000000, + ...> "hash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + ...> "input" => "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + ...> "nonce" => 0, + ...> "publicKey" => "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", + ...> "r" => 78347657398501398198088841525118387115323315106407672963464534626150881627253, + ...> "raw" => "0xf9038d8085174876e8008347b7608080b903396060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b002981bda0ad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75a072caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", + ...> "s" => 51922098313630537482394298802395571009347262093735654389129912200586195014115, + ...> "standardV" => 0, + ...> "to" => nil, + ...> "transactionIndex" => 0, + ...> "v" => 189, + ...> "value" => 0 + ...> } + ...> ], + ...> "transactionsRoot" => "0x2c2e243e9735f6d0081ffe60356c0e4ec4c6a9064c68d10bf8091ff896f33087", + ...> "uncles" => [] + ...> } + ...> ) + [ + %{ + "blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", + "blockNumber" => 34, + "chainId" => 77, + "condition" => nil, + "creates" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", + "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + "gas" => 4700000, + "gasPrice" => 100000000000, + "hash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + "input" => "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + "nonce" => 0, + "publicKey" => "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", + "r" => 78347657398501398198088841525118387115323315106407672963464534626150881627253, + "raw" => "0xf9038d8085174876e8008347b7608080b903396060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b002981bda0ad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75a072caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", + "s" => 51922098313630537482394298802395571009347262093735654389129912200586195014115, + "standardV" => 0, + "to" => nil, + "transactionIndex" => 0, + "v" => 189, + "value" => 0 + } + ] + """ @spec elixir_to_transactions(elixir) :: Transactions.elixir() def elixir_to_transactions(%{"transactions" => transactions}), do: transactions @doc """ Decodes the stringly typed numerical fields to `t:non_neg_integer/0` and the timestamps to `t:DateTime.t/0` + + iex> Explorer.JSONRPC.Block.to_elixir( + ...> %{ + ...> "author" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> "difficulty" => "0xfffffffffffffffffffffffffffffffe", + ...> "extraData" => "0xd5830108048650617269747986312e32322e31826c69", + ...> "gasLimit" => "0x66889b", + ...> "gasUsed" => "0x0", + ...> "hash" => "0x7f035c5f3c0678250853a1fde6027def7cac1812667bd0d5ab7ccb94eb8b6f3a", + ...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + ...> "miner" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> "number" => "0x3", + ...> "parentHash" => "0x5fc539c74f65418c64df413c8cc89828c4657a9fecabaa550ceb44ec67786da7", + ...> "receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + ...> "sealFields" => [ + ...> "0x84120a71bc", + ...> "0xb84116ffce67521cd71e44f9c101a9018020fb296c8c3478a17142d7146aafbb189b3c75e0e554d10f6dd7e4dc4567471e673a957cfcb690c37ca65fafa9ade4455101" + ...> ], + ...> "sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + ...> "signature" => "16ffce67521cd71e44f9c101a9018020fb296c8c3478a17142d7146aafbb189b3c75e0e554d10f6dd7e4dc4567471e673a957cfcb690c37ca65fafa9ade4455101", + ...> "size" => "0x240", + ...> "stateRoot" => "0xf0a110ed0f3173dfb2403c59f4f7971ad3be5ec4eedee0764bd654d607213aba", + ...> "step" => "302674364", + ...> "timestamp" => "0x5a3438ac", + ...> "totalDifficulty" => "0x2ffffffffffffffffffffffffedf78e41", + ...> "transactions" => [], + ...> "transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + ...> "uncles" => [] + ...> } + ...> ) + %{ + "author" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + "difficulty" => 340282366920938463463374607431768211454, + "extraData" => "0xd5830108048650617269747986312e32322e31826c69", + "gasLimit" => 6719643, + "gasUsed" => 0, + "hash" => "0x7f035c5f3c0678250853a1fde6027def7cac1812667bd0d5ab7ccb94eb8b6f3a", + "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + "number" => 3, + "parentHash" => "0x5fc539c74f65418c64df413c8cc89828c4657a9fecabaa550ceb44ec67786da7", + "receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "sealFields" => [ + "0x84120a71bc", + "0xb84116ffce67521cd71e44f9c101a9018020fb296c8c3478a17142d7146aafbb189b3c75e0e554d10f6dd7e4dc4567471e673a957cfcb690c37ca65fafa9ade4455101" + ], + "sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "signature" => "16ffce67521cd71e44f9c101a9018020fb296c8c3478a17142d7146aafbb189b3c75e0e554d10f6dd7e4dc4567471e673a957cfcb690c37ca65fafa9ade4455101", + "size" => 576, + "stateRoot" => "0xf0a110ed0f3173dfb2403c59f4f7971ad3be5ec4eedee0764bd654d607213aba", + "step" => "302674364", + "timestamp" => Timex.parse!("2017-12-15T21:03:40Z", "{ISO:Extended:Z}"), + "totalDifficulty" => 1020847100762815390390123822295002091073, + "transactions" => [], + "transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "uncles" => [] + } + """ def to_elixir(block) when is_map(block) do Enum.into(block, %{}, &entry_to_elixir/1) end - def elixir_to_explorer_chain_block_params(elixir) when is_map(elixir) do - Enum.into(elixir, %{}, &elixir_to_explorer_chain_block_param/1) - end - ## Private Functions - defp elixir_to_explorer_chain_block_param({"difficulty", difficulty}) when is_integer(difficulty), - do: {:difficulty, difficulty} - - defp elixir_to_explorer_chain_block_param({"gasUsed", gas_used}) when is_integer(gas_used), do: {:gas_used, gas_used} - defp elixir_to_explorer_chain_block_param({"hash", hash}), do: {:hash, hash} - defp elixir_to_explorer_chain_block_param({"number", number}) when is_integer(number), do: {:number, number} - defp entry_to_elixir({key, quantity}) when key in ~w(difficulty gasLimit gasUsed number size totalDifficulty) do {key, quantity_to_integer(quantity)} end diff --git a/apps/explorer/lib/explorer/jsonrpc/blocks.ex b/apps/explorer/lib/explorer/jsonrpc/blocks.ex index c26cd57dc3..220016e008 100644 --- a/apps/explorer/lib/explorer/jsonrpc/blocks.ex +++ b/apps/explorer/lib/explorer/jsonrpc/blocks.ex @@ -25,6 +25,63 @@ defmodule Explorer.JSONRPC.Blocks do @doc """ Decodes the stringly typed numerical fields to `t:non_neg_integer/0` and the timestamps to `t:DateTime.t/0` + + iex> Explorer.JSONRPC.Blocks.to_elixir( + ...> [ + ...> %{ + ...> "author" => "0x0000000000000000000000000000000000000000", + ...> "difficulty" => "0x20000", + ...> "extraData" => "0x", + ...> "gasLimit" => "0x663be0", + ...> "gasUsed" => "0x0", + ...> "hash" => "0x5b28c1bfd3a15230c9a46b399cd0f9a6920d432e85381cc6a140b06e8410112f", + ...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + ...> "miner" => "0x0000000000000000000000000000000000000000", + ...> "number" => "0x0", + ...> "parentHash" => "0x0000000000000000000000000000000000000000000000000000000000000000", + ...> "receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + ...> "sealFields" => ["0x80", + ...> "0xb8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"], + ...> "sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + ...> "signature" => "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + ...> "size" => "0x215", + ...> "stateRoot" => "0xfad4af258fd11939fae0c6c6eec9d340b1caac0b0196fd9a1bc3f489c5bf00b3", + ...> "step" => "0", + ...> "timestamp" => "0x0", + ...> "totalDifficulty" => "0x20000", + ...> "transactions" => [], + ...> "transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + ...> "uncles" => [] + ...> } + ...> ] + ...> ) + [ + %{ + "author" => "0x0000000000000000000000000000000000000000", + "difficulty" => 131072, + "extraData" => "0x", + "gasLimit" => 6700000, + "gasUsed" => 0, + "hash" => "0x5b28c1bfd3a15230c9a46b399cd0f9a6920d432e85381cc6a140b06e8410112f", + "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner" => "0x0000000000000000000000000000000000000000", + "number" => 0, + "parentHash" => "0x0000000000000000000000000000000000000000000000000000000000000000", + "receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "sealFields" => ["0x80", + "0xb8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"], + "sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "signature" => "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "size" => 533, + "stateRoot" => "0xfad4af258fd11939fae0c6c6eec9d340b1caac0b0196fd9a1bc3f489c5bf00b3", + "step" => "0", + "timestamp" => Timex.parse!("1970-01-01T00:00:00Z", "{ISO:Extended:Z}"), + "totalDifficulty" => 131072, + "transactions" => [], + "transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "uncles" => [] + } + ] """ @spec to_elixir(t) :: elixir def to_elixir(blocks) when is_list(blocks) do diff --git a/apps/explorer/lib/explorer/jsonrpc/log.ex b/apps/explorer/lib/explorer/jsonrpc/log.ex new file mode 100644 index 0000000000..2ee3ddae65 --- /dev/null +++ b/apps/explorer/lib/explorer/jsonrpc/log.ex @@ -0,0 +1,123 @@ +defmodule Explorer.JSONRPC.Log do + @moduledoc """ + Log included in return from + [`eth_getTransactionReceipt`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionreceipt). + """ + + import Explorer.JSONRPC, only: [quantity_to_integer: 1] + + # Types + + @typedoc """ + * `"address"` - `t:Explorer.JSONRPC.address/0` from which event originated. + * `"blockHash"` - `t:Explorer.JSONRPC.hash/0` of the block this transaction is in. + * `"blockNumber"` - `t:Explorer.JSONRPC.quantity/0` for the block number this transaction is in. + * `"data"` - Data containing non-indexed log parameter + * `"logIndex"` - `t:non_neg_integer/0` of the event index positon in the block. + * `"topics" - `t:list/0` of at most 4 32-byte topics. Topic 1-3 contains indexed parameters of the log. + * `"transactionHash"` - `t:Explorer.JSONRPC.hash/0` of the transaction + * `"transactionIndex"` - `t:Explorer.JSONRPC.quantity/0` for the index of the transaction in the block. + """ + @type t :: %{String.t() => term} + + # Functions + + @doc """ + Converts `t:elixir/0` format to params used in `Explorer.Chain`. + + iex> Explorer.JSONRPC.Log.elixir_to_params( + ...> %{ + ...> "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + ...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", + ...> "blockNumber" => 37, + ...> "data" => "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", + ...> "logIndex" => 0, + ...> "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], + ...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + ...> "transactionIndex" => 0, + ...> "transactionLogIndex" => 0, + ...> "type" => "mined" + ...> } + ...> ) + %{ + address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + data: "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", + first_topic: "0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22", + fourth_topic: nil, + index: 0, + second_topic: nil, + third_topic: nil, + transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + type: "mined" + } + + """ + def elixir_to_params(%{ + "address" => address_hash, + "data" => data, + "logIndex" => index, + "topics" => topics, + "transactionHash" => transaction_hash, + "type" => type + }) do + %{ + address_hash: address_hash, + data: data, + index: index, + transaction_hash: transaction_hash, + type: type + } + |> put_topics(topics) + end + + @doc """ + Decodes the stringly typed numerical fields to `t:non_neg_integer/0`. + + iex> Explorer.JSONRPC.Log.to_elixir( + ...> %{ + ...> "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + ...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", + ...> "blockNumber" => "0x25", + ...> "data" => "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", + ...> "logIndex" => "0x0", + ...> "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], + ...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + ...> "transactionIndex" => "0x0", + ...> "transactionLogIndex" => "0x0", + ...> "type" => "mined" + ...> } + ...> ) + %{ + "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", + "blockNumber" => 37, + "data" => "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", + "logIndex" => 0, + "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], + "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + "transactionIndex" => 0, + "transactionLogIndex" => 0, + "type" => "mined" + } + + """ + def to_elixir(log) when is_map(log) do + Enum.into(log, %{}, &entry_to_elixir/1) + end + + ## Private Functions + + defp entry_to_elixir({key, _} = entry) when key in ~w(address blockHash data topics transactionHash type), do: entry + + defp entry_to_elixir({key, quantity}) when key in ~w(blockNumber logIndex transactionIndex transactionLogIndex) do + {key, quantity_to_integer(quantity)} + end + + defp put_topics(params, topics) when is_map(params) and is_list(topics) do + params + |> Map.put(:first_topic, Enum.at(topics, 0)) + |> Map.put(:second_topic, Enum.at(topics, 1)) + |> Map.put(:third_topic, Enum.at(topics, 2)) + |> Map.put(:fourth_topic, Enum.at(topics, 3)) + end +end diff --git a/apps/explorer/lib/explorer/jsonrpc/logs.ex b/apps/explorer/lib/explorer/jsonrpc/logs.ex new file mode 100644 index 0000000000..07dbafa53e --- /dev/null +++ b/apps/explorer/lib/explorer/jsonrpc/logs.ex @@ -0,0 +1,25 @@ +defmodule Explorer.JSONRPC.Logs do + @moduledoc """ + Collection of logs included in return from + [`eth_getTransactionReceipt`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionreceipt). + """ + + alias Explorer.JSONRPC.Log + + # Types + + @type elixir :: [Log.elixir()] + @type t :: [Log.t()] + + # Functions + + @spec elixir_to_params(elixir) :: [map] + def elixir_to_params(elixir) when is_list(elixir) do + Enum.map(elixir, &Log.elixir_to_params/1) + end + + @spec to_elixir(t) :: elixir + def to_elixir(logs) when is_list(logs) do + Enum.map(logs, &Log.to_elixir/1) + end +end diff --git a/apps/explorer/lib/explorer/jsonrpc/parity.ex b/apps/explorer/lib/explorer/jsonrpc/parity.ex new file mode 100644 index 0000000000..d6dded5f1b --- /dev/null +++ b/apps/explorer/lib/explorer/jsonrpc/parity.ex @@ -0,0 +1,49 @@ +defmodule Explorer.JSONRPC.Parity do + @moduledoc """ + Ethereum JSONRPC methods that are only supported by [Parity](https://wiki.parity.io/). + """ + + import Explorer.JSONRPC, only: [config: 1, json_rpc: 2] + + alias Explorer.JSONRPC.Parity.Traces + + # Functions + + def fetch_internal_transactions(transaction_hashes) when is_list(transaction_hashes) do + with {:ok, responses} <- + transaction_hashes + |> Enum.map(&transaction_hash_to_internal_transaction_json/1) + |> json_rpc(config(:trace_url)) do + internal_transactions_params = + responses + |> responses_to_traces() + |> Traces.to_elixir() + |> Traces.elixir_to_params() + + {:ok, internal_transactions_params} + end + end + + ## Private Functions + + defp response_to_trace(%{"id" => transaction_hash, "result" => %{"trace" => traces}}) when is_list(traces) do + traces + |> Stream.with_index() + |> Enum.map(fn {trace, index} -> + Map.merge(trace, %{"index" => index, "transactionHash" => transaction_hash}) + end) + end + + defp responses_to_traces(responses) when is_list(responses) do + Enum.flat_map(responses, &response_to_trace/1) + end + + defp transaction_hash_to_internal_transaction_json(transaction_hash) do + %{ + "id" => transaction_hash, + "jsonrpc" => "2.0", + "method" => "trace_replayTransaction", + "params" => [transaction_hash, ["trace"]] + } + end +end diff --git a/apps/explorer/lib/explorer/jsonrpc/parity/trace.ex b/apps/explorer/lib/explorer/jsonrpc/parity/trace.ex new file mode 100644 index 0000000000..e461eb3b9c --- /dev/null +++ b/apps/explorer/lib/explorer/jsonrpc/parity/trace.ex @@ -0,0 +1,313 @@ +defmodule Explorer.JSONRPC.Parity.Trace do + @moduledoc """ + Trace returned by + [`trace_replayTransaction`](https://wiki.parity.io/JSONRPC-trace-module.html#trace_replaytransaction), which is an + extension to the Ethereum JSONRPC standard that is only supported by [Parity](https://wiki.parity.io/). + """ + + alias Explorer.JSONRPC.Parity.Trace.{Action, Result} + + # Functions + + @doc """ + Create type traces are generated when a contract is created. + + iex> Explorer.JSONRPC.Parity.Trace.elixir_to_params( + ...> %{ + ...> "action" => %{ + ...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> "gas" => 4597044, + ...> "init" => "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + ...> "value" => 0 + ...> }, + ...> "index" => 0, + ...> "result" => %{ + ...> "address" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", + ...> "code" => "0x606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + ...> "gasUsed" => 166651 + ...> }, + ...> "subtraces" => 0, + ...> "traceAddress" => [], + ...> "transactionHash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + ...> "type" => "create" + ...> } + ...> ) + %{ + created_contract_address_hash: "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", + created_contract_code: "0x606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + gas: 4597044, + gas_used: 166651, + index: 0, + init: "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + trace_address: [], + transaction_hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + type: "create", + value: 0 + } + + A create can fail due to a Bad Instruction in the `init` that is meant to form the `code` of the contract + + iex> Explorer.JSONRPC.Parity.Trace.elixir_to_params( + ...> %{ + ...> "action" => %{ + ...> "from" => "0x78a42d3705fb3c26a4b54737a784bf064f0815fb", + ...> "gas" => 3946728, + ...> "init" => "0x4bb278f3", + ...> "value" => 0 + ...> }, + ...> "error" => "Bad instruction", + ...> "index" => 0, + ...> "subtraces" => 0, + ...> "traceAddress" => [], + ...> "transactionHash" => "0x3c624bb4852fb5e35a8f45644cec7a486211f6ba89034768a2b763194f22f97d", + ...> "type" => "create" + ...> } + ...> ) + %{ + error: "Bad instruction", + from_address_hash: "0x78a42d3705fb3c26a4b54737a784bf064f0815fb", + gas: 3946728, + index: 0, + init: "0x4bb278f3", + trace_address: [], + transaction_hash: "0x3c624bb4852fb5e35a8f45644cec7a486211f6ba89034768a2b763194f22f97d", + type: "create", + value: 0 + } + + Call type traces are generated when a method is called. Calls are further divided by call type. + + iex> Explorer.JSONRPC.Parity.Trace.elixir_to_params( + ...> %{ + ...> "action" => %{ + ...> "callType" => "call", + ...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> "gas" => 4677320, + ...> "input" => "0x10855269000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", + ...> "to" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + ...> "value" => 0 + ...> }, + ...> "index" => 0, + ...> "result" => %{ + ...> "gasUsed" => 27770, + ...> "output" => "0x" + ...> }, + ...> "subtraces" => 0, + ...> "traceAddress" => [], + ...> "transactionHash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + ...> "type" => "call" + ...> } + ...> ) + %{ + call_type: "call", + from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + gas: 4677320, + gas_used: 27770, + index: 0, + output: "0x", + to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + trace_address: [], + transaction_hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + type: "call", + value: 0 + } + + Calls can error and be reverted + + iex> Explorer.JSONRPC.Parity.Trace.elixir_to_params( + ...> %{ + ...> "action" => %{ + ...> "callType" => "call", + ...> "from" => "0xc9266e6fdf5182dc47d27e0dc32bdff9e4cd2e32", + ...> "gas" => 7578728, + ...> "input" => "0xa6f2ae3a", + ...> "to" => "0xfdca0da4158740a93693441b35809b5bb463e527", + ...> "value" => 10000000000000000 + ...> }, + ...> "error" => "Reverted", + ...> "index" => 0, + ...> "subtraces" => 7, + ...> "traceAddress" => [], + ...> "transactionHash" => "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b", + ...> "type" => "call" + ...> } + ...> ) + %{ + call_type: "call", + error: "Reverted", + from_address_hash: "0xc9266e6fdf5182dc47d27e0dc32bdff9e4cd2e32", + gas: 7578728, + index: 0, + to_address_hash: "0xfdca0da4158740a93693441b35809b5bb463e527", + trace_address: [], + transaction_hash: "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b", + type: "call", + value: 10000000000000000 + } + + """ + + def elixir_to_params(%{"type" => "create" = type} = elixir) do + %{ + "action" => %{"from" => from_address_hash, "gas" => gas, "init" => init, "value" => value}, + "index" => index, + "traceAddress" => trace_address, + "transactionHash" => transaction_hash + } = elixir + + %{ + from_address_hash: from_address_hash, + gas: gas, + index: index, + init: init, + trace_address: trace_address, + transaction_hash: transaction_hash, + type: type, + value: value + } + |> put_create_error_or_result(elixir) + end + + def elixir_to_params(%{"type" => "call" = type} = elixir) do + %{ + "action" => %{ + "callType" => call_type, + "from" => from_address_hash, + "gas" => gas, + "to" => to_address_hash, + "value" => value + }, + "index" => index, + "traceAddress" => trace_address, + "transactionHash" => transaction_hash + } = elixir + + %{ + call_type: call_type, + from_address_hash: from_address_hash, + gas: gas, + index: index, + to_address_hash: to_address_hash, + trace_address: trace_address, + transaction_hash: transaction_hash, + type: type, + value: value + } + |> put_call_error_or_result(elixir) + end + + @doc """ + Decodes the stringly typed numerical fields to `t:non_neg_integer/0`. + + iex> Explorer.JSONRPC.Parity.Trace.to_elixir( + ...> %{ + ...> "action" => %{ + ...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> "gas" => "0x462534", + ...> "init" => "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + ...> "value" => "0x0" + ...> }, + ...> "index" => 0, + ...> "result" => %{ + ...> "address" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", + ...> "code" => "0x606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + ...> "gasUsed" => "0x28afb" + ...> }, + ...> "subtraces" => 0, + ...> "traceAddress" => [], + ...> "transactionHash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + ...> "type" => "create" + ...> } + ...> ) + %{ + "action" => %{ + "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + "gas" => 4597044, + "init" => "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + "value" => 0 + }, + "index" => 0, + "result" => %{ + "address" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", + "code" => "0x606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + "gasUsed" => 166651 + }, + "subtraces" => 0, + "traceAddress" => [], + "transactionHash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + "type" => "create" + } + + The caller must put `"index"` and `"transactionHash"` into the incoming map, as Parity itself does not include that + information, but it is needed to locate the trace in history fully. + + iex> Explorer.JSONRPC.Parity.Trace.to_elixir( + ...> %{ + ...> "action" => %{ + ...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> "gas" => "0x462534", + ...> "init" => "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + ...> "value" => "0x0" + ...> }, + ...> "result" => %{ + ...> "address" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", + ...> "code" => "0x606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + ...> "gasUsed" => "0x28afb" + ...> }, + ...> "subtraces" => 0, + ...> "traceAddress" => [], + ...> "type" => "create" + ...> } + ...> ) + ** (ArgumentError) Caller must `Map.put/2` `"index"` and `"transactionHash"` in trace + + """ + def to_elixir(%{"index" => _, "transactionHash" => _} = trace) when is_map(trace) do + Enum.into(trace, %{}, &entry_to_elixir/1) + end + + def to_elixir(_) do + raise ArgumentError, ~S|Caller must `Map.put/2` `"index"` and `"transactionHash"` in trace| + end + + ## Private Functions + + # subtraces is an actual integer in JSON and not hex-encoded + # traceAddress is a list of actual integers, not a list of hex-encoded + defp entry_to_elixir({key, _} = entry) when key in ~w(subtraces traceAddress transactionHash type output), do: entry + + defp entry_to_elixir({"action" = key, action}) do + {key, Action.to_elixir(action)} + end + + defp entry_to_elixir({"error", reason} = entry) when is_binary(reason), do: entry + + defp entry_to_elixir({"index", index} = entry) when is_integer(index), do: entry + + defp entry_to_elixir({"result" = key, result}) do + {key, Result.to_elixir(result)} + end + + defp put_call_error_or_result(params, %{"result" => %{"gasUsed" => gas_used, "output" => output}}) do + Map.merge(params, %{gas_used: gas_used, output: output}) + end + + defp put_call_error_or_result(params, %{"error" => error}) do + Map.put(params, :error, error) + end + + defp put_create_error_or_result(params, %{ + "result" => %{"address" => created_contract_address_hash, "code" => code, "gasUsed" => gas_used} + }) do + Map.merge(params, %{ + created_contract_code: code, + created_contract_address_hash: created_contract_address_hash, + gas_used: gas_used + }) + end + + defp put_create_error_or_result(params, %{"error" => error}) do + Map.put(params, :error, error) + end +end diff --git a/apps/explorer/lib/explorer/jsonrpc/parity/trace/action.ex b/apps/explorer/lib/explorer/jsonrpc/parity/trace/action.ex new file mode 100644 index 0000000000..b7f0bbd299 --- /dev/null +++ b/apps/explorer/lib/explorer/jsonrpc/parity/trace/action.ex @@ -0,0 +1,38 @@ +defmodule Explorer.JSONRPC.Parity.Trace.Action do + @moduledoc """ + The action that was peformed in a `t:Explorer.JSONRPC.Parity.Trace.t/0` + """ + + import Explorer.JSONRPC, only: [quantity_to_integer: 1] + + @doc """ + Decodes the stringly typed numerical fields to `t:non_neg_integer/0`. + + iex> Explorer.JSONRPC.Parity.Trace.Action.to_elixir( + ...> %{ + ...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> "gas" => "0x462534", + ...> "init" => "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + ...> "value" => "0x0" + ...> } + ...> ) + %{ + "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + "gas" => 4597044, + "init" => "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + "value" => 0 + } + + """ + def to_elixir(action) when is_map(action) do + Enum.into(action, %{}, &entry_to_elixir/1) + end + + ## Private Functions + + defp entry_to_elixir({key, _} = entry) when key in ~w(callType from init input to), do: entry + + defp entry_to_elixir({key, quantity}) when key in ~w(gas value) do + {key, quantity_to_integer(quantity)} + end +end diff --git a/apps/explorer/lib/explorer/jsonrpc/parity/trace/result.ex b/apps/explorer/lib/explorer/jsonrpc/parity/trace/result.ex new file mode 100644 index 0000000000..9b343cd7ec --- /dev/null +++ b/apps/explorer/lib/explorer/jsonrpc/parity/trace/result.ex @@ -0,0 +1,36 @@ +defmodule Explorer.JSONRPC.Parity.Trace.Result do + @moduledoc """ + The result of performing the `t:Explorer.JSONRPC.Parity.Action.t/0` in a `t:Explorer.JSONRPC.Parity.Trace.t/0`. + """ + + import Explorer.JSONRPC, only: [quantity_to_integer: 1] + + @doc """ + Decodes the stringly typed numerical fields to `t:non_neg_integer/0`. + + iex> Explorer.JSONRPC.Parity.Trace.Result.to_elixir( + ...> %{ + ...> "address" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", + ...> "code" => "0x606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + ...> "gasUsed" => "0x28afb" + ...> } + ...> ) + %{ + "address" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", + "code" => "0x606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + "gasUsed" => 166651 + } + + """ + def to_elixir(action) when is_map(action) do + Enum.into(action, %{}, &entry_to_elixir/1) + end + + ## Private Functions + + defp entry_to_elixir({key, _} = entry) when key in ~w(address code output), do: entry + + defp entry_to_elixir({key, quantity}) when key in ~w(gasUsed) do + {key, quantity_to_integer(quantity)} + end +end diff --git a/apps/explorer/lib/explorer/jsonrpc/parity/traces.ex b/apps/explorer/lib/explorer/jsonrpc/parity/traces.ex new file mode 100644 index 0000000000..ccb7e4cce6 --- /dev/null +++ b/apps/explorer/lib/explorer/jsonrpc/parity/traces.ex @@ -0,0 +1,17 @@ +defmodule Explorer.JSONRPC.Parity.Traces do + @moduledoc """ + Trace returned by + [`trace_replayTransaction`](https://wiki.parity.io/JSONRPC-trace-module.html#trace_replaytransaction), which is an + extension to the Ethereum JSONRPC standard that is only supported by [Parity](https://wiki.parity.io/). + """ + + alias Explorer.JSONRPC.Parity.Trace + + def elixir_to_params(elixir) when is_list(elixir) do + Enum.map(elixir, &Trace.elixir_to_params/1) + end + + def to_elixir(traces) when is_list(traces) do + Enum.map(traces, &Trace.to_elixir/1) + end +end diff --git a/apps/explorer/lib/explorer/jsonrpc/receipt.ex b/apps/explorer/lib/explorer/jsonrpc/receipt.ex index 60caae417b..c7be978147 100644 --- a/apps/explorer/lib/explorer/jsonrpc/receipt.ex +++ b/apps/explorer/lib/explorer/jsonrpc/receipt.ex @@ -4,7 +4,10 @@ defmodule Explorer.JSONRPC.Receipt do [`eth_getTransactionReceipt`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionreceipt). """ + import Explorer.JSONRPC, only: [quantity_to_integer: 1] + alias Explorer.JSONRPC + alias Explorer.JSONRPC.Logs # Types @@ -29,11 +32,89 @@ defmodule Explorer.JSONRPC.Receipt do # Functions + @doc """ + `Get `t:Explorer.JSONRPC.Logs.elixir/0` from `t:elixir/0` + """ + @spec elixir_to_logs(elixir) :: Logs.elixir() + def elixir_to_logs(%{"logs" => logs}), do: logs + + @doc """ + Converts `t:elixir/0` format to params used in `Explorer.Chain`. + + iex> Explorer.JSONRPC.Receipt.elixir_to_params( + ...> %{ + ...> "blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", + ...> "blockNumber" => 34, + ...> "contractAddress" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", + ...> "cumulativeGasUsed" => 269607, + ...> "gasUsed" => 269607, + ...> "logs" => [], + ...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + ...> "root" => nil, + ...> "status" => :ok, + ...> "transactionHash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + ...> "transactionIndex" => 0 + ...> } + ...> ) + %{ + cumulative_gas_used: 269607, + gas_used: 269607, + status: :ok, + transaction_hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + transaction_index: 0 + } + + """ @spec elixir_to_params(elixir) :: [map] - def elixir_to_params(elixir) when is_list(elixir) do - raise "BOOM" + def elixir_to_params(%{ + "cumulativeGasUsed" => cumulative_gas_used, + "gasUsed" => gas_used, + "status" => status, + "transactionHash" => transaction_hash, + "transactionIndex" => transaction_index + }) do + %{ + cumulative_gas_used: cumulative_gas_used, + gas_used: gas_used, + status: status, + transaction_hash: transaction_hash, + transaction_index: transaction_index + } end + @doc """ + Decodes the stringly typed numerical fields to `t:non_neg_integer/0`. + + iex> Explorer.JSONRPC.Receipt.to_elixir( + ...> %{ + ...> "blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", + ...> "blockNumber" => "0x22", + ...> "contractAddress" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", + ...> "cumulativeGasUsed" => "0x41d27", + ...> "gasUsed" => "0x41d27", + ...> "logs" => [], + ...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + ...> "root" => nil, + ...> "status" => "0x1", + ...> "transactionHash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + ...> "transactionIndex" => "0x0" + ...> } + ...> ) + %{ + "blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", + "blockNumber" => 34, + "contractAddress" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", + "cumulativeGasUsed" => 269607, + "gasUsed" => 269607, + "logs" => [], + "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "root" => nil, + "status" => :ok, + "transactionHash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + "transactionIndex" => 0 + } + + """ @spec to_elixir(t) :: elixir def to_elixir(receipt) when is_map(receipt) do Enum.into(receipt, %{}, &entry_to_elixir/1) @@ -44,5 +125,24 @@ defmodule Explorer.JSONRPC.Receipt do # double check that no new keys are being missed by requiring explicit match for passthrough # `t:Explorer.JSONRPC.address/0` and `t:Explorer.JSONRPC.hash/0` pass through as `Explorer.Chain` can verify correct # hash format - defp entry_to_elixir({key, _} = entry) when key in ~w(foo), do: entry + defp entry_to_elixir({key, _} = entry) when key in ~w(blockHash contractAddress logsBloom root transactionHash), + do: entry + + defp entry_to_elixir({key, quantity}) when key in ~w(blockNumber cumulativeGasUsed gasUsed transactionIndex) do + {key, quantity_to_integer(quantity)} + end + + defp entry_to_elixir({"logs" = key, logs}) do + {key, Logs.to_elixir(logs)} + end + + defp entry_to_elixir({"status" = key, status}) do + elixir_status = + case status do + "0x0" -> :error + "0x1" -> :ok + end + + {key, elixir_status} + end end diff --git a/apps/explorer/lib/explorer/jsonrpc/receipts.ex b/apps/explorer/lib/explorer/jsonrpc/receipts.ex index 63e9b6506f..b141c27911 100644 --- a/apps/explorer/lib/explorer/jsonrpc/receipts.ex +++ b/apps/explorer/lib/explorer/jsonrpc/receipts.ex @@ -5,7 +5,9 @@ defmodule Explorer.JSONRPC.Receipts do requests. """ - alias Explorer.JSONRPC.Receipt + import Explorer.JSONRPC, only: [config: 1, json_rpc: 2] + + alias Explorer.JSONRPC.{Logs, Receipt} # Types @@ -14,13 +16,53 @@ defmodule Explorer.JSONRPC.Receipts do # Functions + @spec elixir_to_logs(elixir) :: Logs.elixir() + def elixir_to_logs(elixir) when is_list(elixir) do + Enum.flat_map(elixir, &Receipt.elixir_to_logs/1) + end + @spec elixir_to_params(elixir) :: [map] def elixir_to_params(elixir) when is_list(elixir) do Enum.map(elixir, &Receipt.elixir_to_params/1) end + def fetch(hashes) when is_list(hashes) do + with {:ok, responses} <- + hashes + |> Enum.map(&hash_to_json/1) + |> json_rpc(config(:url)) do + elixir_receipts = + responses + |> responses_to_receipts() + |> to_elixir() + + elixir_logs = elixir_to_logs(elixir_receipts) + receipts_params = elixir_to_params(elixir_receipts) + logs_params = Logs.elixir_to_params(elixir_logs) + + {:ok, %{logs_params: logs_params, receipts_params: receipts_params}} + end + end + @spec to_elixir(t) :: elixir def to_elixir(receipts) when is_list(receipts) do Enum.map(receipts, &Receipt.to_elixir/1) end + + ## Private Functons + + defp hash_to_json(hash) do + %{ + "id" => hash, + "jsonrpc" => "2.0", + "method" => "eth_getTransactionReceipt", + "params" => [hash] + } + end + + defp response_to_receipt(%{"result" => receipt}), do: receipt + + defp responses_to_receipts(responses) when is_list(responses) do + Enum.map(responses, &response_to_receipt/1) + end end diff --git a/apps/explorer/lib/explorer/jsonrpc/transaction.ex b/apps/explorer/lib/explorer/jsonrpc/transaction.ex index 10ea7289a6..25ed6de9a8 100644 --- a/apps/explorer/lib/explorer/jsonrpc/transaction.ex +++ b/apps/explorer/lib/explorer/jsonrpc/transaction.ex @@ -14,10 +14,15 @@ defmodule Explorer.JSONRPC.Transaction do # Types + @type elixir :: %{String.t() => JSONRPC.address() | JSONRPC.hash() | String.t() | non_neg_integer() | nil} + @typedoc """ * `"blockHash"` - `t:Explorer.JSONRPC.hash/0` of the block this transaction is in. `nil` when transaction is pending. * `"blockNumber"` - `t:Explorer.JSONRPC.quantity/0` for the block number this transaction is in. `nil` when transaction is pending. + * `"chainId"` - the chain on which the transaction exists. + * `"condition"` - UNKNOWN + * `"creates"` - `t:Explorer.JSONRPC.address/0` of the created contract, if the transaction creates a contract. * `"from"` - `t:Explorer.JSONRPC.address/0` of the sender. * `"gas"` - `t:Explorer.JSONRPC.quantity/0` of gas provided by the sender. This is the max gas that may be used. `gas * gasPrice` is the max fee in wei that the sender is willing to pay for the transaction to be executed. @@ -25,16 +30,40 @@ defmodule Explorer.JSONRPC.Transaction do * `"hash"` - `t:Explorer.JSONRPC.hash/0` of the transaction * `"input"` - `t:Explorer.JSONRPC.data/0` sent along with the transaction, such as input to the contract. * `"nonce"` - `t:Explorer.JSONRPC.quantity/0` of transactions made by the sender prior to this one. + * `"publicKey"` - `t:Explorer.JSONRPC.hash/0` of the public key of the signer. + * `"r"` - `t:Explorer.JSONRPC.quantity/0` for the R field of the signature. + * `"raw"` - Raw transaction `t:Explorer.JSONRPC.data/0` + * `"standardV"` - `t:Explorer.JSONRPC.quantity/0` for the standardized V (`0` or `1`) field of the signature. * `"to"` - `t:Explorer.JSONRPC.address/0` of the receiver. `nil` when it is a contract creation transaction. * `"transactionIndex"` - `t:Explorer.JSONRPC.quantity/0` for the index of the transaction in the block. `nil` when transaction is pending. + * `"v"` - `t:Explorer.JSONRPC.quantity/0` for the V field of the signature. * `"value"` - `t:Explorer.JSONRPC.quantity/0` of wei transfered """ - @type t :: %{String.t() => JSONRPC.address() | JSONRPC.hash() | JSONRPC.quantity() | nil} + @type t :: %{String.t() => JSONRPC.address() | JSONRPC.hash() | JSONRPC.quantity() | String.t() | nil} + + @type params :: %{ + block_hash: JSONRPC.hash(), + gas: non_neg_integer(), + gas_price: non_neg_integer(), + hash: JSONRPC.hash(), + index: non_neg_integer(), + input: String.t(), + nonce: non_neg_integer(), + publicKey: String.t(), + r: non_neg_integer(), + s: non_neg_integer(), + standard_v: 0 | 1, + v: non_neg_integer(), + value: non_neg_integer() + } # Functions + @spec elixir_to_params(elixir) :: params def elixir_to_params(%{ + "blockHash" => block_hash, + "from" => from_address_hash, "gas" => gas, "gasPrice" => gas_price, "hash" => hash, @@ -44,11 +73,14 @@ defmodule Explorer.JSONRPC.Transaction do "r" => r, "s" => s, "standardV" => standard_v, + "to" => to_address_hash, "transactionIndex" => index, "v" => v, "value" => value }) do %{ + block_hash: block_hash, + from_address_hash: from_address_hash, gas: gas, gas_price: gas_price, hash: hash, @@ -59,26 +91,63 @@ defmodule Explorer.JSONRPC.Transaction do r: r, s: s, standard_v: standard_v, + to_address_hash: to_address_hash, v: v, value: value } end - def params_to_hash(%{"hash" => hash}), do: hash + @doc """ + Extracts `t:Explorer.JSONRPC.hash/0` from transaction `params` + + iex> Explorer.JSONRPC.Transaction.params_to_hash( + ...> %{ + ...> block_hash: "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", + ...> gas: 4700000, + ...> gas_price: 100000000000, + ...> hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + ...> index: 0, + ...> input: "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + ...> nonce: 0, + ...> public_key: "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", + ...> r: "0xAD3733DF250C87556335FFE46C23E34DBAFFDE93097EF92F52C88632A40F0C75", + ...> s: "0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", + ...> standard_v: 0, + ...> v: "0x8d", + ...> value: 0 + ...> } + ...> ) + "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6" + + """ + def params_to_hash(%{hash: hash}), do: hash @doc """ Decodes the stringly typed numerical fields to `t:non_neg_integer/0`. """ def to_elixir(transaction) when is_map(transaction) do - Enum.into(transaction, %{}, &to_elixir/1) + Enum.into(transaction, %{}, &entry_to_elixir/1) end + ## Private Functions + # double check that no new keys are being missed by requiring explicit match for passthrough # `t:Explorer.JSONRPC.address/0` and `t:Explorer.JSONRPC.hash/0` pass through as `Explorer.Chain` can verify correct # hash format - def to_elixir({key, value}) when key in ~w(blockHash from hash input jsonrpc), do: {key, value} + # r s standardV and v pass through because they exceed postgres integer limits + defp entry_to_elixir({key, value}) + when key in ~w(blockHash condition creates from hash input jsonrpc publicKey r raw s standardV to v), + do: {key, value} - def to_elixir({key, quantity}) when key in ~w(blockNumber gas gasPrice nonce transactionIndex value) do + defp entry_to_elixir({key, quantity}) when key in ~w(blockNumber gas gasPrice nonce transactionIndex value) do {key, quantity_to_integer(quantity)} end + + # chainId is *sometimes* nil + defp entry_to_elixir({"chainId" = key, chainId}) do + case chainId do + nil -> {key, chainId} + _ -> {key, quantity_to_integer(chainId)} + end + end end diff --git a/apps/explorer/priv/repo/migrations/20180117221922_create_blocks.exs b/apps/explorer/priv/repo/migrations/20180117221922_create_blocks.exs index 732560c7e6..e63d559de2 100644 --- a/apps/explorer/priv/repo/migrations/20180117221922_create_blocks.exs +++ b/apps/explorer/priv/repo/migrations/20180117221922_create_blocks.exs @@ -8,7 +8,7 @@ defmodule Explorer.Repo.Migrations.CreateBlocks do add(:gas_used, :integer, null: false) add(:hash, :bytea, null: false, primary_key: true) add(:miner_hash, references(:addresses, column: :hash, type: :bytea), null: false) - add(:nonce, :string, null: false) + add(:nonce, :integer, null: false) add(:number, :bigint, null: false) # not a foreign key to allow skipped blocks diff --git a/apps/explorer/priv/repo/migrations/20180117221923_create_transactions.exs b/apps/explorer/priv/repo/migrations/20180117221923_create_transactions.exs index c32ed01fd1..252e5f0a8b 100644 --- a/apps/explorer/priv/repo/migrations/20180117221923_create_transactions.exs +++ b/apps/explorer/priv/repo/migrations/20180117221923_create_transactions.exs @@ -33,6 +33,16 @@ defmodule Explorer.Repo.Migrations.CreateTransactions do add(:to_address_hash, references(:addresses, column: :hash, on_delete: :delete_all, type: :bytea), null: true) end + # Constraints + + create( + constraint( + :transactions, + :indexed, + check: "(block_hash IS NULL AND index IS NULL) OR (block_hash IS NOT NULL AND index IS NOT NULL)" + ) + ) + # Foreign Key indexes create(index(:transactions, :block_hash)) diff --git a/apps/explorer/priv/repo/migrations/20180212214442_create_receipts.exs b/apps/explorer/priv/repo/migrations/20180212214442_create_receipts.exs index 581722fce9..869098dc43 100644 --- a/apps/explorer/priv/repo/migrations/20180212214442_create_receipts.exs +++ b/apps/explorer/priv/repo/migrations/20180212214442_create_receipts.exs @@ -2,18 +2,16 @@ defmodule Explorer.Repo.Migrations.CreateReceipts do use Ecto.Migration def change do - create table(:receipts) do + create table(:receipts, primary_key: false) do add(:cumulative_gas_used, :numeric, precision: 100, null: false) add(:gas_used, :numeric, precision: 100, null: false) - add(:index, :integer, null: false) add(:status, :integer, null: false) + add(:transaction_index, :integer, null: false) timestamps(null: false) # Foreign keys - add(:receipt_id, references(:receipts, on_delete: :delete_all), null: true) - add( :transaction_hash, references(:transactions, column: :hash, on_delete: :delete_all, type: :bytea), @@ -21,8 +19,8 @@ defmodule Explorer.Repo.Migrations.CreateReceipts do ) end - create(index(:receipts, :index)) create(index(:receipts, :status)) + create(index(:receipts, :transaction_index)) create(unique_index(:receipts, :transaction_hash)) end end diff --git a/apps/explorer/priv/repo/migrations/20180212222309_create_logs.exs b/apps/explorer/priv/repo/migrations/20180212222309_create_logs.exs index ae5e61d8fe..f05d1d477d 100644 --- a/apps/explorer/priv/repo/migrations/20180212222309_create_logs.exs +++ b/apps/explorer/priv/repo/migrations/20180212222309_create_logs.exs @@ -16,13 +16,18 @@ defmodule Explorer.Repo.Migrations.CreateLogs do # Foreign Keys add(:address_hash, references(:addresses, column: :hash, on_delete: :delete_all, type: :bytea), null: true) - add(:receipt_id, references(:receipts, on_delete: :delete_all), null: false) + + add( + :transaction_hash, + references(:receipts, column: :transaction_hash, on_delete: :delete_all, type: :bytea), + null: false + ) end # Foreign Key indexes create(index(:logs, :address_hash)) - create(index(:logs, :receipt_id)) + create(index(:logs, :transaction_hash)) # Search indexes @@ -32,6 +37,6 @@ defmodule Explorer.Repo.Migrations.CreateLogs do create(index(:logs, :second_topic)) create(index(:logs, :third_topic)) create(index(:logs, :fourth_topic)) - create(unique_index(:logs, [:receipt_id, :index])) + create(unique_index(:logs, [:transaction_hash, :index])) end end diff --git a/apps/explorer/priv/repo/migrations/20180221001948_create_internal_transactions.exs b/apps/explorer/priv/repo/migrations/20180221001948_create_internal_transactions.exs index 70cedbd48e..cfadea1dd5 100644 --- a/apps/explorer/priv/repo/migrations/20180221001948_create_internal_transactions.exs +++ b/apps/explorer/priv/repo/migrations/20180221001948_create_internal_transactions.exs @@ -3,19 +3,28 @@ defmodule Explorer.Repo.Migrations.CreateInternalTransactions do def change do create table(:internal_transactions) do - add(:call_type, :string, null: false) + add(:call_type, :string, null: true) + add(:created_contract_code, :text, null: true) + # null unless there is an error + add(:error, :string, null: true) add(:gas, :numeric, precision: 100, null: false) - add(:gas_used, :numeric, precision: 100, null: false) + # can be null when `error` is not `null` + add(:gas_used, :numeric, precision: 100, null: true) add(:index, :integer, null: false) + add(:init, :text) add(:input, :text) + # can be null when `error` is not `null` add(:output, :text) add(:trace_address, {:array, :integer}, null: false) + add(:type, :string, null: false) add(:value, :numeric, precision: 100, null: false) timestamps(null: false) # Foreign keys + # Nullability controlled by create_has_created constraint below + add(:created_contract_address_hash, references(:addresses, column: :hash, type: :bytea), null: true) add(:from_address_hash, references(:addresses, column: :hash, type: :bytea)) add(:to_address_hash, references(:addresses, column: :hash, type: :bytea)) @@ -26,10 +35,40 @@ defmodule Explorer.Repo.Migrations.CreateInternalTransactions do ) end + # Constraints + + create( + constraint( + :internal_transactions, + :create_has_error_or_result, + check: """ + type != 'create' OR + (error IS NULL AND created_contract_address_hash IS NOT NULL AND created_contract_code IS NOT NULL AND gas_used IS NOT NULL) OR + (error IS NOT NULL AND created_contract_address_hash IS NULL AND created_contract_code IS NULL AND gas_used IS NULL) + """ + ) + ) + + create( + constraint( + :internal_transactions, + :call_has_error_or_result, + check: """ + type != 'call' OR + (error IS NULL AND gas_used IS NOT NULL and output IS NOT NULL) OR + (error IS NOT NULL AND gas_used IS NULL and output is NULL) + """ + ) + ) + # Foreign Key indexes create(index(:internal_transactions, :from_address_hash)) create(index(:internal_transactions, :to_address_hash)) create(index(:internal_transactions, :transaction_hash)) + + # Unique indexes + + create(index(:internal_transactions, [:transaction_hash, :index])) end end diff --git a/apps/explorer/test/explorer/chain/credit_test.exs b/apps/explorer/test/explorer/chain/credit_test.exs index 5866f344d0..a1cb4dde6a 100644 --- a/apps/explorer/test/explorer/chain/credit_test.exs +++ b/apps/explorer/test/explorer/chain/credit_test.exs @@ -17,8 +17,18 @@ defmodule Explorer.Chain.CreditTest do test "returns a credit when there is an address with a receipt" do recipient = insert(:address) sender = insert(:address) - transaction = insert(:transaction, to_address_hash: recipient.hash, from_address_hash: sender.hash) - insert(:receipt, transaction: transaction, status: 1) + block = insert(:block) + + transaction = + insert( + :transaction, + block_hash: block.hash, + index: 0, + to_address_hash: recipient.hash, + from_address_hash: sender.hash + ) + + insert(:receipt, status: :ok, transaction_hash: transaction.hash, transaction_index: transaction.index) Credit.refresh() credits = Credit |> Repo.all() assert credits |> Enum.count() == 1 @@ -28,9 +38,19 @@ defmodule Explorer.Chain.CreditTest do recipient = insert(:address) sender = insert(:address) - transaction = insert(:transaction, value: 21, to_address_hash: recipient.hash, from_address_hash: sender.hash) + block = insert(:block) - insert(:receipt, transaction: transaction, status: 1) + transaction = + insert( + :transaction, + block_hash: block.hash, + index: 0, + value: 21, + to_address_hash: recipient.hash, + from_address_hash: sender.hash + ) + + insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index, status: :ok) address_hash = sender.hash Credit.refresh() credit = Credit |> where(address_hash: ^address_hash) |> Repo.one() @@ -41,9 +61,19 @@ defmodule Explorer.Chain.CreditTest do recipient = insert(:address) sender = insert(:address) - transaction = insert(:transaction, value: 21, to_address_hash: recipient.hash, from_address_hash: sender.hash) + block = insert(:block) + + transaction = + insert( + :transaction, + block_hash: block.hash, + index: 0, + value: 21, + to_address_hash: recipient.hash, + from_address_hash: sender.hash + ) - insert(:receipt, transaction: transaction, status: 1) + insert(:receipt, status: :ok, transaction_hash: transaction.hash, transaction_index: transaction.index) address_hash = recipient.hash Credit.refresh() credit = Credit |> where(address_hash: ^address_hash) |> Repo.one() diff --git a/apps/explorer/test/explorer/chain/debit_test.exs b/apps/explorer/test/explorer/chain/debit_test.exs index 3653cd27e3..5cfa47a591 100644 --- a/apps/explorer/test/explorer/chain/debit_test.exs +++ b/apps/explorer/test/explorer/chain/debit_test.exs @@ -17,8 +17,18 @@ defmodule Explorer.Chain.DebitTest do test "returns a debit when there is an address with a receipt" do recipient = insert(:address) sender = insert(:address) - transaction = insert(:transaction, to_address_hash: recipient.hash, from_address_hash: sender.hash) - insert(:receipt, transaction: transaction, status: 1) + block = insert(:block) + + transaction = + insert( + :transaction, + block_hash: block.hash, + index: 0, + to_address_hash: recipient.hash, + from_address_hash: sender.hash + ) + + insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index, status: :ok) Debit.refresh() debits = Debit |> Repo.all() assert debits |> Enum.count() == 1 @@ -28,9 +38,19 @@ defmodule Explorer.Chain.DebitTest do recipient = insert(:address) sender = insert(:address) - transaction = insert(:transaction, value: 21, to_address_hash: recipient.hash, from_address_hash: sender.hash) + block = insert(:block) + + transaction = + insert( + :transaction, + block_hash: block.hash, + index: 0, + value: 21, + to_address_hash: recipient.hash, + from_address_hash: sender.hash + ) - insert(:receipt, transaction: transaction, status: 1) + insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index, status: :ok) address_hash = sender.hash Debit.refresh() debit = Debit |> where(address_hash: ^address_hash) |> Repo.one() @@ -40,10 +60,19 @@ defmodule Explorer.Chain.DebitTest do test "returns no debits against the recipient" do recipient = insert(:address) sender = insert(:address) + block = insert(:block) - transaction = insert(:transaction, value: 21, to_address_hash: recipient.hash, from_address_hash: sender.hash) + transaction = + insert( + :transaction, + block_hash: block.hash, + index: 0, + value: 21, + to_address_hash: recipient.hash, + from_address_hash: sender.hash + ) - insert(:receipt, transaction: transaction, status: 1) + insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index, status: :ok) address_hash = recipient.hash Debit.refresh() debit = Debit |> where(address_hash: ^address_hash) |> Repo.one() diff --git a/apps/explorer/test/explorer/chain/internal_transaction/call_type_test.exs b/apps/explorer/test/explorer/chain/internal_transaction/call_type_test.exs new file mode 100644 index 0000000000..85a44a58c6 --- /dev/null +++ b/apps/explorer/test/explorer/chain/internal_transaction/call_type_test.exs @@ -0,0 +1,5 @@ +defmodule Explorer.Chain.InternalTransaction.CallTypeTest do + use ExUnit.Case, async: true + + doctest Explorer.Chain.InternalTransaction.CallType +end diff --git a/apps/explorer/test/explorer/chain/internal_transaction/type_test.exs b/apps/explorer/test/explorer/chain/internal_transaction/type_test.exs new file mode 100644 index 0000000000..8c3cf56f48 --- /dev/null +++ b/apps/explorer/test/explorer/chain/internal_transaction/type_test.exs @@ -0,0 +1,5 @@ +defmodule Explorer.Chain.InternalTransaction.TypeTest do + use ExUnit.Case, async: true + + doctest Explorer.Chain.InternalTransaction.Type +end diff --git a/apps/explorer/test/explorer/chain/receipt/status_test.exs b/apps/explorer/test/explorer/chain/receipt/status_test.exs new file mode 100644 index 0000000000..c68ef77324 --- /dev/null +++ b/apps/explorer/test/explorer/chain/receipt/status_test.exs @@ -0,0 +1,5 @@ +defmodule Explorer.Chain.Receipt.StatusTest do + use ExUnit.Case, async: true + + doctest Explorer.Chain.Receipt.Status +end diff --git a/apps/explorer/test/explorer/chain/receipt_test.exs b/apps/explorer/test/explorer/chain/receipt_test.exs index 86162b0f79..14317496dc 100644 --- a/apps/explorer/test/explorer/chain/receipt_test.exs +++ b/apps/explorer/test/explorer/chain/receipt_test.exs @@ -5,36 +5,22 @@ defmodule Explorer.Chain.ReceiptTest do describe "changeset/2" do test "accepts valid attributes" do - transaction = insert(:transaction) - params = params_for(:receipt, transaction: transaction) + block = insert(:block) + transaction = insert(:transaction, block_hash: block.hash, index: 0) + params = params_for(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) + changeset = Receipt.changeset(%Receipt{}, params) + assert changeset.valid? end test "rejects missing attributes" do transaction = insert(:transaction) params = params_for(:receipt, transaction: transaction, cumulative_gas_used: nil) - changeset = Receipt.changeset(%Receipt{}, params) - refute changeset.valid? - end - test "accepts logs" do - transaction = insert(:transaction) - address = insert(:address) - log_params = params_for(:log, address: address) - params = params_for(:receipt, transaction: transaction, logs: [log_params]) changeset = Receipt.changeset(%Receipt{}, params) - assert changeset.valid? - end - test "saves logs for the receipt" do - transaction = insert(:transaction) - address = insert(:address) - log_params = params_for(:log, address: address) - params = params_for(:receipt, transaction: transaction, logs: [log_params]) - changeset = Receipt.changeset(%Receipt{}, params) - receipt = Repo.insert!(changeset) |> Repo.preload(logs: :address) - assert List.first(receipt.logs).address == address + refute changeset.valid? end end end diff --git a/apps/explorer/test/explorer/chain/statistics_test.exs b/apps/explorer/test/explorer/chain/statistics_test.exs index ffd28b2100..2c6a046599 100644 --- a/apps/explorer/test/explorer/chain/statistics_test.exs +++ b/apps/explorer/test/explorer/chain/statistics_test.exs @@ -47,8 +47,8 @@ defmodule Explorer.Chain.StatisticsTest do last_week = Timex.shift(time, days: -8) block = insert(:block, timestamp: time) old_block = insert(:block, timestamp: last_week) - insert(:transaction, block_hash: block.hash) - insert(:transaction, block_hash: old_block.hash) + insert(:transaction, block_hash: block.hash, index: 0) + insert(:transaction, block_hash: old_block.hash, index: 0) assert %Statistics{transaction_count: 1} = Statistics.fetch() end @@ -98,7 +98,7 @@ defmodule Explorer.Chain.StatisticsTest do test "returns the last five transactions with blocks" do block = insert(:block) - insert_list(6, :transaction, block_hash: block.hash) + Enum.map(0..5, &insert(:transaction, block_hash: block.hash, index: &1)) statistics = Statistics.fetch() diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 2cf0a52ed2..9448fa634c 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -25,7 +25,7 @@ defmodule Explorer.ChainTest do test "with transactions" do block = insert(:block) - %Transaction{hash: transaction_hash} = insert(:transaction, block_hash: block.hash) + %Transaction{hash: transaction_hash} = insert(:transaction, block_hash: block.hash, index: 0) assert %Scrivener.Page{ entries: [%Transaction{hash: ^transaction_hash}], @@ -37,10 +37,16 @@ defmodule Explorer.ChainTest do test "with transaction with receipt required without receipt does not return transaction" do block = %Block{hash: block_hash} = insert(:block) - %Transaction{hash: transaction_hash_with_receipt} = insert(:transaction, block_hash: block_hash) - insert(:receipt, transaction_hash: transaction_hash_with_receipt) + %Transaction{hash: transaction_hash_with_receipt, index: transaction_index_with_receipt} = + insert(:transaction, block_hash: block_hash, index: 0) - %Transaction{hash: transaction_hash_without_receipt} = insert(:transaction, block_hash: block_hash) + insert( + :receipt, + transaction_hash: transaction_hash_with_receipt, + transaction_index: transaction_index_with_receipt + ) + + %Transaction{hash: transaction_hash_without_receipt} = insert(:transaction, block_hash: block_hash, index: 1) assert %Scrivener.Page{ entries: [%Transaction{hash: ^transaction_hash_with_receipt, receipt: %Receipt{}}], @@ -76,7 +82,7 @@ defmodule Explorer.ChainTest do test "with transactions can be paginated" do block = insert(:block) - transactions = insert_list(2, :transaction, block_hash: block.hash) + transactions = Enum.map(0..1, &insert(:transaction, block_hash: block.hash, index: &1)) [%Transaction{hash: first_transaction_hash}, %Transaction{hash: second_transaction_hash}] = transactions @@ -107,7 +113,7 @@ defmodule Explorer.ChainTest do test "with transactions" do block = insert(:block) - insert(:transaction, block_hash: block.hash) + insert(:transaction, block_hash: block.hash, index: 0) assert Chain.block_to_transaction_count(block) == 1 end @@ -177,9 +183,16 @@ defmodule Explorer.ChainTest do test "with transactions with receipt required without receipt does not return transaction" do address = %Address{hash: from_address_hash} = insert(:address) - %Transaction{hash: transaction_hash_with_receipt} = insert(:transaction, from_address_hash: from_address_hash) + block = insert(:block) + + %Transaction{hash: transaction_hash_with_receipt, index: transaction_index_with_receipt} = + insert(:transaction, block_hash: block.hash, index: 0, from_address_hash: from_address_hash) - insert(:receipt, transaction_hash: transaction_hash_with_receipt) + insert( + :receipt, + transaction_hash: transaction_hash_with_receipt, + transaction_index: transaction_index_with_receipt + ) %Transaction{hash: transaction_hash_without_receipt} = insert(:transaction, from_address_hash: from_address_hash) @@ -240,9 +253,12 @@ defmodule Explorer.ChainTest do describe "hash_to_transaction/2" do test "with transaction with receipt required without receipt returns {:error, :not_found}" do - %Transaction{hash: hash_with_receipt} = insert(:transaction) + block = insert(:block) - insert(:receipt, transaction_hash: hash_with_receipt) + %Transaction{hash: hash_with_receipt, index: index_with_receipt} = + insert(:transaction, block_hash: block.hash, index: 0) + + insert(:receipt, transaction_hash: hash_with_receipt, transaction_index: index_with_receipt) %Transaction{hash: hash_without_receipt} = insert(:transaction) @@ -363,9 +379,16 @@ defmodule Explorer.ChainTest do test "with transactions with receipt required without receipt does not return transaction" do address = %Address{hash: to_address_hash} = insert(:address) - %Transaction{hash: transaction_hash_with_receipt} = insert(:transaction, to_address_hash: to_address_hash) + block = insert(:block) + + %Transaction{hash: transaction_hash_with_receipt, index: transaction_index_with_receipt} = + insert(:transaction, block_hash: block.hash, index: 0, to_address_hash: to_address_hash) - insert(:receipt, transaction_hash: transaction_hash_with_receipt) + insert( + :receipt, + transaction_hash: transaction_hash_with_receipt, + transaction_index: transaction_index_with_receipt + ) %Transaction{hash: transaction_hash_without_receipt} = insert(:transaction, to_address_hash: to_address_hash) @@ -440,7 +463,7 @@ defmodule Explorer.ChainTest do test "with transaction with internal transactions returns all internal transactions for a given transaction hash" do transaction = insert(:transaction) - internal_transaction = insert(:internal_transaction, transaction_hash: transaction.hash) + internal_transaction = insert(:internal_transaction, transaction_hash: transaction.hash, index: 0) result = hd(Chain.transaction_hash_to_internal_transactions(transaction.hash)) @@ -449,7 +472,7 @@ defmodule Explorer.ChainTest do test "with transaction with internal transactions loads associations with in necessity_by_assocation" do %Transaction{hash: hash} = insert(:transaction) - insert(:internal_transaction, transaction_hash: hash) + insert(:internal_transaction, transaction_hash: hash, index: 0) assert [ %InternalTransaction{ @@ -462,7 +485,7 @@ defmodule Explorer.ChainTest do assert [ %InternalTransaction{ from_address: %Address{}, - to_address: %Address{}, + to_address: nil, transaction: %Transaction{} } ] = @@ -490,9 +513,10 @@ defmodule Explorer.ChainTest do end test "with logs" do - transaction = insert(:transaction) - %Receipt{id: receipt_id} = insert(:receipt, transaction_hash: transaction.hash) - %Log{id: id} = insert(:log, receipt_id: receipt_id) + block = insert(:block) + transaction = insert(:transaction, block_hash: block.hash, index: 0) + insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) + %Log{id: id} = insert(:log, transaction_hash: transaction.hash) assert %Scrivener.Page{ entries: [%Log{id: ^id}], @@ -503,9 +527,10 @@ defmodule Explorer.ChainTest do end test "with logs can be paginated" do - transaction = insert(:transaction) - %Receipt{id: receipt_id} = insert(:receipt, transaction_hash: transaction.hash) - logs = insert_list(2, :log, receipt_id: receipt_id) + block = insert(:block) + transaction = insert(:transaction, block_hash: block.hash, index: 0) + insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) + logs = insert_list(2, :log, transaction_hash: transaction.hash) [%Log{id: first_log_id}, %Log{id: second_log_id}] = logs @@ -527,9 +552,10 @@ defmodule Explorer.ChainTest do end test "with logs necessity_by_association loads associations" do - transaction = insert(:transaction) - %Receipt{id: receipt_id} = insert(:receipt, transaction_hash: transaction.hash) - insert(:log, receipt_id: receipt_id) + block = insert(:block) + transaction = insert(:transaction, block_hash: block.hash, index: 0) + insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) + insert(:log, transaction_hash: transaction.hash) assert %Scrivener.Page{ entries: [ diff --git a/apps/explorer/test/explorer/jsonrpc/block_test.exs b/apps/explorer/test/explorer/jsonrpc/block_test.exs new file mode 100644 index 0000000000..c36299629e --- /dev/null +++ b/apps/explorer/test/explorer/jsonrpc/block_test.exs @@ -0,0 +1,5 @@ +defmodule Explorer.JSONRPC.BlockTest do + use ExUnit.Case, async: true + + doctest Explorer.JSONRPC.Block +end diff --git a/apps/explorer/test/explorer/jsonrpc/blocks_test.exs b/apps/explorer/test/explorer/jsonrpc/blocks_test.exs new file mode 100644 index 0000000000..87a2ba6e48 --- /dev/null +++ b/apps/explorer/test/explorer/jsonrpc/blocks_test.exs @@ -0,0 +1,5 @@ +defmodule Explorer.JSONRPC.BlocksTest do + use ExUnit.Case, async: true + + doctest Explorer.JSONRPC.Blocks +end diff --git a/apps/explorer/test/explorer/jsonrpc/log_test.exs b/apps/explorer/test/explorer/jsonrpc/log_test.exs new file mode 100644 index 0000000000..f1d2a81704 --- /dev/null +++ b/apps/explorer/test/explorer/jsonrpc/log_test.exs @@ -0,0 +1,5 @@ +defmodule Explorer.JSONRPC.LogTest do + use ExUnit.Case, async: true + + doctest Explorer.JSONRPC.Log +end diff --git a/apps/explorer/test/explorer/jsonrpc/parity/trace/action_test.exs b/apps/explorer/test/explorer/jsonrpc/parity/trace/action_test.exs new file mode 100644 index 0000000000..1097bf0faa --- /dev/null +++ b/apps/explorer/test/explorer/jsonrpc/parity/trace/action_test.exs @@ -0,0 +1,5 @@ +defmodule Explorer.JSONRPC.Parity.Trace.ActionTest do + use ExUnit.Case, async: true + + doctest Explorer.JSONRPC.Parity.Trace.Action +end diff --git a/apps/explorer/test/explorer/jsonrpc/parity/trace/result_test.exs b/apps/explorer/test/explorer/jsonrpc/parity/trace/result_test.exs new file mode 100644 index 0000000000..9df848f26f --- /dev/null +++ b/apps/explorer/test/explorer/jsonrpc/parity/trace/result_test.exs @@ -0,0 +1,5 @@ +defmodule Explorer.JSONRPC.Parity.Trace.ResultTest do + use ExUnit.Case, async: true + + doctest Explorer.JSONRPC.Parity.Trace.Result +end diff --git a/apps/explorer/test/explorer/jsonrpc/parity/trace_test.exs b/apps/explorer/test/explorer/jsonrpc/parity/trace_test.exs new file mode 100644 index 0000000000..c85e520239 --- /dev/null +++ b/apps/explorer/test/explorer/jsonrpc/parity/trace_test.exs @@ -0,0 +1,5 @@ +defmodule Explorer.JSONRPC.Parity.TraceTest do + use ExUnit.Case, async: true + + doctest Explorer.JSONRPC.Parity.Trace +end diff --git a/apps/explorer/test/explorer/jsonrpc/receipt_test.exs b/apps/explorer/test/explorer/jsonrpc/receipt_test.exs new file mode 100644 index 0000000000..3316dc2c50 --- /dev/null +++ b/apps/explorer/test/explorer/jsonrpc/receipt_test.exs @@ -0,0 +1,5 @@ +defmodule Explorer.JSONRPC.ReceiptTest do + use ExUnit.Case, async: true + + doctest Explorer.JSONRPC.Receipt +end diff --git a/apps/explorer/test/explorer/jsonrpc/transaction_test.exs b/apps/explorer/test/explorer/jsonrpc/transaction_test.exs new file mode 100644 index 0000000000..bb69efbfdd --- /dev/null +++ b/apps/explorer/test/explorer/jsonrpc/transaction_test.exs @@ -0,0 +1,5 @@ +defmodule Explorer.JSONRPC.TransactionTest do + use ExUnit.Case, async: true + + doctest Explorer.JSONRPC.Transaction +end diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index b37f528a60..823b7300d3 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -26,7 +26,7 @@ defmodule Explorer.Factory do number: sequence("block_number", & &1), hash: block_hash(), parent_hash: block_hash(), - nonce: sequence("block_nonce"), + nonce: sequence("block_nonce", & &1), miner_hash: insert(:address).hash, difficulty: Enum.random(1..100_000), total_difficulty: Enum.random(1..100_000), @@ -47,18 +47,14 @@ defmodule Explorer.Factory do end def internal_transaction_factory do - %InternalTransaction{ - call_type: Enum.random(["call", "creates", "calldelegate"]), - from_address_hash: insert(:address).hash, - gas: Enum.random(1..100_000), - gas_used: Enum.random(1..100_000), - index: Enum.random(0..9), - input: sequence("0x"), - output: sequence("0x"), - to_address_hash: insert(:address).hash, - trace_address: [Enum.random(0..4), Enum.random(0..4)], - value: Enum.random(1..100_000) - } + type = internal_transaction_type() + + internal_transaction_factory(type) + end + + # TODO add call, reward, and suicide + def internal_transaction_type do + Enum.random(~w(create)a) end def log_factory do @@ -78,8 +74,7 @@ defmodule Explorer.Factory do %Receipt{ cumulative_gas_used: Enum.random(21_000..100_000), gas_used: Enum.random(21_000..100_000), - status: Enum.random(0..1), - index: sequence("") + status: Enum.random(0..1) } end @@ -120,14 +115,40 @@ defmodule Explorer.Factory do def validate(%Transaction{hash: hash} = transaction) do block = insert(:block) + index = 0 block_transaction = transaction - |> Explorer.Chain.Transaction.changeset(%{block_hash: block.hash, index: 0}) + |> Explorer.Chain.Transaction.changeset(%{block_hash: block.hash, index: index}) |> Repo.update!() - insert(:receipt, transaction_hash: hash) + insert(:receipt, transaction_hash: hash, transaction_index: index) Repo.preload(block_transaction, [:block, :receipt]) end + + ## Private Functions + + defp integer_to_hexadecimal(integer) when is_integer(integer) do + "0x" <> Integer.to_string(integer, 16) + end + + defp internal_transaction_factory(:create = type) do + gas = Enum.random(21_000..100_000) + gas_used = Enum.random(0..gas) + + %InternalTransaction{ + created_contract_code: sequence("internal_transaction_created_contract_code", &integer_to_hexadecimal/1), + created_contract_address_hash: insert(:address).hash, + from_address_hash: insert(:address).hash, + gas: gas, + gas_used: gas_used, + # caller MUST suppy `index` + init: sequence("internal_transaction_init", &integer_to_hexadecimal/1), + trace_address: [], + transaction_hash: insert(:transaction).hash, + type: type, + value: sequence("internal_transaction_value", &Decimal.new(&1)) + } + end end diff --git a/apps/explorer_web/test/explorer_web/controllers/address_transaction_from_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/address_transaction_from_controller_test.exs index 0b1f78ec60..759e752dda 100644 --- a/apps/explorer_web/test/explorer_web/controllers/address_transaction_from_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/address_transaction_from_controller_test.exs @@ -14,7 +14,7 @@ defmodule ExplorerWeb.AddressTransactionFromControllerTest do address = insert(:address) block = insert(:block) transaction = insert(:transaction, block_hash: block.hash, from_address_hash: address.hash, index: 0) - insert(:receipt, transaction: transaction) + insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) conn = get(conn, address_transaction_from_path(ExplorerWeb.Endpoint, :index, :en, address.hash)) @@ -32,7 +32,7 @@ defmodule ExplorerWeb.AddressTransactionFromControllerTest do test "does not return transactions to this address", %{conn: conn} do block = insert(:block) transaction = insert(:transaction, block_hash: block.hash, index: 0) - insert(:receipt, transaction: transaction) + insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) address = insert(:address) conn = get(conn, address_transaction_from_path(ExplorerWeb.Endpoint, :index, :en, address.hash)) @@ -55,7 +55,7 @@ defmodule ExplorerWeb.AddressTransactionFromControllerTest do test "does not return related transactions without a from address", %{conn: conn} do block = insert(:block) transaction = insert(:transaction, block_hash: block.hash, index: 0) - insert(:receipt, transaction: transaction) + insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) address = insert(:address) conn = get(conn, address_transaction_from_path(ExplorerWeb.Endpoint, :index, :en, address.hash)) @@ -67,7 +67,7 @@ defmodule ExplorerWeb.AddressTransactionFromControllerTest do test "does not return related transactions without a to address", %{conn: conn} do block = insert(:block) transaction = insert(:transaction, block_hash: block.hash, index: 0) - insert(:receipt, transaction: transaction) + insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) address = insert(:address) conn = get(conn, address_transaction_from_path(ExplorerWeb.Endpoint, :index, :en, address.hash)) diff --git a/apps/explorer_web/test/explorer_web/controllers/address_transaction_to_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/address_transaction_to_controller_test.exs index 770c91472b..8da9b56bdd 100644 --- a/apps/explorer_web/test/explorer_web/controllers/address_transaction_to_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/address_transaction_to_controller_test.exs @@ -12,8 +12,9 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do test "returns transactions to this address", %{conn: conn} do address = insert(:address) - transaction = insert(:transaction, block_hash: insert(:block).hash, index: 0, to_address_hash: address.hash) - insert(:receipt, transaction: transaction) + block = insert(:block) + transaction = insert(:transaction, block_hash: block.hash, index: 0, to_address_hash: address.hash) + insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address)) @@ -30,8 +31,9 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do end test "does not return transactions from this address", %{conn: conn} do - transaction = insert(:transaction) - insert(:receipt, transaction: transaction) + block = insert(:block) + transaction = insert(:transaction, block_hash: block.hash, index: 0) + insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) address = insert(:address) conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address)) @@ -59,8 +61,9 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do end test "does not return related transactions without a from address", %{conn: conn} do - transaction = insert(:transaction) - insert(:receipt, transaction: transaction) + block = insert(:block) + transaction = insert(:transaction, block_hash: block.hash, index: 0) + insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) address = insert(:address) conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address)) @@ -72,8 +75,8 @@ defmodule ExplorerWeb.AddressTransactionToControllerTest do test "does not return related transactions without a to address", %{conn: conn} do address = insert(:address) block = insert(:block) - transaction = insert(:transaction, block_hash: block.hash, from_address_hash: address.hash, index: 0) - insert(:receipt, transaction: transaction) + transaction = insert(:transaction, block_hash: block.hash, index: 0, from_address_hash: address.hash, index: 0) + insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) conn = get(conn, address_transaction_to_path(ExplorerWeb.Endpoint, :index, :en, address)) diff --git a/apps/explorer_web/test/explorer_web/controllers/block_transaction_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/block_transaction_controller_test.exs index 8753906451..567a01f2f1 100644 --- a/apps/explorer_web/test/explorer_web/controllers/block_transaction_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/block_transaction_controller_test.exs @@ -18,8 +18,8 @@ defmodule ExplorerWeb.BlockTransactionControllerTest do test "returns transactions for the block", %{conn: conn} do block = insert(:block) - transaction = insert(:transaction, block_hash: block.hash) - insert(:receipt, transaction: transaction) + transaction = insert(:transaction, block_hash: block.hash, index: 0) + insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block.number)) @@ -55,7 +55,7 @@ defmodule ExplorerWeb.BlockTransactionControllerTest do test "does not return related transactions without a to address", %{conn: conn} do block = insert(:block) transaction = insert(:transaction, block_hash: block.hash, index: 0, to_address_hash: nil) - insert(:receipt, transaction: transaction) + insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) conn = get(conn, block_transaction_path(ExplorerWeb.Endpoint, :index, :en, block)) diff --git a/apps/explorer_web/test/explorer_web/controllers/chain_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/chain_controller_test.exs index cb51907a46..ed90f61c23 100644 --- a/apps/explorer_web/test/explorer_web/controllers/chain_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/chain_controller_test.exs @@ -67,7 +67,8 @@ defmodule ExplorerWeb.ChainControllerTest do end test "finds a transaction by hash", %{conn: conn} do - transaction = insert(:transaction, block_hash: insert(:block).hash) + block = insert(:block) + transaction = insert(:transaction, block_hash: block.hash, index: 0) conn = get(conn, "/en/search?q=#{to_string(transaction.hash)}") assert redirected_to(conn) == transaction_path(conn, :show, "en", transaction) diff --git a/apps/explorer_web/test/explorer_web/controllers/pending_transaction_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/pending_transaction_controller_test.exs index e589d4a4a3..7a125e3948 100644 --- a/apps/explorer_web/test/explorer_web/controllers/pending_transaction_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/pending_transaction_controller_test.exs @@ -19,7 +19,7 @@ defmodule ExplorerWeb.PendingTransactionControllerTest do test "does not count transactions that have a receipt", %{conn: conn} do block = insert(:block) transaction = insert(:transaction, block_hash: block.hash, index: 0) - insert(:receipt, transaction: transaction) + insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) conn = get(conn, pending_transaction_path(ExplorerWeb.Endpoint, :index, :en)) diff --git a/apps/explorer_web/test/explorer_web/controllers/transaction_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/transaction_controller_test.exs index 23d15f0a14..05cfe86b5a 100644 --- a/apps/explorer_web/test/explorer_web/controllers/transaction_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/transaction_controller_test.exs @@ -17,8 +17,8 @@ defmodule ExplorerWeb.TransactionControllerTest do test "returns a count of transactions", %{conn: conn} do block = insert(:block) - transaction = insert(:transaction, block_hash: block.hash) - insert(:receipt, transaction: transaction) + transaction = insert(:transaction, block_hash: block.hash, index: 0) + insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) conn = get(conn, "/en/transactions") @@ -44,7 +44,7 @@ defmodule ExplorerWeb.TransactionControllerTest do test "paginates transactions using the last seen transaction", %{conn: conn} do block = insert(:block) transaction = insert(:transaction, block_hash: block.hash, index: 0) - insert(:receipt, transaction: transaction) + insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) conn = get( diff --git a/apps/explorer_web/test/explorer_web/controllers/transaction_log_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/transaction_log_controller_test.exs index 3176cce68b..e6d25c889e 100644 --- a/apps/explorer_web/test/explorer_web/controllers/transaction_log_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/transaction_log_controller_test.exs @@ -11,20 +11,21 @@ defmodule ExplorerWeb.TransactionLogControllerTest do end test "returns logs for the transaction", %{conn: conn} do - transaction = insert(:transaction) - receipt = insert(:receipt, transaction: transaction) + block = insert(:block) + transaction = insert(:transaction, block_hash: block.hash, index: 0) + receipt = insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) address = insert(:address) - insert(:log, receipt: receipt, address_hash: address.hash) + insert(:log, address_hash: address.hash, transaction_hash: transaction.hash) - conn = get(conn, transaction_log_path(ExplorerWeb.Endpoint, :index, :en, transaction)) + conn = get(conn, transaction_log_path(conn, :index, :en, transaction)) first_log = List.first(conn.assigns.logs.entries) - assert first_log.receipt_id == receipt.id + assert first_log.transaction_hash == receipt.transaction_hash end test "assigns no logs when there are none", %{conn: conn} do transaction = insert(:transaction) - path = transaction_log_path(ExplorerWeb.Endpoint, :index, :en, transaction) + path = transaction_log_path(conn, :index, :en, transaction) conn = get(conn, path) diff --git a/apps/explorer_web/test/explorer_web/features/contributor_browsing_test.exs b/apps/explorer_web/test/explorer_web/features/contributor_browsing_test.exs index c1dbe2bed2..adc0f608fa 100644 --- a/apps/explorer_web/test/explorer_web/features/contributor_browsing_test.exs +++ b/apps/explorer_web/test/explorer_web/features/contributor_browsing_test.exs @@ -64,7 +64,6 @@ defmodule ExplorerWeb.UserListTest do number: number, timestamp: Timex.now() |> Timex.shift(hours: -1), size: 9_999_999, - nonce: "once upon a nonce", gas_used: 1_010_101, gas_limit: 5_030_101 }) @@ -73,7 +72,7 @@ defmodule ExplorerWeb.UserListTest do insert(:transaction, block_hash: fifth_block.hash, index: 1) insert(:transaction, block_hash: fifth_block.hash, index: 2) - insert(:receipt, transaction: transaction) + insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) Credit.refresh() Debit.refresh() @@ -95,7 +94,7 @@ defmodule ExplorerWeb.UserListTest do |> assert_has(css(".block__item", text: "9,999,999")) |> assert_has(css(".block__item", text: "1 hour ago")) |> assert_has(css(".block__item", text: "5,030,101")) - |> assert_has(css(".block__item", text: "once upon a nonce")) + # |> assert_has(css(".block__item", text: to_string(fifth_block.nonce))) |> assert_has(css(".block__item", text: "1,010,101")) |> click(css(".block__link", text: "Transactions")) |> assert_has(css(".transactions__link--long-hash", text: to_string(transaction.hash))) @@ -131,8 +130,8 @@ defmodule ExplorerWeb.UserListTest do value: Explorer.Chain.Wei.from(Decimal.new(5656), :ether) ) - receipt = insert(:receipt, transaction: transaction, status: 1) - insert(:log, address_hash: lincoln.hash, receipt: receipt) + insert(:receipt, status: :error, transaction_hash: transaction.hash, transaction_index: transaction.index) + insert(:log, address_hash: lincoln.hash, transaction_hash: transaction.hash) # From Lincoln to Taft. transaction_from_lincoln = @@ -144,7 +143,7 @@ defmodule ExplorerWeb.UserListTest do to_address_hash: taft.hash ) - insert(:receipt, transaction: transaction_from_lincoln) + insert(:receipt, transaction_hash: transaction_from_lincoln.hash, transaction_index: transaction_from_lincoln.index) # internal = insert(:internal_transaction, transaction_hash: transaction.hash) diff --git a/apps/explorer_web/test/explorer_web/views/transaction_view_test.exs b/apps/explorer_web/test/explorer_web/views/transaction_view_test.exs index bab1e854a8..430c8e2cde 100644 --- a/apps/explorer_web/test/explorer_web/views/transaction_view_test.exs +++ b/apps/explorer_web/test/explorer_web/views/transaction_view_test.exs @@ -15,10 +15,11 @@ defmodule ExplorerWeb.TransactionViewTest do assert TransactionView.formatted_status(transaction) == "Pending" end - test "with receipt with status 0 with gas_used < gas" do + test "with receipt with status :error with gas_used < gas" do gas = 2 - %Transaction{hash: hash} = insert(:transaction, gas: gas) - insert(:receipt, gas_used: gas - 1, status: 0, transaction_hash: hash) + block = insert(:block) + %Transaction{hash: hash, index: index} = insert(:transaction, block_hash: block.hash, gas: gas, index: 0) + insert(:receipt, gas_used: gas - 1, status: :error, transaction_hash: hash, transaction_index: index) transaction = Transaction @@ -28,10 +29,11 @@ defmodule ExplorerWeb.TransactionViewTest do assert TransactionView.formatted_status(transaction) == "Failed" end - test "with receipt with status 0 with gas <= gas_used" do + test "with receipt with status :error with gas <= gas_used" do gas = 2 - %Transaction{hash: hash} = insert(:transaction, gas: gas) - insert(:receipt, gas_used: gas, status: 0, transaction_hash: hash) + block = insert(:block) + %Transaction{hash: hash, index: index} = insert(:transaction, block_hash: block.hash, gas: gas, index: 0) + insert(:receipt, gas_used: gas, status: 0, transaction_hash: hash, transaction_index: index) transaction = Transaction @@ -41,10 +43,11 @@ defmodule ExplorerWeb.TransactionViewTest do assert TransactionView.formatted_status(transaction) == "Out of Gas" end - test "with receipt with status 1" do + test "with receipt with status :ok" do gas = 2 - %Transaction{hash: hash} = insert(:transaction, gas: gas) - insert(:receipt, gas_used: gas - 1, status: 1, transaction_hash: hash) + block = insert(:block) + %Transaction{hash: hash, index: index} = insert(:transaction, block_hash: block.hash, gas: gas, index: 0) + insert(:receipt, gas_used: gas - 1, status: :ok, transaction_hash: hash, transaction_index: index) transaction = Transaction From 4d82d2df65b37bfb3f1dee7ce47b5b030673ca9c Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 1 May 2018 09:45:11 -0500 Subject: [PATCH 04/77] Fix tests for Sokol full changes --- .../lib/explorer/chain/internal_transaction.ex | 3 +++ .../explorer/chain/internal_transaction_test.exs | 4 +++- apps/explorer/test/explorer/chain/log_test.exs | 5 +++-- apps/explorer/test/explorer/chain_test.exs | 2 +- apps/explorer/test/support/factory.ex | 10 ++++++++-- .../controllers/transaction_controller.ex | 2 +- .../templates/transaction/show.html.eex | 14 ++++++++------ .../controllers/transaction_controller_test.exs | 8 +++++--- 8 files changed, 32 insertions(+), 16 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/internal_transaction.ex b/apps/explorer/lib/explorer/chain/internal_transaction.ex index 1f4906d758..61b2525f12 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction.ex @@ -89,6 +89,7 @@ defmodule Explorer.Chain.InternalTransaction do def changeset(%__MODULE__{} = internal_transaction, attrs \\ %{}) do internal_transaction |> cast(attrs, ~w(type)a) + |> validate_required(~w(type)a) |> type_changeset(attrs) end @@ -139,6 +140,8 @@ defmodule Explorer.Chain.InternalTransaction do |> unique_constraint(:index) end + defp type_changeset(changeset, _, nil), do: changeset + defp validate_disallowed(changeset, field, named_arguments) when is_atom(field) do case get_field(changeset, field) do nil -> changeset diff --git a/apps/explorer/test/explorer/chain/internal_transaction_test.exs b/apps/explorer/test/explorer/chain/internal_transaction_test.exs index aff00cbc63..d2e5a177f3 100644 --- a/apps/explorer/test/explorer/chain/internal_transaction_test.exs +++ b/apps/explorer/test/explorer/chain/internal_transaction_test.exs @@ -19,6 +19,7 @@ defmodule Explorer.Chain.InternalTransactionTest do to_address_hash: "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f", trace_address: [0, 1], transaction_hash: transaction.hash, + type: "call", value: 100 }) @@ -43,7 +44,8 @@ defmodule Explorer.Chain.InternalTransactionTest do output: "munchos", trace_address: [0, 1], transaction: transaction, - value: 100 + type: "call", + value: 100, }) assert Repo.insert(changeset) diff --git a/apps/explorer/test/explorer/chain/log_test.exs b/apps/explorer/test/explorer/chain/log_test.exs index 66b16833ab..22f256e571 100644 --- a/apps/explorer/test/explorer/chain/log_test.exs +++ b/apps/explorer/test/explorer/chain/log_test.exs @@ -1,6 +1,7 @@ defmodule Explorer.Chain.LogTest do use Explorer.DataCase + alias Ecto.Changeset alias Explorer.Chain.Log describe "changeset/2" do @@ -18,8 +19,8 @@ defmodule Explorer.Chain.LogTest do test "accepts optional attributes" do params = Map.put(params_for(:log), :first_topic, "ham") - changeset = Log.changeset(%Log{}, params) - assert changeset.valid? + + assert %Changeset{valid?: true} = Log.changeset(%Log{}, params) end test "assigns optional attributes" do diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 9448fa634c..dd3d24e238 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -530,7 +530,7 @@ defmodule Explorer.ChainTest do block = insert(:block) transaction = insert(:transaction, block_hash: block.hash, index: 0) insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) - logs = insert_list(2, :log, transaction_hash: transaction.hash) + logs = Enum.map(0..1, &insert(:log, index: &1, transaction_hash: transaction.hash)) [%Log{id: first_log_id}, %Log{id: second_log_id}] = logs diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index 823b7300d3..b10db6afd4 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -63,9 +63,10 @@ defmodule Explorer.Factory do data: sequence("0x"), first_topic: nil, fourth_topic: nil, - index: sequence(""), + index: 0, second_topic: nil, third_topic: nil, + transaction_hash: insert(:transaction).hash, type: sequence("0x") } end @@ -137,16 +138,21 @@ defmodule Explorer.Factory do gas = Enum.random(21_000..100_000) gas_used = Enum.random(0..gas) + block = insert(:block) + transaction = insert(:transaction, block_hash: block.hash, index: 0) + receipt = insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) + %InternalTransaction{ created_contract_code: sequence("internal_transaction_created_contract_code", &integer_to_hexadecimal/1), created_contract_address_hash: insert(:address).hash, from_address_hash: insert(:address).hash, gas: gas, gas_used: gas_used, + index: 0, # caller MUST suppy `index` init: sequence("internal_transaction_init", &integer_to_hexadecimal/1), trace_address: [], - transaction_hash: insert(:transaction).hash, + transaction_hash: receipt.transaction_hash, type: type, value: sequence("internal_transaction_value", &Decimal.new(&1)) } diff --git a/apps/explorer_web/lib/explorer_web/controllers/transaction_controller.ex b/apps/explorer_web/lib/explorer_web/controllers/transaction_controller.ex index dded10efd9..3d875dadd2 100644 --- a/apps/explorer_web/lib/explorer_web/controllers/transaction_controller.ex +++ b/apps/explorer_web/lib/explorer_web/controllers/transaction_controller.ex @@ -27,7 +27,7 @@ defmodule ExplorerWeb.TransactionController do internal_transactions = Chain.transaction_hash_to_internal_transactions( transaction.hash, - necessity_by_association: %{from_address: :required, to_address: :required} + necessity_by_association: %{from_address: :required, to_address: :optional} ) max_block_number = Chain.max_block_number() diff --git a/apps/explorer_web/lib/explorer_web/templates/transaction/show.html.eex b/apps/explorer_web/lib/explorer_web/templates/transaction/show.html.eex index 836aa93bef..89d3d0e257 100644 --- a/apps/explorer_web/lib/explorer_web/templates/transaction/show.html.eex +++ b/apps/explorer_web/lib/explorer_web/templates/transaction/show.html.eex @@ -32,16 +32,18 @@ <%= transaction.call_type %> - - <%= link(transaction.to_address.hash, - to: address_path(@conn, :show, @conn.assigns.locale, transaction.to_address.hash), - class: "transaction-log__link") %> - <%= link(transaction.from_address.hash, - to: address_path(@conn, :show, @conn.assigns.locale, transaction.from_address.hash), + to: address_path(@conn, :show, @conn.assigns.locale, transaction.from_address), class: "transaction-log__link") %> + + <%= if transaction.to_address do %> + <%= link(transaction.to_address.hash, + to: address_path(@conn, :show, @conn.assigns.locale, transaction.to_address), + class: "transaction-log__link") %> + <% end %> + <%= value(transaction) %> <%= gas(transaction) %> diff --git a/apps/explorer_web/test/explorer_web/controllers/transaction_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/transaction_controller_test.exs index 05cfe86b5a..c6c7a6dd24 100644 --- a/apps/explorer_web/test/explorer_web/controllers/transaction_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/transaction_controller_test.exs @@ -106,14 +106,16 @@ defmodule ExplorerWeb.TransactionControllerTest do end test "returns internal transactions for the transaction", %{conn: conn} do - transaction = insert(:transaction) - internal_transaction = insert(:internal_transaction, transaction_hash: transaction.hash) + block = insert(:block) + transaction = insert(:transaction, block_hash: block.hash, index: 0) + receipt = insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) + internal_transaction = insert(:internal_transaction, transaction_hash: receipt.transaction_hash, index: 0) path = transaction_path(ExplorerWeb.Endpoint, :show, :en, transaction) conn = get(conn, path) - first_internal_transaction = List.first(conn.assigns.internal_transactions) + assert [first_internal_transaction] = conn.assigns.internal_transactions assert conn.assigns.transaction.hash == transaction.hash assert first_internal_transaction.id == internal_transaction.id From 60fe1b138f743ba01b5d30228b05329d0f11daac Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 1 May 2018 09:45:41 -0500 Subject: [PATCH 05/77] Fix credo for Sokol full changes --- apps/explorer/lib/explorer/chain.ex | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 9ca09707fb..ca7e3c58a9 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -1075,10 +1075,6 @@ defmodule Explorer.Chain do timestamped_changes_list = timestamp_changes_list(changes_list, Keyword.fetch!(options, :timestamps)) {_, inserted} = Repo.safe_insert_all(ecto_schema_module, timestamped_changes_list, Keyword.delete(options, :for)) {:ok, inserted} - rescue - e in Postgrex.Error -> - IO.inspect(changes_list, label: "CHANGES_LIST") - raise e end @spec insert_transactions([map()], [timestamps_option]) :: {:ok, Transaction.t()} | {:error, [Changeset.t()]} From 1c3febc2325e994ab9bea1bcc67015ec51b94f42 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 1 May 2018 09:46:05 -0500 Subject: [PATCH 06/77] mix format for Sokol full changes --- apps/explorer/test/explorer/chain/internal_transaction_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/explorer/test/explorer/chain/internal_transaction_test.exs b/apps/explorer/test/explorer/chain/internal_transaction_test.exs index d2e5a177f3..0e3ca77253 100644 --- a/apps/explorer/test/explorer/chain/internal_transaction_test.exs +++ b/apps/explorer/test/explorer/chain/internal_transaction_test.exs @@ -45,7 +45,7 @@ defmodule Explorer.Chain.InternalTransactionTest do trace_address: [0, 1], transaction: transaction, type: "call", - value: 100, + value: 100 }) assert Repo.insert(changeset) From 2c7a446ab1805fd294537d00240040a38d20680f Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 1 May 2018 15:19:59 -0500 Subject: [PATCH 07/77] Fix unknown types for dialyzer --- apps/explorer/lib/explorer/chain/address.ex | 4 +- apps/explorer/lib/explorer/chain/block.ex | 2 +- apps/explorer/lib/explorer/chain/credit.ex | 19 +++++++++ apps/explorer/lib/explorer/chain/debit.ex | 19 +++++++++ apps/explorer/lib/explorer/chain/log.ex | 40 ++++++++++++++++++- apps/explorer/lib/explorer/chain/receipt.ex | 28 ++++++++++++- .../explorer/lib/explorer/chain/statistics.ex | 2 +- .../lib/explorer/chain/transaction.ex | 2 +- apps/explorer/lib/explorer/jsonrpc/log.ex | 6 ++- .../lib/explorer/jsonrpc/transactions.ex | 7 ++++ apps/explorer_web/lib/explorer_web/chain.ex | 4 +- 11 files changed, 123 insertions(+), 10 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/address.ex b/apps/explorer/lib/explorer/chain/address.ex index 0379e0240a..b67064239e 100644 --- a/apps/explorer/lib/explorer/chain/address.ex +++ b/apps/explorer/lib/explorer/chain/address.ex @@ -31,8 +31,8 @@ defmodule Explorer.Chain.Address do @type t :: %__MODULE__{ balance: Decimal.t(), balance_updated_at: DateTime.t(), - credit: Ecto.Association.NotLoaded.t() | Credit.t() | nil, - debit: Ecto.Association.NotLoaded.t() | Debit.t() | nil, + credit: %Ecto.Association.NotLoaded{} | Credit.t() | nil, + debit: %Ecto.Association.NotLoaded{} | Debit.t() | nil, hash: Hash.Truncated.t(), inserted_at: DateTime.t(), updated_at: DateTime.t() diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index ace23d40eb..02d2dc22fe 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -36,7 +36,7 @@ defmodule Explorer.Chain.Block do still valid and the fee can still be collected by the miner. * `gas_used` - The actual `t:gas/0` used to mine/validate the transactions in the block. * `hash` - the hash of the block. - * `miner` - the hash of the `t:Explorer.Address.t/0` of the miner. In Proof-of-Authority chains, this is the + * `miner` - the hash of the `t:Explorer.Chain.Address.t/0` of the miner. In Proof-of-Authority chains, this is the validator. * `nonce` - the hash of the generated proof-of-work. Not used in Proof-of-Authority chains. * `number` - which block this is along the chain. diff --git a/apps/explorer/lib/explorer/chain/credit.ex b/apps/explorer/lib/explorer/chain/credit.ex index a620d992d4..1f12f87af5 100644 --- a/apps/explorer/lib/explorer/chain/credit.ex +++ b/apps/explorer/lib/explorer/chain/credit.ex @@ -9,6 +9,23 @@ defmodule Explorer.Chain.Credit do alias Explorer.Chain.{Address, Hash} alias Explorer.Repo + # Types + + @typedoc """ + * `address` - address that was the `to_address` + * `address_hash` - foreign key for `address` + * `count` - the number of credits to `address` + * `value` - sum of all credit values. + """ + @type t :: %__MODULE__{ + address: %Ecto.Association.NotLoaded{} | Address.t(), + address_hash: Hash.Truncated.t(), + count: non_neg_integer, + value: Decimal.t() + } + + # Schema + @primary_key false schema "credits" do field(:count, :integer) @@ -19,6 +36,8 @@ defmodule Explorer.Chain.Credit do belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Truncated) end + # Functions + def refresh do SQL.query!(Repo, "REFRESH MATERIALIZED VIEW CONCURRENTLY credits;", [], timeout: 120_000) end diff --git a/apps/explorer/lib/explorer/chain/debit.ex b/apps/explorer/lib/explorer/chain/debit.ex index 6d43bcb33b..d14d719d0e 100644 --- a/apps/explorer/lib/explorer/chain/debit.ex +++ b/apps/explorer/lib/explorer/chain/debit.ex @@ -9,6 +9,23 @@ defmodule Explorer.Chain.Debit do alias Explorer.Chain.{Address, Hash} alias Explorer.Repo + # Types + + @typedoc """ + * `address` - address that was the `from_address` + * `address_hash` - foreign key for `address` + * `count` - the number of debits to `address` + * `value` - sum of all debit values. + """ + @type t :: %__MODULE__{ + address: %Ecto.Association.NotLoaded{} | Address.t(), + address_hash: Hash.Truncated.t(), + count: non_neg_integer, + value: Decimal.t() + } + + # Schema + @primary_key false schema "debits" do field(:count, :integer) @@ -19,6 +36,8 @@ defmodule Explorer.Chain.Debit do belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Truncated) end + # Functions + def refresh do SQL.query!(Repo, "REFRESH MATERIALIZED VIEW CONCURRENTLY debits;", [], timeout: 120_000) end diff --git a/apps/explorer/lib/explorer/chain/log.ex b/apps/explorer/lib/explorer/chain/log.ex index 0cd8b5c701..a3f66db789 100644 --- a/apps/explorer/lib/explorer/chain/log.ex +++ b/apps/explorer/lib/explorer/chain/log.ex @@ -3,11 +3,47 @@ defmodule Explorer.Chain.Log do use Explorer.Schema - alias Explorer.Chain.{Address, Hash, Receipt} + alias Explorer.Chain.{Address, Hash, Receipt, Transaction} + + # Constants @required_attrs ~w(address_hash data index transaction_hash type)a @optional_attrs ~w(first_topic second_topic third_topic fourth_topic)a + # Types + + @typedoc """ + * `address` - address of contract that generate the event + * `address_hash` - foreign key for `address` + * `data` - non-indexed log parameters. + * `first_topic` - `topics[0]` + * `fourth_topic` - `topics[3]` + * `index` - index of the log entry in all logs for the `receipt` / `transaction` + * `receipt` - receipt for the `transaction` being mined in a block + * `second_topic` - `topics[1]` + * `transaction` - transaction for which `receipt` is + * `transaction_hash` - foreign key for `receipt`. **ALWAYS join throught `receipts` and not directly to `transaction` + to ensure that any `t:Explorer.Chain.Transaction.t/0` has a receipt before it has logs in that receipt.** + * `third_topic` - `topics[2]` + * `type` - type of event + """ + @type t :: %__MODULE__{ + address: %Ecto.Association.NotLoaded{} | Address.t(), + address_hash: Hash.Truncated.t(), + data: String.t(), + first_topic: String.t(), + fourth_topic: String.t(), + index: non_neg_integer(), + receipt: %Ecto.Association.NotLoaded{} | Receipt.t(), + second_topic: String.t(), + transaction: %Ecto.Association.NotLoaded{} | Transaction.t(), + transaction_hash: Hash.Full.t(), + third_topic: String.t(), + type: String.t() + } + + # Schema + schema "logs" do field(:data, :string) field(:first_topic, :string) @@ -24,6 +60,8 @@ defmodule Explorer.Chain.Log do has_one(:transaction, through: [:receipt, :transaction]) end + # Functions + def changeset(%__MODULE__{} = log, attrs \\ %{}) do log |> cast(attrs, @required_attrs) diff --git a/apps/explorer/lib/explorer/chain/receipt.ex b/apps/explorer/lib/explorer/chain/receipt.ex index c470d7b809..31a4448404 100644 --- a/apps/explorer/lib/explorer/chain/receipt.ex +++ b/apps/explorer/lib/explorer/chain/receipt.ex @@ -3,13 +3,39 @@ defmodule Explorer.Chain.Receipt do use Explorer.Schema - alias Explorer.Chain.{Hash, Log, Transaction} + alias Explorer.Chain.{Gas, Hash, Log, Transaction} alias Explorer.Chain.Receipt.Status + # Constants + @optional_attrs ~w()a @required_attrs ~w(cumulative_gas_used gas_used status transaction_hash transaction_index)a @allowed_attrs @optional_attrs ++ @required_attrs + # Types + + @typedoc """ + * `cumulative_gas_used` - the cumulative gas used in `transaction`'s `t:Explorer.Chain.Block.t/0` before + `transaction`'s `index` + * `gas_used` - the gas used for just `transaction` + * `logs` - events that occurred while mining the `transaction` + * `status` - whether the transaction was successfully mined or failed + * `transaction` - the transaction for which this receipt is for + * `transaction_hash` - foreign key for `transaction` + * `transaction_index` - index of `transaction` in its `t:Explorer.Chain.Block.t/0`. + """ + @type t :: %__MODULE__{ + cumulative_gas_used: Gas.t(), + gas_used: Gas.t(), + logs: %Ecto.Association.NotLoaded{} | [Log.t()], + status: Status.t(), + transaction: %Ecto.Association.NotLoaded{} | Transaction.t(), + transaction_hash: Hash.Full.t(), + transaction_index: non_neg_integer() + } + + # Schema + @primary_key false schema "receipts" do field(:cumulative_gas_used, :decimal) diff --git a/apps/explorer/lib/explorer/chain/statistics.ex b/apps/explorer/lib/explorer/chain/statistics.ex index b757b6d754..38e632ebdb 100644 --- a/apps/explorer/lib/explorer/chain/statistics.ex +++ b/apps/explorer/lib/explorer/chain/statistics.ex @@ -89,7 +89,7 @@ defmodule Explorer.Chain.Statistics do block_velocity: blocks_per_minute(), blocks: [Block.t()], lag: Duration.t(), - number: Block.number(), + number: Block.block_number(), skipped_blocks: non_neg_integer(), timestamp: :calendar.datetime(), transaction_count: non_neg_integer(), diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index d68390375a..5e4562985a 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -4,7 +4,7 @@ defmodule Explorer.Chain.Transaction do use Explorer.Schema alias Ecto.Changeset - alias Explorer.Chain.{Address, Block, Hash, InternalTransaction, Receipt, Wei} + alias Explorer.Chain.{Address, Block, Gas, Hash, InternalTransaction, Receipt, Wei} # Constants diff --git a/apps/explorer/lib/explorer/jsonrpc/log.ex b/apps/explorer/lib/explorer/jsonrpc/log.ex index 2ee3ddae65..d302996bb6 100644 --- a/apps/explorer/lib/explorer/jsonrpc/log.ex +++ b/apps/explorer/lib/explorer/jsonrpc/log.ex @@ -8,17 +8,19 @@ defmodule Explorer.JSONRPC.Log do # Types + @type elixir :: %{String.t() => String.t() | [String.t()] | non_neg_integer()} + @typedoc """ * `"address"` - `t:Explorer.JSONRPC.address/0` from which event originated. * `"blockHash"` - `t:Explorer.JSONRPC.hash/0` of the block this transaction is in. * `"blockNumber"` - `t:Explorer.JSONRPC.quantity/0` for the block number this transaction is in. * `"data"` - Data containing non-indexed log parameter - * `"logIndex"` - `t:non_neg_integer/0` of the event index positon in the block. + * `"logIndex"` - `t:Explorer.JSONRPC.quantity/0` of the event index positon in the block. * `"topics" - `t:list/0` of at most 4 32-byte topics. Topic 1-3 contains indexed parameters of the log. * `"transactionHash"` - `t:Explorer.JSONRPC.hash/0` of the transaction * `"transactionIndex"` - `t:Explorer.JSONRPC.quantity/0` for the index of the transaction in the block. """ - @type t :: %{String.t() => term} + @type t :: %{String.t() => String.t() | [String.t()]} # Functions diff --git a/apps/explorer/lib/explorer/jsonrpc/transactions.ex b/apps/explorer/lib/explorer/jsonrpc/transactions.ex index 5802ef4309..0155157f21 100644 --- a/apps/explorer/lib/explorer/jsonrpc/transactions.ex +++ b/apps/explorer/lib/explorer/jsonrpc/transactions.ex @@ -7,6 +7,13 @@ defmodule Explorer.JSONRPC.Transactions do alias Explorer.JSONRPC.Transaction + # Types + + @type elixir :: [Transaction.elixir()] + @type t :: [Transaction.t()] + + # Functions + def elixir_to_params(elixir) when is_list(elixir) do Enum.map(elixir, &Transaction.elixir_to_params/1) end diff --git a/apps/explorer_web/lib/explorer_web/chain.ex b/apps/explorer_web/lib/explorer_web/chain.ex index b89bc3cde9..a58d145a03 100644 --- a/apps/explorer_web/lib/explorer_web/chain.ex +++ b/apps/explorer_web/lib/explorer_web/chain.ex @@ -12,9 +12,11 @@ defmodule ExplorerWeb.Chain do string_to_transaction_hash: 1 ] + alias Explorer.Chain.{Address, Block, Transaction} + # Functions - @spec from_param(String.t()) :: {:ok, Address.t() | Transaction.t() | Block.t()} | {:error, :not_found} + @spec from_param(String.t()) :: {:ok, Address.t() | Block.t() | Transaction.t()} | {:error, :not_found} def from_param(param) def from_param("0x" <> number_string = param) do From 35697eef3447b475fdbb0804f1d3a42ba445ea95 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 1 May 2018 16:09:04 -0500 Subject: [PATCH 08/77] Fix dialyzer warnings --- .dialyzer-ignore | 2 ++ apps/explorer/lib/explorer/chain.ex | 3 --- apps/explorer/lib/explorer/chain/hash/truncated.ex | 2 +- apps/explorer/lib/explorer/indexer/block_fetcher.ex | 7 +++++++ apps/explorer/lib/explorer/jsonrpc/receipt.ex | 11 +++++++++-- apps/explorer/lib/explorer/jsonrpc/transaction.ex | 4 +++- 6 files changed, 22 insertions(+), 7 deletions(-) diff --git a/.dialyzer-ignore b/.dialyzer-ignore index 1993ebca1d..3070fb9038 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -1,3 +1,5 @@ :0: Unknown function 'Elixir.ExUnit.Callbacks':'__merge__'/3 :0: Unknown function 'Elixir.ExUnit.CaseTemplate':'__proxy__'/2 :0: Unknown type 'Elixir.Map':t/0 +apps/explorer/lib/explorer/indexer/block_fetcher.ex:239: The created fun has no local return +apps/explorer/lib/explorer/indexer/block_fetcher.ex:246: The created fun has no local return diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 1224f51275..f26efb7988 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -234,9 +234,6 @@ defmodule Explorer.Chain do @doc """ The `t:Explorer.Chain.Transaction.t/0` `gas_price` of the `transaction` in `unit`. """ - @spec gas_price(Transaction.t(), :wei) :: Wei.t() - @spec gas_price(Transaction.t(), :gwei) :: Wei.gwei() - @spec gas_price(Transaction.t(), :ether) :: Wei.ether() def gas_price(%Transaction{gas_price: gas_price}, unit) do Wei.to(gas_price, unit) end diff --git a/apps/explorer/lib/explorer/chain/hash/truncated.ex b/apps/explorer/lib/explorer/chain/hash/truncated.ex index 877d8010f8..1d2b3554f8 100644 --- a/apps/explorer/lib/explorer/chain/hash/truncated.ex +++ b/apps/explorer/lib/explorer/chain/hash/truncated.ex @@ -81,7 +81,7 @@ defmodule Explorer.Chain.Hash.Truncated do """ @impl Ecto.Type - @spec cast(term()) :: {:ok, t()} :: :error + @spec cast(term()) :: {:ok, t()} | :error def cast(term) do Hash.cast(__MODULE__, term) end diff --git a/apps/explorer/lib/explorer/indexer/block_fetcher.ex b/apps/explorer/lib/explorer/indexer/block_fetcher.ex index f8f35fe099..6e580e0e4f 100644 --- a/apps/explorer/lib/explorer/indexer/block_fetcher.ex +++ b/apps/explorer/lib/explorer/indexer/block_fetcher.ex @@ -240,6 +240,13 @@ defmodule Explorer.Indexer.BlockFetcher do "failed to fetch blocks #{inspect(range)}: #{inspect(reason)}. Retrying" end) + :ok = Sequence.inject_range(seq, range) + + {:error, reason, range} -> + Logger.debug(fn -> + "failed to fetch blocks #{inspect(range)}: #{inspect(reason)}. Retrying" + end) + :ok = Sequence.inject_range(seq, range) end end, diff --git a/apps/explorer/lib/explorer/jsonrpc/receipt.ex b/apps/explorer/lib/explorer/jsonrpc/receipt.ex index c7be978147..5e80973131 100644 --- a/apps/explorer/lib/explorer/jsonrpc/receipt.ex +++ b/apps/explorer/lib/explorer/jsonrpc/receipt.ex @@ -6,12 +6,13 @@ defmodule Explorer.JSONRPC.Receipt do import Explorer.JSONRPC, only: [quantity_to_integer: 1] + alias Explorer.Chain.Receipt.Status alias Explorer.JSONRPC alias Explorer.JSONRPC.Logs # Types - @type elixir :: %{String.t() => nil} + @type elixir :: %{String.t() => String.t() | non_neg_integer} @typedoc """ * `"contractAddress"` - The contract `t:Explorer.JSONRPC.address/0` created, if the transaction was a contract @@ -65,7 +66,13 @@ defmodule Explorer.JSONRPC.Receipt do } """ - @spec elixir_to_params(elixir) :: [map] + @spec elixir_to_params(elixir) :: %{ + cumulative_gas_used: non_neg_integer, + gas_used: non_neg_integer, + status: Status.t(), + transaction_hash: String.t(), + transaction_index: non_neg_integer() + } def elixir_to_params(%{ "cumulativeGasUsed" => cumulative_gas_used, "gasUsed" => gas_used, diff --git a/apps/explorer/lib/explorer/jsonrpc/transaction.ex b/apps/explorer/lib/explorer/jsonrpc/transaction.ex index 25ed6de9a8..1f8f6945b1 100644 --- a/apps/explorer/lib/explorer/jsonrpc/transaction.ex +++ b/apps/explorer/lib/explorer/jsonrpc/transaction.ex @@ -44,16 +44,18 @@ defmodule Explorer.JSONRPC.Transaction do @type params :: %{ block_hash: JSONRPC.hash(), + from_address_hash: JSONRPC.address(), gas: non_neg_integer(), gas_price: non_neg_integer(), hash: JSONRPC.hash(), index: non_neg_integer(), input: String.t(), nonce: non_neg_integer(), - publicKey: String.t(), + public_key: String.t(), r: non_neg_integer(), s: non_neg_integer(), standard_v: 0 | 1, + to_address_hash: JSONRPC.address(), v: non_neg_integer(), value: non_neg_integer() } From 7ed078b10a3ec7b1f9ff097bfff3a63b2b596c5f Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 1 May 2018 16:09:22 -0500 Subject: [PATCH 09/77] Enable --halt-exit-status in dialyzer on CI --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7db91c4499..2e3dd51c19 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -229,7 +229,7 @@ jobs: paths: - plts - - run: mix dialyzer + - run: mix dialyzer --halt-exit-status eslint: docker: # Ensure .tool-versions matches From e97827a9262548fe6612b999de2c9ee5c35a35dc Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 2 May 2018 08:24:54 -0500 Subject: [PATCH 10/77] Cover Explorer.Indexer --- apps/explorer/config/config.exs | 2 - apps/explorer/lib/explorer/chain.ex | 24 ++++++-- apps/explorer/lib/explorer/indexer.ex | 59 +++++++++++++++++-- apps/explorer/test/explorer/chain_test.exs | 17 +----- apps/explorer/test/explorer/indexer_test.exs | 9 +++ .../controllers/transaction_controller.ex | 11 +++- .../controllers/transaction_log_controller.ex | 17 +++++- 7 files changed, 103 insertions(+), 36 deletions(-) create mode 100644 apps/explorer/test/explorer/indexer_test.exs diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index d61f2d1674..e4a4b52a52 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -19,8 +19,6 @@ config :explorer, Explorer.JSONRPC, trace_url: "https://sokol-trace.poa.network", url: url -config :explorer, :ethereum, backend: Explorer.Ethereum.Live - config :explorer, Explorer.Integrations.EctoLogger, query_time_ms_threshold: 2_000 config :explorer, Explorer.Repo, migration_timestamps: [type: :utc_datetime] diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index f26efb7988..b2b5449e49 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -227,10 +227,6 @@ defmodule Explorer.Chain do {:actual, fee} end - def get_latest_block do - Repo.one(from(b in Block, limit: 1, order_by: [desc: b.number])) - end - @doc """ The `t:Explorer.Chain.Transaction.t/0` `gas_price` of the `transaction` in `unit`. """ @@ -504,10 +500,26 @@ defmodule Explorer.Chain do @doc """ The maximum `t:Explorer.Chain.Block.t/0` `number` + + If blocks are skipped and inserted out of number order, the max number is still returned + + iex> insert(:block, number: 2) + iex> insert(:block, number: 1) + iex> Explorer.Chain.max_block_number() + {:ok, 2} + + If there are no blocks, `{:error, :not_found}` is returned + + iex> Explorer.Chain.max_block_number() + {:error, :not_found} + """ - @spec max_block_number() :: Block.block_number() + @spec max_block_number() :: {:ok, Block.block_number()} | {:error, :not_found} def max_block_number do - Repo.aggregate(Block, :max, :number) + case Repo.aggregate(Block, :max, :number) do + nil -> {:error, :not_found} + number -> {:ok, number} + end end @doc """ diff --git a/apps/explorer/lib/explorer/indexer.ex b/apps/explorer/lib/explorer/indexer.ex index 95c05da187..5c183a0e88 100644 --- a/apps/explorer/lib/explorer/indexer.ex +++ b/apps/explorer/lib/explorer/indexer.ex @@ -4,10 +4,23 @@ defmodule Explorer.Indexer do """ alias Explorer.Chain - alias Explorer.Chain.Block # Functions + @doc """ + Options passed to `child_spec` are passed to `Explorer.Indexer.Supervisor.start_link/1` + + iex> Explorer.Indexer.child_spec([option: :value]) + %{ + id: Explorer.Indexer, + restart: :permanent, + shutdown: 5000, + start: {Explorer.Indexer.Supervisor, :start_link, + [[option: :value]]}, + type: :supervisor + } + + """ def child_spec(opts) do %{ id: __MODULE__, @@ -18,15 +31,49 @@ defmodule Explorer.Indexer do } end - def last_indexed_block_number do - case Chain.get_latest_block() do - %Block{number: num} -> num - nil -> 0 + @doc """ + The maximum `t:Explorer.Chain.Block.t/0` `number` that was indexed + + If blocks are skipped and inserted out of number order, the max number is still returned + + iex> insert(:block, number: 2) + iex> insert(:block, number: 1) + iex> Explorer.Indexer.max_block_number() + 2 + + If there are no blocks, `0` is returned to indicate to index from genesis block. + + iex> Explorer.Indexer.max_block_number() + 0 + + """ + def max_block_number do + case Chain.max_block_number() do + {:ok, number} -> number + {:error, :not_found} -> 0 end end + @doc """ + The next `t:Explorer.Chain.Block.t/0` `number` that needs to be indexed (excluding skipped blocks) + + When there are no blocks the next block is the 0th block + + iex> Explorer.Indexer.max_block_number() + 0 + iex> Explorer.Indexer.next_block_number() + 0 + + When there is a block, it is the successive block number + + iex> insert(:block, number: 2) + iex> insert(:block, number: 1) + iex> Explorer.Indexer.next_block_number() + 3 + + """ def next_block_number do - case last_indexed_block_number() do + case max_block_number() do 0 -> 0 num -> num + 1 end diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 1cf5b9dbdf..ded5e6f138 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -289,7 +289,7 @@ defmodule Explorer.ChainTest do describe "confirmations/1" do test "with block.number == max_block_number " do block = insert(:block) - max_block_number = Chain.max_block_number() + {:ok, max_block_number} = Chain.max_block_number() assert block.number == max_block_number assert Chain.confirmations(block, max_block_number: max_block_number) == 0 @@ -447,21 +447,6 @@ defmodule Explorer.ChainTest do end end - describe "max_block_number/0" do - test "without blocks is nil" do - assert Chain.max_block_number() == nil - end - - test "with blocks is max number regardless of insertion order" do - max_number = 2 - insert(:block, number: max_number) - - insert(:block, number: 1) - - assert Chain.max_block_number() == max_number - end - end - describe "number_to_block/1" do test "without block" do assert {:error, :not_found} = Chain.number_to_block(-1) diff --git a/apps/explorer/test/explorer/indexer_test.exs b/apps/explorer/test/explorer/indexer_test.exs new file mode 100644 index 0000000000..040d7adeb7 --- /dev/null +++ b/apps/explorer/test/explorer/indexer_test.exs @@ -0,0 +1,9 @@ +defmodule Explorer.IndexerTest do + use Explorer.DataCase, async: true + + alias Explorer.Indexer + + import Explorer.Factory + + doctest Indexer +end diff --git a/apps/explorer_web/lib/explorer_web/controllers/transaction_controller.ex b/apps/explorer_web/lib/explorer_web/controllers/transaction_controller.ex index 3d875dadd2..88c881a265 100644 --- a/apps/explorer_web/lib/explorer_web/controllers/transaction_controller.ex +++ b/apps/explorer_web/lib/explorer_web/controllers/transaction_controller.ex @@ -30,13 +30,11 @@ defmodule ExplorerWeb.TransactionController do necessity_by_association: %{from_address: :required, to_address: :optional} ) - max_block_number = Chain.max_block_number() - render( conn, "show.html", internal_transactions: internal_transactions, - max_block_number: max_block_number, + max_block_number: max_block_number(), transaction: transaction ) else @@ -82,4 +80,11 @@ defmodule ExplorerWeb.TransactionController do defp last_seen_collated_hash(transactions) do List.last(transactions).hash end + + defp max_block_number do + case Chain.max_block_number() do + {:ok, number} -> number + {:error, :not_found} -> 0 + end + end end diff --git a/apps/explorer_web/lib/explorer_web/controllers/transaction_log_controller.ex b/apps/explorer_web/lib/explorer_web/controllers/transaction_log_controller.ex index 2f1efd35ae..3b96725db4 100644 --- a/apps/explorer_web/lib/explorer_web/controllers/transaction_log_controller.ex +++ b/apps/explorer_web/lib/explorer_web/controllers/transaction_log_controller.ex @@ -3,6 +3,10 @@ defmodule ExplorerWeb.TransactionLogController do alias Explorer.Chain + # Functions + + ## Actions + def index(conn, %{"transaction_id" => transaction_hash_string} = params) do with {:ok, transaction_hash} <- Chain.string_to_transaction_hash(transaction_hash_string), {:ok, transaction} <- @@ -22,13 +26,11 @@ defmodule ExplorerWeb.TransactionLogController do pagination: params ) - max_block_number = Chain.max_block_number() - render( conn, "index.html", logs: logs, - max_block_number: max_block_number, + max_block_number: max_block_number(), transaction: transaction ) else @@ -39,4 +41,13 @@ defmodule ExplorerWeb.TransactionLogController do not_found(conn) end end + + ## Private Functions + + defp max_block_number do + case Chain.max_block_number() do + {:ok, number} -> number + {:error, :not_found} -> 0 + end + end end From 0b2c9566bdb797817a4e91010932ee814e6934e4 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 2 May 2018 13:17:11 -0500 Subject: [PATCH 11/77] Cover Expxlorer.Chain.InternalTransaction --- .../explorer/chain/internal_transaction.ex | 273 ++++++++++++++++++ .../chain/internal_transaction_test.exs | 2 + 2 files changed, 275 insertions(+) diff --git a/apps/explorer/lib/explorer/chain/internal_transaction.ex b/apps/explorer/lib/explorer/chain/internal_transaction.ex index 61b2525f12..44df27c275 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction.ex @@ -86,6 +86,222 @@ defmodule Explorer.Chain.InternalTransaction do belongs_to(:transaction, Transaction, foreign_key: :transaction_hash, references: :hash, type: Hash.Full) end + @doc """ + Validates that the `attrs` are valid. + + `:create` type traces generated when a contract is created are valid. `created_contract_address_hash`, + `from_address_hash`, and `transaction_hash` are converted to `t:Explorer.Chain.Hash.t/0`, and `type` is converted to + `t:Explorer.Chain.InternalTransaction.Type.t/0` + + iex> changeset = Explorer.Chain.InternalTransaction.changeset( + ...> %Explorer.Chain.InternalTransaction{}, + ...> %{ + ...> created_contract_address_hash: "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", + ...> created_contract_code: "0x606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + ...> from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> gas: 4597044, + ...> gas_used: 166651, + ...> index: 0, + ...> init: "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + ...> trace_address: [], + ...> transaction_hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + ...> type: "create", + ...> value: 0 + ...> } + ...> ) + iex> changeset.valid? + true + iex> changeset.changes.created_contract_address_hash + %Explorer.Chain.Hash{ + byte_count: 20, + bytes: <<255, 200, 114, 57, 235, 2, 103, 188, 60, 162, 205, 81, 209, 47, 191, 39, 142, 2, 204, 180>> + } + iex> changeset.changes.from_address_hash + %Explorer.Chain.Hash{ + byte_count: 20, + bytes: <<232, 221, 197, 199, 162, 210, 240, 215, 169, 121, 132, 89, 192, 16, 79, 223, 94, 152, 122, 202>> + } + iex> changeset.changes.transaction_hash + %Explorer.Chain.Hash{ + byte_count: 32, + bytes: <<58, 62, 177, 52, 230, 121, 44, 233, 64, 62, 164, 24, 142, 94, 121, 105, 61, 233, 228, 201, 78, 73, 157, + 177, 50, 190, 8, 100, 0, 218, 121, 230>> + } + iex> changeset.changes.type + :create + + `:create` type can fail due to a Bad Instruction in the `init`, but these need to be valid, so we can display the + failures. `to_address_hash` is converted to `t:Explorer.Chain.Hash.t/0`. + + iex> changeset = Explorer.Chain.InternalTransaction.changeset( + ...> %Explorer.Chain.InternalTransaction{}, + ...> %{ + ...> error: "Bad instruction", + ...> from_address_hash: "0x78a42d3705fb3c26a4b54737a784bf064f0815fb", + ...> gas: 3946728, + ...> index: 0, + ...> init: "0x4bb278f3", + ...> trace_address: [], + ...> transaction_hash: "0x3c624bb4852fb5e35a8f45644cec7a486211f6ba89034768a2b763194f22f97d", + ...> type: "create", + ...> value: 0 + ...> } + iex> ) + iex> changeset.valid? + true + iex> changeset.changes.from_address_hash + %Explorer.Chain.Hash{ + byte_count: 20, + bytes: <<120, 164, 45, 55, 5, 251, 60, 38, 164, 181, 71, 55, 167, 132, 191, 6, 79, 8, 21, 251>> + } + + `:call` type traces are generated when a method in a contrat is call. + + iex> changeset = Explorer.Chain.InternalTransaction.changeset( + ...> %Explorer.Chain.InternalTransaction{}, + ...> %{ + ...> call_type: "call", + ...> from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> gas: 4677320, + ...> gas_used: 27770, + ...> index: 0, + ...> output: "0x", + ...> to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + ...> trace_address: [], + ...> transaction_hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + ...> type: "call", + ...> value: 0 + ...> } + ...> ) + iex> changeset.valid? + true + + `:call` type traces can also fail, in which case it will be reverted. + + iex> changeset = Explorer.Chain.InternalTransaction.changeset( + ...> %Explorer.Chain.InternalTransaction{}, + ...> %{ + ...> call_type: "call", + ...> error: "Reverted", + ...> from_address_hash: "0xc9266e6fdf5182dc47d27e0dc32bdff9e4cd2e32", + ...> gas: 7578728, + ...> index: 0, + ...> to_address_hash: "0xfdca0da4158740a93693441b35809b5bb463e527", + ...> trace_address: [], + ...> transaction_hash: "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b", + ...> type: "call", + ...> value: 10000000000000000 + ...> } + ...> ) + iex> changeset.valid? + true + + Failed `:call`s are not allowed to set `gas_used` or `output` because they are part of the successful `result` object + in the Parity JSONRPC response. + + iex> changeset = Explorer.Chain.InternalTransaction.changeset( + ...> %Explorer.Chain.InternalTransaction{}, + ...> %{ + ...> call_type: "call", + ...> error: "Reverted", + ...> from_address_hash: "0xc9266e6fdf5182dc47d27e0dc32bdff9e4cd2e32", + ...> gas: 7578728, + ...> gas_used: 7578727, + ...> index: 0, + ...> output: "0x", + ...> to_address_hash: "0xfdca0da4158740a93693441b35809b5bb463e527", + ...> trace_address: [], + ...> transaction_hash: "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b", + ...> type: "call", + ...> value: 10000000000000000 + ...> } + ...> ) + iex> changeset.valid? + false + iex> changeset.errors + [ + output: {"can't be present for failed call", []}, + gas_used: {"can't be present for failed call", []} + ] + + Likewise, successful `:call`s require `gas_used` and `output` to be set. + + iex> changeset = Explorer.Chain.InternalTransaction.changeset( + ...> %Explorer.Chain.InternalTransaction{}, + ...> %{ + ...> call_type: "call", + ...> from_address_hash: "0xc9266e6fdf5182dc47d27e0dc32bdff9e4cd2e32", + ...> gas: 7578728, + ...> index: 0, + ...> to_address_hash: "0xfdca0da4158740a93693441b35809b5bb463e527", + ...> trace_address: [], + ...> transaction_hash: "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b", + ...> type: "call", + ...> value: 10000000000000000 + ...> } + ...> ) + iex> changeset.valid? + false + iex> changeset.errors + [ + gas_used: {"can't be blank for successful call", [validation: :required]}, + output: {"can't be blank for successful call", [validation: :required]} + ] + + For failed `:create`, `created_contract_code`, `created_contract_address_hash`, and `gas_used` are not allowed to be + set because they come from `result` object, which shouldn't be returned from Parity. + + iex> changeset = Explorer.Chain.InternalTransaction.changeset( + ...> %Explorer.Chain.InternalTransaction{}, + ...> %{ + ...> created_contract_address_hash: "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", + ...> created_contract_code: "0x606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + ...> error: "Bad instruction", + ...> from_address_hash: "0x78a42d3705fb3c26a4b54737a784bf064f0815fb", + ...> gas: 3946728, + ...> gas_used: 166651, + ...> index: 0, + ...> init: "0x4bb278f3", + ...> trace_address: [], + ...> transaction_hash: "0x3c624bb4852fb5e35a8f45644cec7a486211f6ba89034768a2b763194f22f97d", + ...> type: "create", + ...> value: 0 + ...> } + iex> ) + iex> changeset.valid? + false + iex> changeset.errors + [ + gas_used: {"can't be present for failed create", []}, + created_contract_address_hash: {"can't be present for failed create", []}, + created_contract_code: {"can't be present for failed create", []} + ] + + For successful `:create`, `created_contract_code`, `created_contract_address_hash`, and `gas_used` are required. + + iex> changeset = Explorer.Chain.InternalTransaction.changeset( + ...> %Explorer.Chain.InternalTransaction{}, + ...> %{ + ...> from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> gas: 4597044, + ...> index: 0, + ...> init: "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + ...> trace_address: [], + ...> transaction_hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + ...> type: "create", + ...> value: 0 + ...> } + ...> ) + iex> changeset.valid? + false + iex> changeset.errors + [ + created_contract_code: {"can't be blank for successful create", [validation: :required]}, + created_contract_address_hash: {"can't be blank for successful create", [validation: :required]}, + gas_used: {"can't be blank for successful create", [validation: :required]} + ] + + """ def changeset(%__MODULE__{} = internal_transaction, attrs \\ %{}) do internal_transaction |> cast(attrs, ~w(type)a) @@ -93,6 +309,63 @@ defmodule Explorer.Chain.InternalTransaction do |> type_changeset(attrs) end + @doc """ + Extracts non-`nil` `t:Explorer.Chain.Address.t/0` `hash`es from fields + + * `created_contract_address_hash` + * `from_address_hash` + * `to_address_hash` + + For `:call` type internal transactions, `from_address_hash` and `to_address_hash` are set + + iex> %Ecto.Changeset{changes: changes, valid?: true} = Explorer.Chain.InternalTransaction.changeset( + ...> %Explorer.Chain.InternalTransaction{}, + ...> %{ + ...> call_type: "call", + ...> from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> gas: 4677320, + ...> gas_used: 27770, + ...> index: 0, + ...> output: "0x", + ...> to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + ...> trace_address: [], + ...> transaction_hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + ...> type: "call", + ...> value: 0 + ...> } + ...> ) + iex> address_hash_set = Explorer.Chain.InternalTransaction.changes_to_address_hash_set(changes) + iex> changes.from_address_hash in address_hash_set + true + iex> changes.to_address_hash in address_hash_set + true + + For `:create` type internal transactions, `created_contract_address_hash` and `from_address_hash` are set, but + `to_address_hash` is not + + iex> %Ecto.Changeset{changes: changes, valid?: true} = Explorer.Chain.InternalTransaction.changeset( + ...> %Explorer.Chain.InternalTransaction{}, + ...> %{ + ...> created_contract_address_hash: "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", + ...> created_contract_code: "0x606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + ...> from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> gas: 4597044, + ...> gas_used: 166651, + ...> index: 0, + ...> init: "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + ...> trace_address: [], + ...> transaction_hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + ...> type: "create", + ...> value: 0 + ...> } + ...> ) + iex> address_hash_set = Explorer.Chain.InternalTransaction.changes_to_address_hash_set(changes) + iex> changes.created_contract_address_hash in address_hash_set + true + iex> changes.from_address_hash in address_hash_set + true + + """ def changes_to_address_hash_set(changes) do Enum.reduce(~w(created_contract_address_hash from_address_hash to_address_hash)a, MapSet.new(), fn field, acc -> case Map.get(changes, field) do diff --git a/apps/explorer/test/explorer/chain/internal_transaction_test.exs b/apps/explorer/test/explorer/chain/internal_transaction_test.exs index 0e3ca77253..fbf7f5ba15 100644 --- a/apps/explorer/test/explorer/chain/internal_transaction_test.exs +++ b/apps/explorer/test/explorer/chain/internal_transaction_test.exs @@ -3,6 +3,8 @@ defmodule Explorer.Chain.InternalTransactionTest do alias Explorer.Chain.InternalTransaction + doctest InternalTransaction + describe "changeset/2" do test "with valid attributes" do transaction = insert(:transaction) From b71d24f15d3800da42328223a503a667857edee5 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 2 May 2018 13:33:33 -0500 Subject: [PATCH 12/77] Cover Explorer.Chain.Log --- apps/explorer/lib/explorer/chain/log.ex | 57 +++++++++++++++++++ .../explorer/test/explorer/chain/log_test.exs | 2 + 2 files changed, 59 insertions(+) diff --git a/apps/explorer/lib/explorer/chain/log.ex b/apps/explorer/lib/explorer/chain/log.ex index a3f66db789..a41219391a 100644 --- a/apps/explorer/lib/explorer/chain/log.ex +++ b/apps/explorer/lib/explorer/chain/log.ex @@ -62,6 +62,41 @@ defmodule Explorer.Chain.Log do # Functions + @doc """ + `address_hash` and `transaction_hash` are converted to `t:Explorer.Chain.Hash.t/0`. The allowed values for `type` + are currently unknown, so it is left as a `t:String.t/0`. + + iex> changeset = Explorer.Chain.Log.changeset( + ...> %Explorer.Chain.Log{}, + ...> %{ + ...> address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + ...> data: "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", + ...> first_topic: "0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22", + ...> fourth_topic: nil, + ...> index: 0, + ...> second_topic: nil, + ...> third_topic: nil, + ...> transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + ...> type: "mined" + ...> } + ...> ) + iex> changeset.valid? + true + iex> changeset.changes.address_hash + %Explorer.Chain.Hash{ + byte_count: 20, + bytes: <<139, 243, 141, 71, 100, 146, 144, 100, 242, 212, 211, 165, 101, 32, 167, 106, 179, 223, 65, 91>> + } + iex> changeset.changes.transaction_hash + %Explorer.Chain.Hash{ + byte_count: 32, + bytes: <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57, 101, 36, + 140, 57, 254, 153, 47, 255, 212, 51, 229>> + } + iex> changeset.changes.type + "mined" + + """ def changeset(%__MODULE__{} = log, attrs \\ %{}) do log |> cast(attrs, @required_attrs) @@ -71,6 +106,28 @@ defmodule Explorer.Chain.Log do |> validate_required(@required_attrs) end + @doc """ + `address_hash` is always present, so it is always returned in the set. + + iex> %Ecto.Changeset{changes: changes, valid?: true} = Explorer.Chain.Log.changeset( + ...> %Explorer.Chain.Log{}, + ...> %{ + ...> address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + ...> data: "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", + ...> first_topic: "0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22", + ...> fourth_topic: nil, + ...> index: 0, + ...> second_topic: nil, + ...> third_topic: nil, + ...> transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + ...> type: "mined" + ...> } + ...> ) + iex> address_hash_set = Explorer.Chain.Log.changes_to_address_hash_set(changes) + iex> changes.address_hash in address_hash_set + true + + """ def changes_to_address_hash_set(%{address_hash: address_hash}) do MapSet.new([address_hash]) end diff --git a/apps/explorer/test/explorer/chain/log_test.exs b/apps/explorer/test/explorer/chain/log_test.exs index 22f256e571..8bed2a7fca 100644 --- a/apps/explorer/test/explorer/chain/log_test.exs +++ b/apps/explorer/test/explorer/chain/log_test.exs @@ -4,6 +4,8 @@ defmodule Explorer.Chain.LogTest do alias Ecto.Changeset alias Explorer.Chain.Log + doctest Log + describe "changeset/2" do test "accepts valid attributes" do params = params_for(:log) From 40d00802eadfd2303f47d4260994d3a16da10c3c Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Thu, 3 May 2018 09:06:56 -0500 Subject: [PATCH 13/77] Suicide type internal transactions --- .../explorer/chain/internal_transaction.ex | 49 ++++++++ .../lib/explorer/jsonrpc/parity/trace.ex | 108 ++++++++++++++++-- .../explorer/jsonrpc/parity/trace/action.ex | 20 +++- .../explorer/jsonrpc/parity/trace/result.ex | 12 +- ...221001948_create_internal_transactions.exs | 29 +++-- 5 files changed, 196 insertions(+), 22 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/internal_transaction.ex b/apps/explorer/lib/explorer/chain/internal_transaction.ex index 44df27c275..c6a3762e19 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction.ex @@ -301,6 +301,23 @@ defmodule Explorer.Chain.InternalTransaction do gas_used: {"can't be blank for successful create", [validation: :required]} ] + For `:suicide`s, it looks like a simple value transfer between the addresses. + + iex> changeset = Explorer.Chain.InternalTransaction.changeset( + ...> %Explorer.Chain.InternalTransaction{}, + ...> %{ + ...> from_address_hash: "0xa7542d78b9a0be6147536887e0065f16182d294b", + ...> index: 1, + ...> to_address_hash: "0x59e2e9ecf133649b1a7efc731162ff09d29ca5a5", + ...> trace_address: [0], + ...> transaction_hash: "0xb012b8c53498c669d87d85ed90f57385848b86d3f44ed14b2784ec685d6fda98", + ...> type: "suicide", + ...> value: 0 + ...> } + ...> ) + iex> changeset.valid? + true + """ def changeset(%__MODULE__{} = internal_transaction, attrs \\ %{}) do internal_transaction @@ -365,6 +382,26 @@ defmodule Explorer.Chain.InternalTransaction do iex> changes.from_address_hash in address_hash_set true + For `:suicide` type internal transactions, `from_address_hash` and `to_address_hash` are set. + + iex> %Ecto.Changeset{changes: changes, valid?: true} = Explorer.Chain.InternalTransaction.changeset( + ...> %Explorer.Chain.InternalTransaction{}, + ...> %{ + ...> from_address_hash: "0xa7542d78b9a0be6147536887e0065f16182d294b", + ...> index: 1, + ...> to_address_hash: "0x59e2e9ecf133649b1a7efc731162ff09d29ca5a5", + ...> trace_address: [0], + ...> transaction_hash: "0xb012b8c53498c669d87d85ed90f57385848b86d3f44ed14b2784ec685d6fda98", + ...> type: "suicide", + ...> value: 0 + ...> } + ...> ) + iex> address_hash_set = Explorer.Chain.InternalTransaction.changes_to_address_hash_set(changes) + iex> changes.from_address_hash in address_hash_set + true + iex> changes.to_address_hash in address_hash_set + true + """ def changes_to_address_hash_set(changes) do Enum.reduce(~w(created_contract_address_hash from_address_hash to_address_hash)a, MapSet.new(), fn field, acc -> @@ -413,6 +450,18 @@ defmodule Explorer.Chain.InternalTransaction do |> unique_constraint(:index) end + @suicide_required_fields ~w(from_address_hash index to_address_hash trace_address transaction_hash type value)a + @suicide_allowed_fields @suicide_required_fields + + defp type_changeset(changeset, attrs, :suicide) do + changeset + |> cast(attrs, @suicide_allowed_fields) + |> validate_required(@suicide_required_fields) + |> foreign_key_constraint(:from_address_hash) + |> foreign_key_constraint(:to_address_hash) + |> unique_constraint(:index) + end + defp type_changeset(changeset, _, nil), do: changeset defp validate_disallowed(changeset, field, named_arguments) when is_atom(field) do diff --git a/apps/explorer/lib/explorer/jsonrpc/parity/trace.ex b/apps/explorer/lib/explorer/jsonrpc/parity/trace.ex index e461eb3b9c..f4eee69f71 100644 --- a/apps/explorer/lib/explorer/jsonrpc/parity/trace.ex +++ b/apps/explorer/lib/explorer/jsonrpc/parity/trace.ex @@ -146,8 +146,70 @@ defmodule Explorer.JSONRPC.Parity.Trace do value: 10000000000000000 } + Suicides transfer a `"balance"` from `"address"` to `"refundAddress"`. These suicide-unique fields can be mapped to + pre-existing `t:Explorer.Chain.InternalTransaction.t/0` fields. + + | Elixir | Params | + |-------------------|----------------------| + | `"address"` | `:from_address_hash` | + | `"balance"` | `:value` | + | `"refundAddress"` | `:to_address_hash` | + + iex> Explorer.JSONRPC.Parity.Trace.elixir_to_params( + ...> %{ + ...> "action" => %{ + ...> "address" => "0xa7542d78b9a0be6147536887e0065f16182d294b", + ...> "balance" => 0, + ...> "refundAddress" => "0x59e2e9ecf133649b1a7efc731162ff09d29ca5a5" + ...> }, + ...> "index" => 1, + ...> "result" => nil, + ...> "subtraces" => 0, + ...> "traceAddress" => [0], + ...> "transactionHash" => "0xb012b8c53498c669d87d85ed90f57385848b86d3f44ed14b2784ec685d6fda98", + ...> "type" => "suicide" + ...> } + ...> ) + %{ + from_address_hash: "0xa7542d78b9a0be6147536887e0065f16182d294b", + index: 1, + to_address_hash: "0x59e2e9ecf133649b1a7efc731162ff09d29ca5a5", + trace_address: [0], + transaction_hash: "0xb012b8c53498c669d87d85ed90f57385848b86d3f44ed14b2784ec685d6fda98", + type: "suicide", + value: 0 + } + """ + def elixir_to_params(%{"type" => "call" = type} = elixir) do + %{ + "action" => %{ + "callType" => call_type, + "from" => from_address_hash, + "gas" => gas, + "to" => to_address_hash, + "value" => value + }, + "index" => index, + "traceAddress" => trace_address, + "transactionHash" => transaction_hash + } = elixir + + %{ + call_type: call_type, + from_address_hash: from_address_hash, + gas: gas, + index: index, + to_address_hash: to_address_hash, + trace_address: trace_address, + transaction_hash: transaction_hash, + type: type, + value: value + } + |> put_call_error_or_result(elixir) + end + def elixir_to_params(%{"type" => "create" = type} = elixir) do %{ "action" => %{"from" => from_address_hash, "gas" => gas, "init" => init, "value" => value}, @@ -169,24 +231,16 @@ defmodule Explorer.JSONRPC.Parity.Trace do |> put_create_error_or_result(elixir) end - def elixir_to_params(%{"type" => "call" = type} = elixir) do + def elixir_to_params(%{"type" => "suicide" = type} = elixir) do %{ - "action" => %{ - "callType" => call_type, - "from" => from_address_hash, - "gas" => gas, - "to" => to_address_hash, - "value" => value - }, + "action" => %{"address" => from_address_hash, "balance" => value, "refundAddress" => to_address_hash}, "index" => index, "traceAddress" => trace_address, "transactionHash" => transaction_hash } = elixir %{ - call_type: call_type, from_address_hash: from_address_hash, - gas: gas, index: index, to_address_hash: to_address_hash, trace_address: trace_address, @@ -194,7 +248,6 @@ defmodule Explorer.JSONRPC.Parity.Trace do type: type, value: value } - |> put_call_error_or_result(elixir) end @doc """ @@ -262,7 +315,40 @@ defmodule Explorer.JSONRPC.Parity.Trace do ...> ) ** (ArgumentError) Caller must `Map.put/2` `"index"` and `"transactionHash"` in trace + `"suicide"` `"type"` traces are different in that they have a `nil` `"result"`. This is because the `"result"` key + is used to indicate success from Parity. + + iex> Explorer.JSONRPC.Parity.Trace.to_elixir( + ...> %{ + ...> "action" => %{ + ...> "address" => "0xa7542d78b9a0be6147536887e0065f16182d294b", + ...> "balance" => "0x0", + ...> "refundAddress" => "0x59e2e9ecf133649b1a7efc731162ff09d29ca5a5" + ...> }, + ...> "index" => 1, + ...> "result" => nil, + ...> "subtraces" => 0, + ...> "traceAddress" => [0], + ...> "transactionHash" => "0xb012b8c53498c669d87d85ed90f57385848b86d3f44ed14b2784ec685d6fda98", + ...> "type" => "suicide" + ...> } + ...> ) + %{ + "action" => %{ + "address" => "0xa7542d78b9a0be6147536887e0065f16182d294b", + "balance" => 0, + "refundAddress" => "0x59e2e9ecf133649b1a7efc731162ff09d29ca5a5" + }, + "index" => 1, + "result" => nil, + "subtraces" => 0, + "traceAddress" => [0], + "transactionHash" => "0xb012b8c53498c669d87d85ed90f57385848b86d3f44ed14b2784ec685d6fda98", + "type" => "suicide" + } + """ + def to_elixir(%{"index" => _, "transactionHash" => _} = trace) when is_map(trace) do Enum.into(trace, %{}, &entry_to_elixir/1) end diff --git a/apps/explorer/lib/explorer/jsonrpc/parity/trace/action.ex b/apps/explorer/lib/explorer/jsonrpc/parity/trace/action.ex index b7f0bbd299..efe2311cd0 100644 --- a/apps/explorer/lib/explorer/jsonrpc/parity/trace/action.ex +++ b/apps/explorer/lib/explorer/jsonrpc/parity/trace/action.ex @@ -23,6 +23,22 @@ defmodule Explorer.JSONRPC.Parity.Trace.Action do "value" => 0 } + For a suicide, the `"balance"` is converted to a `t:non_neg_integer/0` while the `"address" and `"refundAddress"` + `t:Explorer.JSONRPC.hash/0` pass through. + + iex> Explorer.JSONRPC.Parity.Trace.Action.to_elixir( + ...> %{ + ...> "address" => "0xa7542d78b9a0be6147536887e0065f16182d294b", + ...> "balance" => "0x0", + ...> "refundAddress" => "0x59e2e9ecf133649b1a7efc731162ff09d29ca5a5" + ...> } + ...> ) + %{ + "address" => "0xa7542d78b9a0be6147536887e0065f16182d294b", + "balance" => 0, + "refundAddress" => "0x59e2e9ecf133649b1a7efc731162ff09d29ca5a5" + } + """ def to_elixir(action) when is_map(action) do Enum.into(action, %{}, &entry_to_elixir/1) @@ -30,9 +46,9 @@ defmodule Explorer.JSONRPC.Parity.Trace.Action do ## Private Functions - defp entry_to_elixir({key, _} = entry) when key in ~w(callType from init input to), do: entry + defp entry_to_elixir({key, _} = entry) when key in ~w(address callType from init input refundAddress to), do: entry - defp entry_to_elixir({key, quantity}) when key in ~w(gas value) do + defp entry_to_elixir({key, quantity}) when key in ~w(balance gas value) do {key, quantity_to_integer(quantity)} end end diff --git a/apps/explorer/lib/explorer/jsonrpc/parity/trace/result.ex b/apps/explorer/lib/explorer/jsonrpc/parity/trace/result.ex index 9b343cd7ec..e2ea511ac7 100644 --- a/apps/explorer/lib/explorer/jsonrpc/parity/trace/result.ex +++ b/apps/explorer/lib/explorer/jsonrpc/parity/trace/result.ex @@ -21,11 +21,19 @@ defmodule Explorer.JSONRPC.Parity.Trace.Result do "gasUsed" => 166651 } + `nil` resultscan occur for suicide type traces. + + iex> Explorer.JSONRPC.Parity.Trace.Result.to_elixir(nil) + nil + """ - def to_elixir(action) when is_map(action) do - Enum.into(action, %{}, &entry_to_elixir/1) + + def to_elixir(result) when is_map(result) do + Enum.into(result, %{}, &entry_to_elixir/1) end + def to_elixir(nil), do: nil + ## Private Functions defp entry_to_elixir({key, _} = entry) when key in ~w(address code output), do: entry diff --git a/apps/explorer/priv/repo/migrations/20180221001948_create_internal_transactions.exs b/apps/explorer/priv/repo/migrations/20180221001948_create_internal_transactions.exs index cfadea1dd5..9d25b578ce 100644 --- a/apps/explorer/priv/repo/migrations/20180221001948_create_internal_transactions.exs +++ b/apps/explorer/priv/repo/migrations/20180221001948_create_internal_transactions.exs @@ -7,8 +7,10 @@ defmodule Explorer.Repo.Migrations.CreateInternalTransactions do add(:created_contract_code, :text, null: true) # null unless there is an error add(:error, :string, null: true) - add(:gas, :numeric, precision: 100, null: false) + # no gas budget for suicide + add(:gas, :numeric, precision: 100, null: true) # can be null when `error` is not `null` + # no gas_used for suicide add(:gas_used, :numeric, precision: 100, null: true) add(:index, :integer, null: false) add(:init, :text) @@ -37,14 +39,28 @@ defmodule Explorer.Repo.Migrations.CreateInternalTransactions do # Constraints + create( + constraint( + :internal_transactions, + :call_has_error_or_result, + check: """ + type != 'call' OR + (gas IS NOT NULL AND + ((error IS NULL AND gas_used IS NOT NULL and output IS NOT NULL) OR + (error IS NOT NULL AND gas_used IS NULL and output is NULL))) + """ + ) + ) + create( constraint( :internal_transactions, :create_has_error_or_result, check: """ type != 'create' OR - (error IS NULL AND created_contract_address_hash IS NOT NULL AND created_contract_code IS NOT NULL AND gas_used IS NOT NULL) OR - (error IS NOT NULL AND created_contract_address_hash IS NULL AND created_contract_code IS NULL AND gas_used IS NULL) + (gas IS NOT NULL AND + ((error IS NULL AND created_contract_address_hash IS NOT NULL AND created_contract_code IS NOT NULL AND gas_used IS NOT NULL) OR + (error IS NOT NULL AND created_contract_address_hash IS NULL AND created_contract_code IS NULL AND gas_used IS NULL))) """ ) ) @@ -52,11 +68,10 @@ defmodule Explorer.Repo.Migrations.CreateInternalTransactions do create( constraint( :internal_transactions, - :call_has_error_or_result, + :suicide_has_from_and_to_address_hashes, check: """ - type != 'call' OR - (error IS NULL AND gas_used IS NOT NULL and output IS NOT NULL) OR - (error IS NOT NULL AND gas_used IS NULL and output is NULL) + type != 'suicide' OR + (from_address_hash IS NOT NULL AND gas IS NULL AND to_address_hash IS NOT NULL) """ ) ) From b9154901e9c4348d953dbadadcd062b69ae74d36 Mon Sep 17 00:00:00 2001 From: Chris McCord Date: Thu, 3 May 2018 10:17:24 -0400 Subject: [PATCH 14/77] Add address balance fetching --- apps/explorer/lib/explorer/chain.ex | 48 ++++++- apps/explorer/lib/explorer/chain/address.ex | 27 ++-- .../lib/explorer/indexer/address_fetcher.ex | 125 ++++++++++++++++++ .../lib/explorer/indexer/block_fetcher.ex | 11 +- .../lib/explorer/indexer/supervisor.ex | 11 +- apps/explorer/lib/explorer/jsonrpc.ex | 39 +++++- .../20180117221921_create_address.exs | 4 +- .../test/explorer/chain/address_test.exs | 2 +- apps/explorer/test/explorer/chain_test.exs | 16 +-- 9 files changed, 239 insertions(+), 44 deletions(-) create mode 100644 apps/explorer/lib/explorer/indexer/address_fetcher.ex diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index b2b5449e49..09e07ac119 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -72,13 +72,33 @@ defmodule Explorer.Chain do @spec balance(Address.t(), :wei) :: Wei.t() | nil @spec balance(Address.t(), :gwei) :: Wei.gwei() | nil @spec balance(Address.t(), :ether) :: Wei.ether() | nil - def balance(%Address{balance: balance}, unit) do + def balance(%Address{fetched_balance: balance}, unit) do case balance do nil -> nil _ -> Wei.to(balance, unit) end end + @spec update_balances( + %{address_hash :: String.t => balance :: integer} + ) :: :ok | {:error, reason :: term} + def update_balances(balances) do + timestamps = timestamps() + changes = + for {hash_string, amount} <- balances do + {:ok, truncated_hash} = Explorer.Chain.Hash.Truncated.cast(hash_string) + Map.merge(timestamps, %{ + hash: truncated_hash, + fetched_balance: amount, + balance_fetched_at: timestamps.updated_at, + }) + end + + {_, _} = Repo.safe_insert_all(Address, changes, + conflict_target: :hash, on_conflict: :replace_all) + :ok + end + @doc """ The number of `t:Explorer.Chain.Block.t/0`. @@ -380,7 +400,7 @@ defmodule Explorer.Chain do end @doc """ - Bulk insert tree of resource from a list of blocks. + Bulk insert blocks from a list of blocks. ## Tree @@ -391,7 +411,7 @@ defmodule Explorer.Chain do * `t.Explorer.Chain.Log.t/0` """ - def insert(%{ + def import_blocks(%{ blocks_params: blocks_params, logs_params: logs_params, internal_transactions_params: internal_transactions_params, @@ -412,6 +432,13 @@ defmodule Explorer.Chain do end end + @doc """ + The number of `t:Explorer.Chain.Address.t/0`. + """ + def address_count do + Repo.aggregate(Address, :count, :hash) + end + @doc """ The number of `t:Explorer.Chain.InternalTransaction.t/0`. @@ -478,6 +505,17 @@ defmodule Explorer.Chain do |> Repo.paginate(pagination) end + @doc """ + Returns a stream of unfetched `Explorer.Chain.Address.t/0`. + """ + def stream_unfetched_addresses(initial, reducer) when is_function(reducer) do + Repo.transaction(fn -> + from(a in Address, where: is_nil(a.balance_fetched_at)) + |> Repo.stream() + |> Enum.reduce(initial, reducer) + end) + end + @doc """ The number of `t:Explorer.Chain.Log.t/0`. @@ -1058,11 +1096,11 @@ defmodule Explorer.Chain do insert_changes_list( changes_list, conflict_target: :hash, - # Do nothing so that pre-existing balance is not overwritten - on_conflict: :nothing, + on_conflict: [set: [balance_fetched_at: nil]], for: Address, timestamps: timestamps ) + {:ok, for(changes <- changes_list, do: changes.hash)} end @spec insert_blocks([map()], [timestamps_option]) :: {:ok, Block.t()} | {:error, [Changeset.t()]} diff --git a/apps/explorer/lib/explorer/chain/address.ex b/apps/explorer/lib/explorer/chain/address.ex index b67064239e..30268b6f9c 100644 --- a/apps/explorer/lib/explorer/chain/address.ex +++ b/apps/explorer/lib/explorer/chain/address.ex @@ -20,8 +20,8 @@ defmodule Explorer.Chain.Address do @type hash :: Hash.t() @typedoc """ - * `balance` - `credit.value - debit.value` - * `balance_updated_at` - the last time `balance` was recalculated + * `fetched_balance` - The last fetched balance from Parity + * `balance_fetched_at` - the last time `balance` was fetched * `credit` - accumulation of all credits to the address `hash` * `debit` - accumulation of all debits to the address `hash` * `hash` - the hash of the address's public key @@ -29,8 +29,8 @@ defmodule Explorer.Chain.Address do * `updated_at` when this address was last updated """ @type t :: %__MODULE__{ - balance: Decimal.t(), - balance_updated_at: DateTime.t(), + fetched_balance: Decimal.t(), + balance_fetched_at: DateTime.t(), credit: %Ecto.Association.NotLoaded{} | Credit.t() | nil, debit: %Ecto.Association.NotLoaded{} | Debit.t() | nil, hash: Hash.Truncated.t(), @@ -40,8 +40,8 @@ defmodule Explorer.Chain.Address do @primary_key {:hash, Hash.Truncated, autogenerate: false} schema "addresses" do - field(:balance, :decimal) - field(:balance_updated_at, Timex.Ecto.DateTime) + field(:fetched_balance, :decimal) + field(:balance_fetched_at, Timex.Ecto.DateTime) timestamps() @@ -49,13 +49,11 @@ defmodule Explorer.Chain.Address do has_one(:debit, Debit) end - # Functions - def balance_changeset(%__MODULE__{} = address, attrs) do address - |> cast(attrs, [:balance]) - |> validate_required([:balance]) - |> put_balance_updated_at() + |> cast(attrs, [:fetched_balance]) + |> validate_required([:fetched_balance]) + |> put_change(:balance_fetched_at, Timex.now()) end def changeset(%__MODULE__{} = address, attrs) do @@ -69,17 +67,10 @@ defmodule Explorer.Chain.Address do Enum.map(hash_set, &hash_to_changes/1) end - ## Private Functions - defp hash_to_changes(%Hash{byte_count: 20} = hash) do %{hash: hash} end - defp put_balance_updated_at(changeset) do - changeset - |> put_change(:balance_updated_at, Timex.now()) - end - defimpl String.Chars do def to_string(%@for{hash: hash}) do @protocol.to_string(hash) diff --git a/apps/explorer/lib/explorer/indexer/address_fetcher.ex b/apps/explorer/lib/explorer/indexer/address_fetcher.ex new file mode 100644 index 0000000000..da22f4ac96 --- /dev/null +++ b/apps/explorer/lib/explorer/indexer/address_fetcher.ex @@ -0,0 +1,125 @@ +defmodule Explorer.Indexer.AddressFetcher do + @moduledoc """ + TODO + """ + use GenServer + require Logger + + alias Explorer.Chain + alias Explorer.Chain.{ + Address, + Hash, + } + alias Explorer.JSONRPC + + @fetch_interval :timer.seconds(3) + @max_batch_size 500 + + def async_fetch_addresses(address_hashes) do + GenServer.cast(__MODULE__, {:buffer_addresses, address_hashes}) + end + + def start_link(opts) do + GenServer.start_link(__MODULE__, opts, name: __MODULE__) + end + + def init(_opts) do + send(self(), :fetch_unfetched_addresses) + + {:ok, %{buffer: MapSet.new(), tasks: %{}}} + end + + def handle_info(:fetch_unfetched_addresses, state) do + schedule_next_buffer_fetch(0) + {:noreply, stream_unfetched_addresses(state)} + end + + def handle_info(:buffer_fetch, state) do + schedule_next_buffer_fetch() + {:noreply, flush_buffer(state)} + end + + def handle_info({ref, {:fetched_balances, results}}, state) do + :ok = Chain.update_balances(results) + {:noreply, drop_task(state, ref)} + end + + def handle_info({:DOWN, _ref, :process, _pid, :normal}, state) do + {:noreply, state} + end + def handle_info({:DOWN, ref, :process, _pid, _reason}, state) do + batch = Map.fetch!(state.tasks, ref) + + new_state = + state + |> drop_task(ref) + |> buffer_addresses(batch) + + {:noreply, new_state} + end + + def handle_cast({:buffer_addresses, address_hashes}, state) do + {:noreply, buffer_addresses(state, address_hashes)} + end + + defp drop_task(state, ref) do + %{state | tasks: Map.delete(state.tasks, ref)} + end + + defp buffer_addresses(state, address_hashes) do + string_hashes = for hash <- address_hashes, do: Hash.to_string(hash) + %{state | buffer: MapSet.union(state.buffer, MapSet.new(string_hashes))} + end + + defp stream_unfetched_addresses(state) do + tasks = + {state.tasks, state.buffer} + |> Chain.stream_unfetched_addresses(fn %Address{hash: hash}, {tasks, batch} -> + batch = MapSet.put(batch, Hash.to_string(hash)) + if MapSet.size(batch) >= @max_batch_size do + task = async_fetch_balances(batch) + {Map.put(tasks, task.ref, batch), MapSet.new()} + else + {tasks, batch} + end + end) + |> fetch_remaining() + + %{state | tasks: tasks} + end + defp fetch_remaining({:ok, {tasks, batch}}) do + if MapSet.size(batch) > 0 do + task = async_fetch_balances(batch) + Map.put(tasks, task.ref, batch) + else + tasks + end + end + + defp flush_buffer(state) do + if MapSet.size(state.buffer) > 0 do + task = async_fetch_balances(state.buffer) + new_tasks = Map.put(state.tasks, task.ref, state.buffer) + + %{state | tasks: new_tasks, buffer: MapSet.new()} + else + state + end + end + + defp schedule_next_buffer_fetch(after_ms \\ @fetch_interval) do + Process.send_after(self(), :buffer_fetch, after_ms) + end + + defp do_fetch_addresses(address_hashes) do + JSONRPC.fetch_balances_by_hash(address_hashes) + end + + defp async_fetch_balances(hashes_mapset) do + Task.Supervisor.async_nolink(Explorer.Indexer.TaskSupervisor, fn -> + Logger.debug(fn -> "fetching #{MapSet.size(hashes_mapset)} balances" end) + {:ok, balances} = do_fetch_addresses(Enum.to_list(hashes_mapset)) + {:fetched_balances, balances} + end) + end +end diff --git a/apps/explorer/lib/explorer/indexer/block_fetcher.ex b/apps/explorer/lib/explorer/indexer/block_fetcher.ex index 6e580e0e4f..3317eb37d2 100644 --- a/apps/explorer/lib/explorer/indexer/block_fetcher.ex +++ b/apps/explorer/lib/explorer/indexer/block_fetcher.ex @@ -12,7 +12,10 @@ defmodule Explorer.Indexer.BlockFetcher do require Logger alias Explorer.{Chain, Indexer, JSONRPC} - alias Explorer.Indexer.Sequence + alias Explorer.Indexer.{ + Sequence, + AddressFetcher, + } alias Explorer.JSONRPC.Transactions # Struct @@ -92,6 +95,7 @@ defmodule Explorer.Indexer.BlockFetcher do internal transactions: #{Chain.internal_transaction_count()} receipts: #{Chain.receipt_count()} logs: #{Chain.log_count()} + addresses: #{Chain.address_count()} """ end) @@ -166,14 +170,15 @@ defmodule Explorer.Indexer.BlockFetcher do seq: seq, transactions_params: transactions_params }) do - case Chain.insert(%{ + case Chain.import_blocks(%{ blocks_params: blocks_params, internal_transactions_params: internal_transactions_params, logs_params: log_params, receipts_params: receipt_params, transactions_params: transactions_params }) do - {:ok, _results} -> + {:ok, %{addresses: address_hashes}} -> + :ok = AddressFetcher.async_fetch_addresses(address_hashes) :ok {:error, step, reason, _changes} -> diff --git a/apps/explorer/lib/explorer/indexer/supervisor.ex b/apps/explorer/lib/explorer/indexer/supervisor.ex index 686df6f0ad..d846fd5193 100644 --- a/apps/explorer/lib/explorer/indexer/supervisor.ex +++ b/apps/explorer/lib/explorer/indexer/supervisor.ex @@ -5,7 +5,10 @@ defmodule Explorer.Indexer.Supervisor do use Supervisor - alias Explorer.Indexer.BlockFetcher + alias Explorer.Indexer.{ + BlockFetcher, + AddressFetcher + } # Functions @@ -18,9 +21,11 @@ defmodule Explorer.Indexer.Supervisor do @impl Supervisor def init(_opts) do children = [ - {BlockFetcher, []} + {Task.Supervisor, name: Explorer.Indexer.TaskSupervisor}, + {BlockFetcher, []}, + {AddressFetcher, []}, ] - Supervisor.init(children, strategy: :one_for_one) + Supervisor.init(children, strategy: :rest_for_one) end end diff --git a/apps/explorer/lib/explorer/jsonrpc.ex b/apps/explorer/lib/explorer/jsonrpc.ex index 0d110e05dc..687154e8b7 100644 --- a/apps/explorer/lib/explorer/jsonrpc.ex +++ b/apps/explorer/lib/explorer/jsonrpc.ex @@ -101,6 +101,35 @@ defmodule Explorer.JSONRPC do |> Keyword.fetch!(key) end + @doc """ + Fetches address balances by address hashes. + """ + def fetch_balances_by_hash(address_hashes) do + batched_requests = + for hash <- address_hashes do + %{ + "id" => hash, + "jsonrpc" => "2.0", + "method" => "eth_getBalance", + "params" => [hash, "latest"] + } + end + + batched_requests + |> json_rpc(config(:url)) + |> handle_balances() + end + defp handle_balances({:ok, results}) do + native_results = + for response <- results, into: %{} do + {response["id"], hexadecimal_to_integer(response["result"])} + end + + {:ok, native_results} + end + defp handle_balances({:error, _reason} = err), do: err + + @doc """ Fetches blocks by block hashes. @@ -157,7 +186,7 @@ defmodule Explorer.JSONRPC do case HTTPoison.post(url, json, headers, config(:http)) do {:ok, %HTTPoison.Response{body: body, status_code: code}} -> - body |> decode_json(payload) |> handle_response(code) + body |> decode_json(payload, url) |> handle_response(code) {:error, %HTTPoison.Error{reason: reason}} -> {:error, reason} @@ -218,16 +247,18 @@ defmodule Explorer.JSONRPC do defp encode_json(data), do: Jason.encode_to_iodata!(data) - defp decode_json(body, posted_payload) do + defp decode_json(body, posted_payload, url) do Jason.decode!(body) rescue Jason.DecodeError -> Logger.error(""" failed to decode json payload: - #{inspect(body)} + url: #{inspect(url)} + + body: #{inspect(body)} - #{inspect(posted_payload)} + posted payload: #{inspect(posted_payload)} """) diff --git a/apps/explorer/priv/repo/migrations/20180117221921_create_address.exs b/apps/explorer/priv/repo/migrations/20180117221921_create_address.exs index 22c8cbebef..f3ca2af556 100644 --- a/apps/explorer/priv/repo/migrations/20180117221921_create_address.exs +++ b/apps/explorer/priv/repo/migrations/20180117221921_create_address.exs @@ -3,8 +3,8 @@ defmodule Explorer.Repo.Migrations.CreateAddress do def change do create table(:addresses, primary_key: false) do - add(:balance, :numeric, precision: 100) - add(:balance_updated_at, :utc_datetime) + add(:fetched_balance, :numeric, precision: 100) + add(:balance_fetched_at, :utc_datetime) add(:hash, :bytea, null: false, primary_key: true) timestamps(null: false) diff --git a/apps/explorer/test/explorer/chain/address_test.exs b/apps/explorer/test/explorer/chain/address_test.exs index 2a631664f4..746fb6fc53 100644 --- a/apps/explorer/test/explorer/chain/address_test.exs +++ b/apps/explorer/test/explorer/chain/address_test.exs @@ -18,7 +18,7 @@ defmodule Explorer.Chain.AddressTest do describe "balance_changeset/2" do test "with a new balance" do - changeset = Address.balance_changeset(%Address{}, %{balance: 99}) + changeset = Address.balance_changeset(%Address{}, %{fetched_balance: 99}) assert changeset.valid? end diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index ded5e6f138..5c9bbed186 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -160,20 +160,20 @@ defmodule Explorer.ChainTest do describe "balance/2" do test "with Address.t with :wei" do - assert Chain.balance(%Address{balance: Decimal.new(1)}, :wei) == Decimal.new(1) - assert Chain.balance(%Address{balance: nil}, :wei) == nil + assert Chain.balance(%Address{fetched_balance: Decimal.new(1)}, :wei) == Decimal.new(1) + assert Chain.balance(%Address{fetched_balance: nil}, :wei) == nil end test "with Address.t with :gwei" do - assert Chain.balance(%Address{balance: Decimal.new(1)}, :gwei) == Decimal.new("1e-9") - assert Chain.balance(%Address{balance: Decimal.new("1e9")}, :gwei) == Decimal.new(1) - assert Chain.balance(%Address{balance: nil}, :gwei) == nil + assert Chain.balance(%Address{fetched_balance: Decimal.new(1)}, :gwei) == Decimal.new("1e-9") + assert Chain.balance(%Address{fetched_balance: Decimal.new("1e9")}, :gwei) == Decimal.new(1) + assert Chain.balance(%Address{fetched_balance: nil}, :gwei) == nil end test "with Address.t with :ether" do - assert Chain.balance(%Address{balance: Decimal.new(1)}, :ether) == Decimal.new("1e-18") - assert Chain.balance(%Address{balance: Decimal.new("1e18")}, :ether) == Decimal.new(1) - assert Chain.balance(%Address{balance: nil}, :ether) == nil + assert Chain.balance(%Address{fetched_balance: Decimal.new(1)}, :ether) == Decimal.new("1e-18") + assert Chain.balance(%Address{fetched_balance: Decimal.new("1e18")}, :ether) == Decimal.new(1) + assert Chain.balance(%Address{fetched_balance: nil}, :ether) == nil end end From d907bd3f75c20ccb17fe15e19d6686beaa137595 Mon Sep 17 00:00:00 2001 From: Chris McCord Date: Thu, 3 May 2018 10:31:42 -0400 Subject: [PATCH 15/77] Bump docs --- .../lib/explorer/indexer/address_fetcher.ex | 19 ++++++++++++------- .../lib/explorer/indexer/block_fetcher.ex | 2 +- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/apps/explorer/lib/explorer/indexer/address_fetcher.ex b/apps/explorer/lib/explorer/indexer/address_fetcher.ex index da22f4ac96..41e711780e 100644 --- a/apps/explorer/lib/explorer/indexer/address_fetcher.ex +++ b/apps/explorer/lib/explorer/indexer/address_fetcher.ex @@ -1,21 +1,23 @@ defmodule Explorer.Indexer.AddressFetcher do @moduledoc """ - TODO + Fetches and indexes `t:Explorer.Chain.Address.t/0` balances. """ use GenServer require Logger alias Explorer.Chain + alias Explorer.Chain.{ Address, - Hash, + Hash } + alias Explorer.JSONRPC @fetch_interval :timer.seconds(3) @max_batch_size 500 - def async_fetch_addresses(address_hashes) do + def async_fetch_balances(address_hashes) do GenServer.cast(__MODULE__, {:buffer_addresses, address_hashes}) end @@ -47,6 +49,7 @@ defmodule Explorer.Indexer.AddressFetcher do def handle_info({:DOWN, _ref, :process, _pid, :normal}, state) do {:noreply, state} end + def handle_info({:DOWN, ref, :process, _pid, _reason}, state) do batch = Map.fetch!(state.tasks, ref) @@ -76,8 +79,9 @@ defmodule Explorer.Indexer.AddressFetcher do {state.tasks, state.buffer} |> Chain.stream_unfetched_addresses(fn %Address{hash: hash}, {tasks, batch} -> batch = MapSet.put(batch, Hash.to_string(hash)) + if MapSet.size(batch) >= @max_batch_size do - task = async_fetch_balances(batch) + task = do_async_fetch_balances(batch) {Map.put(tasks, task.ref, batch), MapSet.new()} else {tasks, batch} @@ -87,9 +91,10 @@ defmodule Explorer.Indexer.AddressFetcher do %{state | tasks: tasks} end + defp fetch_remaining({:ok, {tasks, batch}}) do if MapSet.size(batch) > 0 do - task = async_fetch_balances(batch) + task = do_async_fetch_balances(batch) Map.put(tasks, task.ref, batch) else tasks @@ -98,7 +103,7 @@ defmodule Explorer.Indexer.AddressFetcher do defp flush_buffer(state) do if MapSet.size(state.buffer) > 0 do - task = async_fetch_balances(state.buffer) + task = do_async_fetch_balances(state.buffer) new_tasks = Map.put(state.tasks, task.ref, state.buffer) %{state | tasks: new_tasks, buffer: MapSet.new()} @@ -115,7 +120,7 @@ defmodule Explorer.Indexer.AddressFetcher do JSONRPC.fetch_balances_by_hash(address_hashes) end - defp async_fetch_balances(hashes_mapset) do + defp do_async_fetch_balances(hashes_mapset) do Task.Supervisor.async_nolink(Explorer.Indexer.TaskSupervisor, fn -> Logger.debug(fn -> "fetching #{MapSet.size(hashes_mapset)} balances" end) {:ok, balances} = do_fetch_addresses(Enum.to_list(hashes_mapset)) diff --git a/apps/explorer/lib/explorer/indexer/block_fetcher.ex b/apps/explorer/lib/explorer/indexer/block_fetcher.ex index 3317eb37d2..945b7f9b57 100644 --- a/apps/explorer/lib/explorer/indexer/block_fetcher.ex +++ b/apps/explorer/lib/explorer/indexer/block_fetcher.ex @@ -178,7 +178,7 @@ defmodule Explorer.Indexer.BlockFetcher do transactions_params: transactions_params }) do {:ok, %{addresses: address_hashes}} -> - :ok = AddressFetcher.async_fetch_addresses(address_hashes) + :ok = AddressFetcher.async_fetch_balances(address_hashes) :ok {:error, step, reason, _changes} -> From 910792faace1b975e2b57e0bd464b1fda1982a8e Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Thu, 3 May 2018 09:33:50 -0500 Subject: [PATCH 16/77] Fix internal transaction indexes --- .../migrations/20180221001948_create_internal_transactions.exs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/explorer/priv/repo/migrations/20180221001948_create_internal_transactions.exs b/apps/explorer/priv/repo/migrations/20180221001948_create_internal_transactions.exs index 9d25b578ce..055f82072c 100644 --- a/apps/explorer/priv/repo/migrations/20180221001948_create_internal_transactions.exs +++ b/apps/explorer/priv/repo/migrations/20180221001948_create_internal_transactions.exs @@ -78,12 +78,13 @@ defmodule Explorer.Repo.Migrations.CreateInternalTransactions do # Foreign Key indexes + create(index(:internal_transactions, :created_contract_address_hash)) create(index(:internal_transactions, :from_address_hash)) create(index(:internal_transactions, :to_address_hash)) create(index(:internal_transactions, :transaction_hash)) # Unique indexes - create(index(:internal_transactions, [:transaction_hash, :index])) + create(unique_index(:internal_transactions, [:transaction_hash, :index])) end end From 7474dccbb650b696ea1e560465740498009986fe Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Thu, 3 May 2018 13:15:13 -0500 Subject: [PATCH 17/77] Don't fetch Statistics in `init` It can cause the whole supervision tree to die when it times out AND it is a blocking call, which you shouldn't do in `init`. --- apps/explorer/lib/explorer/chain/statistics/server.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/statistics/server.ex b/apps/explorer/lib/explorer/chain/statistics/server.ex index c1728b9f92..5b89f99e32 100644 --- a/apps/explorer/lib/explorer/chain/statistics/server.ex +++ b/apps/explorer/lib/explorer/chain/statistics/server.ex @@ -26,11 +26,11 @@ defmodule Explorer.Chain.Statistics.Server do end def init(true) do - {:noreply, chain} = handle_cast({:update, Statistics.fetch()}, %Statistics{}) - {:ok, chain} + send(self(), :refresh) + init(false) end - def init(false), do: {:ok, Statistics.fetch()} + def init(false), do: {:ok, %Statistics{}} def handle_info(:refresh, %Statistics{} = statistics) do Task.start_link(fn -> From a5277b5103beeee090ce4a85e2a021a25baceb02 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Thu, 3 May 2018 13:35:32 -0500 Subject: [PATCH 18/77] Fix dialyzer warnings --- .dialyzer-ignore | 2 +- apps/explorer/lib/explorer/chain.ex | 14 ++++++++++---- apps/explorer/lib/explorer/chain/address.ex | 1 + 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.dialyzer-ignore b/.dialyzer-ignore index 3070fb9038..fddb449ee4 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -1,5 +1,5 @@ :0: Unknown function 'Elixir.ExUnit.Callbacks':'__merge__'/3 :0: Unknown function 'Elixir.ExUnit.CaseTemplate':'__proxy__'/2 :0: Unknown type 'Elixir.Map':t/0 -apps/explorer/lib/explorer/indexer/block_fetcher.ex:239: The created fun has no local return apps/explorer/lib/explorer/indexer/block_fetcher.ex:246: The created fun has no local return +apps/explorer/lib/explorer/indexer/block_fetcher.ex:253: The created fun has no local return diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 7c2258d962..f6d9cf4a9f 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -40,7 +40,7 @@ defmodule Explorer.Chain do @typep inserted_after_option :: {:inserted_after, DateTime.t()} @typep necessity_by_association_option :: {:necessity_by_association, necessity_by_association} @typep pagination_option :: {:pagination, pagination} - @typep timestamps :: %{inserted_at: DateTime.t(), updated_at: DateTime.t()} + @typep timestamps :: %{inserted_at: %Ecto.DateTime{}, updated_at: %Ecto.DateTime{}} @typep timestamps_option :: {:timestamps, timestamps} # Functions @@ -757,7 +757,9 @@ defmodule Explorer.Chain do * `:pagination` - pagination params to pass to scrivener. """ - @spec recent_pending_transactions([inserted_after_option | necessity_by_association_option]) :: [Transaction.t()] + @spec recent_pending_transactions([inserted_after_option | necessity_by_association_option]) :: %Scrivener.Page{ + entries: [Transaction.t()] + } def recent_pending_transactions(options \\ []) when is_list(options) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) pagination = Keyword.get(options, :pagination, %{}) @@ -924,7 +926,9 @@ defmodule Explorer.Chain do * `:pagination` - pagination params to pass to scrivener. """ - @spec transaction_hash_to_internal_transactions(Hash.Full.t()) :: [InternalTransaction.t()] + @spec transaction_hash_to_internal_transactions(Hash.Full.t()) :: %Scrivener.Page{entries: [InternalTransaction.t()]} + @spec transaction_hash_to_internal_transactions(Hash.Full.t(), [necessity_by_association_option | pagination_option]) :: + %Scrivener.Page{entries: [InternalTransaction.t()]} def transaction_hash_to_internal_transactions( %Hash{byte_count: unquote(Hash.Full.byte_count())} = hash, options \\ [] @@ -1073,6 +1077,7 @@ defmodule Explorer.Chain do end) end + @spec ecto_schema_module_to_changes_list_to_address_hash_set(%{module => [map()]}) :: MapSet.t(Hash.Truncated.t()) defp ecto_schema_module_to_changes_list_to_address_hash_set(ecto_schema_module_to_changes_list) do Enum.reduce(ecto_schema_module_to_changes_list, MapSet.new(), fn ecto_schema_module_changes_list, acc -> ecto_schema_module_changes_list @@ -1109,7 +1114,7 @@ defmodule Explorer.Chain do ) end - @spec insert_addresses([map()], [timestamps_option]) :: {:ok, Block.t()} | {:error, [Changeset.t()]} + @spec insert_addresses([%{hash: Hash.Truncated.t()}], [timestamps_option, ...]) :: {:ok, [Hash.Truncated.t()]} defp insert_addresses(changes_list, named_arguments) when is_list(changes_list) and is_list(named_arguments) do timestamps = Keyword.fetch!(named_arguments, :timestamps) @@ -1261,6 +1266,7 @@ defmodule Explorer.Chain do Enum.map(changes_list, ×tamp_params(&1, timestamps)) end + @spec timestamps() :: timestamps defp timestamps do now = Ecto.DateTime.utc() %{inserted_at: now, updated_at: now} diff --git a/apps/explorer/lib/explorer/chain/address.ex b/apps/explorer/lib/explorer/chain/address.ex index 30268b6f9c..7816aca8c6 100644 --- a/apps/explorer/lib/explorer/chain/address.ex +++ b/apps/explorer/lib/explorer/chain/address.ex @@ -63,6 +63,7 @@ defmodule Explorer.Chain.Address do |> unique_constraint(:hash) end + @spec hash_set_to_changes_list(MapSet.t(Hash.Truncated.t())) :: [%{hash: Hash.Truncated.t()}] def hash_set_to_changes_list(hash_set) do Enum.map(hash_set, &hash_to_changes/1) end From 3df97df54653668628782f1902806101a8c21b30 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Thu, 3 May 2018 13:35:32 -0500 Subject: [PATCH 19/77] Fix dialyzer warnings --- .dialyzer-ignore | 2 +- apps/explorer/lib/explorer/chain.ex | 16 ++++++++++++---- apps/explorer/lib/explorer/chain/address.ex | 1 + 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/.dialyzer-ignore b/.dialyzer-ignore index 3070fb9038..fddb449ee4 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -1,5 +1,5 @@ :0: Unknown function 'Elixir.ExUnit.Callbacks':'__merge__'/3 :0: Unknown function 'Elixir.ExUnit.CaseTemplate':'__proxy__'/2 :0: Unknown type 'Elixir.Map':t/0 -apps/explorer/lib/explorer/indexer/block_fetcher.ex:239: The created fun has no local return apps/explorer/lib/explorer/indexer/block_fetcher.ex:246: The created fun has no local return +apps/explorer/lib/explorer/indexer/block_fetcher.ex:253: The created fun has no local return diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 7c2258d962..cd8956a837 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -40,7 +40,7 @@ defmodule Explorer.Chain do @typep inserted_after_option :: {:inserted_after, DateTime.t()} @typep necessity_by_association_option :: {:necessity_by_association, necessity_by_association} @typep pagination_option :: {:pagination, pagination} - @typep timestamps :: %{inserted_at: DateTime.t(), updated_at: DateTime.t()} + @typep timestamps :: %{inserted_at: %Ecto.DateTime{}, updated_at: %Ecto.DateTime{}} @typep timestamps_option :: {:timestamps, timestamps} # Functions @@ -757,7 +757,9 @@ defmodule Explorer.Chain do * `:pagination` - pagination params to pass to scrivener. """ - @spec recent_pending_transactions([inserted_after_option | necessity_by_association_option]) :: [Transaction.t()] + @spec recent_pending_transactions([inserted_after_option | necessity_by_association_option]) :: %Scrivener.Page{ + entries: [Transaction.t()] + } def recent_pending_transactions(options \\ []) when is_list(options) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) pagination = Keyword.get(options, :pagination, %{}) @@ -924,7 +926,11 @@ defmodule Explorer.Chain do * `:pagination` - pagination params to pass to scrivener. """ - @spec transaction_hash_to_internal_transactions(Hash.Full.t()) :: [InternalTransaction.t()] + @spec transaction_hash_to_internal_transactions(Hash.Full.t()) :: %Scrivener.Page{entries: [InternalTransaction.t()]} + @spec transaction_hash_to_internal_transactions(Hash.Full.t(), [necessity_by_association_option | pagination_option]) :: + %Scrivener.Page{ + entries: [InternalTransaction.t()] + } def transaction_hash_to_internal_transactions( %Hash{byte_count: unquote(Hash.Full.byte_count())} = hash, options \\ [] @@ -1073,6 +1079,7 @@ defmodule Explorer.Chain do end) end + @spec ecto_schema_module_to_changes_list_to_address_hash_set(%{module => [map()]}) :: MapSet.t(Hash.Truncated.t()) defp ecto_schema_module_to_changes_list_to_address_hash_set(ecto_schema_module_to_changes_list) do Enum.reduce(ecto_schema_module_to_changes_list, MapSet.new(), fn ecto_schema_module_changes_list, acc -> ecto_schema_module_changes_list @@ -1109,7 +1116,7 @@ defmodule Explorer.Chain do ) end - @spec insert_addresses([map()], [timestamps_option]) :: {:ok, Block.t()} | {:error, [Changeset.t()]} + @spec insert_addresses([%{hash: Hash.Truncated.t()}], [timestamps_option, ...]) :: {:ok, [Hash.Truncated.t()]} defp insert_addresses(changes_list, named_arguments) when is_list(changes_list) and is_list(named_arguments) do timestamps = Keyword.fetch!(named_arguments, :timestamps) @@ -1261,6 +1268,7 @@ defmodule Explorer.Chain do Enum.map(changes_list, ×tamp_params(&1, timestamps)) end + @spec timestamps() :: timestamps defp timestamps do now = Ecto.DateTime.utc() %{inserted_at: now, updated_at: now} diff --git a/apps/explorer/lib/explorer/chain/address.ex b/apps/explorer/lib/explorer/chain/address.ex index 30268b6f9c..7816aca8c6 100644 --- a/apps/explorer/lib/explorer/chain/address.ex +++ b/apps/explorer/lib/explorer/chain/address.ex @@ -63,6 +63,7 @@ defmodule Explorer.Chain.Address do |> unique_constraint(:hash) end + @spec hash_set_to_changes_list(MapSet.t(Hash.Truncated.t())) :: [%{hash: Hash.Truncated.t()}] def hash_set_to_changes_list(hash_set) do Enum.map(hash_set, &hash_to_changes/1) end From ee6a82c19f0ba8311a8f1ba00ae0bb5a8f8ca7f1 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Thu, 3 May 2018 14:13:41 -0500 Subject: [PATCH 20/77] Disable credo MaxLineLength Let `mix format` handle it so that `mix credo` doesn't flag `mix format` bugs. --- .credo.exs | 3 ++- apps/explorer/lib/explorer/chain.ex | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.credo.exs b/.credo.exs index da7e5d1501..ed2e6ecbf1 100644 --- a/.credo.exs +++ b/.credo.exs @@ -84,7 +84,8 @@ {Credo.Check.Design.TagFIXME}, {Credo.Check.Readability.FunctionNames}, {Credo.Check.Readability.LargeNumbers}, - {Credo.Check.Readability.MaxLineLength, priority: :low, max_length: 120}, + # mix format handles line length and using both hits some `mix format` bugs + {Credo.Check.Readability.MaxLineLength, false}, {Credo.Check.Readability.ModuleAttributeNames}, {Credo.Check.Readability.ModuleDoc}, {Credo.Check.Readability.ModuleNames}, diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index cd8956a837..f6d9cf4a9f 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -928,9 +928,7 @@ defmodule Explorer.Chain do """ @spec transaction_hash_to_internal_transactions(Hash.Full.t()) :: %Scrivener.Page{entries: [InternalTransaction.t()]} @spec transaction_hash_to_internal_transactions(Hash.Full.t(), [necessity_by_association_option | pagination_option]) :: - %Scrivener.Page{ - entries: [InternalTransaction.t()] - } + %Scrivener.Page{entries: [InternalTransaction.t()]} def transaction_hash_to_internal_transactions( %Hash{byte_count: unquote(Hash.Full.byte_count())} = hash, options \\ [] From 195b50f14ac8e2bdebc00af3dbadaa194f54284f Mon Sep 17 00:00:00 2001 From: Chris McCord Date: Fri, 4 May 2018 13:55:09 -0400 Subject: [PATCH 21/77] Transition to realtime index and limit address task spawning --- apps/explorer/lib/explorer/chain.ex | 10 +- .../lib/explorer/indexer/address_fetcher.ex | 118 +++++++----- .../lib/explorer/indexer/block_fetcher.ex | 176 ++++++++---------- .../lib/explorer/indexer/block_importer.ex | 36 ++++ .../lib/explorer/indexer/supervisor.ex | 10 +- apps/explorer/lib/explorer/jsonrpc.ex | 24 ++- .../explorer/lib/explorer/jsonrpc/receipts.ex | 32 ++-- apps/explorer/mix.exs | 11 +- mix.exs | 7 +- 9 files changed, 233 insertions(+), 191 deletions(-) create mode 100644 apps/explorer/lib/explorer/indexer/block_importer.ex diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index f6d9cf4a9f..6adf799425 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -411,11 +411,11 @@ defmodule Explorer.Chain do """ def import_blocks(%{ - blocks_params: blocks_params, - logs_params: logs_params, - internal_transactions_params: internal_transactions_params, - receipts_params: receipts_params, - transactions_params: transactions_params + blocks: blocks_params, + logs: logs_params, + internal_transactions: internal_transactions_params, + receipts: receipts_params, + transactions: transactions_params }) when is_list(blocks_params) and is_list(internal_transactions_params) and is_list(logs_params) and is_list(receipts_params) and is_list(transactions_params) do diff --git a/apps/explorer/lib/explorer/indexer/address_fetcher.ex b/apps/explorer/lib/explorer/indexer/address_fetcher.ex index 27eec1492a..0ac7c906b6 100644 --- a/apps/explorer/lib/explorer/indexer/address_fetcher.ex +++ b/apps/explorer/lib/explorer/indexer/address_fetcher.ex @@ -13,7 +13,8 @@ defmodule Explorer.Indexer.AddressFetcher do } @fetch_interval :timer.seconds(3) - @max_batch_size 500 + @max_batch_size 100 + @max_concurrency 2 def async_fetch_balances(address_hashes) do GenServer.cast(__MODULE__, {:buffer_addresses, address_hashes}) @@ -26,17 +27,25 @@ defmodule Explorer.Indexer.AddressFetcher do def init(_opts) do send(self(), :fetch_unfetched_addresses) - {:ok, %{buffer: MapSet.new(), tasks: %{}}} + state = %{ + flush_timer: nil, + fetch_interval: @fetch_interval, + buffer: :queue.new(), + tasks: %{} + } + + {:ok, state} end def handle_info(:fetch_unfetched_addresses, state) do - schedule_next_buffer_fetch(0) {:noreply, stream_unfetched_addresses(state)} end - def handle_info(:buffer_fetch, state) do - schedule_next_buffer_fetch() - {:noreply, flush_buffer(state)} + def handle_info(:flush, state) do + {:noreply, state |> fetch_next_batch([]) |> schedule_next_buffer_flush()} + end + def handle_info({:async_fetch, hashes}, state) do + {:noreply, fetch_next_batch(state, hashes)} end def handle_info({ref, {:fetched_balances, results}}, state) do @@ -60,69 +69,82 @@ defmodule Explorer.Indexer.AddressFetcher do end def handle_cast({:buffer_addresses, address_hashes}, state) do - {:noreply, buffer_addresses(state, address_hashes)} + string_hashes = for hash <- address_hashes, do: Hash.to_string(hash) + {:noreply, buffer_addresses(state, string_hashes)} end defp drop_task(state, ref) do + schedule_async_fetch([]) %{state | tasks: Map.delete(state.tasks, ref)} end - defp buffer_addresses(state, address_hashes) do - string_hashes = for hash <- address_hashes, do: Hash.to_string(hash) - %{state | buffer: MapSet.union(state.buffer, MapSet.new(string_hashes))} + defp buffer_addresses(state, string_hashes) do + %{state | buffer: :queue.join(state.buffer, :queue.from_list(string_hashes))} end defp stream_unfetched_addresses(state) do - tasks = - {state.tasks, state.buffer} - |> Chain.stream_unfetched_addresses(fn %Address{hash: hash}, {tasks, batch} -> - batch = MapSet.put(batch, Hash.to_string(hash)) - - if MapSet.size(batch) >= @max_batch_size do - task = do_async_fetch_balances(batch) - {Map.put(tasks, task.ref, batch), MapSet.new()} - else - {tasks, batch} - end - end) - |> fetch_remaining() + state.buffer + |> Chain.stream_unfetched_addresses(fn %Address{hash: hash}, batch -> + batch = :queue.in(Hash.to_string(hash), batch) + + if :queue.len(batch) >= @max_batch_size do + schedule_async_fetch(:queue.to_list(batch)) + :queue.new() + else + batch + end + end) + |> fetch_remaining() - %{state | tasks: tasks} + schedule_next_buffer_flush(state) end - defp fetch_remaining({:ok, {tasks, batch}}) do - if MapSet.size(batch) > 0 do - task = do_async_fetch_balances(batch) - Map.put(tasks, task.ref, batch) - else - tasks + defp fetch_remaining({:ok, batch}) do + if :queue.len(batch) > 0 do + schedule_async_fetch(:queue.to_list(batch)) end + :ok end - defp flush_buffer(state) do - if MapSet.size(state.buffer) > 0 do - task = do_async_fetch_balances(state.buffer) - new_tasks = Map.put(state.tasks, task.ref, state.buffer) + defp do_fetch_addresses(address_hashes) do + JSONRPC.fetch_balances_by_hash(address_hashes) + end - %{state | tasks: new_tasks, buffer: MapSet.new()} - else - state - end + defp take_batch(queue) do + {hashes, remaining_queue} = + Enum.reduce_while(1..@max_batch_size, {[], queue}, fn _, {hashes, queue_acc} -> + case :queue.out(queue_acc) do + {{:value, hash}, new_queue} -> {:cont, {[hash | hashes], new_queue}} + {:empty, new_queue} -> {:halt, {hashes, new_queue}} + end + end) + + {Enum.reverse(hashes), remaining_queue} end - defp schedule_next_buffer_fetch(after_ms \\ @fetch_interval) do - Process.send_after(self(), :buffer_fetch, after_ms) + defp schedule_async_fetch(hashes, after_ms \\ 0) do + Process.send_after(self(), {:async_fetch, hashes}, after_ms) end - defp do_fetch_addresses(address_hashes) do - JSONRPC.fetch_balances_by_hash(address_hashes) + defp schedule_next_buffer_flush(state) do + timer = Process.send_after(self(), :flush, state.fetch_interval) + %{state | flush_timer: timer} end - defp do_async_fetch_balances(hashes_mapset) do - Task.Supervisor.async_nolink(Explorer.Indexer.TaskSupervisor, fn -> - Logger.debug(fn -> "fetching #{MapSet.size(hashes_mapset)} balances" end) - {:ok, balances} = do_fetch_addresses(Enum.to_list(hashes_mapset)) - {:fetched_balances, balances} - end) + defp fetch_next_batch(state, hashes) do + state = buffer_addresses(state, hashes) + + if Enum.count(state.tasks) < @max_concurrency and :queue.len(state.buffer) > 0 do + {batch, new_queue} = take_batch(state.buffer) + task = Task.Supervisor.async_nolink(Explorer.Indexer.TaskSupervisor, fn -> + Logger.debug(fn -> "fetching #{Enum.count(batch)} balances" end) + {:ok, balances} = do_fetch_addresses(batch) + {:fetched_balances, balances} + end) + + %{state | tasks: Map.put(state.tasks, task.ref, batch), buffer: new_queue} + else + buffer_addresses(state, hashes) + end end end diff --git a/apps/explorer/lib/explorer/indexer/block_fetcher.ex b/apps/explorer/lib/explorer/indexer/block_fetcher.ex index c8f3882af6..f5036c09dc 100644 --- a/apps/explorer/lib/explorer/indexer/block_fetcher.ex +++ b/apps/explorer/lib/explorer/indexer/block_fetcher.ex @@ -1,10 +1,6 @@ defmodule Explorer.Indexer.BlockFetcher do @moduledoc """ - TODO - - ## Next steps - - - after gensis index transition to RT index + Fetches and indexes block ranges from gensis to realtime. """ use GenServer @@ -15,39 +11,50 @@ defmodule Explorer.Indexer.BlockFetcher do alias Explorer.Indexer.{ Sequence, - AddressFetcher + BlockImporter, } alias Explorer.JSONRPC.Transactions - # Struct - - defstruct ~w(current_block genesis_task subscription_id)a - - # Constants - @batch_size 50 - @blocks_concurrency 20 + @blocks_concurrency 10 @internal_batch_size 50 @internal_concurrency 8 - @polling_interval 20_000 + @block_rate 5_000 @receipts_batch_size 250 @receipts_concurrency 20 - # Functions + @doc """ + Starts the server. + + ## Options + + * `:block_rate` - The millisecond rate new blocks are published at. + Defaults to `#{@block_rate}`. + """ def start_link(opts) do GenServer.start_link(__MODULE__, opts, name: __MODULE__) end - ## GenServer callbacks - @impl GenServer + def init(opts) do + send(self(), :catchup_index) + :timer.send_interval(15_000, self(), :debug_count) - def handle_info(:index, state) do + state = %{ + genesis_task: nil, + realtime_interval: (opts[:block_rate] || @block_rate) * 2, + } + + {:ok, state} + end + + @impl GenServer + def handle_info(:catchup_index, state) do {count, missing_ranges} = missing_block_numbers() current_block = Indexer.next_block_number() @@ -55,35 +62,34 @@ defmodule Explorer.Indexer.BlockFetcher do {:ok, genesis_task} = Task.start_link(fn -> - stream_import(missing_ranges, current_block) + {:ok, seq} = Sequence.start_link(missing_ranges, current_block, @batch_size) + stream_import(seq, max_concurrency: @blocks_concurrency) end) Process.monitor(genesis_task) - {:noreply, %__MODULE__{state | genesis_task: genesis_task}} + {:noreply, %{state | genesis_task: genesis_task}} end - def handle_info(:poll, %__MODULE__{subscription_id: subscription_id} = state) do - Process.send_after(self(), :poll, @polling_interval) - - with {:ok, blocks} when length(blocks) > 0 <- JSONRPC.check_for_updates(subscription_id) do - Logger.debug(fn -> "Processing #{length(blocks)} new block(s)" end) + def handle_info(:realtime_index, state) do + {:ok, realtime_task} = + Task.start_link(fn -> + {:ok, seq} = Sequence.start_link([], Indexer.next_block_number(), 2) + stream_import(seq, max_concurrency: 1) + end) - # TODO do something with the new blocks - JSONRPC.fetch_blocks_by_hash(blocks) - end + Process.monitor(realtime_task) - {:noreply, state} + {:noreply, %{state | realtime_task: realtime_task}} end - def handle_info({:DOWN, _ref, :process, pid, :normal}, %__MODULE__{genesis_task: pid} = state) do - Logger.info(fn -> "Finished index from genesis" end) - - {:ok, subscription_id} = JSONRPC.listen_for_new_blocks() - - send(self(), :poll) + def handle_info({:DOWN, _ref, :process, pid, :normal}, %{realtime_task: pid} = state) do + {:noreply, schedule_next_realtime_fetch(%{state | realtime_task: nil})} + end - {:noreply, %__MODULE__{state | genesis_task: nil, subscription_id: subscription_id}} + def handle_info({:DOWN, _ref, :process, pid, :normal}, %{genesis_task: pid} = state) do + Logger.info(fn -> "Finished index from genesis. Transitioning to realtime index." end) + {:noreply, schedule_next_realtime_fetch(%{state | genesis_task: nil})} end def handle_info(:debug_count, state) do @@ -104,18 +110,7 @@ defmodule Explorer.Indexer.BlockFetcher do {:noreply, state} end - @impl GenServer - def init(_opts) do - send(self(), :index) - :timer.send_interval(15_000, self(), :debug_count) - - {:ok, %__MODULE__{current_block: 0, genesis_task: nil, subscription_id: nil}} - end - - ## Private Functions - - defp cap_seq(seq, :end_of_chain, {_block_start, block_end}) do - Logger.info("Reached end of blockchain #{inspect(block_end)}") + defp cap_seq(seq, :end_of_chain, {_block_start, _block_end}) do :ok = Sequence.cap(seq) end @@ -134,13 +129,13 @@ defmodule Explorer.Indexer.BlockFetcher do |> Enum.chunk_every(@internal_batch_size) |> Task.async_stream(&JSONRPC.fetch_internal_transactions(&1), stream_opts) |> Enum.reduce_while({:ok, []}, fn - {:ok, {:ok, internal_transactions_params}}, {:ok, acc} -> {:cont, {:ok, acc ++ internal_transactions_params}} + {:ok, {:ok, internal_transactions}}, {:ok, acc} -> {:cont, {:ok, acc ++ internal_transactions}} {:ok, {:error, reason}}, {:ok, _acc} -> {:halt, {:error, reason}} {:error, reason}, {:ok, _acc} -> {:halt, {:error, reason}} end) end - defp fetch_transaction_receipts([]), do: {:ok, %{logs_params: [], receipts_params: []}} + defp fetch_transaction_receipts([]), do: {:ok, %{logs: [], receipts: []}} defp fetch_transaction_receipts(hashes) do Logger.debug(fn -> "fetching #{length(hashes)} transaction receipts" end) @@ -149,11 +144,10 @@ defmodule Explorer.Indexer.BlockFetcher do hashes |> Enum.chunk_every(@receipts_batch_size) |> Task.async_stream(&JSONRPC.fetch_transaction_receipts(&1), stream_opts) - |> Enum.reduce_while({:ok, %{logs_params: [], receipts_params: []}}, fn - {:ok, {:ok, %{logs_params: logs_params, receipts_params: receipts_params}}}, - {:ok, %{logs_params: acc_log_params, receipts_params: acc_receipts_params}} -> - {:cont, - {:ok, %{logs_params: acc_log_params ++ logs_params, receipts_params: acc_receipts_params ++ receipts_params}}} + |> Enum.reduce_while({:ok, %{logs: [], receipts: []}}, fn + {:ok, {:ok, %{logs: logs, receipts: receipts}}}, + {:ok, %{logs: acc_logs, receipts: acc_receipts}} -> + {:cont, {:ok, %{logs: acc_logs ++ logs, receipts: acc_receipts ++ receipts}}} {:ok, {:error, reason}}, {:ok, _acc} -> {:halt, {:error, reason}} @@ -163,27 +157,12 @@ defmodule Explorer.Indexer.BlockFetcher do end) end - defp insert(%{ - blocks_params: blocks_params, - internal_transactions_params: internal_transactions_params, - logs_params: log_params, - range: range, - receipts_params: receipt_params, - seq: seq, - transactions_params: transactions_params - }) do - case Chain.import_blocks(%{ - blocks_params: blocks_params, - internal_transactions_params: internal_transactions_params, - logs_params: log_params, - receipts_params: receipt_params, - transactions_params: transactions_params - }) do - {:ok, %{addresses: address_hashes}} -> - :ok = AddressFetcher.async_fetch_balances(address_hashes) + defp insert(seq, range, params) do + case BlockImporter.import_blocks(params) do + :ok -> :ok - {:error, step, reason, _changes} -> + {:error, step, reason} -> Logger.debug(fn -> "failed to insert blocks during #{step} #{inspect(range)}: #{inspect(reason)}. Retrying" end) @@ -216,50 +195,43 @@ defmodule Explorer.Indexer.BlockFetcher do {count, chunked_ranges} end - defp stream_import(missing_ranges, current_block) do - {:ok, seq} = Sequence.start_link(missing_ranges, current_block, @batch_size) - + defp stream_import(seq, task_opts) do seq |> Sequence.build_stream() |> Task.async_stream( fn {block_start, block_end} = range -> - with {:ok, value} <- JSONRPC.fetch_blocks_by_range(block_start, block_end), - # `mix format` bug made the line too long when pattern combined into above line - %{next: next, blocks_params: blocks_params, range: range, transactions_params: transactions_params} = - value, + with {:ok, next, result} <- JSONRPC.fetch_blocks_by_range(block_start, block_end), + %{blocks: blocks, transactions: transactions} <- result, :ok <- cap_seq(seq, next, range), - transaction_hashes <- Transactions.params_to_hashes(transactions_params), - {:ok, %{logs_params: logs_params, receipts_params: receipts_params}} <- - fetch_transaction_receipts(transaction_hashes), - {:ok, internal_transactions_params} <- fetch_internal_transactions(transaction_hashes) do - insert(%{ - blocks_params: blocks_params, - internal_transactions_params: internal_transactions_params, - logs_params: logs_params, - range: range, - receipts_params: receipts_params, - seq: seq, - transactions_params: transactions_params + transaction_hashes <- Transactions.params_to_hashes(transactions), + {:ok, receipt_params} <- fetch_transaction_receipts(transaction_hashes), + %{logs: logs, receipts: receipts} <- receipt_params, + {:ok, internal_transactions} <- fetch_internal_transactions(transaction_hashes) do + + insert(seq, range, %{ + blocks: blocks, + internal_transactions: internal_transactions, + logs: logs, + receipts: receipts, + transactions: transactions }) + else {:error, reason} -> Logger.debug(fn -> "failed to fetch blocks #{inspect(range)}: #{inspect(reason)}. Retrying" end) - :ok = Sequence.inject_range(seq, range) - - {:error, reason, range} -> - Logger.debug(fn -> - "failed to fetch blocks #{inspect(range)}: #{inspect(reason)}. Retrying" - end) - :ok = Sequence.inject_range(seq, range) end end, - max_concurrency: @blocks_concurrency, - timeout: :infinity + Keyword.merge(task_opts, timeout: :infinity) ) |> Enum.each(fn {:ok, :ok} -> :ok end) end + + defp schedule_next_realtime_fetch(state) do + timer = Process.send_after(self(), :realtime_index, state.realtime_interval) + %{state | poll_timer: timer} + end end diff --git a/apps/explorer/lib/explorer/indexer/block_importer.ex b/apps/explorer/lib/explorer/indexer/block_importer.ex new file mode 100644 index 0000000000..0364ab7263 --- /dev/null +++ b/apps/explorer/lib/explorer/indexer/block_importer.ex @@ -0,0 +1,36 @@ +defmodule Explorer.Indexer.BlockImporter do + @moduledoc """ + Imports blocks to the chain. + + Batched block ranges are serialized through the importer to avoid + races and lock contention against conurrent address upserts. + """ + + use GenServer + + alias Explorer.Chain + alias Explorer.Indexer.AddressFetcher + + def import_blocks(blocks) do + GenServer.call(__MODULE__, {:import, blocks}) + end + + def start_link(opts) do + GenServer.start_link(__MODULE__, opts, name: __MODULE__) + end + + def init(_opts) do + {:ok, %{}} + end + + def handle_call({:import, blocks}, _from, state) do + case Chain.import_blocks(blocks) do + {:ok, %{addresses: address_hashes}} -> + :ok = AddressFetcher.async_fetch_balances(address_hashes) + {:reply, :ok, state} + + {:error, step, reason, _changes} -> + {:reply, {:error, step, reason}, state} + end + end +end diff --git a/apps/explorer/lib/explorer/indexer/supervisor.ex b/apps/explorer/lib/explorer/indexer/supervisor.ex index 9ff3bba149..1f25943d24 100644 --- a/apps/explorer/lib/explorer/indexer/supervisor.ex +++ b/apps/explorer/lib/explorer/indexer/supervisor.ex @@ -7,23 +7,21 @@ defmodule Explorer.Indexer.Supervisor do alias Explorer.Indexer.{ BlockFetcher, - AddressFetcher + BlockImporter, + AddressFetcher, } - # Functions - def start_link(opts) do Supervisor.start_link(__MODULE__, opts) end - ## Supervisor callbacks - @impl Supervisor def init(_opts) do children = [ {Task.Supervisor, name: Explorer.Indexer.TaskSupervisor}, {BlockFetcher, []}, - {AddressFetcher, []} + {BlockImporter, []}, + {AddressFetcher, []}, ] Supervisor.init(children, strategy: :rest_for_one) diff --git a/apps/explorer/lib/explorer/jsonrpc.ex b/apps/explorer/lib/explorer/jsonrpc.ex index 22f3c3a50d..776e5049c5 100644 --- a/apps/explorer/lib/explorer/jsonrpc.ex +++ b/apps/explorer/lib/explorer/jsonrpc.ex @@ -147,7 +147,13 @@ defmodule Explorer.JSONRPC do } end - json_rpc(batched_requests, config(:url)) + batched_requests + |> json_rpc(config(:url)) + |> handle_get_block_by_number() + |> case do + {:ok, _next, results} -> {:ok, results} + {:error, reason} -> {:error, reason} + end end @doc """ @@ -157,7 +163,7 @@ defmodule Explorer.JSONRPC do block_start |> build_batch_get_block_by_number(block_end) |> json_rpc(config(:url)) - |> handle_get_block_by_number(block_start, block_end) + |> handle_get_block_by_number() end @doc """ @@ -266,7 +272,7 @@ defmodule Explorer.JSONRPC do raise("bad jason") end - defp handle_get_block_by_number({:ok, results}, block_start, block_end) do + defp handle_get_block_by_number({:ok, results}) do {blocks, next} = Enum.reduce(results, {[], :more}, fn %{"result" => nil}, {blocks, _} -> {blocks, :end_of_chain} @@ -278,17 +284,15 @@ defmodule Explorer.JSONRPC do blocks_params = Blocks.elixir_to_params(elixir_blocks) transactions_params = Transactions.elixir_to_params(elixir_transactions) - {:ok, + {:ok, next, %{ - next: next, - blocks_params: blocks_params, - range: {block_start, block_end}, - transactions_params: transactions_params + blocks: blocks_params, + transactions: transactions_params }} end - defp handle_get_block_by_number({:error, reason}, block_start, block_end) do - {:error, reason, {block_start, block_end}} + defp handle_get_block_by_number({:error, reason}) do + {:error, reason} end defp handle_response(resp, 200) do diff --git a/apps/explorer/lib/explorer/jsonrpc/receipts.ex b/apps/explorer/lib/explorer/jsonrpc/receipts.ex index b141c27911..e9751d464f 100644 --- a/apps/explorer/lib/explorer/jsonrpc/receipts.ex +++ b/apps/explorer/lib/explorer/jsonrpc/receipts.ex @@ -27,20 +27,24 @@ defmodule Explorer.JSONRPC.Receipts do end def fetch(hashes) when is_list(hashes) do - with {:ok, responses} <- - hashes - |> Enum.map(&hash_to_json/1) - |> json_rpc(config(:url)) do - elixir_receipts = - responses - |> responses_to_receipts() - |> to_elixir() - - elixir_logs = elixir_to_logs(elixir_receipts) - receipts_params = elixir_to_params(elixir_receipts) - logs_params = Logs.elixir_to_params(elixir_logs) - - {:ok, %{logs_params: logs_params, receipts_params: receipts_params}} + hashes + |> Enum.map(&hash_to_json/1) + |> json_rpc(config(:url)) + |> case do + {:ok, responses} -> + elixir_receipts = + responses + |> responses_to_receipts() + |> to_elixir() + + elixir_logs = elixir_to_logs(elixir_receipts) + receipts = elixir_to_params(elixir_receipts) + logs = Logs.elixir_to_params(elixir_logs) + + {:ok, %{logs: logs, receipts: receipts}} + + {:error, _reason} = err -> + err end end diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index 68dcf2b4fd..eb37aed4b4 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -3,7 +3,7 @@ defmodule Explorer.Mixfile do def project do [ - aliases: aliases(), + aliases: aliases(Mix.env()), app: :explorer, build_path: "../../_build", config_path: "../../config/config.exs", @@ -98,13 +98,16 @@ defmodule Explorer.Mixfile do # $ mix ecto.setup # # See the documentation for `Mix` for more info on aliases. - defp aliases do + defp aliases(env) do [ - compile: "compile --warnings-as-errors", "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], "ecto.reset": ["ecto.drop", "ecto.setup"], test: ["ecto.drop", "ecto.create --quiet", "ecto.migrate", "test"] - ] + ] ++ env_aliases(env) + end + defp env_aliases(:dev), do: [] + defp env_aliases(_env) do + [compile: "compile --warnings-as-errors"] end defp package do diff --git a/mix.exs b/mix.exs index a7c7592385..6b7e26a243 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule ExplorerUmbrella.Mixfile do def project do [ - aliases: aliases(), + aliases: aliases(Mix.env()), apps_path: "apps", deps: deps(), dialyzer: [ @@ -27,7 +27,10 @@ defmodule ExplorerUmbrella.Mixfile do ## Private Functions - defp aliases do + defp aliases(:dev) do + [] + end + defp aliases(_env) do [ compile: "compile --warnings-as-errors" ] From c1b0a71c1bc8afd7e6a00464d490d13c788ea9ea Mon Sep 17 00:00:00 2001 From: Chris McCord Date: Fri, 4 May 2018 14:13:27 -0400 Subject: [PATCH 22/77] Add configuration based verbose debug logs --- apps/explorer/config/config.exs | 4 ++ .../lib/explorer/indexer/address_fetcher.ex | 9 +++- .../lib/explorer/indexer/block_fetcher.ex | 52 +++++++++++-------- 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index e4a4b52a52..78f548ad38 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -7,6 +7,10 @@ use Mix.Config url = "https://sokol.poa.network" +config :explorer, :indexer, + block_rate: 5_000, + debug_logs: !!System.get_env("DEBUG_LOGS") + config :ethereumex, url: url, http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :eth]] diff --git a/apps/explorer/lib/explorer/indexer/address_fetcher.ex b/apps/explorer/lib/explorer/indexer/address_fetcher.ex index 0ac7c906b6..39311ef1fd 100644 --- a/apps/explorer/lib/explorer/indexer/address_fetcher.ex +++ b/apps/explorer/lib/explorer/indexer/address_fetcher.ex @@ -24,10 +24,12 @@ defmodule Explorer.Indexer.AddressFetcher do GenServer.start_link(__MODULE__, opts, name: __MODULE__) end - def init(_opts) do + def init(opts) do + opts = Keyword.merge(Application.fetch_env!(:explorer, :indexer), opts) send(self(), :fetch_unfetched_addresses) state = %{ + debug_logs: Keyword.get(opts, :debug_logs, false), flush_timer: nil, fetch_interval: @fetch_interval, buffer: :queue.new(), @@ -137,7 +139,7 @@ defmodule Explorer.Indexer.AddressFetcher do if Enum.count(state.tasks) < @max_concurrency and :queue.len(state.buffer) > 0 do {batch, new_queue} = take_batch(state.buffer) task = Task.Supervisor.async_nolink(Explorer.Indexer.TaskSupervisor, fn -> - Logger.debug(fn -> "fetching #{Enum.count(batch)} balances" end) + debug(state, fn -> "fetching #{Enum.count(batch)} balances" end) {:ok, balances} = do_fetch_addresses(batch) {:fetched_balances, balances} end) @@ -147,4 +149,7 @@ defmodule Explorer.Indexer.AddressFetcher do buffer_addresses(state, hashes) end end + + defp debug(%{debug_logs: true}, func), do: Logger.debug(func) + defp debug(%{debug_logs: false}, _func), do: :noop end diff --git a/apps/explorer/lib/explorer/indexer/block_fetcher.ex b/apps/explorer/lib/explorer/indexer/block_fetcher.ex index f5036c09dc..f27c009b82 100644 --- a/apps/explorer/lib/explorer/indexer/block_fetcher.ex +++ b/apps/explorer/lib/explorer/indexer/block_fetcher.ex @@ -33,6 +33,10 @@ defmodule Explorer.Indexer.BlockFetcher do ## Options + Default options are pulled from application config under the + `:explorer, :index` keyspace. The follow options can be overridden: + + * `:debug_logs` - When `true` logs verbose index progress. Defaults `false`. * `:block_rate` - The millisecond rate new blocks are published at. Defaults to `#{@block_rate}`. """ @@ -42,11 +46,14 @@ defmodule Explorer.Indexer.BlockFetcher do @impl GenServer def init(opts) do + opts = Keyword.merge(Application.fetch_env!(:explorer, :indexer), opts) + send(self(), :catchup_index) :timer.send_interval(15_000, self(), :debug_count) state = %{ genesis_task: nil, + debug_logs: Keyword.get(opts, :debug_logs, false), realtime_interval: (opts[:block_rate] || @block_rate) * 2, } @@ -58,12 +65,12 @@ defmodule Explorer.Indexer.BlockFetcher do {count, missing_ranges} = missing_block_numbers() current_block = Indexer.next_block_number() - Logger.debug(fn -> "#{count} missed block ranges between genesis and #{current_block}" end) + debug(state, fn -> "#{count} missed block ranges between genesis and #{current_block}" end) {:ok, genesis_task} = Task.start_link(fn -> {:ok, seq} = Sequence.start_link(missing_ranges, current_block, @batch_size) - stream_import(seq, max_concurrency: @blocks_concurrency) + stream_import(state, seq, max_concurrency: @blocks_concurrency) end) Process.monitor(genesis_task) @@ -75,7 +82,7 @@ defmodule Explorer.Indexer.BlockFetcher do {:ok, realtime_task} = Task.start_link(fn -> {:ok, seq} = Sequence.start_link([], Indexer.next_block_number(), 2) - stream_import(seq, max_concurrency: 1) + stream_import(state, seq, max_concurrency: 1) end) Process.monitor(realtime_task) @@ -93,7 +100,7 @@ defmodule Explorer.Indexer.BlockFetcher do end def handle_info(:debug_count, state) do - Logger.debug(fn -> + debug(state, fn -> """ ================================ @@ -110,19 +117,19 @@ defmodule Explorer.Indexer.BlockFetcher do {:noreply, state} end - defp cap_seq(seq, :end_of_chain, {_block_start, _block_end}) do + defp cap_seq(seq, :end_of_chain, {_block_start, _block_end}, _state) do :ok = Sequence.cap(seq) end - defp cap_seq(_seq, :more, {block_start, block_end}) do - Logger.debug(fn -> "got blocks #{block_start} - #{block_end}" end) + defp cap_seq(_seq, :more, {block_start, block_end}, state) do + debug(state, fn -> "got blocks #{block_start} - #{block_end}" end) :ok end - defp fetch_internal_transactions([]), do: {:ok, []} + defp fetch_internal_transactions(_state, []), do: {:ok, []} - defp fetch_internal_transactions(hashes) do - Logger.debug(fn -> "fetching #{length(hashes)} internal transactions" end) + defp fetch_internal_transactions(state, hashes) do + debug(state, fn -> "fetching #{length(hashes)} internal transactions" end) stream_opts = [max_concurrency: @internal_concurrency, timeout: :infinity] hashes @@ -135,10 +142,10 @@ defmodule Explorer.Indexer.BlockFetcher do end) end - defp fetch_transaction_receipts([]), do: {:ok, %{logs: [], receipts: []}} + defp fetch_transaction_receipts(_state, []), do: {:ok, %{logs: [], receipts: []}} - defp fetch_transaction_receipts(hashes) do - Logger.debug(fn -> "fetching #{length(hashes)} transaction receipts" end) + defp fetch_transaction_receipts(state, hashes) do + debug(state, fn -> "fetching #{length(hashes)} transaction receipts" end) stream_opts = [max_concurrency: @receipts_concurrency, timeout: :infinity] hashes @@ -157,13 +164,13 @@ defmodule Explorer.Indexer.BlockFetcher do end) end - defp insert(seq, range, params) do + defp insert(state, seq, range, params) do case BlockImporter.import_blocks(params) do :ok -> :ok {:error, step, reason} -> - Logger.debug(fn -> + debug(state, fn -> "failed to insert blocks during #{step} #{inspect(range)}: #{inspect(reason)}. Retrying" end) @@ -195,20 +202,20 @@ defmodule Explorer.Indexer.BlockFetcher do {count, chunked_ranges} end - defp stream_import(seq, task_opts) do + defp stream_import(state, seq, task_opts) do seq |> Sequence.build_stream() |> Task.async_stream( fn {block_start, block_end} = range -> with {:ok, next, result} <- JSONRPC.fetch_blocks_by_range(block_start, block_end), %{blocks: blocks, transactions: transactions} <- result, - :ok <- cap_seq(seq, next, range), + :ok <- cap_seq(seq, next, range, state), transaction_hashes <- Transactions.params_to_hashes(transactions), - {:ok, receipt_params} <- fetch_transaction_receipts(transaction_hashes), + {:ok, receipt_params} <- fetch_transaction_receipts(state, transaction_hashes), %{logs: logs, receipts: receipts} <- receipt_params, - {:ok, internal_transactions} <- fetch_internal_transactions(transaction_hashes) do + {:ok, internal_transactions} <- fetch_internal_transactions(state, transaction_hashes) do - insert(seq, range, %{ + insert(state, seq, range, %{ blocks: blocks, internal_transactions: internal_transactions, logs: logs, @@ -218,7 +225,7 @@ defmodule Explorer.Indexer.BlockFetcher do else {:error, reason} -> - Logger.debug(fn -> + debug(state, fn -> "failed to fetch blocks #{inspect(range)}: #{inspect(reason)}. Retrying" end) @@ -234,4 +241,7 @@ defmodule Explorer.Indexer.BlockFetcher do timer = Process.send_after(self(), :realtime_index, state.realtime_interval) %{state | poll_timer: timer} end + + defp debug(%{debug_logs: true}, func), do: Logger.debug(func) + defp debug(%{debug_logs: false}, _func), do: :noop end From 3500daaf14fedcfd578488cfed029470ee14a275 Mon Sep 17 00:00:00 2001 From: Chris McCord Date: Fri, 4 May 2018 14:19:38 -0400 Subject: [PATCH 23/77] typo --- apps/explorer/lib/explorer/indexer/block_fetcher.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/indexer/block_fetcher.ex b/apps/explorer/lib/explorer/indexer/block_fetcher.ex index f27c009b82..50cf8d98b1 100644 --- a/apps/explorer/lib/explorer/indexer/block_fetcher.ex +++ b/apps/explorer/lib/explorer/indexer/block_fetcher.ex @@ -34,7 +34,7 @@ defmodule Explorer.Indexer.BlockFetcher do ## Options Default options are pulled from application config under the - `:explorer, :index` keyspace. The follow options can be overridden: + `:explorer, :indexer` keyspace. The follow options can be overridden: * `:debug_logs` - When `true` logs verbose index progress. Defaults `false`. * `:block_rate` - The millisecond rate new blocks are published at. From d7a4706d036cd70549f2065fc669776f5aa6eb18 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Mon, 7 May 2018 20:04:35 -0500 Subject: [PATCH 24/77] mix format --- .../lib/explorer/indexer/address_fetcher.ex | 14 +++++++++----- .../explorer/lib/explorer/indexer/block_fetcher.ex | 10 +++------- apps/explorer/lib/explorer/indexer/supervisor.ex | 4 ++-- apps/explorer/lib/explorer/jsonrpc.ex | 2 +- apps/explorer/mix.exs | 2 ++ mix.exs | 1 + 6 files changed, 18 insertions(+), 15 deletions(-) diff --git a/apps/explorer/lib/explorer/indexer/address_fetcher.ex b/apps/explorer/lib/explorer/indexer/address_fetcher.ex index 39311ef1fd..b972f6c365 100644 --- a/apps/explorer/lib/explorer/indexer/address_fetcher.ex +++ b/apps/explorer/lib/explorer/indexer/address_fetcher.ex @@ -46,6 +46,7 @@ defmodule Explorer.Indexer.AddressFetcher do def handle_info(:flush, state) do {:noreply, state |> fetch_next_batch([]) |> schedule_next_buffer_flush()} end + def handle_info({:async_fetch, hashes}, state) do {:noreply, fetch_next_batch(state, hashes)} end @@ -105,6 +106,7 @@ defmodule Explorer.Indexer.AddressFetcher do if :queue.len(batch) > 0 do schedule_async_fetch(:queue.to_list(batch)) end + :ok end @@ -138,11 +140,13 @@ defmodule Explorer.Indexer.AddressFetcher do if Enum.count(state.tasks) < @max_concurrency and :queue.len(state.buffer) > 0 do {batch, new_queue} = take_batch(state.buffer) - task = Task.Supervisor.async_nolink(Explorer.Indexer.TaskSupervisor, fn -> - debug(state, fn -> "fetching #{Enum.count(batch)} balances" end) - {:ok, balances} = do_fetch_addresses(batch) - {:fetched_balances, balances} - end) + + task = + Task.Supervisor.async_nolink(Explorer.Indexer.TaskSupervisor, fn -> + debug(state, fn -> "fetching #{Enum.count(batch)} balances" end) + {:ok, balances} = do_fetch_addresses(batch) + {:fetched_balances, balances} + end) %{state | tasks: Map.put(state.tasks, task.ref, batch), buffer: new_queue} else diff --git a/apps/explorer/lib/explorer/indexer/block_fetcher.ex b/apps/explorer/lib/explorer/indexer/block_fetcher.ex index 50cf8d98b1..403ff6bba7 100644 --- a/apps/explorer/lib/explorer/indexer/block_fetcher.ex +++ b/apps/explorer/lib/explorer/indexer/block_fetcher.ex @@ -11,7 +11,7 @@ defmodule Explorer.Indexer.BlockFetcher do alias Explorer.Indexer.{ Sequence, - BlockImporter, + BlockImporter } alias Explorer.JSONRPC.Transactions @@ -27,7 +27,6 @@ defmodule Explorer.Indexer.BlockFetcher do @receipts_batch_size 250 @receipts_concurrency 20 - @doc """ Starts the server. @@ -54,7 +53,7 @@ defmodule Explorer.Indexer.BlockFetcher do state = %{ genesis_task: nil, debug_logs: Keyword.get(opts, :debug_logs, false), - realtime_interval: (opts[:block_rate] || @block_rate) * 2, + realtime_interval: (opts[:block_rate] || @block_rate) * 2 } {:ok, state} @@ -152,8 +151,7 @@ defmodule Explorer.Indexer.BlockFetcher do |> Enum.chunk_every(@receipts_batch_size) |> Task.async_stream(&JSONRPC.fetch_transaction_receipts(&1), stream_opts) |> Enum.reduce_while({:ok, %{logs: [], receipts: []}}, fn - {:ok, {:ok, %{logs: logs, receipts: receipts}}}, - {:ok, %{logs: acc_logs, receipts: acc_receipts}} -> + {:ok, {:ok, %{logs: logs, receipts: receipts}}}, {:ok, %{logs: acc_logs, receipts: acc_receipts}} -> {:cont, {:ok, %{logs: acc_logs ++ logs, receipts: acc_receipts ++ receipts}}} {:ok, {:error, reason}}, {:ok, _acc} -> @@ -214,7 +212,6 @@ defmodule Explorer.Indexer.BlockFetcher do {:ok, receipt_params} <- fetch_transaction_receipts(state, transaction_hashes), %{logs: logs, receipts: receipts} <- receipt_params, {:ok, internal_transactions} <- fetch_internal_transactions(state, transaction_hashes) do - insert(state, seq, range, %{ blocks: blocks, internal_transactions: internal_transactions, @@ -222,7 +219,6 @@ defmodule Explorer.Indexer.BlockFetcher do receipts: receipts, transactions: transactions }) - else {:error, reason} -> debug(state, fn -> diff --git a/apps/explorer/lib/explorer/indexer/supervisor.ex b/apps/explorer/lib/explorer/indexer/supervisor.ex index 1f25943d24..bedbdaac96 100644 --- a/apps/explorer/lib/explorer/indexer/supervisor.ex +++ b/apps/explorer/lib/explorer/indexer/supervisor.ex @@ -8,7 +8,7 @@ defmodule Explorer.Indexer.Supervisor do alias Explorer.Indexer.{ BlockFetcher, BlockImporter, - AddressFetcher, + AddressFetcher } def start_link(opts) do @@ -21,7 +21,7 @@ defmodule Explorer.Indexer.Supervisor do {Task.Supervisor, name: Explorer.Indexer.TaskSupervisor}, {BlockFetcher, []}, {BlockImporter, []}, - {AddressFetcher, []}, + {AddressFetcher, []} ] Supervisor.init(children, strategy: :rest_for_one) diff --git a/apps/explorer/lib/explorer/jsonrpc.ex b/apps/explorer/lib/explorer/jsonrpc.ex index 776e5049c5..53408d015f 100644 --- a/apps/explorer/lib/explorer/jsonrpc.ex +++ b/apps/explorer/lib/explorer/jsonrpc.ex @@ -151,7 +151,7 @@ defmodule Explorer.JSONRPC do |> json_rpc(config(:url)) |> handle_get_block_by_number() |> case do - {:ok, _next, results} -> {:ok, results} + {:ok, _next, results} -> {:ok, results} {:error, reason} -> {:error, reason} end end diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index eb37aed4b4..7dacf37811 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -105,7 +105,9 @@ defmodule Explorer.Mixfile do test: ["ecto.drop", "ecto.create --quiet", "ecto.migrate", "test"] ] ++ env_aliases(env) end + defp env_aliases(:dev), do: [] + defp env_aliases(_env) do [compile: "compile --warnings-as-errors"] end diff --git a/mix.exs b/mix.exs index 6b7e26a243..79bbdb3311 100644 --- a/mix.exs +++ b/mix.exs @@ -30,6 +30,7 @@ defmodule ExplorerUmbrella.Mixfile do defp aliases(:dev) do [] end + defp aliases(_env) do [ compile: "compile --warnings-as-errors" From 568e73c3de353eb5866a8407d0b4b5cd9196fc6e Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Mon, 7 May 2018 20:09:01 -0500 Subject: [PATCH 25/77] Move dialyzer ignore to match logging refactor --- .dialyzer-ignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.dialyzer-ignore b/.dialyzer-ignore index fddb449ee4..4a48b6b1ba 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -1,5 +1,4 @@ :0: Unknown function 'Elixir.ExUnit.Callbacks':'__merge__'/3 :0: Unknown function 'Elixir.ExUnit.CaseTemplate':'__proxy__'/2 :0: Unknown type 'Elixir.Map':t/0 -apps/explorer/lib/explorer/indexer/block_fetcher.ex:246: The created fun has no local return -apps/explorer/lib/explorer/indexer/block_fetcher.ex:253: The created fun has no local return +apps/explorer/lib/explorer/indexer/block_fetcher.ex:224: The created fun has no local return From 45c32667a221a0497a108e41abf8bc346d87df81 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 8 May 2018 08:33:28 -0500 Subject: [PATCH 26/77] Remove (sub)section comments --- apps/explorer/lib/explorer/application.ex | 6 ------ apps/explorer/lib/explorer/chain.ex | 6 ------ apps/explorer/lib/explorer/chain/address.ex | 4 ---- apps/explorer/lib/explorer/chain/block.ex | 8 -------- apps/explorer/lib/explorer/chain/credit.ex | 6 ------ apps/explorer/lib/explorer/chain/debit.ex | 6 ------ apps/explorer/lib/explorer/chain/hash.ex | 14 -------------- apps/explorer/lib/explorer/chain/hash/full.ex | 10 ---------- .../lib/explorer/chain/hash/truncated.ex | 10 ---------- .../lib/explorer/chain/internal_transaction.ex | 2 -- .../chain/internal_transaction/call_type.ex | 6 ------ .../explorer/chain/internal_transaction/type.ex | 6 ------ apps/explorer/lib/explorer/chain/log.ex | 8 -------- apps/explorer/lib/explorer/chain/receipt.ex | 8 -------- .../lib/explorer/chain/receipt/status.ex | 6 ------ apps/explorer/lib/explorer/chain/statistics.ex | 10 ---------- apps/explorer/lib/explorer/chain/transaction.ex | 10 ---------- apps/explorer/lib/explorer/chain/wei.ex | 4 ---- .../explorer/exchange_rates/exchange_rates.ex | 8 -------- apps/explorer/lib/explorer/indexer.ex | 2 -- apps/explorer/lib/explorer/indexer/sequence.ex | 6 ------ apps/explorer/lib/explorer/jsonrpc.ex | 6 ------ apps/explorer/lib/explorer/jsonrpc/block.ex | 4 ---- apps/explorer/lib/explorer/jsonrpc/blocks.ex | 4 ---- apps/explorer/lib/explorer/jsonrpc/log.ex | 6 ------ apps/explorer/lib/explorer/jsonrpc/logs.ex | 4 ---- apps/explorer/lib/explorer/jsonrpc/parity.ex | 4 ---- .../lib/explorer/jsonrpc/parity/trace.ex | 4 ---- .../lib/explorer/jsonrpc/parity/trace/action.ex | 2 -- .../lib/explorer/jsonrpc/parity/trace/result.ex | 2 -- apps/explorer/lib/explorer/jsonrpc/receipt.ex | 6 ------ apps/explorer/lib/explorer/jsonrpc/receipts.ex | 6 ------ .../explorer/lib/explorer/jsonrpc/transaction.ex | 6 ------ .../lib/explorer/jsonrpc/transactions.ex | 4 ---- .../lib/explorer/market/history/cataloger.ex | 4 ---- .../20180117221923_create_transactions.exs | 12 ------------ .../20180212214442_create_receipts.exs | 2 -- .../migrations/20180212222309_create_logs.exs | 6 ------ ...180221001948_create_internal_transactions.exs | 8 -------- apps/explorer/test/explorer/chain_test.exs | 2 -- apps/explorer/test/support/factory.ex | 2 -- apps/explorer_web/lib/explorer_web/chain.ex | 4 ---- .../lib/explorer_web/channels/user_socket.ex | 2 -- .../pending_transaction_controller.ex | 2 -- .../controllers/transaction_controller.ex | 2 -- .../controllers/transaction_log_controller.ex | 6 ------ .../lib/explorer_web/social_media.ex | 2 -- .../explorer_web/views/block_transaction_view.ex | 2 -- .../lib/explorer_web/views/block_view.ex | 2 -- .../views/pending_transaction_view.ex | 2 -- .../lib/explorer_web/views/transaction_view.ex | 2 -- apps/explorer_web/priv/gettext/default.pot | 16 ++++++++-------- .../priv/gettext/en/LC_MESSAGES/default.po | 16 ++++++++-------- 53 files changed, 16 insertions(+), 282 deletions(-) diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index afb9bd0725..3dfdb3feb1 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -7,10 +7,6 @@ defmodule Explorer.Application do import Supervisor.Spec, only: [supervisor: 3] - # Functions - - ## Application callbacks - @impl Application def start(_type, _args) do # Children to start in all environments @@ -26,8 +22,6 @@ defmodule Explorer.Application do Supervisor.start_link(children, opts) end - ## Private Functions - defp secondary_children(:test), do: [] # Children to start when not testing diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index efda82ec79..4a22c27a90 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -9,8 +9,6 @@ defmodule Explorer.Chain do alias Explorer.Chain.{Address, Block, Hash, InternalTransaction, Log, Receipt, Transaction, Wei} alias Explorer.Repo - # Types - @typedoc """ The name of an association on the `t:Ecto.Schema.t/0` """ @@ -43,8 +41,6 @@ defmodule Explorer.Chain do @typep timestamps :: %{inserted_at: %Ecto.DateTime{}, updated_at: %Ecto.DateTime{}} @typep timestamps_option :: {:timestamps, timestamps} - # Functions - @doc """ `t:Explorer.Chain.InternalTransaction/0`s from `address`. @@ -1037,8 +1033,6 @@ defmodule Explorer.Chain do Wei.to(value, unit) end - ## Private Functions - defp address_hash_to_transactions( %Hash{byte_count: unquote(Hash.Truncated.byte_count())} = address_hash, named_arguments diff --git a/apps/explorer/lib/explorer/chain/address.ex b/apps/explorer/lib/explorer/chain/address.ex index 7816aca8c6..48d6544622 100644 --- a/apps/explorer/lib/explorer/chain/address.ex +++ b/apps/explorer/lib/explorer/chain/address.ex @@ -7,13 +7,9 @@ defmodule Explorer.Chain.Address do alias Explorer.Chain.{Credit, Debit, Hash} - # Constants - @optional_attrs ~w()a @required_attrs ~w(hash)a - # Types - @typedoc """ Hash of the public key for this address. """ diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index 02d2dc22fe..2e0baf14f7 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -9,13 +9,9 @@ defmodule Explorer.Chain.Block do alias Explorer.Chain.{Address, Gas, Hash, Transaction} - # Constants - @required_attrs ~w(difficulty gas_limit gas_used hash miner_hash nonce number parent_hash size timestamp total_difficulty)a - # Types - @typedoc """ How much work is required to find a hash with some number of leading 0s. It is measured in hashes for PoW (Proof-of-Work) chains like Ethereum. In PoA (Proof-of-Authority) chains, it does not apply as blocks are validated @@ -62,8 +58,6 @@ defmodule Explorer.Chain.Block do transactions: %Ecto.Association.NotLoaded{} | [Transaction.t()] } - # Schema - @primary_key {:hash, Hash.Full, autogenerate: false} schema "blocks" do field(:difficulty, :decimal) @@ -82,8 +76,6 @@ defmodule Explorer.Chain.Block do has_many(:transactions, Transaction) end - # Functions - def changeset(%__MODULE__{} = block, attrs) do block |> cast(attrs, @required_attrs) diff --git a/apps/explorer/lib/explorer/chain/credit.ex b/apps/explorer/lib/explorer/chain/credit.ex index 1f12f87af5..5d1fc32563 100644 --- a/apps/explorer/lib/explorer/chain/credit.ex +++ b/apps/explorer/lib/explorer/chain/credit.ex @@ -9,8 +9,6 @@ defmodule Explorer.Chain.Credit do alias Explorer.Chain.{Address, Hash} alias Explorer.Repo - # Types - @typedoc """ * `address` - address that was the `to_address` * `address_hash` - foreign key for `address` @@ -24,8 +22,6 @@ defmodule Explorer.Chain.Credit do value: Decimal.t() } - # Schema - @primary_key false schema "credits" do field(:count, :integer) @@ -36,8 +32,6 @@ defmodule Explorer.Chain.Credit do belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Truncated) end - # Functions - def refresh do SQL.query!(Repo, "REFRESH MATERIALIZED VIEW CONCURRENTLY credits;", [], timeout: 120_000) end diff --git a/apps/explorer/lib/explorer/chain/debit.ex b/apps/explorer/lib/explorer/chain/debit.ex index d14d719d0e..81e5aa7529 100644 --- a/apps/explorer/lib/explorer/chain/debit.ex +++ b/apps/explorer/lib/explorer/chain/debit.ex @@ -9,8 +9,6 @@ defmodule Explorer.Chain.Debit do alias Explorer.Chain.{Address, Hash} alias Explorer.Repo - # Types - @typedoc """ * `address` - address that was the `from_address` * `address_hash` - foreign key for `address` @@ -24,8 +22,6 @@ defmodule Explorer.Chain.Debit do value: Decimal.t() } - # Schema - @primary_key false schema "debits" do field(:count, :integer) @@ -36,8 +32,6 @@ defmodule Explorer.Chain.Debit do belongs_to(:address, Address, foreign_key: :address_hash, references: :hash, type: Hash.Truncated) end - # Functions - def refresh do SQL.query!(Repo, "REFRESH MATERIALIZED VIEW CONCURRENTLY debits;", [], timeout: 120_000) end diff --git a/apps/explorer/lib/explorer/chain/hash.ex b/apps/explorer/lib/explorer/chain/hash.ex index faedc666e8..78b6660095 100644 --- a/apps/explorer/lib/explorer/chain/hash.ex +++ b/apps/explorer/lib/explorer/chain/hash.ex @@ -5,18 +5,12 @@ defmodule Explorer.Chain.Hash do import Bitwise - # Constants - @bits_per_byte 8 @hexadecimal_digits_per_byte 2 @max_byte_count 32 - # Struct - defstruct ~w(byte_count bytes)a - # Types - @typedoc """ A full [KECCAK-256](https://en.wikipedia.org/wiki/SHA-3) hash is #{@max_byte_count}, but it can also be truncated to fewer bytes. @@ -31,12 +25,8 @@ defmodule Explorer.Chain.Hash do bytes: <<_::_*8>> } - # Callbacks - @callback byte_count() :: byte_count() - # Functions - @doc """ Number of bits in a byte """ @@ -47,8 +37,6 @@ defmodule Explorer.Chain.Hash do """ def hexadecimal_digits_per_byte, do: 2 - ## Ecto.Type callbacks - @doc """ Casts `term` to `t:t/0` using `c:byte_count/0` in `module` """ @@ -237,8 +225,6 @@ defmodule Explorer.Chain.Hash do |> IO.iodata_to_binary() end - ## Private Functions - defp byte_count_to_hexadecimal_digit_count(byte_count) do byte_count * @hexadecimal_digits_per_byte end diff --git a/apps/explorer/lib/explorer/chain/hash/full.ex b/apps/explorer/lib/explorer/chain/hash/full.ex index ba906e41c5..f5c0855536 100644 --- a/apps/explorer/lib/explorer/chain/hash/full.ex +++ b/apps/explorer/lib/explorer/chain/hash/full.ex @@ -8,22 +8,14 @@ defmodule Explorer.Chain.Hash.Full do @behaviour Ecto.Type @behaviour Hash - # Constants - @byte_count 32 @hexadecimal_digit_count Hash.hexadecimal_digits_per_byte() * @byte_count - # Types - @typedoc """ A #{@byte_count}-byte hash of the `t:Explorer.Chain.Block.t/0` or `t:Explorer.Chain.Transaction.t/0`. """ @type t :: %Hash{byte_count: unquote(@byte_count), bytes: <<_::unquote(@byte_count * Hash.bits_per_byte())>>} - # Functions - - ## Ecto.Type callbacks - @doc """ Casts `term` to `t:t/0` @@ -153,8 +145,6 @@ defmodule Explorer.Chain.Hash.Full do @spec type() :: :binary def type, do: :binary - ## Explorer.Chain.Hash callbacks - @impl Hash def byte_count, do: @byte_count end diff --git a/apps/explorer/lib/explorer/chain/hash/truncated.ex b/apps/explorer/lib/explorer/chain/hash/truncated.ex index 1d2b3554f8..e3fa59c1f1 100644 --- a/apps/explorer/lib/explorer/chain/hash/truncated.ex +++ b/apps/explorer/lib/explorer/chain/hash/truncated.ex @@ -11,22 +11,14 @@ defmodule Explorer.Chain.Hash.Truncated do @behaviour Ecto.Type @behaviour Hash - # Constants - @byte_count 20 @hexadecimal_digit_count Hash.hexadecimal_digits_per_byte() * @byte_count - # Types - @typedoc """ A #{@byte_count}-byte hash of the address public key. """ @type t :: %Hash{byte_count: unquote(@byte_count), bytes: <<_::unquote(@byte_count * Hash.bits_per_byte())>>} - # Functions - - ## Ecto.Type callbacks - @doc """ Casts `term` to `t:t/0`. @@ -154,8 +146,6 @@ defmodule Explorer.Chain.Hash.Truncated do @spec type() :: :binary def type, do: :binary - ## Explorer.Chain.Hash callbacks - @impl Hash def byte_count, do: @byte_count end diff --git a/apps/explorer/lib/explorer/chain/internal_transaction.ex b/apps/explorer/lib/explorer/chain/internal_transaction.ex index c6a3762e19..8c1ef20603 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction.ex @@ -412,8 +412,6 @@ defmodule Explorer.Chain.InternalTransaction do end) end - ## Private Functions - defp type_changeset(changeset, attrs) do type = get_field(changeset, :type) diff --git a/apps/explorer/lib/explorer/chain/internal_transaction/call_type.ex b/apps/explorer/lib/explorer/chain/internal_transaction/call_type.ex index b9bdeeb32a..4c7a6045a5 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction/call_type.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction/call_type.ex @@ -5,8 +5,6 @@ defmodule Explorer.Chain.InternalTransaction.CallType do @behaviour Ecto.Type - # Types - @typedoc """ * `:call` - call a function in a contract by jumping into the contract's context * `:callcode` @@ -17,10 +15,6 @@ defmodule Explorer.Chain.InternalTransaction.CallType do """ @type t :: :call | :callcode | :delegatecall | :staticcall - # Functions - - ## Ecto.Type callbacks - @doc """ Casts `term` to `t:t/0` diff --git a/apps/explorer/lib/explorer/chain/internal_transaction/type.ex b/apps/explorer/lib/explorer/chain/internal_transaction/type.ex index e71ac51515..8f7fe1f7ff 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction/type.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction/type.ex @@ -5,8 +5,6 @@ defmodule Explorer.Chain.InternalTransaction.Type do @behaviour Ecto.Type - # Types - @typedoc """ * `:call` * `:create` @@ -15,10 +13,6 @@ defmodule Explorer.Chain.InternalTransaction.Type do """ @type t :: :call | :create | :suicide | :reward - # Functions - - ## Ecto.Type callbacks - @doc """ Casts `term` to `t:t/0` diff --git a/apps/explorer/lib/explorer/chain/log.ex b/apps/explorer/lib/explorer/chain/log.ex index a41219391a..2751ef7f6c 100644 --- a/apps/explorer/lib/explorer/chain/log.ex +++ b/apps/explorer/lib/explorer/chain/log.ex @@ -5,13 +5,9 @@ defmodule Explorer.Chain.Log do alias Explorer.Chain.{Address, Hash, Receipt, Transaction} - # Constants - @required_attrs ~w(address_hash data index transaction_hash type)a @optional_attrs ~w(first_topic second_topic third_topic fourth_topic)a - # Types - @typedoc """ * `address` - address of contract that generate the event * `address_hash` - foreign key for `address` @@ -42,8 +38,6 @@ defmodule Explorer.Chain.Log do type: String.t() } - # Schema - schema "logs" do field(:data, :string) field(:first_topic, :string) @@ -60,8 +54,6 @@ defmodule Explorer.Chain.Log do has_one(:transaction, through: [:receipt, :transaction]) end - # Functions - @doc """ `address_hash` and `transaction_hash` are converted to `t:Explorer.Chain.Hash.t/0`. The allowed values for `type` are currently unknown, so it is left as a `t:String.t/0`. diff --git a/apps/explorer/lib/explorer/chain/receipt.ex b/apps/explorer/lib/explorer/chain/receipt.ex index 31a4448404..f0ff6f5a08 100644 --- a/apps/explorer/lib/explorer/chain/receipt.ex +++ b/apps/explorer/lib/explorer/chain/receipt.ex @@ -6,14 +6,10 @@ defmodule Explorer.Chain.Receipt do alias Explorer.Chain.{Gas, Hash, Log, Transaction} alias Explorer.Chain.Receipt.Status - # Constants - @optional_attrs ~w()a @required_attrs ~w(cumulative_gas_used gas_used status transaction_hash transaction_index)a @allowed_attrs @optional_attrs ++ @required_attrs - # Types - @typedoc """ * `cumulative_gas_used` - the cumulative gas used in `transaction`'s `t:Explorer.Chain.Block.t/0` before `transaction`'s `index` @@ -34,8 +30,6 @@ defmodule Explorer.Chain.Receipt do transaction_index: non_neg_integer() } - # Schema - @primary_key false schema "receipts" do field(:cumulative_gas_used, :decimal) @@ -49,8 +43,6 @@ defmodule Explorer.Chain.Receipt do timestamps() end - # Functions - def changeset(%__MODULE__{} = transaction_receipt, attrs \\ %{}) do transaction_receipt |> cast(attrs, @allowed_attrs) diff --git a/apps/explorer/lib/explorer/chain/receipt/status.ex b/apps/explorer/lib/explorer/chain/receipt/status.ex index cea77792f9..bb78880b93 100644 --- a/apps/explorer/lib/explorer/chain/receipt/status.ex +++ b/apps/explorer/lib/explorer/chain/receipt/status.ex @@ -8,18 +8,12 @@ defmodule Explorer.Chain.Receipt.Status do @behaviour Ecto.Type - # Types - @typedoc """ * `:ok` - transaction succeeded * `:error` - transaction failed """ @type t :: :ok | :error - # Functions - - ## Ecto.Type callbacks - @doc """ Casts `term` to `t:t/0` diff --git a/apps/explorer/lib/explorer/chain/statistics.ex b/apps/explorer/lib/explorer/chain/statistics.ex index 38e632ebdb..e5b0f839fc 100644 --- a/apps/explorer/lib/explorer/chain/statistics.ex +++ b/apps/explorer/lib/explorer/chain/statistics.ex @@ -10,8 +10,6 @@ defmodule Explorer.Chain.Statistics do alias Explorer.Chain.{Block, Transaction} alias Timex.Duration - # Constants - @average_time_query """ SELECT coalesce(avg(difference), interval '0 seconds') FROM ( @@ -58,8 +56,6 @@ defmodule Explorer.Chain.Statistics do WHERE transactions.inserted_at > NOW() - interval '1 minute' """ - # Types - @typedoc """ The number of `t:Explorer.Chain.Block.t/0` mined/validated per minute. """ @@ -97,8 +93,6 @@ defmodule Explorer.Chain.Statistics do transactions: [Transaction.t()] } - # Struct - defstruct average_time: %Duration{seconds: 0, megaseconds: 0, microseconds: 0}, block_velocity: 0, blocks: [], @@ -110,8 +104,6 @@ defmodule Explorer.Chain.Statistics do transaction_velocity: 0, transactions: [] - # Functions - def fetch do blocks = from( @@ -142,8 +134,6 @@ defmodule Explorer.Chain.Statistics do |> put_max_numbered_block() end - ## Private Functions - defp put_max_numbered_block(state) do case Chain.max_numbered_block() do {:ok, %Block{number: number, timestamp: timestamp}} -> diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index 5e4562985a..97d17f4746 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -6,13 +6,9 @@ defmodule Explorer.Chain.Transaction do alias Ecto.Changeset alias Explorer.Chain.{Address, Block, Gas, Hash, InternalTransaction, Receipt, Wei} - # Constants - @optional_attrs ~w(block_hash from_address_hash index to_address_hash)a @required_attrs ~w(gas gas_price hash input nonce public_key r s standard_v v value)a - # Types - @typedoc """ The full public key of the signer of the transaction. """ @@ -120,8 +116,6 @@ defmodule Explorer.Chain.Transaction do value: Wei.t() } - # Schema - @primary_key {:hash, Hash.Full, autogenerate: false} schema "transactions" do field(:gas, :decimal) @@ -160,8 +154,6 @@ defmodule Explorer.Chain.Transaction do ) end - # Functions - def changeset(%__MODULE__{} = transaction, attrs \\ %{}) do transaction |> cast(attrs, @required_attrs ++ @optional_attrs) @@ -185,8 +177,6 @@ defmodule Explorer.Chain.Transaction do end) end - ## Private Functions - defp validate_collated(%Changeset{} = changeset) do case {Changeset.get_field(changeset, :block_hash), Changeset.get_field(changeset, :index)} do {nil, nil} -> diff --git a/apps/explorer/lib/explorer/chain/wei.ex b/apps/explorer/lib/explorer/chain/wei.ex index 8d1e0f37e0..c90ad0bd9a 100644 --- a/apps/explorer/lib/explorer/chain/wei.ex +++ b/apps/explorer/lib/explorer/chain/wei.ex @@ -30,13 +30,9 @@ defmodule Explorer.Chain.Wei do @typedoc @moduledoc @type t :: Decimal.t() - # Constants - @wei_per_ether Decimal.new(1_000_000_000_000_000_000) @wei_per_gwei Decimal.new(1_000_000_000) - ## Functions - @doc """ Convert wei to itself. diff --git a/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex b/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex index e0c2dcee4a..bda2f4bbbf 100644 --- a/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex +++ b/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex @@ -14,8 +14,6 @@ defmodule Explorer.ExchangeRates do @interval :timer.minutes(5) @table_name :exchange_rates - ## GenServer functions - @impl GenServer def handle_info(:update, state) do Logger.debug(fn -> "Updating cached exchange rates" end) @@ -76,8 +74,6 @@ defmodule Explorer.ExchangeRates do GenServer.start_link(__MODULE__, opts, name: __MODULE__) end - ## Public functions - @doc """ Lists exchange rates for the tracked tickers. """ @@ -89,14 +85,10 @@ defmodule Explorer.ExchangeRates do |> Enum.sort_by(fn %Token{symbol: symbol} -> symbol end) end - ## Undocumented public functions - @doc false @spec table_name() :: atom() def table_name, do: @table_name - ## Private functions - @spec config(atom()) :: term defp config(key) do Application.get_env(:explorer, __MODULE__, [])[key] diff --git a/apps/explorer/lib/explorer/indexer.ex b/apps/explorer/lib/explorer/indexer.ex index 5c183a0e88..cf3586e1fe 100644 --- a/apps/explorer/lib/explorer/indexer.ex +++ b/apps/explorer/lib/explorer/indexer.ex @@ -5,8 +5,6 @@ defmodule Explorer.Indexer do alias Explorer.Chain - # Functions - @doc """ Options passed to `child_spec` are passed to `Explorer.Indexer.Supervisor.start_link/1` diff --git a/apps/explorer/lib/explorer/indexer/sequence.ex b/apps/explorer/lib/explorer/indexer/sequence.ex index 60da351423..deb21d706a 100644 --- a/apps/explorer/lib/explorer/indexer/sequence.ex +++ b/apps/explorer/lib/explorer/indexer/sequence.ex @@ -3,16 +3,10 @@ defmodule Explorer.Indexer.Sequence do use Agent - # Struct - defstruct ~w(current mode queue step)a - # Types - @type range :: {pos_integer(), pos_integer()} - # Functions - @doc """ Builds an enumerable stream using a sequencer agent. """ diff --git a/apps/explorer/lib/explorer/jsonrpc.ex b/apps/explorer/lib/explorer/jsonrpc.ex index 53408d015f..9102de8170 100644 --- a/apps/explorer/lib/explorer/jsonrpc.ex +++ b/apps/explorer/lib/explorer/jsonrpc.ex @@ -20,8 +20,6 @@ defmodule Explorer.JSONRPC do alias Explorer.JSONRPC.{Blocks, Parity, Receipts, Transactions} - # Types - @typedoc """ Truncated 20-byte [KECCAK-256](https://en.wikipedia.org/wiki/SHA-3) hash encoded as a hexadecimal number in a `String.t`. @@ -63,8 +61,6 @@ defmodule Explorer.JSONRPC do """ @type timestamp :: String.t() - # Functions - def child_spec(_opts) do :hackney_pool.child_spec(:eth, recv_timeout: 60_000, timeout: 60_000, max_connections: 1000) end @@ -239,8 +235,6 @@ defmodule Explorer.JSONRPC do |> Timex.from_unix() end - ## Private Functions - defp build_batch_get_block_by_number(block_start, block_end) do for current <- block_start..block_end do %{ diff --git a/apps/explorer/lib/explorer/jsonrpc/block.ex b/apps/explorer/lib/explorer/jsonrpc/block.ex index 533ac6ff1f..c14edfb184 100644 --- a/apps/explorer/lib/explorer/jsonrpc/block.ex +++ b/apps/explorer/lib/explorer/jsonrpc/block.ex @@ -9,8 +9,6 @@ defmodule Explorer.JSONRPC.Block do alias Explorer.JSONRPC alias Explorer.JSONRPC.Transactions - # Types - @type elixir :: %{String.t() => non_neg_integer | DateTime.t() | String.t() | nil} @typedoc """ @@ -275,8 +273,6 @@ defmodule Explorer.JSONRPC.Block do Enum.into(block, %{}, &entry_to_elixir/1) end - ## Private Functions - defp entry_to_elixir({key, quantity}) when key in ~w(difficulty gasLimit gasUsed number size totalDifficulty) do {key, quantity_to_integer(quantity)} end diff --git a/apps/explorer/lib/explorer/jsonrpc/blocks.ex b/apps/explorer/lib/explorer/jsonrpc/blocks.ex index 220016e008..1662f6ed0d 100644 --- a/apps/explorer/lib/explorer/jsonrpc/blocks.ex +++ b/apps/explorer/lib/explorer/jsonrpc/blocks.ex @@ -6,13 +6,9 @@ defmodule Explorer.JSONRPC.Blocks do alias Explorer.JSONRPC.{Block, Transactions} - # Types - @type elixir :: [Block.elixir()] @type t :: [Block.t()] - # Functions - @spec elixir_to_params(elixir) :: [map] def elixir_to_params(elixir) when is_list(elixir) do Enum.map(elixir, &Block.elixir_to_params/1) diff --git a/apps/explorer/lib/explorer/jsonrpc/log.ex b/apps/explorer/lib/explorer/jsonrpc/log.ex index d302996bb6..209fa654dd 100644 --- a/apps/explorer/lib/explorer/jsonrpc/log.ex +++ b/apps/explorer/lib/explorer/jsonrpc/log.ex @@ -6,8 +6,6 @@ defmodule Explorer.JSONRPC.Log do import Explorer.JSONRPC, only: [quantity_to_integer: 1] - # Types - @type elixir :: %{String.t() => String.t() | [String.t()] | non_neg_integer()} @typedoc """ @@ -22,8 +20,6 @@ defmodule Explorer.JSONRPC.Log do """ @type t :: %{String.t() => String.t() | [String.t()]} - # Functions - @doc """ Converts `t:elixir/0` format to params used in `Explorer.Chain`. @@ -107,8 +103,6 @@ defmodule Explorer.JSONRPC.Log do Enum.into(log, %{}, &entry_to_elixir/1) end - ## Private Functions - defp entry_to_elixir({key, _} = entry) when key in ~w(address blockHash data topics transactionHash type), do: entry defp entry_to_elixir({key, quantity}) when key in ~w(blockNumber logIndex transactionIndex transactionLogIndex) do diff --git a/apps/explorer/lib/explorer/jsonrpc/logs.ex b/apps/explorer/lib/explorer/jsonrpc/logs.ex index 07dbafa53e..591ee635aa 100644 --- a/apps/explorer/lib/explorer/jsonrpc/logs.ex +++ b/apps/explorer/lib/explorer/jsonrpc/logs.ex @@ -6,13 +6,9 @@ defmodule Explorer.JSONRPC.Logs do alias Explorer.JSONRPC.Log - # Types - @type elixir :: [Log.elixir()] @type t :: [Log.t()] - # Functions - @spec elixir_to_params(elixir) :: [map] def elixir_to_params(elixir) when is_list(elixir) do Enum.map(elixir, &Log.elixir_to_params/1) diff --git a/apps/explorer/lib/explorer/jsonrpc/parity.ex b/apps/explorer/lib/explorer/jsonrpc/parity.ex index d6dded5f1b..9df06f02fa 100644 --- a/apps/explorer/lib/explorer/jsonrpc/parity.ex +++ b/apps/explorer/lib/explorer/jsonrpc/parity.ex @@ -7,8 +7,6 @@ defmodule Explorer.JSONRPC.Parity do alias Explorer.JSONRPC.Parity.Traces - # Functions - def fetch_internal_transactions(transaction_hashes) when is_list(transaction_hashes) do with {:ok, responses} <- transaction_hashes @@ -24,8 +22,6 @@ defmodule Explorer.JSONRPC.Parity do end end - ## Private Functions - defp response_to_trace(%{"id" => transaction_hash, "result" => %{"trace" => traces}}) when is_list(traces) do traces |> Stream.with_index() diff --git a/apps/explorer/lib/explorer/jsonrpc/parity/trace.ex b/apps/explorer/lib/explorer/jsonrpc/parity/trace.ex index f4eee69f71..764213289d 100644 --- a/apps/explorer/lib/explorer/jsonrpc/parity/trace.ex +++ b/apps/explorer/lib/explorer/jsonrpc/parity/trace.ex @@ -7,8 +7,6 @@ defmodule Explorer.JSONRPC.Parity.Trace do alias Explorer.JSONRPC.Parity.Trace.{Action, Result} - # Functions - @doc """ Create type traces are generated when a contract is created. @@ -357,8 +355,6 @@ defmodule Explorer.JSONRPC.Parity.Trace do raise ArgumentError, ~S|Caller must `Map.put/2` `"index"` and `"transactionHash"` in trace| end - ## Private Functions - # subtraces is an actual integer in JSON and not hex-encoded # traceAddress is a list of actual integers, not a list of hex-encoded defp entry_to_elixir({key, _} = entry) when key in ~w(subtraces traceAddress transactionHash type output), do: entry diff --git a/apps/explorer/lib/explorer/jsonrpc/parity/trace/action.ex b/apps/explorer/lib/explorer/jsonrpc/parity/trace/action.ex index efe2311cd0..379ec1c279 100644 --- a/apps/explorer/lib/explorer/jsonrpc/parity/trace/action.ex +++ b/apps/explorer/lib/explorer/jsonrpc/parity/trace/action.ex @@ -44,8 +44,6 @@ defmodule Explorer.JSONRPC.Parity.Trace.Action do Enum.into(action, %{}, &entry_to_elixir/1) end - ## Private Functions - defp entry_to_elixir({key, _} = entry) when key in ~w(address callType from init input refundAddress to), do: entry defp entry_to_elixir({key, quantity}) when key in ~w(balance gas value) do diff --git a/apps/explorer/lib/explorer/jsonrpc/parity/trace/result.ex b/apps/explorer/lib/explorer/jsonrpc/parity/trace/result.ex index e2ea511ac7..bf505a24df 100644 --- a/apps/explorer/lib/explorer/jsonrpc/parity/trace/result.ex +++ b/apps/explorer/lib/explorer/jsonrpc/parity/trace/result.ex @@ -34,8 +34,6 @@ defmodule Explorer.JSONRPC.Parity.Trace.Result do def to_elixir(nil), do: nil - ## Private Functions - defp entry_to_elixir({key, _} = entry) when key in ~w(address code output), do: entry defp entry_to_elixir({key, quantity}) when key in ~w(gasUsed) do diff --git a/apps/explorer/lib/explorer/jsonrpc/receipt.ex b/apps/explorer/lib/explorer/jsonrpc/receipt.ex index 5e80973131..c37c36c3e2 100644 --- a/apps/explorer/lib/explorer/jsonrpc/receipt.ex +++ b/apps/explorer/lib/explorer/jsonrpc/receipt.ex @@ -10,8 +10,6 @@ defmodule Explorer.JSONRPC.Receipt do alias Explorer.JSONRPC alias Explorer.JSONRPC.Logs - # Types - @type elixir :: %{String.t() => String.t() | non_neg_integer} @typedoc """ @@ -31,8 +29,6 @@ defmodule Explorer.JSONRPC.Receipt do """ @type t :: %{String.t() => JSONRPC.address() | JSONRPC.data() | JSONRPC.hash() | JSONRPC.quantity() | list | nil} - # Functions - @doc """ `Get `t:Explorer.JSONRPC.Logs.elixir/0` from `t:elixir/0` """ @@ -127,8 +123,6 @@ defmodule Explorer.JSONRPC.Receipt do Enum.into(receipt, %{}, &entry_to_elixir/1) end - ## Private Functions - # double check that no new keys are being missed by requiring explicit match for passthrough # `t:Explorer.JSONRPC.address/0` and `t:Explorer.JSONRPC.hash/0` pass through as `Explorer.Chain` can verify correct # hash format diff --git a/apps/explorer/lib/explorer/jsonrpc/receipts.ex b/apps/explorer/lib/explorer/jsonrpc/receipts.ex index e9751d464f..462abd96a4 100644 --- a/apps/explorer/lib/explorer/jsonrpc/receipts.ex +++ b/apps/explorer/lib/explorer/jsonrpc/receipts.ex @@ -9,13 +9,9 @@ defmodule Explorer.JSONRPC.Receipts do alias Explorer.JSONRPC.{Logs, Receipt} - # Types - @type elixir :: [Receipt.elixir()] @type t :: [Receipt.t()] - # Functions - @spec elixir_to_logs(elixir) :: Logs.elixir() def elixir_to_logs(elixir) when is_list(elixir) do Enum.flat_map(elixir, &Receipt.elixir_to_logs/1) @@ -53,8 +49,6 @@ defmodule Explorer.JSONRPC.Receipts do Enum.map(receipts, &Receipt.to_elixir/1) end - ## Private Functons - defp hash_to_json(hash) do %{ "id" => hash, diff --git a/apps/explorer/lib/explorer/jsonrpc/transaction.ex b/apps/explorer/lib/explorer/jsonrpc/transaction.ex index 1f8f6945b1..573a95a08d 100644 --- a/apps/explorer/lib/explorer/jsonrpc/transaction.ex +++ b/apps/explorer/lib/explorer/jsonrpc/transaction.ex @@ -12,8 +12,6 @@ defmodule Explorer.JSONRPC.Transaction do alias Explorer.JSONRPC - # Types - @type elixir :: %{String.t() => JSONRPC.address() | JSONRPC.hash() | String.t() | non_neg_integer() | nil} @typedoc """ @@ -60,8 +58,6 @@ defmodule Explorer.JSONRPC.Transaction do value: non_neg_integer() } - # Functions - @spec elixir_to_params(elixir) :: params def elixir_to_params(%{ "blockHash" => block_hash, @@ -131,8 +127,6 @@ defmodule Explorer.JSONRPC.Transaction do Enum.into(transaction, %{}, &entry_to_elixir/1) end - ## Private Functions - # double check that no new keys are being missed by requiring explicit match for passthrough # `t:Explorer.JSONRPC.address/0` and `t:Explorer.JSONRPC.hash/0` pass through as `Explorer.Chain` can verify correct # hash format diff --git a/apps/explorer/lib/explorer/jsonrpc/transactions.ex b/apps/explorer/lib/explorer/jsonrpc/transactions.ex index 0155157f21..fd775b9289 100644 --- a/apps/explorer/lib/explorer/jsonrpc/transactions.ex +++ b/apps/explorer/lib/explorer/jsonrpc/transactions.ex @@ -7,13 +7,9 @@ defmodule Explorer.JSONRPC.Transactions do alias Explorer.JSONRPC.Transaction - # Types - @type elixir :: [Transaction.elixir()] @type t :: [Transaction.t()] - # Functions - def elixir_to_params(elixir) when is_list(elixir) do Enum.map(elixir, &Transaction.elixir_to_params/1) end diff --git a/apps/explorer/lib/explorer/market/history/cataloger.ex b/apps/explorer/lib/explorer/market/history/cataloger.ex index 461a0b9fbc..7eb629d95a 100644 --- a/apps/explorer/lib/explorer/market/history/cataloger.ex +++ b/apps/explorer/lib/explorer/market/history/cataloger.ex @@ -28,8 +28,6 @@ defmodule Explorer.Market.History.Cataloger do @typep milliseconds :: non_neg_integer() - ## GenServer callbacks - @impl GenServer def init(:ok) do send(self(), {:fetch_history, 365}) @@ -80,8 +78,6 @@ defmodule Explorer.Market.History.Cataloger do GenServer.start_link(__MODULE__, :ok, name: __MODULE__) end - ## Private Functions - @spec base_backoff :: milliseconds() defp base_backoff do config_or_default(:base_backoff, 100) diff --git a/apps/explorer/priv/repo/migrations/20180117221923_create_transactions.exs b/apps/explorer/priv/repo/migrations/20180117221923_create_transactions.exs index 252e5f0a8b..6bf9710885 100644 --- a/apps/explorer/priv/repo/migrations/20180117221923_create_transactions.exs +++ b/apps/explorer/priv/repo/migrations/20180117221923_create_transactions.exs @@ -3,8 +3,6 @@ defmodule Explorer.Repo.Migrations.CreateTransactions do def change do create table(:transactions, primary_key: false) do - # Fields - add(:gas, :numeric, precision: 100, null: false) add(:gas_price, :numeric, precision: 100, null: false) add(:hash, :bytea, null: false, primary_key: true) @@ -23,8 +21,6 @@ defmodule Explorer.Repo.Migrations.CreateTransactions do timestamps(null: false) - # Foreign Keys - # `null` when a pending transaction add(:block_hash, references(:blocks, column: :hash, on_delete: :delete_all, type: :bytea), null: true) @@ -33,8 +29,6 @@ defmodule Explorer.Repo.Migrations.CreateTransactions do add(:to_address_hash, references(:addresses, column: :hash, on_delete: :delete_all, type: :bytea), null: true) end - # Constraints - create( constraint( :transactions, @@ -43,19 +37,13 @@ defmodule Explorer.Repo.Migrations.CreateTransactions do ) ) - # Foreign Key indexes - create(index(:transactions, :block_hash)) create(index(:transactions, :from_address_hash)) create(index(:transactions, :to_address_hash)) - # Search indexes - create(index(:transactions, :inserted_at)) create(index(:transactions, :updated_at)) - # Unique indexes - create(unique_index(:transactions, [:block_hash, :index])) end end diff --git a/apps/explorer/priv/repo/migrations/20180212214442_create_receipts.exs b/apps/explorer/priv/repo/migrations/20180212214442_create_receipts.exs index 869098dc43..5e243df86d 100644 --- a/apps/explorer/priv/repo/migrations/20180212214442_create_receipts.exs +++ b/apps/explorer/priv/repo/migrations/20180212214442_create_receipts.exs @@ -10,8 +10,6 @@ defmodule Explorer.Repo.Migrations.CreateReceipts do timestamps(null: false) - # Foreign keys - add( :transaction_hash, references(:transactions, column: :hash, on_delete: :delete_all, type: :bytea), diff --git a/apps/explorer/priv/repo/migrations/20180212222309_create_logs.exs b/apps/explorer/priv/repo/migrations/20180212222309_create_logs.exs index f05d1d477d..2d51aab416 100644 --- a/apps/explorer/priv/repo/migrations/20180212222309_create_logs.exs +++ b/apps/explorer/priv/repo/migrations/20180212222309_create_logs.exs @@ -13,8 +13,6 @@ defmodule Explorer.Repo.Migrations.CreateLogs do timestamps(null: false) - # Foreign Keys - add(:address_hash, references(:addresses, column: :hash, on_delete: :delete_all, type: :bytea), null: true) add( @@ -24,13 +22,9 @@ defmodule Explorer.Repo.Migrations.CreateLogs do ) end - # Foreign Key indexes - create(index(:logs, :address_hash)) create(index(:logs, :transaction_hash)) - # Search indexes - create(index(:logs, :index)) create(index(:logs, :type)) create(index(:logs, :first_topic)) diff --git a/apps/explorer/priv/repo/migrations/20180221001948_create_internal_transactions.exs b/apps/explorer/priv/repo/migrations/20180221001948_create_internal_transactions.exs index 055f82072c..a7a5880845 100644 --- a/apps/explorer/priv/repo/migrations/20180221001948_create_internal_transactions.exs +++ b/apps/explorer/priv/repo/migrations/20180221001948_create_internal_transactions.exs @@ -23,8 +23,6 @@ defmodule Explorer.Repo.Migrations.CreateInternalTransactions do timestamps(null: false) - # Foreign keys - # Nullability controlled by create_has_created constraint below add(:created_contract_address_hash, references(:addresses, column: :hash, type: :bytea), null: true) add(:from_address_hash, references(:addresses, column: :hash, type: :bytea)) @@ -37,8 +35,6 @@ defmodule Explorer.Repo.Migrations.CreateInternalTransactions do ) end - # Constraints - create( constraint( :internal_transactions, @@ -76,15 +72,11 @@ defmodule Explorer.Repo.Migrations.CreateInternalTransactions do ) ) - # Foreign Key indexes - create(index(:internal_transactions, :created_contract_address_hash)) create(index(:internal_transactions, :from_address_hash)) create(index(:internal_transactions, :to_address_hash)) create(index(:internal_transactions, :transaction_hash)) - # Unique indexes - create(unique_index(:internal_transactions, [:transaction_hash, :index])) end end diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 8b1241c62b..79ebd4d570 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -8,8 +8,6 @@ defmodule Explorer.ChainTest do doctest Explorer.Chain - # Tests - describe "address_to_transactions/2" do test "without transactions" do address = insert(:address) diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index caae7b1148..131531928a 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -145,8 +145,6 @@ defmodule Explorer.Factory do Repo.preload(transaction, :receipt) end - ## Private Functions - defp block_hash_to_next_transaction_index(block_hash) do query = from( diff --git a/apps/explorer_web/lib/explorer_web/chain.ex b/apps/explorer_web/lib/explorer_web/chain.ex index a58d145a03..e1be5fb7b0 100644 --- a/apps/explorer_web/lib/explorer_web/chain.ex +++ b/apps/explorer_web/lib/explorer_web/chain.ex @@ -14,8 +14,6 @@ defmodule ExplorerWeb.Chain do alias Explorer.Chain.{Address, Block, Transaction} - # Functions - @spec from_param(String.t()) :: {:ok, Address.t() | Block.t() | Transaction.t()} | {:error, :not_found} def from_param(param) @@ -41,8 +39,6 @@ defmodule ExplorerWeb.Chain do end end - ## Private Functions - defp address_from_param(param) do with {:ok, hash} <- string_to_address_hash(param) do hash_to_address(hash) diff --git a/apps/explorer_web/lib/explorer_web/channels/user_socket.ex b/apps/explorer_web/lib/explorer_web/channels/user_socket.ex index 3dd17f26c2..8db7d44814 100644 --- a/apps/explorer_web/lib/explorer_web/channels/user_socket.ex +++ b/apps/explorer_web/lib/explorer_web/channels/user_socket.ex @@ -1,10 +1,8 @@ defmodule ExplorerWeb.UserSocket do use Phoenix.Socket - ## Channels # channel "room:*", ExplorerWeb.RoomChannel - ## Transports transport(:websocket, Phoenix.Transports.WebSocket, timeout: 45_000) # transport :longpoll, Phoenix.Transports.LongPoll diff --git a/apps/explorer_web/lib/explorer_web/controllers/pending_transaction_controller.ex b/apps/explorer_web/lib/explorer_web/controllers/pending_transaction_controller.ex index d616238389..6ee3a3e21c 100644 --- a/apps/explorer_web/lib/explorer_web/controllers/pending_transaction_controller.ex +++ b/apps/explorer_web/lib/explorer_web/controllers/pending_transaction_controller.ex @@ -12,8 +12,6 @@ defmodule ExplorerWeb.PendingTransactionController do end end - ## Private Functions - defp do_index(conn, options \\ []) when is_list(options) do full_options = Keyword.merge([necessity_by_association: %{from_address: :optional, to_address: :optional}], options) transactions = Chain.recent_pending_transactions(full_options) diff --git a/apps/explorer_web/lib/explorer_web/controllers/transaction_controller.ex b/apps/explorer_web/lib/explorer_web/controllers/transaction_controller.ex index 88c881a265..41bb92df54 100644 --- a/apps/explorer_web/lib/explorer_web/controllers/transaction_controller.ex +++ b/apps/explorer_web/lib/explorer_web/controllers/transaction_controller.ex @@ -46,8 +46,6 @@ defmodule ExplorerWeb.TransactionController do end end - ## Private Functions - defp do_index(conn, options \\ []) when is_list(options) do full_options = Keyword.merge( diff --git a/apps/explorer_web/lib/explorer_web/controllers/transaction_log_controller.ex b/apps/explorer_web/lib/explorer_web/controllers/transaction_log_controller.ex index 3b96725db4..81cb635233 100644 --- a/apps/explorer_web/lib/explorer_web/controllers/transaction_log_controller.ex +++ b/apps/explorer_web/lib/explorer_web/controllers/transaction_log_controller.ex @@ -3,10 +3,6 @@ defmodule ExplorerWeb.TransactionLogController do alias Explorer.Chain - # Functions - - ## Actions - def index(conn, %{"transaction_id" => transaction_hash_string} = params) do with {:ok, transaction_hash} <- Chain.string_to_transaction_hash(transaction_hash_string), {:ok, transaction} <- @@ -42,8 +38,6 @@ defmodule ExplorerWeb.TransactionLogController do end end - ## Private Functions - defp max_block_number do case Chain.max_block_number() do {:ok, number} -> number diff --git a/apps/explorer_web/lib/explorer_web/social_media.ex b/apps/explorer_web/lib/explorer_web/social_media.ex index 86c36e955c..12f095c389 100644 --- a/apps/explorer_web/lib/explorer_web/social_media.ex +++ b/apps/explorer_web/lib/explorer_web/social_media.ex @@ -17,8 +17,6 @@ defmodule ExplorerWeb.SocialMedia do |> filter_and_build_links() end - # Private functions - defp filter_and_build_links(configured_services) do for {name, account} <- configured_services, Map.has_key?(@services, name) do {name, @services[name] <> account} diff --git a/apps/explorer_web/lib/explorer_web/views/block_transaction_view.ex b/apps/explorer_web/lib/explorer_web/views/block_transaction_view.ex index 9f6aa76b3f..0afa574bbc 100644 --- a/apps/explorer_web/lib/explorer_web/views/block_transaction_view.ex +++ b/apps/explorer_web/lib/explorer_web/views/block_transaction_view.ex @@ -3,8 +3,6 @@ defmodule ExplorerWeb.BlockTransactionView do alias ExplorerWeb.{BlockView, TransactionView} - # Functions - defdelegate from_address(transaction), to: TransactionView defdelegate block(transaction), to: TransactionView defdelegate hash(transaction), to: TransactionView diff --git a/apps/explorer_web/lib/explorer_web/views/block_view.ex b/apps/explorer_web/lib/explorer_web/views/block_view.ex index 1afeee9e69..97d14bf578 100644 --- a/apps/explorer_web/lib/explorer_web/views/block_view.ex +++ b/apps/explorer_web/lib/explorer_web/views/block_view.ex @@ -5,8 +5,6 @@ defmodule ExplorerWeb.BlockView do @dialyzer :no_match - # Functions - def age(%Block{timestamp: timestamp}) do Timex.from_now(timestamp) end diff --git a/apps/explorer_web/lib/explorer_web/views/pending_transaction_view.ex b/apps/explorer_web/lib/explorer_web/views/pending_transaction_view.ex index 2401d9070f..1c4c94025d 100644 --- a/apps/explorer_web/lib/explorer_web/views/pending_transaction_view.ex +++ b/apps/explorer_web/lib/explorer_web/views/pending_transaction_view.ex @@ -5,8 +5,6 @@ defmodule ExplorerWeb.PendingTransactionView do @dialyzer :no_match - # Functions - defdelegate from_address(transaction), to: TransactionView defdelegate hash(transaction), to: TransactionView defdelegate last_seen(transaction), to: TransactionView diff --git a/apps/explorer_web/lib/explorer_web/views/transaction_view.ex b/apps/explorer_web/lib/explorer_web/views/transaction_view.ex index d645f91c8b..34934513a7 100644 --- a/apps/explorer_web/lib/explorer_web/views/transaction_view.ex +++ b/apps/explorer_web/lib/explorer_web/views/transaction_view.ex @@ -6,8 +6,6 @@ defmodule ExplorerWeb.TransactionView do alias Explorer.Chain.{Address, Block, InternalTransaction, Transaction} alias ExplorerWeb.BlockView - # Functions - def block(%Transaction{block: block}) do case block do nil -> gettext("Pending") diff --git a/apps/explorer_web/priv/gettext/default.pot b/apps/explorer_web/priv/gettext/default.pot index 9471458632..292ab4d334 100644 --- a/apps/explorer_web/priv/gettext/default.pot +++ b/apps/explorer_web/priv/gettext/default.pot @@ -180,7 +180,7 @@ msgstr "" msgid "Overview" msgstr "" -#: lib/explorer_web/views/transaction_view.ex:99 +#: lib/explorer_web/views/transaction_view.ex:97 msgid "Success" msgstr "" @@ -238,11 +238,11 @@ msgstr "" #: lib/explorer_web/templates/transaction/index.html.eex:19 #: lib/explorer_web/templates/transaction/overview.html.eex:56 #: lib/explorer_web/templates/transaction/overview.html.eex:70 -#: lib/explorer_web/views/transaction_view.ex:13 -#: lib/explorer_web/views/transaction_view.ex:27 -#: lib/explorer_web/views/transaction_view.ex:54 -#: lib/explorer_web/views/transaction_view.ex:61 -#: lib/explorer_web/views/transaction_view.ex:98 +#: lib/explorer_web/views/transaction_view.ex:11 +#: lib/explorer_web/views/transaction_view.ex:25 +#: lib/explorer_web/views/transaction_view.ex:52 +#: lib/explorer_web/views/transaction_view.ex:59 +#: lib/explorer_web/views/transaction_view.ex:96 msgid "Pending" msgstr "" @@ -305,11 +305,11 @@ msgstr "" msgid "Next Page" msgstr "" -#: lib/explorer_web/views/transaction_view.ex:96 +#: lib/explorer_web/views/transaction_view.ex:94 msgid "Failed" msgstr "" -#: lib/explorer_web/views/transaction_view.ex:97 +#: lib/explorer_web/views/transaction_view.ex:95 msgid "Out of Gas" msgstr "" diff --git a/apps/explorer_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/explorer_web/priv/gettext/en/LC_MESSAGES/default.po index 049395b13c..5dfea689ee 100644 --- a/apps/explorer_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/explorer_web/priv/gettext/en/LC_MESSAGES/default.po @@ -192,7 +192,7 @@ msgstr "From" msgid "Overview" msgstr "Overview" -#: lib/explorer_web/views/transaction_view.ex:99 +#: lib/explorer_web/views/transaction_view.ex:97 msgid "Success" msgstr "Success" @@ -250,11 +250,11 @@ msgstr "Showing %{count} Transactions" #: lib/explorer_web/templates/transaction/index.html.eex:19 #: lib/explorer_web/templates/transaction/overview.html.eex:56 #: lib/explorer_web/templates/transaction/overview.html.eex:70 -#: lib/explorer_web/views/transaction_view.ex:13 -#: lib/explorer_web/views/transaction_view.ex:27 -#: lib/explorer_web/views/transaction_view.ex:54 -#: lib/explorer_web/views/transaction_view.ex:61 -#: lib/explorer_web/views/transaction_view.ex:98 +#: lib/explorer_web/views/transaction_view.ex:11 +#: lib/explorer_web/views/transaction_view.ex:25 +#: lib/explorer_web/views/transaction_view.ex:52 +#: lib/explorer_web/views/transaction_view.ex:59 +#: lib/explorer_web/views/transaction_view.ex:96 msgid "Pending" msgstr "Pending" @@ -317,11 +317,11 @@ msgstr "" msgid "Next Page" msgstr "" -#: lib/explorer_web/views/transaction_view.ex:96 +#: lib/explorer_web/views/transaction_view.ex:94 msgid "Failed" msgstr "" -#: lib/explorer_web/views/transaction_view.ex:97 +#: lib/explorer_web/views/transaction_view.ex:95 msgid "Out of Gas" msgstr "" From cb69fae46e4b2ff935dc7ca2d5bd1f5a29181d53 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 8 May 2018 09:06:28 -0500 Subject: [PATCH 27/77] Cover Explorer.Chain.Transaction --- .../lib/explorer/chain/transaction.ex | 155 ++++++++++++++++++ .../test/explorer/chain/transaction_test.exs | 2 + 2 files changed, 157 insertions(+) diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index 97d17f4746..a73138f200 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -154,6 +154,101 @@ defmodule Explorer.Chain.Transaction do ) end + @doc """ + A pending transaction has neither `block_hash` nor an `index` + + iex> changeset = Explorer.Chain.Transaction.changeset( + ...> %Transaction{}, + ...> %{ + ...> gas: 4700000, + ...> gas_price: 100000000000, + ...> hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + ...> input: "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + ...> nonce: 0, + ...> public_key: "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", + ...> r: "0xAD3733DF250C87556335FFE46C23E34DBAFFDE93097EF92F52C88632A40F0C75", + ...> s: "0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", + ...> standard_v: "0x0", + ...> v: "0x8d", + ...> value: 0 + ...> } + ...> ) + iex> changeset.valid? + true + + A pending transaction can't have an `index` + + iex> changeset = Explorer.Chain.Transaction.changeset( + ...> %Transaction{}, + ...> %{ + ...> gas: 4700000, + ...> gas_price: 100000000000, + ...> hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + ...> index: 0, + ...> input: "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + ...> nonce: 0, + ...> public_key: "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", + ...> r: "0xAD3733DF250C87556335FFE46C23E34DBAFFDE93097EF92F52C88632A40F0C75", + ...> s: "0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", + ...> standard_v: "0x0", + ...> v: "0x8d", + ...> value: 0 + ...> } + ...> ) + iex> changeset.valid? + false + iex> changeset.errors + [index: {"can't be set when the transaction is pending", []}] + + A collated transaction has a `block_hash` for the block in which it was collated. + + iex> changeset = Explorer.Chain.Transaction.changeset( + ...> %Transaction{}, + ...> %{ + ...> block_hash: "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", + ...> gas: 4700000, + ...> gas_price: 100000000000, + ...> hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + ...> index: 0, + ...> input: "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + ...> nonce: 0, + ...> public_key: "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", + ...> r: "0xAD3733DF250C87556335FFE46C23E34DBAFFDE93097EF92F52C88632A40F0C75", + ...> s: "0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", + ...> standard_v: "0x0", + ...> v: "0x8d", + ...> value: 0 + ...> } + ...> ) + iex> changeset.valid? + true + + A collated transaction MUST have an `index` so its position in the `block` is known. + + iex> changeset = Explorer.Chain.Transaction.changeset( + ...> %Transaction{}, + ...> %{ + ...> block_hash: "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", + ...> gas: 4700000, + ...> gas_price: 100000000000, + ...> hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + ...> input: "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + ...> nonce: 0, + ...> public_key: "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", + ...> r: "0xAD3733DF250C87556335FFE46C23E34DBAFFDE93097EF92F52C88632A40F0C75", + ...> s: "0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", + ...> standard_v: "0x0", + ...> v: "0x8d", + ...> value: 0 + ...> } + ...> ) + iex> changeset.valid? + false + iex> changeset.errors + [index: {"can't be blank when transaction is collated into a block", []}] + + + """ def changeset(%__MODULE__{} = transaction, attrs \\ %{}) do transaction |> cast(attrs, @required_attrs ++ @optional_attrs) @@ -168,6 +263,66 @@ defmodule Explorer.Chain.Transaction do |> unique_constraint(:hash) end + @doc """ + A transaction transfering `value` between two addresses has a `from_address_hash` and `to_address_hash` + + iex> %Ecto.Changeset{changes: changes, valid?: true} = Explorer.Chain.Transaction.changeset( + ...> %Transaction{}, + ...> %{ + ...> block_hash: "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", + ...> from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> gas: 4700000, + ...> gas_price: 100000000000, + ...> hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + ...> index: 0, + ...> input: "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + ...> nonce: 0, + ...> public_key: "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", + ...> r: "0xAD3733DF250C87556335FFE46C23E34DBAFFDE93097EF92F52C88632A40F0C75", + ...> s: "0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", + ...> standard_v: "0x0", + ...> to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + ...> v: "0x8d", + ...> value: 0 + ...> } + ...> ) + iex> address_hash_set = Explorer.Chain.Transaction.changes_to_address_hash_set(changes) + iex> changes.from_address_hash in address_hash_set + true + iex> changes.to_address_hash in address_hash_set + true + + A contract creation transaction does not have a `to_address_hash`, so the `t:MapSet.t/0` only contains the + `from_address_hash`. + + iex> %Ecto.Changeset{changes: changes, valid?: true} = Explorer.Chain.Transaction.changeset( + ...> %Transaction{}, + ...> %{ + ...> block_hash: "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", + ...> gas: 4700000, + ...> gas_price: 100000000000, + ...> hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + ...> index: 0, + ...> input: "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + ...> nonce: 0, + ...> public_key: "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", + ...> r: "0xAD3733DF250C87556335FFE46C23E34DBAFFDE93097EF92F52C88632A40F0C75", + ...> s: "0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", + ...> standard_v: "0x0", + ...> to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + ...> v: "0x8d", + ...> value: 0 + ...> } + ...> ) + iex> Explorer.Chain.Transaction.changes_to_address_hash_set(changes) + MapSet.new([ + %Explorer.Chain.Hash{ + byte_count: 20, + bytes: <<139, 243, 141, 71, 100, 146, 144, 100, 242, 212, 211, 165, 101, 32, 167, 106, 179, 223, 65, 91>> + } + ]) + + """ def changes_to_address_hash_set(changes) do Enum.reduce(~w(from_address_hash to_address_hash)a, MapSet.new(), fn field, acc -> case Map.get(changes, field) do diff --git a/apps/explorer/test/explorer/chain/transaction_test.exs b/apps/explorer/test/explorer/chain/transaction_test.exs index 6cd33dbd82..083eb36e9a 100644 --- a/apps/explorer/test/explorer/chain/transaction_test.exs +++ b/apps/explorer/test/explorer/chain/transaction_test.exs @@ -3,6 +3,8 @@ defmodule Explorer.Chain.TransactionTest do alias Explorer.Chain.Transaction + doctest Transaction + describe "changeset/2" do test "with valid attributes" do changeset = From 4bd61162d9333684cbf21b6c9eb7d685833cb327 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 8 May 2018 09:09:13 -0500 Subject: [PATCH 28/77] Remove unused files --- apps/explorer_web/lib/explorer_web.ex | 2 +- .../lib/explorer_web/views/error_helpers.ex | 40 ------------------- .../explorer_web/test/support/channel_case.ex | 37 ----------------- 3 files changed, 1 insertion(+), 78 deletions(-) delete mode 100644 apps/explorer_web/lib/explorer_web/views/error_helpers.ex delete mode 100644 apps/explorer_web/test/support/channel_case.ex diff --git a/apps/explorer_web/lib/explorer_web.ex b/apps/explorer_web/lib/explorer_web.ex index b0bd86ba6d..0e5ae8ea22 100644 --- a/apps/explorer_web/lib/explorer_web.ex +++ b/apps/explorer_web/lib/explorer_web.ex @@ -40,7 +40,7 @@ defmodule ExplorerWeb do # Use all HTML functionality (forms, tags, etc) use Phoenix.HTML - import ExplorerWeb.{ErrorHelpers, Gettext, Router.Helpers} + import ExplorerWeb.{Gettext, Router.Helpers} import Scrivener.HTML end end diff --git a/apps/explorer_web/lib/explorer_web/views/error_helpers.ex b/apps/explorer_web/lib/explorer_web/views/error_helpers.ex deleted file mode 100644 index dbc30dcb40..0000000000 --- a/apps/explorer_web/lib/explorer_web/views/error_helpers.ex +++ /dev/null @@ -1,40 +0,0 @@ -defmodule ExplorerWeb.ErrorHelpers do - @moduledoc """ - Conveniences for translating and building error messages. - """ - - use Phoenix.HTML - - @doc """ - Generates tag for inlined form input errors. - """ - def error_tag(form, field) do - Enum.map(Keyword.get_values(form.errors, field), fn error -> - content_tag(:span, translate_error(error), class: "help-block") - end) - end - - @doc """ - Translates an error message using gettext. - """ - def translate_error({msg, opts}) do - # Because error messages were defined within Ecto, we must - # call the Gettext module passing our Gettext backend. We - # also use the "errors" domain as translations are placed - # in the errors.po file. - # Ecto will pass the :count keyword if the error message is - # meant to be pluralized. - # On your own code and templates, depending on whether you - # need the message to be pluralized or not, this could be - # written simply as: - # - # dngettext "errors", "1 file", "%{count} files", count - # dgettext "errors", "is invalid" - # - if count = opts[:count] do - Gettext.dngettext(ExplorerWeb.Gettext, "errors", msg, msg, count, opts) - else - Gettext.dgettext(ExplorerWeb.Gettext, "errors", msg, opts) - end - end -end diff --git a/apps/explorer_web/test/support/channel_case.ex b/apps/explorer_web/test/support/channel_case.ex deleted file mode 100644 index a11584b281..0000000000 --- a/apps/explorer_web/test/support/channel_case.ex +++ /dev/null @@ -1,37 +0,0 @@ -defmodule ExplorerWeb.ChannelCase do - @moduledoc """ - This module defines the test case to be used by - channel tests. - - Such tests rely on `Phoenix.ChannelTest` and also - import other functionality to make it easier - to build common datastructures and query the data layer. - - Finally, if the test case interacts with the database, - it cannot be async. For this reason, every test runs - inside a transaction which is reset at the beginning - of the test unless the test case is marked as async. - """ - - use ExUnit.CaseTemplate - - using do - quote do - # Import conveniences for testing with channels - use Phoenix.ChannelTest - - # The default endpoint for testing - @endpoint ExplorerWeb.Endpoint - end - end - - setup tags do - :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo) - - unless tags[:async] do - Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()}) - end - - :ok - end -end From eb8ba0fe2e5cc59261fbf236c775a5b7a981309b Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 8 May 2018 14:36:50 -0500 Subject: [PATCH 29/77] Cover Explorer.Chain --- apps/explorer/lib/explorer/chain.ex | 437 ++++++++++++++++-- .../lib/explorer/chain/statistics/server.ex | 2 +- .../lib/explorer/indexer/block_fetcher.ex | 2 +- 3 files changed, 393 insertions(+), 48 deletions(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 4a22c27a90..01e738aa90 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -110,6 +110,23 @@ defmodule Explorer.Chain do end end + @doc """ + Updates `t:Explorer.Chain.Address.t/0` with `hash` of `address_hash` to have `fetched_balance` of `balance` in + `t:map/0` `balances` of `address_hash` to `balance`. + + iex> Explorer.Chain.update_balances(%{"0x8bf38d4764929064f2d4d3a56520a76ab3df415b" => 100}) + :ok + iex> {:ok, hash} = Explorer.Chain.string_to_address_hash("0x8bf38d4764929064f2d4d3a56520a76ab3df415b") + iex> {:ok, address} = Explorer.Chain.hash_to_address(hash) + iex> address.fetched_balance + Decimal.new(100) + + There don't need to be any updates. + + iex> Explorer.Chain.update_balances(%{}) + :ok + + """ @spec update_balances(%{(address_hash :: String.t()) => balance :: integer}) :: :ok | {:error, reason :: term} def update_balances(balances) do timestamps = timestamps() @@ -432,6 +449,272 @@ defmodule Explorer.Chain do @doc """ Bulk insert blocks from a list of blocks. + The import returns the unique key(s0 for each type of record inserted. For record that don't have a primary key, such + as `t:Explorer.Chain.Receipt.t/0`, the key that uniquely identifies the record is returned. + + | Key | Value Type | Value Description | + |--------------------------|----------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------| + | `:addresses` | `[Explorer.Chain.Hash.t()]` | List of `t:Explorer.Chain.Address.t/0` `hash` | + | `:blocks` | `[Explorer.Chain.Hash.t()]` | List of `t:Explorer.Chain.Block.t/0` `hash` | + | `:internal_transactions` | `[%{index: non_neg_integer(), transaction_hash: Explorer.Chain.Hash.t()}]` | List of maps of the `t:Explorer.Chain.InternalTransaction.t/0` `index` and `transaction_hash` | + | `:logs` | `[%{index: non_neg_integer(), transaction_hash: Explorer.Chain.Hash.t()}]` | List of maps of the `t:Explorer.Chain.Log.t/0` `index` and `transaction_hash` | + | `:receipts` | `[Explorer.Chain.Hash.t()]` | List of `t:Explorer.Chain.Receipt.t/0` `transaction_hash` | + | `:transactions` | `[Explorer.Chain.Hash.t()]` | List of `t:Explorer.Chain.Transaction.t/0` `hash` | + + iex> Explorer.Chain.import_blocks( + ...> %{ + ...> blocks: [ + ...> %{ + ...> difficulty: 340282366920938463463374607431768211454, + ...> gas_limit: 6946336, + ...> gas_used: 50450, + ...> hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", + ...> miner_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> nonce: 0, + ...> number: 37, + ...> parent_hash: "0xc37bbad7057945d1bf128c1ff009fb1ad632110bf6a000aac025a80f7766b66e", + ...> size: 719, + ...> timestamp: Timex.parse!("2017-12-15T21:06:30Z", "{ISO:Extended:Z}"), + ...> total_difficulty: 12590447576074723148144860474975121280509 + ...> } + ...> ], + ...> internal_transactions: [ + ...> %{ + ...> call_type: "call", + ...> from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> gas: 4677320, + ...> gas_used: 27770, + ...> index: 0, + ...> output: "0x", + ...> to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + ...> trace_address: [], + ...> transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + ...> type: "call", + ...> value: 0 + ...> } + ...> ], + ...> logs: [ + ...> %{ + ...> address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + ...> data: "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", + ...> first_topic: "0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22", + ...> fourth_topic: nil, + ...> index: 0, + ...> second_topic: nil, + ...> third_topic: nil, + ...> transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + ...> type: "mined" + ...> } + ...> ], + ...> receipts: [ + ...> %{ + ...> cumulative_gas_used: 50450, + ...> gas_used: 50450, + ...> status: :ok, + ...> transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + ...> transaction_index: 0 + ...> } + ...> ], + ...> transactions: [ + ...> %{ + ...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", + ...> from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> gas: 4700000, + ...> gas_price: 100000000000, + ...> hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + ...> index: 0, + ...> input: "0x10855269000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", + ...> nonce: 4, + ...> public_key: "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", + ...> r: "0xa7f8f45cce375bb7af8750416e1b03e0473f93c256da2285d1134fc97a700e01", + ...> s: "0x1f87a076f13824f4be8963e3dffd7300dae64d5f23c9a062af0c6ead347c135f", + ...> standard_v: "0x1", + ...> to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + ...> v: "0xbe", + ...> value: 0 + ...> } + ...> ] + ...> } + ...> ) + {:ok, + %{ + addresses: [ + %Explorer.Chain.Hash{ + byte_count: 20, + bytes: <<139, 243, 141, 71, 100, 146, 144, 100, 242, 212, 211, + 165, 101, 32, 167, 106, 179, 223, 65, 91>> + }, + %Explorer.Chain.Hash{ + byte_count: 20, + bytes: <<232, 221, 197, 199, 162, 210, 240, 215, 169, 121, + 132, 89, 192, 16, 79, 223, 94, 152, 122, 202>> + } + ], + blocks: [ + %Explorer.Chain.Hash{ + byte_count: 32, + bytes: <<246, 180, 184, 200, 141, 243, 235, 210, 82, 236, 71, + 99, 40, 51, 77, 192, 38, 207, 102, 96, 106, 132, 251, 118, + 155, 61, 60, 188, 204, 132, 113, 189>> + } + ], + internal_transactions: [ + %{ + index: 0, + transaction_hash: %Explorer.Chain.Hash{ + byte_count: 32, + bytes: <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, + 27, 174, 236, 38, 46, 123, 149, 35, 77, 57, 101, 36, 140, + 57, 254, 153, 47, 255, 212, 51, 229>> + } + } + ], + logs: [ + %{ + index: 0, + transaction_hash: %Explorer.Chain.Hash{ + byte_count: 32, + bytes: <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, + 27, 174, 236, 38, 46, 123, 149, 35, 77, 57, 101, 36, 140, + 57, 254, 153, 47, 255, 212, 51, 229>> + } + } + ], + receipts: [ + %Explorer.Chain.Hash{ + byte_count: 32, + bytes: <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, + 27, 174, 236, 38, 46, 123, 149, 35, 77, 57, 101, 36, 140, + 57, 254, 153, 47, 255, 212, 51, 229>> + } + ], + transactions: [ + %Explorer.Chain.Hash{ + byte_count: 32, + bytes: <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, + 27, 174, 236, 38, 46, 123, 149, 35, 77, 57, 101, 36, 140, + 57, 254, 153, 47, 255, 212, 51, 229>> + } + ] + }} + + A completely empty tree can be imported, but all `t:list/0` arguments must still be supplied + + iex> Explorer.Chain.import_blocks( + ...> %{ + ...> blocks: [], + ...> logs: [], + ...> internal_transactions: [], + ...> receipts: [], + ...> transactions: [] + ...> } + ...> ) + {:ok, + %{ + addresses: [], + blocks: [], + internal_transactions: [], + logs: [], + receipts: [], + transactions: [] + }} + + The params for each key are validated using the corresponding `Ecto.Schema` module's `changeset/2` function. If there + are errors, they are returned in `Ecto.Changeset.t`s, so that the original, invalid value can be reconstructed for any + error messages. + + iex> {:error, [internal_transaction_changeset, receipt_changeset]} = Explorer.Chain.import_blocks( + ...> %{ + ...> blocks: [ + ...> %{ + ...> difficulty: 340282366920938463463374607431768211454, + ...> gas_limit: 6946336, + ...> gas_used: 50450, + ...> hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", + ...> miner_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> nonce: 0, + ...> number: 37, + ...> parent_hash: "0xc37bbad7057945d1bf128c1ff009fb1ad632110bf6a000aac025a80f7766b66e", + ...> size: 719, + ...> timestamp: Timex.parse!("2017-12-15T21:06:30Z", "{ISO:Extended:Z}"), + ...> total_difficulty: 12590447576074723148144860474975121280509 + ...> } + ...> ], + ...> internal_transactions: [ + ...> %{ + ...> from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> gas: 4677320, + ...> gas_used: 27770, + ...> index: 0, + ...> output: "0x", + ...> to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + ...> trace_address: [], + ...> transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + ...> type: "call", + ...> value: 0 + ...> }, + ...> # valid after invalid + ...> %{ + ...> created_contract_address_hash: "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", + ...> created_contract_code: "0x606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + ...> from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> gas: 4597044, + ...> gas_used: 166651, + ...> index: 0, + ...> init: "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + ...> trace_address: [], + ...> transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + ...> type: "create", + ...> value: 0 + ...> } + ...> ], + ...> logs: [ + ...> %{ + ...> address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + ...> data: "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", + ...> first_topic: "0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22", + ...> fourth_topic: nil, + ...> index: 0, + ...> second_topic: nil, + ...> third_topic: nil, + ...> transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + ...> type: "mined" + ...> } + ...> ], + ...> receipts: [ + ...> %{ + ...> cumulative_gas_used: 50450, + ...> gas_used: 50450, + ...> transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + ...> transaction_index: 0 + ...> } + ...> ], + ...> transactions: [ + ...> %{ + ...> block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", + ...> from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> gas: 4700000, + ...> gas_price: 100000000000, + ...> hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + ...> index: 0, + ...> input: "0x10855269000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", + ...> nonce: 4, + ...> public_key: "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", + ...> r: "0xa7f8f45cce375bb7af8750416e1b03e0473f93c256da2285d1134fc97a700e01", + ...> s: "0x1f87a076f13824f4be8963e3dffd7300dae64d5f23c9a062af0c6ead347c135f", + ...> standard_v: "0x1", + ...> to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + ...> v: "0xbe", + ...> value: 0 + ...> } + ...> ] + ...> } + ...> ) + iex> internal_transaction_changeset.errors + [call_type: {"can't be blank", [validation: :required]}] + iex> receipt_changeset.errors + [status: {"can't be blank", [validation: :required]}] + ## Tree * `t:Explorer.Chain.Block.t/0`s @@ -464,6 +747,16 @@ defmodule Explorer.Chain do @doc """ The number of `t:Explorer.Chain.Address.t/0`. + + iex> insert_list(2, :address) + iex> Explorer.Chain.address_count() + 2 + + When there are no `t:Explorer.Chain.Address.t/0`, the count is `0`. + + iex> Explorer.Chain.address_count() + 0 + """ def address_count do Repo.aggregate(Address, :count, :hash) @@ -537,6 +830,32 @@ defmodule Explorer.Chain do @doc """ Returns a stream of unfetched `Explorer.Chain.Address.t/0`. + + When there are addresses, the `reducer` is called for each `t:Explorer.Chain.Address.t/0`. + + iex> [first_address_hash, second_address_hash] = 2 |> insert_list(:address) |> Enum.map(& &1.hash) + iex> {:ok, address_hash_set} = Explorer.Chain.stream_unfetched_addresses( + ...> MapSet.new([]), + ...> fn %Explorer.Chain.Address{hash: hash}, acc -> + ...> MapSet.put(acc, hash) + ...> end + ...> ) + ...> first_address_hash in address_hash_set + true + ...> second_address_hash in address_hash_set + true + + When there are no addresses, the `reducer` is never called and the `initial` is returned in an `:ok` tuple. + + iex> {:ok, pid} = Agent.start_link(fn -> 0 end) + iex> Explorer.Chain.stream_unfetched_addresses(MapSet.new([]), fn %Explorer.Chain.Address{hash: hash}, acc -> + ...> Agent.update(pid, &(&1 + 1)) + ...> MapSet.put(acc, hash) + ...> end) + {:ok, MapSet.new([])} + iex> Agent.get(pid, & &1) + 0 + """ def stream_unfetched_addresses(initial, reducer) when is_function(reducer) do Repo.transaction(fn -> @@ -593,7 +912,32 @@ defmodule Explorer.Chain do end @doc """ - TODO + Calculates the overall missing number of blocks and the ranges of missing blocks. + + `missing_block_numbers/0` does not take into account block numbers that have appeared on-chain after the + `max_block_number/0`; it only uses the missing blocks in the database between `0` and `max_block_number/0`. + + When there are no `t:Explorer.Chain.Block.t/0`, there can be no missing blocks. + + iex> Explorer.Chain.missing_block_numbers() + {0, []} + + If the block numbers from `0` to `max_block_number/0` are contiguous, then no block numbers are missing + + iex> insert(:block, number: 0) + iex> insert(:block, number: 1) + iex> Explorer.Chain.missing_block_numbers() + {0, []} + + If there are gaps between `0` and `max_block_number/0`, then the missing numbers are compacted into ranges. Single + missing numbers become ranges with the single number as the start and end. + + iex> insert(:block, number: 0) + iex> insert(:block, number: 2) + iex> insert(:block, number: 5) + iex> Explorer.Chain.missing_block_numbers() + {3, [{3, 4}, {1, 1}]} + """ def missing_block_numbers do {:ok, {_, missing_count, missing_ranges}} = @@ -887,24 +1231,6 @@ defmodule Explorer.Chain do Hash.Full.cast(string) end - @doc """ - `t:Explorer.Chain.Transaction/0`s to `address`. - - ## Options - - * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is - `:required`, and the `t:Explorer.Chain.Transaction.t/0` has no associated record for that association, then the - `t:Explorer.Chain.Transaction.t/0` will not be included in the page `entries`. - * `:pagination` - pagination params to pass to scrivener. - - """ - @spec to_address_to_transactions(Address.t(), [ - necessity_by_association_option | pagination_option - ]) :: %Scrivener.Page{entries: [Transaction.t()]} - def to_address_to_transactions(address = %Address{}, options \\ []) when is_list(options) do - address_to_transactions(address, Keyword.put(options, :direction, :to)) - end - @doc """ Count of `t:Explorer.Chain.Transaction.t/0`. @@ -1150,13 +1476,16 @@ defmodule Explorer.Chain do defp insert_blocks(changes_list, named_arguments) when is_list(changes_list) and is_list(named_arguments) do timestamps = Keyword.fetch!(named_arguments, :timestamps) - insert_changes_list( - changes_list, - conflict_target: :number, - on_conflict: :replace_all, - for: Block, - timestamps: timestamps - ) + {:ok, _} = + insert_changes_list( + changes_list, + conflict_target: :number, + on_conflict: :replace_all, + for: Block, + timestamps: timestamps + ) + + {:ok, for(changes <- changes_list, do: changes.hash)} end defp insert_ecto_schema_module_to_changes_list( @@ -1191,37 +1520,50 @@ defmodule Explorer.Chain do when is_list(changes_list) and is_list(named_arguments) do timestamps = Keyword.fetch!(named_arguments, :timestamps) - insert_changes_list( - changes_list, - for: InternalTransaction, - timestamps: timestamps - ) + {:ok, internal_transactions} = + insert_changes_list( + changes_list, + for: InternalTransaction, + returning: [:index, :transaction_hash], + timestamps: timestamps + ) + + {:ok, + for(internal_transaction <- internal_transactions, do: Map.take(internal_transaction, [:index, :transaction_hash]))} end @spec insert_logs([map()], [timestamps_option]) :: {:ok, Log.t()} | {:error, [Changeset.t()]} defp insert_logs(changes_list, named_arguments) when is_list(changes_list) and is_list(named_arguments) do timestamps = Keyword.fetch!(named_arguments, :timestamps) - insert_changes_list( - changes_list, - conflict_target: [:transaction_hash, :index], - on_conflict: :replace_all, - for: Log, - timestamps: timestamps - ) + {:ok, logs} = + insert_changes_list( + changes_list, + conflict_target: [:transaction_hash, :index], + on_conflict: :replace_all, + for: Log, + returning: [:index, :transaction_hash], + timestamps: timestamps + ) + + {:ok, for(log <- logs, do: Map.take(log, [:index, :transaction_hash]))} end @spec insert_receipts([map()], [timestamps_option]) :: {:ok, Receipt.t()} | {:error, [Changeset.t()]} defp insert_receipts(changes_list, named_arguments) when is_list(changes_list) and is_list(named_arguments) do timestamps = Keyword.fetch!(named_arguments, :timestamps) - insert_changes_list( - changes_list, - conflict_target: :transaction_hash, - on_conflict: :replace_all, - for: Receipt, - timestamps: timestamps - ) + {:ok, receipts} = + insert_changes_list( + changes_list, + conflict_target: :transaction_hash, + on_conflict: :replace_all, + for: Receipt, + returning: [:transaction_hash], + timestamps: timestamps + ) + + {:ok, for(receipt <- receipts, do: receipt.transaction_hash)} end defp insert_changes_list(changes_list, options) when is_list(changes_list) do @@ -1236,13 +1578,16 @@ defmodule Explorer.Chain do defp insert_transactions(changes_list, named_arguments) when is_list(changes_list) and is_list(named_arguments) do timestamps = Keyword.fetch!(named_arguments, :timestamps) - insert_changes_list( + {:ok, transactions} = insert_changes_list( changes_list, conflict_target: :hash, on_conflict: :replace_all, for: Transaction, + returning: [:hash], timestamps: timestamps ) + + {:ok, for(transaction <- transactions, do: transaction.hash)} end defp inserted_after(query, options) do diff --git a/apps/explorer/lib/explorer/chain/statistics/server.ex b/apps/explorer/lib/explorer/chain/statistics/server.ex index a6f6ea9e12..1721d1af3b 100644 --- a/apps/explorer/lib/explorer/chain/statistics/server.ex +++ b/apps/explorer/lib/explorer/chain/statistics/server.ex @@ -8,7 +8,7 @@ defmodule Explorer.Chain.Statistics.Server do @interval 1_000 def child_spec(_) do - Supervisor.Spec.worker(__MODULE__, [true]) + Supervisor.Spec.worker(__MODULE__, [[refresh: true]]) end @spec fetch() :: Statistics.t() diff --git a/apps/explorer/lib/explorer/indexer/block_fetcher.ex b/apps/explorer/lib/explorer/indexer/block_fetcher.ex index 403ff6bba7..1545f3d477 100644 --- a/apps/explorer/lib/explorer/indexer/block_fetcher.ex +++ b/apps/explorer/lib/explorer/indexer/block_fetcher.ex @@ -16,7 +16,7 @@ defmodule Explorer.Indexer.BlockFetcher do alias Explorer.JSONRPC.Transactions - @batch_size 50 + @batch_size 1 @blocks_concurrency 10 @internal_batch_size 50 From a54dba29f548f9ff5e2b69534a94f305c45623e2 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 8 May 2018 14:46:30 -0500 Subject: [PATCH 30/77] Cover Explorer.Chain.Address and impls --- apps/explorer/lib/explorer/chain/address.ex | 18 ++++++++++++++++++ .../chars/explorer/chain/address_test.exs | 5 +++++ 2 files changed, 23 insertions(+) create mode 100644 apps/explorer/test/string/chars/explorer/chain/address_test.exs diff --git a/apps/explorer/lib/explorer/chain/address.ex b/apps/explorer/lib/explorer/chain/address.ex index 48d6544622..d648415e4b 100644 --- a/apps/explorer/lib/explorer/chain/address.ex +++ b/apps/explorer/lib/explorer/chain/address.ex @@ -69,6 +69,24 @@ defmodule Explorer.Chain.Address do end defimpl String.Chars do + @doc """ + Uses `hash` as string representation + + iex> address = %Explorer.Chain.Address{ + ...> hash: %Explorer.Chain.Hash{ + ...> byte_count: 20, + ...> bytes: <<139, 243, 141, 71, 100, 146, 144, 100, 242, 212, 211, + ...> 165, 101, 32, 167, 106, 179, 223, 65, 91>> + ...> } + ...> } + iex> to_string(address) + "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + iex> to_string(address.hash) + "0x8bf38d4764929064f2d4d3a56520a76ab3df415b" + iex> to_string(address) == to_string(address.hash) + true + + """ def to_string(%@for{hash: hash}) do @protocol.to_string(hash) end diff --git a/apps/explorer/test/string/chars/explorer/chain/address_test.exs b/apps/explorer/test/string/chars/explorer/chain/address_test.exs new file mode 100644 index 0000000000..5d08c3e03c --- /dev/null +++ b/apps/explorer/test/string/chars/explorer/chain/address_test.exs @@ -0,0 +1,5 @@ +defmodule String.Chars.Explorer.Chain.AddressTest do + use ExUnit.Case, async: true + + doctest String.Chars.Explorer.Chain.Address +end From 7473f8bfe89bd0cf28355c36048eb43a57abb5c6 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 8 May 2018 14:51:23 -0500 Subject: [PATCH 31/77] Cover Explorer.Chain.Statistics.Server --- apps/explorer/lib/explorer/chain/statistics/server.ex | 5 +---- .../explorer/test/explorer/chain/statistics/server_test.exs | 6 ++++++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/statistics/server.ex b/apps/explorer/lib/explorer/chain/statistics/server.ex index 1721d1af3b..196059c3b0 100644 --- a/apps/explorer/lib/explorer/chain/statistics/server.ex +++ b/apps/explorer/lib/explorer/chain/statistics/server.ex @@ -13,10 +13,7 @@ defmodule Explorer.Chain.Statistics.Server do @spec fetch() :: Statistics.t() def fetch do - case GenServer.whereis(__MODULE__) do - nil -> Statistics.fetch() - _ -> GenServer.call(__MODULE__, :fetch) - end + GenServer.call(__MODULE__, :fetch) end def start_link(opts \\ []) do diff --git a/apps/explorer/test/explorer/chain/statistics/server_test.exs b/apps/explorer/test/explorer/chain/statistics/server_test.exs index c5cb075701..166dbc5fed 100644 --- a/apps/explorer/test/explorer/chain/statistics/server_test.exs +++ b/apps/explorer/test/explorer/chain/statistics/server_test.exs @@ -4,6 +4,12 @@ defmodule Explorer.Chain.Statistics.ServerTest do alias Explorer.Chain.Statistics alias Explorer.Chain.Statistics.Server + describe "child_spec/1" do + test "it defines a child_spec/1 that works with supervisors" do + assert {:ok, _} = start_supervised(Server) + end + end + describe "init/1" do test "returns a new chain when not told to refresh" do {:ok, statistics} = Server.init(refresh: false) From 9ffef9f1d690d76e209a05520b1ec22a2328571c Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 8 May 2018 15:40:28 -0500 Subject: [PATCH 32/77] Feature tests should always work async --- apps/explorer_web/test/support/feature_case.ex | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/explorer_web/test/support/feature_case.ex b/apps/explorer_web/test/support/feature_case.ex index 40d177c69d..1bd857e2d1 100644 --- a/apps/explorer_web/test/support/feature_case.ex +++ b/apps/explorer_web/test/support/feature_case.ex @@ -15,13 +15,9 @@ defmodule ExplorerWeb.FeatureCase do end end - setup tags do + setup do :ok = Ecto.Adapters.SQL.Sandbox.checkout(Explorer.Repo) - unless tags[:async] do - Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()}) - end - metadata = Phoenix.Ecto.SQL.Sandbox.metadata_for(Explorer.Repo, self()) {:ok, session} = Wallaby.start_session(metadata: metadata) session = Wallaby.Browser.resize_window(session, 1200, 800) From 29ffbc6319cfec0b87cfbd748d9951f55963a4d7 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 8 May 2018 15:51:52 -0500 Subject: [PATCH 33/77] Use protocols in templates Don't access .hash for transactions and addresses or .number for block. --- .../templates/address/overview.html.eex | 6 +++--- .../address_internal_transaction/index.html.eex | 16 ++++++++-------- .../explorer_web/templates/block/index.html.eex | 4 ++-- .../explorer_web/templates/chain/show.html.eex | 14 +++++++------- .../templates/transaction/overview.html.eex | 14 +++++++------- .../templates/transaction/show.html.eex | 8 ++++---- .../templates/transaction_log/index.html.eex | 8 ++++---- 7 files changed, 35 insertions(+), 35 deletions(-) diff --git a/apps/explorer_web/lib/explorer_web/templates/address/overview.html.eex b/apps/explorer_web/lib/explorer_web/templates/address/overview.html.eex index 405ab8419a..f587e90a9e 100644 --- a/apps/explorer_web/lib/explorer_web/templates/address/overview.html.eex +++ b/apps/explorer_web/lib/explorer_web/templates/address/overview.html.eex @@ -1,14 +1,14 @@

<%= gettext "Address" %>

-

<%= @address.hash %>

+

<%= @address %>

<%= gettext "Balance" %>
-
- <%= balance(@address) %> +
+ <%= balance(@address) %>
diff --git a/apps/explorer_web/lib/explorer_web/templates/address_internal_transaction/index.html.eex b/apps/explorer_web/lib/explorer_web/templates/address_internal_transaction/index.html.eex index 649710302f..7d3d9cc8c2 100644 --- a/apps/explorer_web/lib/explorer_web/templates/address_internal_transaction/index.html.eex +++ b/apps/explorer_web/lib/explorer_web/templates/address_internal_transaction/index.html.eex @@ -71,26 +71,26 @@ - <%= link(internal_transaction.transaction.hash, - to: transaction_path(@conn, :show, @conn.assigns.locale, internal_transaction.transaction.hash), + <%= link(internal_transaction.transaction, + to: transaction_path(@conn, :show, @conn.assigns.locale, internal_transaction.transaction), class: "transaction-log__link") %> - <%= link(internal_transaction.transaction.block.number, - to: block_path(@conn, :show, @conn.assigns.locale, internal_transaction.transaction.block.number), + <%= link(internal_transaction.transaction.block, + to: block_path(@conn, :show, @conn.assigns.locale, internal_transaction.transaction.block), class: "transaction-log__link") %> <%= ExplorerWeb.BlockView.age(internal_transaction.transaction.block) %> - <%= link(internal_transaction.from_address.hash, - to: address_path(@conn, :show, @conn.assigns.locale, internal_transaction.from_address.hash), + <%= link(internal_transaction.from_address, + to: address_path(@conn, :show, @conn.assigns.locale, internal_transaction.from_address), class: "transaction-log__link") %> <% to_address = internal_transaction.to_address %> <%= if to_address do %> - <%= link(to_address.hash, - to: address_path(@conn, :show, @conn.assigns.locale, internal_transaction.to_address.hash), + <%= link(to_address, + to: address_path(@conn, :show, @conn.assigns.locale, internal_transaction.to_address), class: "transaction-log__link") %> <% end %> diff --git a/apps/explorer_web/lib/explorer_web/templates/block/index.html.eex b/apps/explorer_web/lib/explorer_web/templates/block/index.html.eex index 0591615ea5..60a1869677 100644 --- a/apps/explorer_web/lib/explorer_web/templates/block/index.html.eex +++ b/apps/explorer_web/lib/explorer_web/templates/block/index.html.eex @@ -39,9 +39,9 @@ <%= link( - block.number, + block, class: "blocks__link", - to: block_path(@conn, :show, @conn.assigns.locale, block.number) + to: block_path(@conn, :show, @conn.assigns.locale, block) ) %> <%= block.timestamp |> Timex.from_now %> diff --git a/apps/explorer_web/lib/explorer_web/templates/chain/show.html.eex b/apps/explorer_web/lib/explorer_web/templates/chain/show.html.eex index 3c0cf063e2..dd0c5bbfcb 100644 --- a/apps/explorer_web/lib/explorer_web/templates/chain/show.html.eex +++ b/apps/explorer_web/lib/explorer_web/templates/chain/show.html.eex @@ -78,9 +78,9 @@ <%= link( - block.number, + block, class: "blocks__link", - to: block_path(@conn, :show, @conn.assigns.locale, block.number) + to: block_path(@conn, :show, @conn.assigns.locale, block) ) %> <%= block.timestamp |> Timex.from_now %> @@ -115,19 +115,19 @@ <%= for transaction <- @chain.transactions do %> -
+
<%= link( - transaction.hash, + transaction, class: "transactions__link transactions__link--truncated transactions__link--hash", - to: transaction_path(@conn, :show, @conn.assigns.locale, transaction.hash) + to: transaction_path(@conn, :show, @conn.assigns.locale, transaction) ) %>
<%= link( - transaction.block.number, + transaction.block, class: "transactions__link", - to: block_path(@conn, :show, @conn.assigns.locale, transaction.block.number) + to: block_path(@conn, :show, @conn.assigns.locale, transaction.block) ) %> diff --git a/apps/explorer_web/lib/explorer_web/templates/transaction/overview.html.eex b/apps/explorer_web/lib/explorer_web/templates/transaction/overview.html.eex index 6a55b732f4..1247f7a128 100644 --- a/apps/explorer_web/lib/explorer_web/templates/transaction/overview.html.eex +++ b/apps/explorer_web/lib/explorer_web/templates/transaction/overview.html.eex @@ -1,6 +1,6 @@

<%= gettext "Transaction Details" %>

-

<%= @transaction.hash %>

+

<%= @transaction %>

@@ -22,9 +22,9 @@ <% block = @transaction.block %> <%= if block do %> <%= link( - block.number, + block, class: "transaction__link", - to: block_path(@conn, :show, @conn.assigns.locale, block.number) + to: block_path(@conn, :show, @conn.assigns.locale, block) ) %> <% end %> @@ -48,9 +48,9 @@
<%= if @transaction.from_address do %> <%= link( - @transaction.from_address.hash, + @transaction.from_address, class: "transaction__link", - to: address_path(@conn, :show, @conn.assigns.locale, @transaction.from_address.hash) + to: address_path(@conn, :show, @conn.assigns.locale, @transaction.from_address) ) %> <% else %> <%= gettext "Pending" %> @@ -62,9 +62,9 @@
<%= if @transaction.to_address do %> <%= link( - @transaction.to_address.hash, + @transaction.to_address, class: "transaction__link", - to: address_path(@conn, :show, @conn.assigns.locale, @transaction.to_address.hash) + to: address_path(@conn, :show, @conn.assigns.locale, @transaction.to_address) ) %> <% else %> <%= gettext "Pending" %> diff --git a/apps/explorer_web/lib/explorer_web/templates/transaction/show.html.eex b/apps/explorer_web/lib/explorer_web/templates/transaction/show.html.eex index e675075f0e..849fe5b242 100644 --- a/apps/explorer_web/lib/explorer_web/templates/transaction/show.html.eex +++ b/apps/explorer_web/lib/explorer_web/templates/transaction/show.html.eex @@ -7,14 +7,14 @@ <%= link( gettext("Internal Transactions"), class: "transaction__link transaction__link--active", - to: transaction_path(@conn, :show, @conn.assigns.locale, @transaction.hash) + to: transaction_path(@conn, :show, @conn.assigns.locale, @transaction) ) %>

<%= link( gettext("Logs"), class: "transaction__link", - to: transaction_log_path(@conn, :index, @conn.assigns.locale, @transaction.hash) + to: transaction_log_path(@conn, :index, @conn.assigns.locale, @transaction) ) %>

@@ -33,13 +33,13 @@ <%= transaction.call_type %> - <%= link(transaction.from_address.hash, + <%= link(transaction.from_address, to: address_path(@conn, :show, @conn.assigns.locale, transaction.from_address), class: "transaction-log__link") %> <%= if transaction.to_address do %> - <%= link(transaction.to_address.hash, + <%= link(transaction.to_address, to: address_path(@conn, :show, @conn.assigns.locale, transaction.to_address), class: "transaction-log__link") %> <% end %> diff --git a/apps/explorer_web/lib/explorer_web/templates/transaction_log/index.html.eex b/apps/explorer_web/lib/explorer_web/templates/transaction_log/index.html.eex index f929c23384..80760527a2 100644 --- a/apps/explorer_web/lib/explorer_web/templates/transaction_log/index.html.eex +++ b/apps/explorer_web/lib/explorer_web/templates/transaction_log/index.html.eex @@ -7,14 +7,14 @@ <%= link( gettext("Internal Transactions"), class: "transaction__link", - to: transaction_path(@conn, :show, @conn.assigns.locale, @transaction.hash) + to: transaction_path(@conn, :show, @conn.assigns.locale, @transaction) ) %>

<%= link( gettext("Logs"), class: "transaction__link transaction__link--active", - to: transaction_log_path(@conn, :index, @conn.assigns.locale, @transaction.hash) + to: transaction_log_path(@conn, :index, @conn.assigns.locale, @transaction) ) %>

@@ -31,9 +31,9 @@ <%= link( - log.address.hash, + log.address, class: "transaction-log__link", - to: address_path(@conn, :show, @conn.assigns.locale, log.address.hash) + to: address_path(@conn, :show, @conn.assigns.locale, log.address) ) %> <%= log.first_topic %> From e017e648d954a8f3fbbdd5c495ce4a1cfcb4a729 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 8 May 2018 15:58:18 -0500 Subject: [PATCH 34/77] Remove unused view functions --- .../explorer_web/views/address_transaction_view.ex | 5 +---- .../lib/explorer_web/views/block_transaction_view.ex | 1 - .../lib/explorer_web/views/block_view.ex | 12 ------------ .../explorer_web/views/pending_transaction_view.ex | 1 - 4 files changed, 1 insertion(+), 18 deletions(-) diff --git a/apps/explorer_web/lib/explorer_web/views/address_transaction_view.ex b/apps/explorer_web/lib/explorer_web/views/address_transaction_view.ex index 8f4b015f49..1c0adb894a 100644 --- a/apps/explorer_web/lib/explorer_web/views/address_transaction_view.ex +++ b/apps/explorer_web/lib/explorer_web/views/address_transaction_view.ex @@ -1,11 +1,9 @@ defmodule ExplorerWeb.AddressTransactionView do use ExplorerWeb, :view - alias ExplorerWeb.{AddressView, TransactionView} + alias ExplorerWeb.TransactionView - defdelegate balance(address), to: AddressView defdelegate block(transaction), to: TransactionView - defdelegate fee(transaction), to: TransactionView defdelegate from_address(transaction), to: TransactionView defdelegate hash(transaction), to: TransactionView @@ -19,5 +17,4 @@ defmodule ExplorerWeb.AddressTransactionView do defdelegate status(transacton), to: TransactionView defdelegate to_address(transaction), to: TransactionView - defdelegate value(transaction), to: TransactionView end diff --git a/apps/explorer_web/lib/explorer_web/views/block_transaction_view.ex b/apps/explorer_web/lib/explorer_web/views/block_transaction_view.ex index 957b3c2560..9bba8e8397 100644 --- a/apps/explorer_web/lib/explorer_web/views/block_transaction_view.ex +++ b/apps/explorer_web/lib/explorer_web/views/block_transaction_view.ex @@ -10,5 +10,4 @@ defmodule ExplorerWeb.BlockTransactionView do defdelegate hash(transaction), to: TransactionView defdelegate status(transacton), to: TransactionView defdelegate to_address(transaction), to: TransactionView - defdelegate value(transaction), to: TransactionView end diff --git a/apps/explorer_web/lib/explorer_web/views/block_view.ex b/apps/explorer_web/lib/explorer_web/views/block_view.ex index 46b5b1c8e0..2a813cf487 100644 --- a/apps/explorer_web/lib/explorer_web/views/block_view.ex +++ b/apps/explorer_web/lib/explorer_web/views/block_view.ex @@ -27,16 +27,4 @@ defmodule ExplorerWeb.BlockView do def formatted_timestamp(%Block{timestamp: timestamp}) do Timex.format!(timestamp, "%b-%d-%Y %H:%M:%S %p %Z", :strftime) end - - def hash(%Block{hash: hash}) do - to_string(hash) - end - - def miner_hash(%Block{miner_hash: miner_hash}) do - to_string(miner_hash) - end - - def parent_hash(%Block{parent_hash: parent_hash}) do - to_string(parent_hash) - end end diff --git a/apps/explorer_web/lib/explorer_web/views/pending_transaction_view.ex b/apps/explorer_web/lib/explorer_web/views/pending_transaction_view.ex index 1c4c94025d..e28d5fdb74 100644 --- a/apps/explorer_web/lib/explorer_web/views/pending_transaction_view.ex +++ b/apps/explorer_web/lib/explorer_web/views/pending_transaction_view.ex @@ -9,5 +9,4 @@ defmodule ExplorerWeb.PendingTransactionView do defdelegate hash(transaction), to: TransactionView defdelegate last_seen(transaction), to: TransactionView defdelegate to_address(transaction), to: TransactionView - defdelegate value(transaction), to: TransactionView end From 9f874df1f52af85961a31470729b050f461172c5 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 8 May 2018 15:59:13 -0500 Subject: [PATCH 35/77] Remove unused :api pipeline --- apps/explorer_web/lib/explorer_web/router.ex | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/explorer_web/lib/explorer_web/router.ex b/apps/explorer_web/lib/explorer_web/router.ex index 9f679a19d1..6c8edcce81 100644 --- a/apps/explorer_web/lib/explorer_web/router.ex +++ b/apps/explorer_web/lib/explorer_web/router.ex @@ -22,10 +22,6 @@ defmodule ExplorerWeb.Router do plug(SetLocale, gettext: ExplorerWeb.Gettext, default_locale: "en") end - pipeline :api do - plug(:accepts, ["json"]) - end - scope "/", ExplorerWeb do pipe_through(:browser) pipe_through(:set_locale) From f1848db0f6dcaed32bf048df336340670ae66fb4 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 8 May 2018 16:17:12 -0500 Subject: [PATCH 36/77] Test both forms of 404 for controllers 1. Not a valid hash 2. Valid hash, but does not exist --- apps/explorer_web/priv/gettext/default.pot | 6 +++--- .../priv/gettext/en/LC_MESSAGES/default.po | 6 +++--- ...ddress_internal_transaction_controller_test.exs | 11 +++++++++-- .../address_transaction_controller_test.exs | 10 ++++++++-- .../controllers/transaction_controller_test.exs | 14 ++++++++++++-- .../transaction_log_controller_test.exs | 14 ++++++++++++-- 6 files changed, 47 insertions(+), 14 deletions(-) diff --git a/apps/explorer_web/priv/gettext/default.pot b/apps/explorer_web/priv/gettext/default.pot index 55bb04f70b..b3f7e8801e 100644 --- a/apps/explorer_web/priv/gettext/default.pot +++ b/apps/explorer_web/priv/gettext/default.pot @@ -171,7 +171,7 @@ msgstr "" #: lib/explorer_web/templates/transaction/overview.html.eex:47 #: lib/explorer_web/templates/transaction/show.html.eex:26 #: lib/explorer_web/views/address_internal_transaction_view.ex:7 -#: lib/explorer_web/views/address_transaction_view.ex:15 +#: lib/explorer_web/views/address_transaction_view.ex:13 msgid "From" msgstr "" @@ -194,7 +194,7 @@ msgstr "" #: lib/explorer_web/templates/transaction/overview.html.eex:61 #: lib/explorer_web/templates/transaction/show.html.eex:27 #: lib/explorer_web/views/address_internal_transaction_view.ex:6 -#: lib/explorer_web/views/address_transaction_view.ex:14 +#: lib/explorer_web/views/address_transaction_view.ex:12 msgid "To" msgstr "" @@ -382,7 +382,7 @@ msgstr "" #: lib/explorer_web/templates/address_internal_transaction/index.html.eex:28 #: lib/explorer_web/templates/address_transaction/index.html.eex:29 #: lib/explorer_web/views/address_internal_transaction_view.ex:8 -#: lib/explorer_web/views/address_transaction_view.ex:16 +#: lib/explorer_web/views/address_transaction_view.ex:14 msgid "All" msgstr "" diff --git a/apps/explorer_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/explorer_web/priv/gettext/en/LC_MESSAGES/default.po index 7142e863a5..0d7fe3604e 100644 --- a/apps/explorer_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/explorer_web/priv/gettext/en/LC_MESSAGES/default.po @@ -183,7 +183,7 @@ msgstr "Address" #: lib/explorer_web/templates/transaction/overview.html.eex:47 #: lib/explorer_web/templates/transaction/show.html.eex:26 #: lib/explorer_web/views/address_internal_transaction_view.ex:7 -#: lib/explorer_web/views/address_transaction_view.ex:15 +#: lib/explorer_web/views/address_transaction_view.ex:13 msgid "From" msgstr "From" @@ -206,7 +206,7 @@ msgstr "Success" #: lib/explorer_web/templates/transaction/overview.html.eex:61 #: lib/explorer_web/templates/transaction/show.html.eex:27 #: lib/explorer_web/views/address_internal_transaction_view.ex:6 -#: lib/explorer_web/views/address_transaction_view.ex:14 +#: lib/explorer_web/views/address_transaction_view.ex:12 msgid "To" msgstr "To" @@ -394,7 +394,7 @@ msgstr "" #: lib/explorer_web/templates/address_internal_transaction/index.html.eex:28 #: lib/explorer_web/templates/address_transaction/index.html.eex:29 #: lib/explorer_web/views/address_internal_transaction_view.ex:8 -#: lib/explorer_web/views/address_transaction_view.ex:16 +#: lib/explorer_web/views/address_transaction_view.ex:14 msgid "All" msgstr "" diff --git a/apps/explorer_web/test/explorer_web/controllers/address_internal_transaction_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/address_internal_transaction_controller_test.exs index ee8dd81ba6..73d52b0497 100644 --- a/apps/explorer_web/test/explorer_web/controllers/address_internal_transaction_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/address_internal_transaction_controller_test.exs @@ -4,10 +4,17 @@ defmodule ExplorerWeb.AddressInternalTransactionControllerTest do import ExplorerWeb.Router.Helpers, only: [address_internal_transaction_path: 4] describe "GET index/3" do - test "without address", %{conn: conn} do + test "with invalid address hash", %{conn: conn} do conn = conn - |> get(address_internal_transaction_path(ExplorerWeb.Endpoint, :index, :en, "0xcafe")) + |> get(address_internal_transaction_path(ExplorerWeb.Endpoint, :index, :en, "invalid_address")) + + assert html_response(conn, 404) + end + + test "with valid address hash without address", %{conn: conn} do + conn = + get(conn, address_internal_transaction_path(conn, :index, :en, "0x8bf38d4764929064f2d4d3a56520a76ab3df415b")) assert html_response(conn, 404) end diff --git a/apps/explorer_web/test/explorer_web/controllers/address_transaction_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/address_transaction_controller_test.exs index 4a70581ed5..abad5dcb58 100644 --- a/apps/explorer_web/test/explorer_web/controllers/address_transaction_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/address_transaction_controller_test.exs @@ -4,8 +4,14 @@ defmodule ExplorerWeb.AddressTransactionControllerTest do import ExplorerWeb.Router.Helpers, only: [address_transaction_path: 4] describe "GET index/2" do - test "without address", %{conn: conn} do - conn = get(conn, address_transaction_path(conn, :index, :en, "unknown")) + test "with invalid address hash", %{conn: conn} do + conn = get(conn, address_transaction_path(conn, :index, :en, "invalid_address")) + + assert html_response(conn, 404) + end + + test "with valid address hash without address", %{conn: conn} do + conn = get(conn, address_transaction_path(conn, :index, :en, "0x8bf38d4764929064f2d4d3a56520a76ab3df415b")) assert html_response(conn, 404) end diff --git a/apps/explorer_web/test/explorer_web/controllers/transaction_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/transaction_controller_test.exs index ba9c3377a4..6b3b3472bb 100644 --- a/apps/explorer_web/test/explorer_web/controllers/transaction_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/transaction_controller_test.exs @@ -73,8 +73,18 @@ defmodule ExplorerWeb.TransactionControllerTest do end describe "GET show/3" do - test "without transaction", %{conn: conn} do - conn = get(conn, "/en/transactions/0x1") + test "with invalid transaction hash", %{conn: conn} do + conn = get(conn, transaction_path(conn, :show, :en, "invalid_transaction_hash")) + + assert html_response(conn, 404) + end + + test "with valid transaction hash without transaction", %{conn: conn} do + conn = + get( + conn, + transaction_path(conn, :show, :en, "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6") + ) assert html_response(conn, 404) end diff --git a/apps/explorer_web/test/explorer_web/controllers/transaction_log_controller_test.exs b/apps/explorer_web/test/explorer_web/controllers/transaction_log_controller_test.exs index e6d25c889e..7688c74ba8 100644 --- a/apps/explorer_web/test/explorer_web/controllers/transaction_log_controller_test.exs +++ b/apps/explorer_web/test/explorer_web/controllers/transaction_log_controller_test.exs @@ -4,8 +4,18 @@ defmodule ExplorerWeb.TransactionLogControllerTest do import ExplorerWeb.Router.Helpers, only: [transaction_log_path: 4] describe "GET index/2" do - test "without transaction", %{conn: conn} do - conn = get(conn, transaction_log_path(conn, :index, :en, "unknown")) + test "with invalid transaction hash", %{conn: conn} do + conn = get(conn, transaction_log_path(conn, :index, :en, "invalid_transaction_string")) + + assert html_response(conn, 404) + end + + test "with valid transaction hash without transaction", %{conn: conn} do + conn = + get( + conn, + transaction_log_path(conn, :index, :en, "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6") + ) assert html_response(conn, 404) end From 75d362e024899e964dc3fca16b81cd7951de811d Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 8 May 2018 16:30:02 -0500 Subject: [PATCH 37/77] Use nowarn_function instead of .dialyzer-ignore --- .dialyzer-ignore | 1 - apps/explorer/lib/explorer/indexer/block_fetcher.ex | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.dialyzer-ignore b/.dialyzer-ignore index 4a48b6b1ba..1993ebca1d 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -1,4 +1,3 @@ :0: Unknown function 'Elixir.ExUnit.Callbacks':'__merge__'/3 :0: Unknown function 'Elixir.ExUnit.CaseTemplate':'__proxy__'/2 :0: Unknown type 'Elixir.Map':t/0 -apps/explorer/lib/explorer/indexer/block_fetcher.ex:224: The created fun has no local return diff --git a/apps/explorer/lib/explorer/indexer/block_fetcher.ex b/apps/explorer/lib/explorer/indexer/block_fetcher.ex index 1545f3d477..0f1d7c18d1 100644 --- a/apps/explorer/lib/explorer/indexer/block_fetcher.ex +++ b/apps/explorer/lib/explorer/indexer/block_fetcher.ex @@ -16,6 +16,9 @@ defmodule Explorer.Indexer.BlockFetcher do alias Explorer.JSONRPC.Transactions + # dialyzer thinks that Logger.debug functions always have no_local_return + @dialyzer {:nowarn_function, stream_import: 3} + @batch_size 1 @blocks_concurrency 10 From d77b27dde988382b4baba2ffb015bb043db1362a Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 8 May 2018 16:53:27 -0500 Subject: [PATCH 38/77] Run dialyzer in test environment --- apps/explorer/mix.exs | 3 ++- apps/explorer_web/mix.exs | 3 ++- apps/explorer_web/test/support/feature_case.ex | 3 +++ mix.exs | 3 ++- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index 77604ed937..51935e23a2 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -22,7 +22,8 @@ defmodule Explorer.Mixfile do coveralls: :test, "coveralls.detail": :test, "coveralls.post": :test, - "coveralls.html": :test + "coveralls.html": :test, + dialyzer: :test ], start_permanent: Mix.env() == :prod, test_coverage: [tool: ExCoveralls], diff --git a/apps/explorer_web/mix.exs b/apps/explorer_web/mix.exs index 2a2ed34063..a4d185c495 100644 --- a/apps/explorer_web/mix.exs +++ b/apps/explorer_web/mix.exs @@ -22,7 +22,8 @@ defmodule ExplorerWeb.Mixfile do coveralls: :test, "coveralls.detail": :test, "coveralls.post": :test, - "coveralls.html": :test + "coveralls.html": :test, + dialyzer: :test ], start_permanent: Mix.env() == :prod, test_coverage: [tool: ExCoveralls], diff --git a/apps/explorer_web/test/support/feature_case.ex b/apps/explorer_web/test/support/feature_case.ex index 1bd857e2d1..25dc313046 100644 --- a/apps/explorer_web/test/support/feature_case.ex +++ b/apps/explorer_web/test/support/feature_case.ex @@ -1,6 +1,9 @@ defmodule ExplorerWeb.FeatureCase do use ExUnit.CaseTemplate + # Types on Wallaby.Browser.resize_window don't allow session from start_session to be passed, so setup breaks + @dialyzer {:nowarn_function, __ex_unit_setup_0: 1} + using do quote do use Wallaby.DSL diff --git a/mix.exs b/mix.exs index 79bbdb3311..c8e4a3e43f 100644 --- a/mix.exs +++ b/mix.exs @@ -18,7 +18,8 @@ defmodule ExplorerUmbrella.Mixfile do coveralls: :test, "coveralls.detail": :test, "coveralls.post": :test, - "coveralls.html": :test + "coveralls.html": :test, + dialyzer: :test ], start_permanent: Mix.env() == :prod, test_coverage: [tool: ExCoveralls] From dd7d3277ad913703eb1e9f3148fdadb66ea2d7b2 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 8 May 2018 16:59:06 -0500 Subject: [PATCH 39/77] Move ExplorerWeb-only factory helpers to ExplorerWeb.Factory --- apps/explorer/test/support/factory.ex | 28 ---------------- apps/explorer_web/test/support/factory.ex | 33 +++++++++++++++++++ .../explorer_web/test/support/feature_case.ex | 1 + 3 files changed, 34 insertions(+), 28 deletions(-) create mode 100644 apps/explorer_web/test/support/factory.ex diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index 131531928a..18df4ffdad 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -1,8 +1,6 @@ defmodule Explorer.Factory do use ExMachina.Ecto, repo: Explorer.Repo - import Ecto.Query - alias Explorer.Chain.{Address, Block, Hash, InternalTransaction, Log, Receipt, Transaction} alias Explorer.Repo @@ -130,32 +128,6 @@ defmodule Explorer.Factory do Repo.preload(block_transaction, [:block, :receipt]) end - def with_block(%Transaction{index: nil} = transaction, %Block{hash: block_hash}) do - next_transaction_index = block_hash_to_next_transaction_index(block_hash) - - transaction - |> Transaction.changeset(%{block_hash: block_hash, index: next_transaction_index}) - |> Repo.update!() - |> Repo.preload(:block) - end - - def with_receipt(%Transaction{hash: hash, index: index} = transaction) do - insert(:receipt, transaction_hash: hash, transaction_index: index) - - Repo.preload(transaction, :receipt) - end - - defp block_hash_to_next_transaction_index(block_hash) do - query = - from( - transaction in Transaction, - select: transaction.index, - where: transaction.block_hash == ^block_hash - ) - - Repo.one!(query) + 1 - end - defp integer_to_hexadecimal(integer) when is_integer(integer) do "0x" <> Integer.to_string(integer, 16) end diff --git a/apps/explorer_web/test/support/factory.ex b/apps/explorer_web/test/support/factory.ex new file mode 100644 index 0000000000..4511abfea1 --- /dev/null +++ b/apps/explorer_web/test/support/factory.ex @@ -0,0 +1,33 @@ +defmodule ExplorerWeb.Factory do + import Ecto.Query + import Explorer.Factory + + alias Explorer.Chain.{Block, Transaction} + alias Explorer.Repo + + def with_block(%Transaction{index: nil} = transaction, %Block{hash: block_hash}) do + next_transaction_index = block_hash_to_next_transaction_index(block_hash) + + transaction + |> Transaction.changeset(%{block_hash: block_hash, index: next_transaction_index}) + |> Repo.update!() + |> Repo.preload(:block) + end + + def with_receipt(%Transaction{hash: hash, index: index} = transaction) do + insert(:receipt, transaction_hash: hash, transaction_index: index) + + Repo.preload(transaction, :receipt) + end + + defp block_hash_to_next_transaction_index(block_hash) do + query = + from( + transaction in Transaction, + select: transaction.index, + where: transaction.block_hash == ^block_hash + ) + + Repo.one!(query) + 1 + end +end diff --git a/apps/explorer_web/test/support/feature_case.ex b/apps/explorer_web/test/support/feature_case.ex index 25dc313046..679c0d1022 100644 --- a/apps/explorer_web/test/support/feature_case.ex +++ b/apps/explorer_web/test/support/feature_case.ex @@ -15,6 +15,7 @@ defmodule ExplorerWeb.FeatureCase do import Ecto.Query import ExplorerWeb.Router.Helpers import Explorer.Factory + import ExplorerWeb.Factory end end From eacef49b62d8e05397f7dcd48617f9e35c0e3ce7 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 8 May 2018 17:14:02 -0500 Subject: [PATCH 40/77] Cover Explorer.JSONRPC.Transaction(s) --- .../lib/explorer/jsonrpc/transactions.ex | 130 ++++++++++++++++++ .../explorer/jsonrpc/transactions_test.exs | 5 + 2 files changed, 135 insertions(+) create mode 100644 apps/explorer/test/explorer/jsonrpc/transactions_test.exs diff --git a/apps/explorer/lib/explorer/jsonrpc/transactions.ex b/apps/explorer/lib/explorer/jsonrpc/transactions.ex index fd775b9289..9551a537f3 100644 --- a/apps/explorer/lib/explorer/jsonrpc/transactions.ex +++ b/apps/explorer/lib/explorer/jsonrpc/transactions.ex @@ -10,14 +10,144 @@ defmodule Explorer.JSONRPC.Transactions do @type elixir :: [Transaction.elixir()] @type t :: [Transaction.t()] + @doc """ + Converts each entry in `elixir` to params used in `Explorer.Chain.Transaction.changeset/2`. + + iex> Explorer.JSONRPC.Transactions.elixir_to_params( + ...> [ + ...> %{ + ...> "blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", + ...> "blockNumber" => 34, + ...> "chainId" => 77, + ...> "condition" => nil, + ...> "creates" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", + ...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> "gas" => 4700000, + ...> "gasPrice" => 100000000000, + ...> "hash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + ...> "input" => "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + ...> "nonce" => 0, + ...> "publicKey" => "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", + ...> "r" => "0xad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75", + ...> "raw" => "0xf9038d8085174876e8008347b7608080b903396060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b002981bda0ad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75a072caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", + ...> "s" => "0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", + ...> "standardV" => "0x0", + ...> "to" => nil, + ...> "transactionIndex" => 0, + ...> "v" => "0xbd", + ...> "value" => 0 + ...> } + ...> ] + ...> ) + [ + %{ + block_hash: "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", + from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + gas: 4700000, + gas_price: 100000000000, + hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + index: 0, + input: "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + nonce: 0, + public_key: "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", + r: "0xad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75", + s: "0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", + standard_v: "0x0", + to_address_hash: nil, + v: "0xbd", + value: 0 + } + ] + + """ def elixir_to_params(elixir) when is_list(elixir) do Enum.map(elixir, &Transaction.elixir_to_params/1) end + @doc """ + Extract just the `t:Explorer.Chain.Transaction.t/0` `hash` from `params` list elements. + + iex> Explorer.JSONRPC.Transactions.params_to_hashes( + ...> [ + ...> %{ + ...> block_hash: "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", + ...> from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> gas: 4700000, + ...> gas_price: 100000000000, + ...> hash: "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + ...> index: 0, + ...> input: "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + ...> nonce: 0, + ...> public_key: "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", + ...> r: "0xad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75", + ...> s: "0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", + ...> standard_v: "0x0", + ...> to_address_hash: nil, + ...> v: "0xbd", + ...> value: 0 + ...> } + ...> ] + ...> ) + ["0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6"] + + """ def params_to_hashes(params) when is_list(params) do Enum.map(params, &Transaction.params_to_hash/1) end + @doc """ + Decodes stringly typed fields in entries in `transactions` + + iex> Explorer.JSONRPC.Transactions.to_elixir([ + ...> %{ + ...> "blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", + ...> "blockNumber" => "0x22", + ...> "chainId" => "0x4d", + ...> "condition" => nil, + ...> "creates" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", + ...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> "gas" => "0x47b760", + ...> "gasPrice" => "0x174876e800", + ...> "hash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + ...> "input" => "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + ...> "nonce" => "0x0", + ...> "publicKey" => "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", + ...> "r" => "0xad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75", + ...> "raw" => "0xf9038d8085174876e8008347b7608080b903396060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b002981bda0ad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75a072caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", + ...> "s" => "0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", + ...> "standardV" => "0x0", + ...> "to" => nil, + ...> "transactionIndex" => "0x0", + ...> "v" => "0xbd", + ...> "value" => "0x0" + ...> } + ...> ]) + [ + %{ + "blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", + "blockNumber" => 34, + "chainId" => 77, + "condition" => nil, + "creates" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", + "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + "gas" => 4700000, + "gasPrice" => 100000000000, + "hash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + "input" => "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + "nonce" => 0, + "publicKey" => "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", + "r" => "0xad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75", + "raw" => "0xf9038d8085174876e8008347b7608080b903396060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b002981bda0ad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75a072caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", + "s" => "0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", + "standardV" => "0x0", + "to" => nil, + "transactionIndex" => 0, + "v" => "0xbd", + "value" => 0 + } + ] + + """ def to_elixir(transactions) when is_list(transactions) do Enum.map(transactions, &Transaction.to_elixir/1) end diff --git a/apps/explorer/test/explorer/jsonrpc/transactions_test.exs b/apps/explorer/test/explorer/jsonrpc/transactions_test.exs new file mode 100644 index 0000000000..5243096457 --- /dev/null +++ b/apps/explorer/test/explorer/jsonrpc/transactions_test.exs @@ -0,0 +1,5 @@ +defmodule Explorer.JSONRPC.TransactionsTest do + use ExUnit.Case, async: true + + doctest Explorer.JSONRPC.Transactions +end From 92989beffee44b47a00bea4f5eeded6dfbdd9a15 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 8 May 2018 17:34:21 -0500 Subject: [PATCH 41/77] Cover pure Explorer.JSONRPC.Receipt(s) --- .../explorer/lib/explorer/jsonrpc/receipts.ex | 150 ++++++++++++++++++ .../test/explorer/jsonrpc/receipts_test.exs | 5 + 2 files changed, 155 insertions(+) create mode 100644 apps/explorer/test/explorer/jsonrpc/receipts_test.exs diff --git a/apps/explorer/lib/explorer/jsonrpc/receipts.ex b/apps/explorer/lib/explorer/jsonrpc/receipts.ex index 462abd96a4..53098ca4cc 100644 --- a/apps/explorer/lib/explorer/jsonrpc/receipts.ex +++ b/apps/explorer/lib/explorer/jsonrpc/receipts.ex @@ -12,11 +12,100 @@ defmodule Explorer.JSONRPC.Receipts do @type elixir :: [Receipt.elixir()] @type t :: [Receipt.t()] + @doc """ + Extracts logs from `t:elixir/0` + + iex> Explorer.JSONRPC.Receipts.elixir_to_logs([ + ...> %{ + ...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", + ...> "blockNumber" => 37, + ...> "contractAddress" => nil, + ...> "cumulativeGasUsed" => 50450, + ...> "gasUsed" => 50450, + ...> "logs" => [ + ...> %{ + ...> "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + ...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", + ...> "blockNumber" => 37, + ...> "data" => "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", + ...> "logIndex" => 0, + ...> "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], + ...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + ...> "transactionIndex" => 0, + ...> "transactionLogIndex" => 0, + ...> "type" => "mined" + ...> } + ...> ], + ...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000200000000000000000000020000000000000000200000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + ...> "root" => nil, + ...> "status" => :ok, + ...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + ...> "transactionIndex" => 0 + ...> } + ...> ]) + [ + %{ + "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", + "blockNumber" => 37, + "data" => "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", + "logIndex" => 0, + "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], + "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + "transactionIndex" => 0, + "transactionLogIndex" => 0, + "type" => "mined" + } + ] + + """ @spec elixir_to_logs(elixir) :: Logs.elixir() def elixir_to_logs(elixir) when is_list(elixir) do Enum.flat_map(elixir, &Receipt.elixir_to_logs/1) end + @doc """ + Converts each element of `t:elixir/0` to params used by `Explorer.Chain.Receipt.changeset/2`. + + iex> Explorer.JSONRPC.Receipts.elixir_to_params([ + ...> %{ + ...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", + ...> "blockNumber" => 37, + ...> "contractAddress" => nil, + ...> "cumulativeGasUsed" => 50450, + ...> "gasUsed" => 50450, + ...> "logs" => [ + ...> %{ + ...> "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + ...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", + ...> "blockNumber" => 37, + ...> "data" => "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", + ...> "logIndex" => 0, + ...> "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], + ...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + ...> "transactionIndex" => 0, + ...> "transactionLogIndex" => 0, + ...> "type" => "mined" + ...> } + ...> ], + ...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000200000000000000000000020000000000000000200000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + ...> "root" => nil, + ...> "status" => :ok, + ...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + ...> "transactionIndex" => 0 + ...> } + ...> ]) + [ + %{ + cumulative_gas_used: 50450, + gas_used: 50450, + status: :ok, + transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + transaction_index: 0 + } + ] + + """ @spec elixir_to_params(elixir) :: [map] def elixir_to_params(elixir) when is_list(elixir) do Enum.map(elixir, &Receipt.elixir_to_params/1) @@ -44,6 +133,67 @@ defmodule Explorer.JSONRPC.Receipts do end end + @doc """ + Converts stringly typed fields to native Elixir types. + + iex> Explorer.JSONRPC.Receipts.to_elixir([ + ...> %{ + ...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", + ...> "blockNumber" => "0x25", + ...> "contractAddress" => nil, + ...> "cumulativeGasUsed" => "0xc512", + ...> "gasUsed" => "0xc512", + ...> "logs" => [ + ...> %{ + ...> "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + ...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", + ...> "blockNumber" => "0x25", + ...> "data" => "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", + ...> "logIndex" => "0x0", + ...> "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], + ...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + ...> "transactionIndex" => "0x0", + ...> "transactionLogIndex" => "0x0", + ...> "type" => "mined" + ...> } + ...> ], + ...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000200000000000000000000020000000000000000200000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + ...> "root" => nil, + ...> "status" => "0x1", + ...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + ...> "transactionIndex" => "0x0" + ...> } + ...> ]) + [ + %{ + "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", + "blockNumber" => 37, + "contractAddress" => nil, + "cumulativeGasUsed" => 50450, + "gasUsed" => 50450, + "logs" => [ + %{ + "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", + "blockNumber" => 37, + "data" => "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", + "logIndex" => 0, + "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], + "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + "transactionIndex" => 0, + "transactionLogIndex" => 0, + "type" => "mined" + } + ], + "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000200000000000000000000020000000000000000200000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "root" => nil, + "status" => :ok, + "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + "transactionIndex" => 0 + } + ] + + """ @spec to_elixir(t) :: elixir def to_elixir(receipts) when is_list(receipts) do Enum.map(receipts, &Receipt.to_elixir/1) diff --git a/apps/explorer/test/explorer/jsonrpc/receipts_test.exs b/apps/explorer/test/explorer/jsonrpc/receipts_test.exs new file mode 100644 index 0000000000..63b82420f2 --- /dev/null +++ b/apps/explorer/test/explorer/jsonrpc/receipts_test.exs @@ -0,0 +1,5 @@ +defmodule Explorer.JSONRPC.ReceiptsTest do + use ExUnit.Case, async: true + + doctest Explorer.JSONRPC.Receipts +end From 9997de8223b932baa4e80372250e709159117581 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 8 May 2018 19:31:48 -0500 Subject: [PATCH 42/77] Cover Explorer.JSONRPC.Block(s) --- apps/explorer/lib/explorer/jsonrpc/blocks.ex | 128 +++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/apps/explorer/lib/explorer/jsonrpc/blocks.ex b/apps/explorer/lib/explorer/jsonrpc/blocks.ex index 1662f6ed0d..c0bcb09ed7 100644 --- a/apps/explorer/lib/explorer/jsonrpc/blocks.ex +++ b/apps/explorer/lib/explorer/jsonrpc/blocks.ex @@ -9,11 +9,139 @@ defmodule Explorer.JSONRPC.Blocks do @type elixir :: [Block.elixir()] @type t :: [Block.t()] + @doc """ + Converts `t:elixir/0` elements to params used by `Explorer.Chain.Block.changeset/2`. + + iex> Explorer.JSONRPC.Blocks.elixir_to_params( + ...> [ + ...> %{ + ...> "author" => "0x0000000000000000000000000000000000000000", + ...> "difficulty" => 131072, + ...> "extraData" => "0x", + ...> "gasLimit" => 6700000, + ...> "gasUsed" => 0, + ...> "hash" => "0x5b28c1bfd3a15230c9a46b399cd0f9a6920d432e85381cc6a140b06e8410112f", + ...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + ...> "miner" => "0x0000000000000000000000000000000000000000", + ...> "number" => 0, + ...> "parentHash" => "0x0000000000000000000000000000000000000000000000000000000000000000", + ...> "receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + ...> "sealFields" => ["0x80", + ...> "0xb8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"], + ...> "sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + ...> "signature" => "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + ...> "size" => 533, + ...> "stateRoot" => "0xfad4af258fd11939fae0c6c6eec9d340b1caac0b0196fd9a1bc3f489c5bf00b3", + ...> "step" => "0", + ...> "timestamp" => Timex.parse!("1970-01-01T00:00:00Z", "{ISO:Extended:Z}"), + ...> "totalDifficulty" => 131072, + ...> "transactions" => [], + ...> "transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + ...> "uncles" => [] + ...> } + ...> ] + ...> ) + [ + %{ + difficulty: 131072, + gas_limit: 6700000, + gas_used: 0, + hash: "0x5b28c1bfd3a15230c9a46b399cd0f9a6920d432e85381cc6a140b06e8410112f", + miner_hash: "0x0000000000000000000000000000000000000000", + nonce: 0, + number: 0, + parent_hash: "0x0000000000000000000000000000000000000000000000000000000000000000", + size: 533, + timestamp: Timex.parse!("1970-01-01T00:00:00Z", "{ISO:Extended:Z}"), + total_difficulty: 131072 + } + ] + + """ @spec elixir_to_params(elixir) :: [map] def elixir_to_params(elixir) when is_list(elixir) do Enum.map(elixir, &Block.elixir_to_params/1) end + @doc """ + Extracts the `t:Explorer.JSONRPC.Transactions.elixir/0` from the `t:elixir/0`. + + iex> Explorer.JSONRPC.Blocks.elixir_to_transactions([ + ...> %{ + ...> "author" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> "difficulty" => 340282366920938463463374607431768211454, + ...> "extraData" => "0xd5830108048650617269747986312e32322e31826c69", + ...> "gasLimit" => 6926030, + ...> "gasUsed" => 269607, + ...> "hash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", + ...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + ...> "miner" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> "number" => 34, + ...> "parentHash" => "0x106d528393159b93218dd410e2a778f083538098e46f1a44902aa67a164aed0b", + ...> "receiptsRoot" => "0xf45ed4ab910504ffe231230879c86e32b531bb38a398a7c9e266b4a992e12dfb", + ...> "sealFields" => ["0x84120a71db", + ...> "0xb8417ad0ecca535f81e1807dac338a57c7ccffd05d3e7f0687944650cd005360a192205df306a68eddfe216e0674c6b113050d90eff9b627c1762d43657308f986f501"], + ...> "sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + ...> "signature" => "7ad0ecca535f81e1807dac338a57c7ccffd05d3e7f0687944650cd005360a192205df306a68eddfe216e0674c6b113050d90eff9b627c1762d43657308f986f501", + ...> "size" => 1493, + ...> "stateRoot" => "0x6eaa6281df37b9b010f4779affc25ee059088240547ce86cf7ca7b7acd952d4f", + ...> "step" => "302674395", + ...> "timestamp" => Timex.parse!("2017-12-15T21:06:15Z", "{ISO:Extended:Z}"), + ...> "totalDifficulty" => 11569600475311907757754736652679816646147, + ...> "transactions" => [ + ...> %{ + ...> "blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", + ...> "blockNumber" => 34, + ...> "chainId" => 77, + ...> "condition" => nil, + ...> "creates" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", + ...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + ...> "gas" => 4700000, + ...> "gasPrice" => 100000000000, + ...> "hash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + ...> "input" => "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + ...> "nonce" => 0, + ...> "publicKey" => "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", + ...> "r" => "0xad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75", + ...> "raw" => "0xf9038d8085174876e8008347b7608080b903396060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b002981bda0ad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75a072caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", + ...> "s" => "0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", + ...> "standardV" => "0x0", + ...> "to" => nil, + ...> "transactionIndex" => 0, + ...> "v" => "0xbd", + ...> "value" => 0 + ...> } + ...> ], + ...> "transactionsRoot" => "0x2c2e243e9735f6d0081ffe60356c0e4ec4c6a9064c68d10bf8091ff896f33087", + ...> "uncles" => [] + ...> } + ...> ]) + [ + %{ + "blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", + "blockNumber" => 34, + "chainId" => 77, + "condition" => nil, + "creates" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", + "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + "gas" => 4700000, + "gasPrice" => 100000000000, + "hash" => "0x3a3eb134e6792ce9403ea4188e5e79693de9e4c94e499db132be086400da79e6", + "input" => "0x6060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", + "nonce" => 0, + "publicKey" => "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", + "r" => "0xad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75", + "raw" => "0xf9038d8085174876e8008347b7608080b903396060604052341561000f57600080fd5b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506102db8061005e6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b002981bda0ad3733df250c87556335ffe46c23e34dbaffde93097ef92f52c88632a40f0c75a072caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", + "s" => "0x72caddc0371451a58de2ca6ab64e0f586ccdb9465ff54e1c82564940e89291e3", + "standardV" => "0x0", + "to" => nil, + "transactionIndex" => 0, + "v" => "0xbd", + "value" => 0 + } + ] + + """ @spec elixir_to_transactions(t) :: Transactions.elixir() def elixir_to_transactions(elixir) when is_list(elixir) do Enum.flat_map(elixir, &Block.elixir_to_transactions/1) From 016e91c76853cbcc356d55cfa55c23a344d7d710 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 8 May 2018 20:04:22 -0500 Subject: [PATCH 43/77] Test happy-path Explorer.JSONRPC.Receipts.fetch/1 --- .../test/explorer/jsonrpc/receipts_test.exs | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/apps/explorer/test/explorer/jsonrpc/receipts_test.exs b/apps/explorer/test/explorer/jsonrpc/receipts_test.exs index 63b82420f2..46b887152c 100644 --- a/apps/explorer/test/explorer/jsonrpc/receipts_test.exs +++ b/apps/explorer/test/explorer/jsonrpc/receipts_test.exs @@ -1,5 +1,43 @@ defmodule Explorer.JSONRPC.ReceiptsTest do use ExUnit.Case, async: true - doctest Explorer.JSONRPC.Receipts + alias Explorer.JSONRPC.Receipts + + doctest Receipts + + # These are integration tests that depend on the sokol chain being used. sokol can be used with the following config + # + # config :explorer, Explorer.JSONRPC, + # trace_url: "https://sokol-trace.poa.network", + # url: "https://sokol.poa.network" + # + describe "fetch/1" do + test "with receipts and logs" do + assert {:ok, + %{ + logs: [ + %{ + address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + data: "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", + first_topic: "0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22", + fourth_topic: nil, + index: 0, + second_topic: nil, + third_topic: nil, + transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + type: "mined" + } + ], + receipts: [ + %{ + cumulative_gas_used: 50450, + gas_used: 50450, + status: :ok, + transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + transaction_index: 0 + } + ] + }} = Receipts.fetch(["0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5"]) + end + end end From dfe6af7ddf15f30527f7581381532ed12df7b6e6 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 8 May 2018 20:21:02 -0500 Subject: [PATCH 44/77] Test happy-path Explorer.Indexer.BlockImporter --- .../explorer/indexer/block_importer_test.exs | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 apps/explorer/test/explorer/indexer/block_importer_test.exs diff --git a/apps/explorer/test/explorer/indexer/block_importer_test.exs b/apps/explorer/test/explorer/indexer/block_importer_test.exs new file mode 100644 index 0000000000..d5f6353525 --- /dev/null +++ b/apps/explorer/test/explorer/indexer/block_importer_test.exs @@ -0,0 +1,90 @@ +defmodule Explorer.Indexer.BlockImporterTest do + # must be `async: false` due to use of named GenServer + use Explorer.DataCase, async: false + + alias Explorer.Indexer.BlockImporter + + setup do + start_supervised!({BlockImporter, []}) + + :ok + end + + test "import_blocks" do + assert :ok = + BlockImporter.import_blocks(%{ + blocks: [ + %{ + difficulty: 340_282_366_920_938_463_463_374_607_431_768_211_454, + gas_limit: 6_946_336, + gas_used: 50450, + hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", + miner_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + nonce: 0, + number: 37, + parent_hash: "0xc37bbad7057945d1bf128c1ff009fb1ad632110bf6a000aac025a80f7766b66e", + size: 719, + timestamp: Timex.parse!("2017-12-15T21:06:30Z", "{ISO:Extended:Z}"), + total_difficulty: 12_590_447_576_074_723_148_144_860_474_975_121_280_509 + } + ], + internal_transactions: [ + %{ + call_type: "call", + from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + gas: 4_677_320, + gas_used: 27770, + index: 0, + output: "0x", + to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + trace_address: [], + transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + type: "call", + value: 0 + } + ], + logs: [ + %{ + address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + data: "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", + first_topic: "0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22", + fourth_topic: nil, + index: 0, + second_topic: nil, + third_topic: nil, + transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + type: "mined" + } + ], + receipts: [ + %{ + cumulative_gas_used: 50450, + gas_used: 50450, + status: :ok, + transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + transaction_index: 0 + } + ], + transactions: [ + %{ + block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", + from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + gas: 4_700_000, + gas_price: 100_000_000_000, + hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", + index: 0, + input: "0x10855269000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", + nonce: 4, + public_key: + "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", + r: "0xa7f8f45cce375bb7af8750416e1b03e0473f93c256da2285d1134fc97a700e01", + s: "0x1f87a076f13824f4be8963e3dffd7300dae64d5f23c9a062af0c6ead347c135f", + standard_v: "0x1", + to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", + v: "0xbe", + value: 0 + } + ] + }) + end +end From a868961e8a10afe1b6efd05fe1940121bcfc2f0d Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 8 May 2018 20:37:09 -0500 Subject: [PATCH 45/77] Cover Explorer.JSONRPC.Parity --- .../lib/explorer/indexer/block_fetcher.ex | 2 +- apps/explorer/lib/explorer/jsonrpc/parity.ex | 24 +++++++++++++++++++ .../test/explorer/jsonrpc/parity_test.exs | 5 ++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 apps/explorer/test/explorer/jsonrpc/parity_test.exs diff --git a/apps/explorer/lib/explorer/indexer/block_fetcher.ex b/apps/explorer/lib/explorer/indexer/block_fetcher.ex index 0f1d7c18d1..605bea3256 100644 --- a/apps/explorer/lib/explorer/indexer/block_fetcher.ex +++ b/apps/explorer/lib/explorer/indexer/block_fetcher.ex @@ -19,7 +19,7 @@ defmodule Explorer.Indexer.BlockFetcher do # dialyzer thinks that Logger.debug functions always have no_local_return @dialyzer {:nowarn_function, stream_import: 3} - @batch_size 1 + @batch_size 50 @blocks_concurrency 10 @internal_batch_size 50 diff --git a/apps/explorer/lib/explorer/jsonrpc/parity.ex b/apps/explorer/lib/explorer/jsonrpc/parity.ex index 9df06f02fa..f5ecde2afd 100644 --- a/apps/explorer/lib/explorer/jsonrpc/parity.ex +++ b/apps/explorer/lib/explorer/jsonrpc/parity.ex @@ -7,6 +7,30 @@ defmodule Explorer.JSONRPC.Parity do alias Explorer.JSONRPC.Parity.Traces + @doc """ + Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params from the Parity trace URL. + + iex> Explorer.JSONRPC.Parity.fetch_internal_transactions([ + ...> "0x0fa6f723216dba694337f9bb37d8870725655bdf2573526a39454685659e39b1" + ...> ]) + {:ok, + [ + %{ + created_contract_address_hash: "0x1e0eaa06d02f965be2dfe0bc9ff52b2d82133461", + created_contract_code: "0x60606040526004361061008e576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063247b3210146100935780632ffdfc8a146100bc57806374294144146100f6578063ae4b1b5b14610125578063bf7370d11461017a578063d1104cb2146101a3578063eecd1079146101f8578063fcff021c14610221575b600080fd5b341561009e57600080fd5b6100a661024a565b6040518082815260200191505060405180910390f35b34156100c757600080fd5b6100e0600480803560ff16906020019091905050610253565b6040518082815260200191505060405180910390f35b341561010157600080fd5b610123600480803590602001909190803560ff16906020019091905050610276565b005b341561013057600080fd5b61013861037a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561018557600080fd5b61018d61039f565b6040518082815260200191505060405180910390f35b34156101ae57600080fd5b6101b66104d9565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561020357600080fd5b61020b610588565b6040518082815260200191505060405180910390f35b341561022c57600080fd5b6102346105bd565b6040518082815260200191505060405180910390f35b600060c8905090565b6000600160008360ff1660ff168152602001908152602001600020549050919050565b61027e6104d9565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156102b757600080fd5b60008160ff161115156102c957600080fd5b6002808111156102d557fe5b60ff168160ff16111515156102e957600080fd5b6000821180156103125750600160008260ff1660ff168152602001908152602001600020548214155b151561031d57600080fd5b81600160008360ff1660ff168152602001908152602001600020819055508060ff167fe868bbbdd6cd2efcd9ba6e0129d43c349b0645524aba13f8a43bfc7c5ffb0889836040518082815260200191505060405180910390a25050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000806000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16638b8414c46000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561042f57600080fd5b6102c65a03f1151561044057600080fd5b5050506040518051905090508073ffffffffffffffffffffffffffffffffffffffff16630eaba26a6000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b15156104b857600080fd5b6102c65a03f115156104c957600080fd5b5050506040518051905091505090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a3b3fff16000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561056857600080fd5b6102c65a03f1151561057957600080fd5b50505060405180519050905090565b60006105b860016105aa600261059c61039f565b6105e590919063ffffffff16565b61060090919063ffffffff16565b905090565b60006105e06105ca61039f565b6105d261024a565b6105e590919063ffffffff16565b905090565b60008082848115156105f357fe5b0490508091505092915050565b600080828401905083811015151561061457fe5b80915050929150505600a165627a7a723058206b7eef2a57eb659d5e77e45ab5bc074e99c6a841921038cdb931e119c6aac46c0029", + from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", + gas: 4533872, + gas_used: 382953, + index: 0, + init: "0x6060604052341561000f57600080fd5b60405160208061071a83398101604052808051906020019091905050806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506003600160006001600281111561007e57fe5b60ff1660ff168152602001908152602001600020819055506002600160006002808111156100a857fe5b60ff1660ff168152602001908152602001600020819055505061064a806100d06000396000f30060606040526004361061008e576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063247b3210146100935780632ffdfc8a146100bc57806374294144146100f6578063ae4b1b5b14610125578063bf7370d11461017a578063d1104cb2146101a3578063eecd1079146101f8578063fcff021c14610221575b600080fd5b341561009e57600080fd5b6100a661024a565b6040518082815260200191505060405180910390f35b34156100c757600080fd5b6100e0600480803560ff16906020019091905050610253565b6040518082815260200191505060405180910390f35b341561010157600080fd5b610123600480803590602001909190803560ff16906020019091905050610276565b005b341561013057600080fd5b61013861037a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561018557600080fd5b61018d61039f565b6040518082815260200191505060405180910390f35b34156101ae57600080fd5b6101b66104d9565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561020357600080fd5b61020b610588565b6040518082815260200191505060405180910390f35b341561022c57600080fd5b6102346105bd565b6040518082815260200191505060405180910390f35b600060c8905090565b6000600160008360ff1660ff168152602001908152602001600020549050919050565b61027e6104d9565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156102b757600080fd5b60008160ff161115156102c957600080fd5b6002808111156102d557fe5b60ff168160ff16111515156102e957600080fd5b6000821180156103125750600160008260ff1660ff168152602001908152602001600020548214155b151561031d57600080fd5b81600160008360ff1660ff168152602001908152602001600020819055508060ff167fe868bbbdd6cd2efcd9ba6e0129d43c349b0645524aba13f8a43bfc7c5ffb0889836040518082815260200191505060405180910390a25050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000806000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16638b8414c46000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561042f57600080fd5b6102c65a03f1151561044057600080fd5b5050506040518051905090508073ffffffffffffffffffffffffffffffffffffffff16630eaba26a6000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b15156104b857600080fd5b6102c65a03f115156104c957600080fd5b5050506040518051905091505090565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a3b3fff16000604051602001526040518163ffffffff167c0100000000000000000000000000000000000000000000000000000000028152600401602060405180830381600087803b151561056857600080fd5b6102c65a03f1151561057957600080fd5b50505060405180519050905090565b60006105b860016105aa600261059c61039f565b6105e590919063ffffffff16565b61060090919063ffffffff16565b905090565b60006105e06105ca61039f565b6105d261024a565b6105e590919063ffffffff16565b905090565b60008082848115156105f357fe5b0490508091505092915050565b600080828401905083811015151561061457fe5b80915050929150505600a165627a7a723058206b7eef2a57eb659d5e77e45ab5bc074e99c6a841921038cdb931e119c6aac46c0029000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", + trace_address: [], + transaction_hash: "0x0fa6f723216dba694337f9bb37d8870725655bdf2573526a39454685659e39b1", + type: "create", + value: 0 + } + ]} + + """ def fetch_internal_transactions(transaction_hashes) when is_list(transaction_hashes) do with {:ok, responses} <- transaction_hashes diff --git a/apps/explorer/test/explorer/jsonrpc/parity_test.exs b/apps/explorer/test/explorer/jsonrpc/parity_test.exs new file mode 100644 index 0000000000..a13a32f946 --- /dev/null +++ b/apps/explorer/test/explorer/jsonrpc/parity_test.exs @@ -0,0 +1,5 @@ +defmodule Explorer.JSONRPC.ParityTest do + use ExUnit.Case, async: true + + doctest Explorer.JSONRPC.Parity +end From 9cb8ee7e92d7563fe274fdc17536bdbd689ad4fa Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 8 May 2018 22:20:05 -0500 Subject: [PATCH 46/77] Happy-path tests for Explorer.Indexer.AddressFetcher --- .../lib/explorer/indexer/address_fetcher.ex | 5 +- .../explorer/indexer/address_fetcher_test.exs | 85 +++++++++++++++++++ coveralls.json | 2 +- 3 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 apps/explorer/test/explorer/indexer/address_fetcher_test.exs diff --git a/apps/explorer/lib/explorer/indexer/address_fetcher.ex b/apps/explorer/lib/explorer/indexer/address_fetcher.ex index b972f6c365..91f69cd9b6 100644 --- a/apps/explorer/lib/explorer/indexer/address_fetcher.ex +++ b/apps/explorer/lib/explorer/indexer/address_fetcher.ex @@ -31,7 +31,8 @@ defmodule Explorer.Indexer.AddressFetcher do state = %{ debug_logs: Keyword.get(opts, :debug_logs, false), flush_timer: nil, - fetch_interval: @fetch_interval, + fetch_interval: Keyword.get(opts, :fetch_interval, @fetch_interval), + max_batch_size: Keyword.get(opts, :max_batch_size, @max_batch_size), buffer: :queue.new(), tasks: %{} } @@ -90,7 +91,7 @@ defmodule Explorer.Indexer.AddressFetcher do |> Chain.stream_unfetched_addresses(fn %Address{hash: hash}, batch -> batch = :queue.in(Hash.to_string(hash), batch) - if :queue.len(batch) >= @max_batch_size do + if :queue.len(batch) >= state.max_batch_size do schedule_async_fetch(:queue.to_list(batch)) :queue.new() else diff --git a/apps/explorer/test/explorer/indexer/address_fetcher_test.exs b/apps/explorer/test/explorer/indexer/address_fetcher_test.exs new file mode 100644 index 0000000000..7fafffc6fa --- /dev/null +++ b/apps/explorer/test/explorer/indexer/address_fetcher_test.exs @@ -0,0 +1,85 @@ +defmodule Explorer.Indexer.AddressFetcherTest do + # MUST be `async: false` so that {:shared, pid} is set for connection to allow AddressFetcher's self-send to have + # connection allowed immediately. + use Explorer.DataCase, async: false + + alias Explorer.Chain.Address + alias Explorer.JSONRPC + alias Explorer.Indexer.AddressFetcher + + @hash %Explorer.Chain.Hash{ + byte_count: 20, + bytes: <<139, 243, 141, 71, 100, 146, 144, 100, 242, 212, 211, 165, 101, 32, 167, 106, 179, 223, 65, 91>> + } + + setup do + start_supervised!({JSONRPC, []}) + start_supervised!({Task.Supervisor, name: Explorer.Indexer.TaskSupervisor}) + + :ok + end + + describe "init/1" do + test "fetches unfetched addresses" do + unfetched_address = insert(:address, hash: @hash) + + assert unfetched_address.fetched_balance == nil + assert unfetched_address.balance_fetched_at == nil + + start_address_fetcher() + + fetched_address = + wait(fn -> + Repo.one!(from(address in Address, where: address.hash == ^@hash and not is_nil(address.fetched_balance))) + end) + + refute fetched_address.balance_fetched_at == nil + end + + test "fetches unfetched addresses when less than max batch size" do + insert(:address, hash: @hash) + + start_address_fetcher(max_batch_size: 2) + + fetched_address = + wait(fn -> + Repo.one!(from(address in Address, where: address.hash == ^@hash and not is_nil(address.fetched_balance))) + end) + + refute fetched_address.balance_fetched_at == nil + end + end + + describe "async_fetch_balances/1" do + test "fetches balances for address_hashes" do + start_address_fetcher() + + assert :ok = AddressFetcher.async_fetch_balances([@hash]) + + address = + wait(fn -> + Repo.get!(Address, @hash) + end) + + refute address.fetched_balance == nil + end + end + + defp start_address_fetcher(options \\ []) when is_list(options) do + start_supervised!( + {AddressFetcher, + Keyword.merge( + [debug_logs: false, fetch_interval: 1, max_batch_size: 1, max_concurrency: 1], + options + )} + ) + end + + defp wait(producer) do + producer.() + rescue + Ecto.NoResultsError -> + Process.sleep(100) + wait(producer) + end +end diff --git a/coveralls.json b/coveralls.json index 252126b717..9e27d7c3ad 100644 --- a/coveralls.json +++ b/coveralls.json @@ -1,7 +1,7 @@ { "coverage_options": { "treat_no_relevant_lines_as_covered": true, - "minimum_coverage": 85 + "minimum_coverage": 87.2 }, "terminal_options": { "file_column_width": 120 From d9cd93d850a313d193772ae5e09f75894b4108fc Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 8 May 2018 22:38:28 -0500 Subject: [PATCH 47/77] Cover Explorer.JSONRPC.Parity.Trace --- .../lib/explorer/jsonrpc/parity/trace.ex | 37 +++++++++++++++++++ coveralls.json | 2 +- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/jsonrpc/parity/trace.ex b/apps/explorer/lib/explorer/jsonrpc/parity/trace.ex index 764213289d..43922fcc76 100644 --- a/apps/explorer/lib/explorer/jsonrpc/parity/trace.ex +++ b/apps/explorer/lib/explorer/jsonrpc/parity/trace.ex @@ -345,6 +345,43 @@ defmodule Explorer.JSONRPC.Parity.Trace do "type" => "suicide" } + A call type trace can error and be reverted. + + iex> Explorer.JSONRPC.Parity.Trace.to_elixir( + ...> %{ + ...> "action" => %{ + ...> "callType" => "call", + ...> "from" => "0xc9266e6fdf5182dc47d27e0dc32bdff9e4cd2e32", + ...> "gas" => "0x73a468", + ...> "input" => "0xa6f2ae3a", + ...> "to" => "0xfdca0da4158740a93693441b35809b5bb463e527", + ...> "value" => "0x2386f26fc10000" + ...> }, + ...> "error" => "Reverted", + ...> "index" => 0, + ...> "subtraces" => 7, + ...> "traceAddress" => [], + ...> "transactionHash" => "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b", + ...> "type" => "call" + ...> } + ...> ) + %{ + "action" => %{ + "callType" => "call", + "from" => "0xc9266e6fdf5182dc47d27e0dc32bdff9e4cd2e32", + "gas" => 7578728, + "input" => "0xa6f2ae3a", + "to" => "0xfdca0da4158740a93693441b35809b5bb463e527", + "value" => 10000000000000000 + }, + "error" => "Reverted", + "index" => 0, + "subtraces" => 7, + "traceAddress" => [], + "transactionHash" => "0xcd7c15dbbc797722bef6e1d551edfd644fc7f4fb2ccd6a7947b2d1ade9ed140b", + "type" => "call" + } + """ def to_elixir(%{"index" => _, "transactionHash" => _} = trace) when is_map(trace) do diff --git a/coveralls.json b/coveralls.json index 9e27d7c3ad..5b805a41b7 100644 --- a/coveralls.json +++ b/coveralls.json @@ -1,7 +1,7 @@ { "coverage_options": { "treat_no_relevant_lines_as_covered": true, - "minimum_coverage": 87.2 + "minimum_coverage": 87.4 }, "terminal_options": { "file_column_width": 120 From 6a4ce6b6254a779f34fd5d7d1f0d7b3c291c7281 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Tue, 8 May 2018 22:55:41 -0500 Subject: [PATCH 48/77] Explorer.Chain.missing_block_numnbers must list ranges in order To match expectation of missing_block_numbers in BlockFetcher, which splits it into batches and ensures order by doing its own Enum.reverse. --- apps/explorer/lib/explorer/chain.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index bbdb58f031..599f3e650e 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -941,7 +941,7 @@ defmodule Explorer.Chain do iex> insert(:block, number: 2) iex> insert(:block, number: 5) iex> Explorer.Chain.missing_block_numbers() - {3, [{3, 4}, {1, 1}]} + {3, [{1, 1}, {3, 4}]} """ def missing_block_numbers do @@ -960,7 +960,7 @@ defmodule Explorer.Chain do end) end) - {missing_count, missing_ranges} + {missing_count, Enum.reverse(missing_ranges)} end @doc """ From b12bcdf617ee4b1de1e2e6bfda32c772bb54b813 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 9 May 2018 08:17:33 -0500 Subject: [PATCH 49/77] Log which step failed during stream_import --- .../lib/explorer/indexer/block_fetcher.ex | 78 ++++++++++--------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/apps/explorer/lib/explorer/indexer/block_fetcher.ex b/apps/explorer/lib/explorer/indexer/block_fetcher.ex index 605bea3256..75bff2828b 100644 --- a/apps/explorer/lib/explorer/indexer/block_fetcher.ex +++ b/apps/explorer/lib/explorer/indexer/block_fetcher.ex @@ -17,9 +17,9 @@ defmodule Explorer.Indexer.BlockFetcher do alias Explorer.JSONRPC.Transactions # dialyzer thinks that Logger.debug functions always have no_local_return - @dialyzer {:nowarn_function, stream_import: 3} + @dialyzer {:nowarn_function, import_range: 3} - @batch_size 50 + @blocks_batch_size 10 @blocks_concurrency 10 @internal_batch_size 50 @@ -56,7 +56,9 @@ defmodule Explorer.Indexer.BlockFetcher do state = %{ genesis_task: nil, debug_logs: Keyword.get(opts, :debug_logs, false), - realtime_interval: (opts[:block_rate] || @block_rate) * 2 + realtime_interval: (opts[:block_rate] || @block_rate) * 2, + blocks_batch_size: Keyword.get(opts, :blocks_batch_size, @blocks_batch_size), + blocks_concurrency: Keyword.get(opts, :blocks_concurrency, @blocks_concurrency) } {:ok, state} @@ -64,15 +66,15 @@ defmodule Explorer.Indexer.BlockFetcher do @impl GenServer def handle_info(:catchup_index, state) do - {count, missing_ranges} = missing_block_numbers() + {count, missing_ranges} = missing_block_numbers(state) current_block = Indexer.next_block_number() debug(state, fn -> "#{count} missed block ranges between genesis and #{current_block}" end) {:ok, genesis_task} = Task.start_link(fn -> - {:ok, seq} = Sequence.start_link(missing_ranges, current_block, @batch_size) - stream_import(state, seq, max_concurrency: @blocks_concurrency) + {:ok, seq} = Sequence.start_link(missing_ranges, current_block, state.blocks_batch_size) + stream_import(state, seq, max_concurrency: state.blocks_concurrency) end) Process.monitor(genesis_task) @@ -179,23 +181,23 @@ defmodule Explorer.Indexer.BlockFetcher do end end - defp missing_block_numbers do + defp missing_block_numbers(%{blocks_batch_size: blocks_batch_size}) do {count, missing_ranges} = Chain.missing_block_numbers() chunked_ranges = Enum.flat_map(missing_ranges, fn - {start, ending} when ending - start <= @batch_size -> + {start, ending} when ending - start <= blocks_batch_size -> [{start, ending}] {start, ending} -> start - |> Stream.iterate(&(&1 + @batch_size)) + |> Stream.iterate(&(&1 + blocks_batch_size)) |> Enum.reduce_while([], fn - chunk_start, acc when chunk_start + @batch_size >= ending -> + chunk_start, acc when chunk_start + blocks_batch_size >= ending -> {:halt, [{chunk_start, ending} | acc]} chunk_start, acc -> - {:cont, [{chunk_start, chunk_start + @batch_size - 1} | acc]} + {:cont, [{chunk_start, chunk_start + blocks_batch_size - 1} | acc]} end) |> Enum.reverse() end) @@ -206,36 +208,36 @@ defmodule Explorer.Indexer.BlockFetcher do defp stream_import(state, seq, task_opts) do seq |> Sequence.build_stream() - |> Task.async_stream( - fn {block_start, block_end} = range -> - with {:ok, next, result} <- JSONRPC.fetch_blocks_by_range(block_start, block_end), - %{blocks: blocks, transactions: transactions} <- result, - :ok <- cap_seq(seq, next, range, state), - transaction_hashes <- Transactions.params_to_hashes(transactions), - {:ok, receipt_params} <- fetch_transaction_receipts(state, transaction_hashes), - %{logs: logs, receipts: receipts} <- receipt_params, - {:ok, internal_transactions} <- fetch_internal_transactions(state, transaction_hashes) do - insert(state, seq, range, %{ - blocks: blocks, - internal_transactions: internal_transactions, - logs: logs, - receipts: receipts, - transactions: transactions - }) - else - {:error, reason} -> - debug(state, fn -> - "failed to fetch blocks #{inspect(range)}: #{inspect(reason)}. Retrying" - end) - - :ok = Sequence.inject_range(seq, range) - end - end, - Keyword.merge(task_opts, timeout: :infinity) - ) + |> Task.async_stream(&import_range(&1, state, seq), Keyword.merge(task_opts, timeout: :infinity)) |> Enum.each(fn {:ok, :ok} -> :ok end) end + defp import_range({block_start, block_end} = range, state, seq) do + with {:blocks, {:ok, next, result}} <- {:blocks, JSONRPC.fetch_blocks_by_range(block_start, block_end)}, + %{blocks: blocks, transactions: transactions} = result, + cap_seq(seq, next, range, state), + transaction_hashes = Transactions.params_to_hashes(transactions), + {:receipts, {:ok, receipt_params}} <- {:receipts, fetch_transaction_receipts(state, transaction_hashes)}, + %{logs: logs, receipts: receipts} = receipt_params, + {:internal_transactions, {:ok, internal_transactions}} <- + {:internal_transactions, fetch_internal_transactions(state, transaction_hashes)} do + insert(state, seq, range, %{ + blocks: blocks, + internal_transactions: internal_transactions, + logs: logs, + receipts: receipts, + transactions: transactions + }) + else + {step, {:error, reason}} -> + debug(state, fn -> + "failed to fetch #{step} for blocks #{block_start} - #{block_end}: #{inspect(reason)}. Retrying block range." + end) + + :ok = Sequence.inject_range(seq, range) + end + end + defp schedule_next_realtime_fetch(state) do timer = Process.send_after(self(), :realtime_index, state.realtime_interval) %{state | poll_timer: timer} From 29ffa04bc7a6a4772376baa972a59825624b3e2e Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 9 May 2018 09:00:16 -0500 Subject: [PATCH 50/77] Fix unclosed ` in docs --- .../lib/explorer/chain/internal_transaction/call_type.ex | 2 +- apps/explorer/lib/explorer/jsonrpc/log.ex | 2 +- apps/explorer/lib/explorer/jsonrpc/parity/trace/action.ex | 2 +- apps/explorer/lib/explorer/jsonrpc/receipt.ex | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/internal_transaction/call_type.ex b/apps/explorer/lib/explorer/chain/internal_transaction/call_type.ex index 4c7a6045a5..1ccf269fcc 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction/call_type.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction/call_type.ex @@ -8,7 +8,7 @@ defmodule Explorer.Chain.InternalTransaction.CallType do @typedoc """ * `:call` - call a function in a contract by jumping into the contract's context * `:callcode` - * `:delegatecall` - Instead of jumping into the code as with `"call", and using the call's contract's context, use + * `:delegatecall` - Instead of jumping into the code as with `"call"`, and using the call's contract's context, use the current contract's context with the delegated contract's code. There's some good chances for finding bugs when fuzzing these if the memory layout differs between the current contract and the delegated contract. * `:staticcall` diff --git a/apps/explorer/lib/explorer/jsonrpc/log.ex b/apps/explorer/lib/explorer/jsonrpc/log.ex index 209fa654dd..d064a95545 100644 --- a/apps/explorer/lib/explorer/jsonrpc/log.ex +++ b/apps/explorer/lib/explorer/jsonrpc/log.ex @@ -14,7 +14,7 @@ defmodule Explorer.JSONRPC.Log do * `"blockNumber"` - `t:Explorer.JSONRPC.quantity/0` for the block number this transaction is in. * `"data"` - Data containing non-indexed log parameter * `"logIndex"` - `t:Explorer.JSONRPC.quantity/0` of the event index positon in the block. - * `"topics" - `t:list/0` of at most 4 32-byte topics. Topic 1-3 contains indexed parameters of the log. + * `"topics"` - `t:list/0` of at most 4 32-byte topics. Topic 1-3 contains indexed parameters of the log. * `"transactionHash"` - `t:Explorer.JSONRPC.hash/0` of the transaction * `"transactionIndex"` - `t:Explorer.JSONRPC.quantity/0` for the index of the transaction in the block. """ diff --git a/apps/explorer/lib/explorer/jsonrpc/parity/trace/action.ex b/apps/explorer/lib/explorer/jsonrpc/parity/trace/action.ex index 379ec1c279..7fd26ccf4b 100644 --- a/apps/explorer/lib/explorer/jsonrpc/parity/trace/action.ex +++ b/apps/explorer/lib/explorer/jsonrpc/parity/trace/action.ex @@ -23,7 +23,7 @@ defmodule Explorer.JSONRPC.Parity.Trace.Action do "value" => 0 } - For a suicide, the `"balance"` is converted to a `t:non_neg_integer/0` while the `"address" and `"refundAddress"` + For a suicide, the `"balance"` is converted to a `t:non_neg_integer/0` while the `"address"` and `"refundAddress"` `t:Explorer.JSONRPC.hash/0` pass through. iex> Explorer.JSONRPC.Parity.Trace.Action.to_elixir( diff --git a/apps/explorer/lib/explorer/jsonrpc/receipt.ex b/apps/explorer/lib/explorer/jsonrpc/receipt.ex index c37c36c3e2..2d9018f721 100644 --- a/apps/explorer/lib/explorer/jsonrpc/receipt.ex +++ b/apps/explorer/lib/explorer/jsonrpc/receipt.ex @@ -24,13 +24,13 @@ defmodule Explorer.JSONRPC.Receipt do [Bloom filter](https://en.wikipedia.org/wiki/Bloom_filter) for light clients to quickly retrieve related logs. * `"root"` - `t:Explorer.JSONRPC.hash/0` of post-transaction stateroot (pre-Byzantium) * `"status"` - `t:Explorer.JSONRPC.quantity/0` of either 1 (success) or 0 (failure) (post-Byzantium) - * `"transactionHash" - `t:Explorer.JSONRPC.hash/0` the transaction. + * `"transactionHash"` - `t:Explorer.JSONRPC.hash/0` the transaction. * `"transactionIndex"` - `t:Explorer.JSONRPC.quantity/0` for the transaction index in the block. """ @type t :: %{String.t() => JSONRPC.address() | JSONRPC.data() | JSONRPC.hash() | JSONRPC.quantity() | list | nil} @doc """ - `Get `t:Explorer.JSONRPC.Logs.elixir/0` from `t:elixir/0` + Get `t:Explorer.JSONRPC.Logs.elixir/0` from `t:elixir/0` """ @spec elixir_to_logs(elixir) :: Logs.elixir() def elixir_to_logs(%{"logs" => logs}), do: logs From 8855d686beaddb49706170dc362197908328fb2b Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 9 May 2018 09:17:45 -0500 Subject: [PATCH 51/77] Make all batch_sizes and concurrency configurable through start_link Document all the options, their defaults, and how they interact. --- .../lib/explorer/indexer/block_fetcher.ex | 57 +++++++++++++++---- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/apps/explorer/lib/explorer/indexer/block_fetcher.ex b/apps/explorer/lib/explorer/indexer/block_fetcher.ex index 75bff2828b..ce944f9c00 100644 --- a/apps/explorer/lib/explorer/indexer/block_fetcher.ex +++ b/apps/explorer/lib/explorer/indexer/block_fetcher.ex @@ -19,12 +19,17 @@ defmodule Explorer.Indexer.BlockFetcher do # dialyzer thinks that Logger.debug functions always have no_local_return @dialyzer {:nowarn_function, import_range: 3} + # These are all the *default* values for options. DO NOT use them directly in the code. Get options from `state`. + + @debug_logs false + @blocks_batch_size 10 @blocks_concurrency 10 - @internal_batch_size 50 - @internal_concurrency 8 + @internal_transactions_batch_size 50 + @internal_transactions_concurrency 8 + # milliseconds @block_rate 5_000 @receipts_batch_size 250 @@ -39,8 +44,31 @@ defmodule Explorer.Indexer.BlockFetcher do `:explorer, :indexer` keyspace. The follow options can be overridden: * `:debug_logs` - When `true` logs verbose index progress. Defaults `false`. - * `:block_rate` - The millisecond rate new blocks are published at. - Defaults to `#{@block_rate}`. + * `:blocks_batch_size` - The number of blocks to request in one call to the JSONRPC. Defaults to + `#{@blocks_batch_size}`. Block requests also include the transactions for those blocks. *These transactions + are not paginated.* + * `:blocks_concurrency` - The number of concurrent requests of `:blocks_batch_size` to allow against the JSONRPC. + Defaults to #{@blocks_concurrency}. So upto `blocks_concurrency * block_batch_size` (defaults to + `#{@blocks_concurrency * @blocks_batch_size}`) blocks can be requested from the JSONRPC at once over all + connections. + * `:block_rate` - The millisecond rate new blocks are published at. Defaults to `#{@block_rate}` milliseconds. + * `:internal transactions_batch_size` - The number of transaction hashes to request internal transactions for + in one call to the JSONRPC. Defaults to `#{@internal_transactions_batch_size}`. + * `:internal transactions_concurrency` - The number of concurrent requests of `:internal transactions_batch_size` to + allow against the JSONRPC **for each block range**. Defaults to `#{@internal_transactions_concurrency}`. So upto + `block_concurrency * internal_transactions_batch_size * internal transactions_concurrency` (defaults to + `#{@blocks_concurrency * @internal_transactions_concurrency * @internal_transactions_batch_size}`) transactions + can be requesting their internal transactions can be requested from the JSONRPC at once over all connections. + *The internal transactions for individual transactions cannot be paginated, so the total number of internal + transactions that could be produced is unknown.* + * `:receipts_batch_size` - The number of receipts to request in one call to the JSONRPC. Defaults to + `#{@receipts_batch_size}`. Receipt requests also include the logs for when the transaction was collated into the + block. *These logs are not paginated.* + * `:receipts_concurrency` - The number of concurrent requests of `:receipts_batch_size` to allow against the JSONRPC + **for each block range**. Defaults to `#{@receipts_concurrency}`. So upto + `block_concurrency * receipts_batch_size * receipts_concurrency` (defaults to + `#{@blocks_concurrency * @receipts_concurrency * @receipts_batch_size}`) receipts can be requested from the + JSONRPC at once over all connections. *Each transaction only has one receipt.* """ def start_link(opts) do GenServer.start_link(__MODULE__, opts, name: __MODULE__) @@ -55,10 +83,16 @@ defmodule Explorer.Indexer.BlockFetcher do state = %{ genesis_task: nil, - debug_logs: Keyword.get(opts, :debug_logs, false), + debug_logs: Keyword.get(opts, :debug_logs, @debug_logs), realtime_interval: (opts[:block_rate] || @block_rate) * 2, blocks_batch_size: Keyword.get(opts, :blocks_batch_size, @blocks_batch_size), - blocks_concurrency: Keyword.get(opts, :blocks_concurrency, @blocks_concurrency) + blocks_concurrency: Keyword.get(opts, :blocks_concurrency, @blocks_concurrency), + internal_transactions_batch_size: + Keyword.get(opts, :internal_transactions_batch_size, @internal_transactions_batch_size), + internal_transactions_concurrency: + Keyword.get(opts, :internal_transactions_concurrency, @internal_transactions_concurrency), + receipts_batch_size: Keyword.get(opts, :receipts_batch_size, @receipts_batch_size), + receipts_concurrency: Keyword.get(opts, :receipts_concurrency, @receipts_concurrency) } {:ok, state} @@ -133,11 +167,11 @@ defmodule Explorer.Indexer.BlockFetcher do defp fetch_internal_transactions(_state, []), do: {:ok, []} defp fetch_internal_transactions(state, hashes) do - debug(state, fn -> "fetching #{length(hashes)} internal transactions" end) - stream_opts = [max_concurrency: @internal_concurrency, timeout: :infinity] + debug(state, fn -> "fetching internal transactions for #{length(hashes)} transactions" end) + stream_opts = [max_concurrency: state.internal_transactions_concurrency, timeout: :infinity] hashes - |> Enum.chunk_every(@internal_batch_size) + |> Enum.chunk_every(state.internal_transactions_batch_size) |> Task.async_stream(&JSONRPC.fetch_internal_transactions(&1), stream_opts) |> Enum.reduce_while({:ok, []}, fn {:ok, {:ok, internal_transactions}}, {:ok, acc} -> {:cont, {:ok, acc ++ internal_transactions}} @@ -150,10 +184,10 @@ defmodule Explorer.Indexer.BlockFetcher do defp fetch_transaction_receipts(state, hashes) do debug(state, fn -> "fetching #{length(hashes)} transaction receipts" end) - stream_opts = [max_concurrency: @receipts_concurrency, timeout: :infinity] + stream_opts = [max_concurrency: state.receipts_concurrency, timeout: :infinity] hashes - |> Enum.chunk_every(@receipts_batch_size) + |> Enum.chunk_every(state.receipts_batch_size) |> Task.async_stream(&JSONRPC.fetch_transaction_receipts(&1), stream_opts) |> Enum.reduce_while({:ok, %{logs: [], receipts: []}}, fn {:ok, {:ok, %{logs: logs, receipts: receipts}}}, {:ok, %{logs: acc_logs, receipts: acc_receipts}} -> @@ -212,6 +246,7 @@ defmodule Explorer.Indexer.BlockFetcher do |> Enum.each(fn {:ok, :ok} -> :ok end) end + # Run at state.blocks_concurrency max_concurrency defp import_range({block_start, block_end} = range, state, seq) do with {:blocks, {:ok, next, result}} <- {:blocks, JSONRPC.fetch_blocks_by_range(block_start, block_end)}, %{blocks: blocks, transactions: transactions} = result, From 5c187420db55c62584c7c579d1fc7219ec65517e Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 9 May 2018 09:56:33 -0500 Subject: [PATCH 52/77] Remove ethereumex --- apps/explorer/config/config.exs | 8 +------- apps/explorer/config/dev.secret.exs.example | 4 ---- apps/explorer/config/prod.exs | 3 --- apps/explorer/config/test.exs | 2 -- apps/explorer/mix.exs | 2 -- coveralls.json | 2 +- mix.lock | 1 - 7 files changed, 2 insertions(+), 20 deletions(-) diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index 6d5d7fe11d..a229111292 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -5,16 +5,10 @@ # is restricted to this project. use Mix.Config -url = "https://sokol.poa.network" - config :explorer, :indexer, block_rate: 5_000, debug_logs: !!System.get_env("DEBUG_LOGS") -config :ethereumex, - url: url, - http_options: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :eth]] - # General application configuration config :explorer, ecto_repos: [Explorer.Repo], @@ -23,7 +17,7 @@ config :explorer, config :explorer, Explorer.JSONRPC, http: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :eth]], trace_url: "https://sokol-trace.poa.network", - url: url + url: "https://sokol.poa.network" config :explorer, Explorer.Integrations.EctoLogger, query_time_ms_threshold: 2_000 diff --git a/apps/explorer/config/dev.secret.exs.example b/apps/explorer/config/dev.secret.exs.example index 9a91a86f41..d2d855e6d0 100644 --- a/apps/explorer/config/dev.secret.exs.example +++ b/apps/explorer/config/dev.secret.exs.example @@ -1,5 +1 @@ use Mix.Config - -# Configure ethereumex -config :ethereumex, - url: "http://localhost:8545" diff --git a/apps/explorer/config/prod.exs b/apps/explorer/config/prod.exs index 6faf422c2d..62cf6f922b 100644 --- a/apps/explorer/config/prod.exs +++ b/apps/explorer/config/prod.exs @@ -9,6 +9,3 @@ config :explorer, Explorer.Repo, prepare: :unnamed, timeout: 60_000, pool_timeout: 60_000 - -# Configure Web3 -config :ethereumex, url: System.get_env("ETHEREUM_URL") diff --git a/apps/explorer/config/test.exs b/apps/explorer/config/test.exs index 6b3b23a798..c4f3a7c113 100644 --- a/apps/explorer/config/test.exs +++ b/apps/explorer/config/test.exs @@ -7,5 +7,3 @@ config :explorer, Explorer.Repo, hostname: "localhost", pool: Ecto.Adapters.SQL.Sandbox, ownership_timeout: 60_000 - -config :explorer, :ethereum, backend: Explorer.Ethereum.Test diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index 51935e23a2..8663ecc8cb 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -54,7 +54,6 @@ defmodule Explorer.Mixfile do defp extra_applications, do: [ :crontab, - :ethereumex, :logger, :mix, :runtime_tools, @@ -72,7 +71,6 @@ defmodule Explorer.Mixfile do {:credo, "0.9.2", only: [:dev, :test], runtime: false}, {:crontab, "~> 1.1"}, {:dialyxir, "~> 0.5", only: [:dev, :test], runtime: false}, - {:ethereumex, "~> 0.3"}, {:ex_machina, "~> 2.1", only: [:test]}, # Code coverage {:excoveralls, "~> 0.8.1", only: [:test]}, diff --git a/coveralls.json b/coveralls.json index 5b805a41b7..9ed1f11e62 100644 --- a/coveralls.json +++ b/coveralls.json @@ -1,7 +1,7 @@ { "coverage_options": { "treat_no_relevant_lines_as_covered": true, - "minimum_coverage": 87.4 + "minimum_coverage": 87.0 }, "terminal_options": { "file_column_width": 120 diff --git a/mix.lock b/mix.lock index d8968d87cf..633cf64a9a 100644 --- a/mix.lock +++ b/mix.lock @@ -14,7 +14,6 @@ "dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], [], "hexpm"}, "earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [:mix], [], "hexpm"}, "ecto": {:hex, :ecto, "2.2.8", "a4463c0928b970f2cee722cd29aaac154e866a15882c5737e0038bbfcf03ec2c", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, - "ethereumex": {:hex, :ethereumex, "0.3.0", "9d5e25dc2f9ed357f9f7f2a49e09b608b41a56283c20f50e896a11af225cc723", [:mix], [{:httpoison, "~> 1.0.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 3.1.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, "ex_cldr": {:hex, :ex_cldr, "1.3.2", "8f4a00c99d1c537b8e8db7e7903f4bd78d82a7289502d080f70365392b13921b", [:mix], [{:abnf2, "~> 0.1", [hex: :abnf2, repo: "hexpm", optional: false]}, {:decimal, "~> 1.4", [hex: :decimal, repo: "hexpm", optional: false]}, {:gettext, "~> 0.13", [hex: :gettext, repo: "hexpm", optional: true]}, {:poison, "~> 2.1 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"}, "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "1.2.0", "ef27299922da913ffad1ed296cacf28b6452fc1243b77301dc17c03276c6ee34", [:mix], [{:decimal, "~> 1.4", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 1.3", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:poison, "~> 2.1 or ~> 3.1", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, "ex_cldr_units": {:hex, :ex_cldr_units, "1.1.1", "b3c7256709bdeb3740a5f64ce2bce659eb9cf4cc1afb4cf94aba033b4a18bc5f", [:mix], [{:ex_cldr, "~> 1.0", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 1.0", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}], "hexpm"}, From 942534eeb4bbacda0fcb9ad330c77391a714ab0d Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 9 May 2018 10:06:40 -0500 Subject: [PATCH 53/77] Typo Indexer -> Indexes --- apps/explorer/lib/explorer/indexer.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/explorer/lib/explorer/indexer.ex b/apps/explorer/lib/explorer/indexer.ex index cf3586e1fe..1e44b5fa69 100644 --- a/apps/explorer/lib/explorer/indexer.ex +++ b/apps/explorer/lib/explorer/indexer.ex @@ -1,6 +1,6 @@ defmodule Explorer.Indexer do @moduledoc """ - Indexers an Ethereum-based chain using JSONRPC. + Indexes an Ethereum-based chain using JSONRPC. """ alias Explorer.Chain From 87cf56603fee3202a7b8b90d58ad0deec96be3f4 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 9 May 2018 12:55:19 -0500 Subject: [PATCH 54/77] Separate out tasks in block_fetcher --- .../lib/explorer/indexer/block_fetcher.ex | 32 ++++----- .../explorer/indexer/block_fetcher_test.exs | 67 +++++++++++++++++++ 2 files changed, 84 insertions(+), 15 deletions(-) create mode 100644 apps/explorer/test/explorer/indexer/block_fetcher_test.exs diff --git a/apps/explorer/lib/explorer/indexer/block_fetcher.ex b/apps/explorer/lib/explorer/indexer/block_fetcher.ex index ce944f9c00..b0573770aa 100644 --- a/apps/explorer/lib/explorer/indexer/block_fetcher.ex +++ b/apps/explorer/lib/explorer/indexer/block_fetcher.ex @@ -100,16 +100,7 @@ defmodule Explorer.Indexer.BlockFetcher do @impl GenServer def handle_info(:catchup_index, state) do - {count, missing_ranges} = missing_block_numbers(state) - current_block = Indexer.next_block_number() - - debug(state, fn -> "#{count} missed block ranges between genesis and #{current_block}" end) - - {:ok, genesis_task} = - Task.start_link(fn -> - {:ok, seq} = Sequence.start_link(missing_ranges, current_block, state.blocks_batch_size) - stream_import(state, seq, max_concurrency: state.blocks_concurrency) - end) + {:ok, genesis_task} = Task.start_link(fn -> genesis_task(state) end) Process.monitor(genesis_task) @@ -117,11 +108,7 @@ defmodule Explorer.Indexer.BlockFetcher do end def handle_info(:realtime_index, state) do - {:ok, realtime_task} = - Task.start_link(fn -> - {:ok, seq} = Sequence.start_link([], Indexer.next_block_number(), 2) - stream_import(state, seq, max_concurrency: 1) - end) + {:ok, realtime_task} = Task.start_link(fn -> realtime_task(state) end) Process.monitor(realtime_task) @@ -201,6 +188,16 @@ defmodule Explorer.Indexer.BlockFetcher do end) end + defp genesis_task(state) do + {count, missing_ranges} = missing_block_numbers(state) + current_block = Indexer.next_block_number() + + debug(state, fn -> "#{count} missed block ranges between genesis and #{current_block}" end) + + {:ok, seq} = Sequence.start_link(missing_ranges, current_block, state.blocks_batch_size) + stream_import(state, seq, max_concurrency: state.blocks_concurrency) + end + defp insert(state, seq, range, params) do case BlockImporter.import_blocks(params) do :ok -> @@ -239,6 +236,11 @@ defmodule Explorer.Indexer.BlockFetcher do {count, chunked_ranges} end + defp realtime_task(state) do + {:ok, seq} = Sequence.start_link([], Indexer.next_block_number(), 2) + stream_import(state, seq, max_concurrency: 1) + end + defp stream_import(state, seq, task_opts) do seq |> Sequence.build_stream() diff --git a/apps/explorer/test/explorer/indexer/block_fetcher_test.exs b/apps/explorer/test/explorer/indexer/block_fetcher_test.exs new file mode 100644 index 0000000000..f40cc58fea --- /dev/null +++ b/apps/explorer/test/explorer/indexer/block_fetcher_test.exs @@ -0,0 +1,67 @@ +defmodule Explorer.Indexer.BlockFetcherTest do + # `async: false` due to use of named GenServer + use Explorer.DataCase, async: false + + import ExUnit.CaptureLog + + alias Explorer.Indexer.BlockFetcher + + @tag capture_log: true + + describe "handle_info(:debug_count, state)" do + setup do + block = insert(:block) + + Enum.map(0..2, fn index -> + transaction = insert(:transaction, block_hash: block.hash, index: index) + receipt = insert(:receipt, transaction_hash: transaction.hash, transaction_index: transaction.index) + insert(:log, transaction_hash: receipt.transaction_hash) + insert(:internal_transaction, transaction_hash: transaction.hash) + end) + + :ok + end + + test "without debug_logs" do + assert capture_log_at_level(:debug, fn -> + BlockFetcher.handle_info(:debug_count, %{debug_logs: false}) + end) == "" + end + + test "with debug_logs" do + log = capture_log_at_level(:debug, fn -> + BlockFetcher.handle_info(:debug_count, %{debug_logs: true}) + end) + + assert log =~ "blocks: 4" + assert log =~ "internal transactions: 3" + assert log =~ "receipts: 6" + assert log =~ "logs: 3" + assert log =~ "addresses: 31" + end + end + + defp capture_log_at_level(level, block) do + logger_level_transaction(fn -> + Logger.configure(level: level) + capture_log(fn -> + block.() + Process.sleep(10) + end) + end) + end + + defp logger_level_transaction(block) do + level_before = Logger.level() + + on_exit(fn -> + Logger.configure(level: level_before) + end) + + return = block.() + + Logger.configure(level: level_before) + + return + end +end From 88652b7d84e997ef5c180caab0b6108507a5de13 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 9 May 2018 13:49:48 -0500 Subject: [PATCH 55/77] Sort inserts consistently to avoid deadlocks The Postgres docs and various blog posts advise that taking locks (either implicit, such as with ShareLock, or explicitly, which we're not doing) needs to be done in a consistent order to avoid deadlocks that we're seeing. Since the ShareLock is implicit on the rows we're inserting, the only way to do consistent lock taking is to order those inserts, so for all `insert_*` functions, sort the `changes_list`, so that it is consist and importantly between `insert_addresses` and `update_balances`, use the same ordering for Addresses. This eliminates the need to serialize writes, so BlockImporter is removed. --- apps/explorer/lib/explorer/chain.ex | 46 +++++++--- .../lib/explorer/indexer/block_fetcher.ex | 13 ++- .../lib/explorer/indexer/block_importer.ex | 36 -------- .../lib/explorer/indexer/supervisor.ex | 11 +-- .../explorer/indexer/block_importer_test.exs | 90 ------------------- coveralls.json | 2 +- 6 files changed, 45 insertions(+), 153 deletions(-) delete mode 100644 apps/explorer/lib/explorer/indexer/block_importer.ex delete mode 100644 apps/explorer/test/explorer/indexer/block_importer_test.exs diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 599f3e650e..1095ee73f2 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -131,7 +131,7 @@ defmodule Explorer.Chain do def update_balances(balances) do timestamps = timestamps() - changes = + changes_list = for {hash_string, amount} <- balances do {:ok, truncated_hash} = Explorer.Chain.Hash.Truncated.cast(hash_string) {:ok, wei} = Wei.cast(amount) @@ -143,7 +143,11 @@ defmodule Explorer.Chain do }) end - {_, _} = Repo.safe_insert_all(Address, changes, conflict_target: :hash, on_conflict: :replace_all) + # order so that row ShareLocks are grabbed in a consistent order. + # MUST match order used in `insert_addresses/2` + ordered_changes_list = sort_address_changes_list(changes_list) + + {_, _} = Repo.safe_insert_all(Address, ordered_changes_list, conflict_target: :hash, on_conflict: :replace_all) :ok end @@ -1466,31 +1470,41 @@ defmodule Explorer.Chain do defp insert_addresses(changes_list, named_arguments) when is_list(changes_list) and is_list(named_arguments) do timestamps = Keyword.fetch!(named_arguments, :timestamps) + # order so that row ShareLocks are grabbed in a consistent order + ordered_changes_list = sort_address_changes_list(changes_list) + insert_changes_list( - changes_list, + ordered_changes_list, conflict_target: :hash, on_conflict: [set: [balance_fetched_at: nil]], for: Address, timestamps: timestamps ) - {:ok, for(changes <- changes_list, do: changes.hash)} + {:ok, for(changes <- ordered_changes_list, do: changes.hash)} + end + + defp sort_address_changes_list(changes_list) do + Enum.sort_by(changes_list, & &1.hash) end @spec insert_blocks([map()], [timestamps_option]) :: {:ok, [Hash.t()]} | {:error, [Changeset.t()]} defp insert_blocks(changes_list, named_arguments) when is_list(changes_list) and is_list(named_arguments) do timestamps = Keyword.fetch!(named_arguments, :timestamps) + # order so that row ShareLocks are grabbed in a consistent order + ordered_changes_list = Enum.sort_by(changes_list, &{&1.number, &1.hash}) + {:ok, _} = insert_changes_list( - changes_list, + ordered_changes_list, conflict_target: :number, on_conflict: :replace_all, for: Block, timestamps: timestamps ) - {:ok, for(changes <- changes_list, do: changes.hash)} + {:ok, for(changes <- ordered_changes_list, do: changes.hash)} end defp insert_ecto_schema_module_to_changes_list( @@ -1525,9 +1539,12 @@ defmodule Explorer.Chain do when is_list(changes_list) and is_list(named_arguments) do timestamps = Keyword.fetch!(named_arguments, :timestamps) + # order so that row ShareLocks are grabbed in a consistent order + ordered_changes_list = Enum.sort_by(changes_list, & {&1.transaction_hash, &1.index}) + {:ok, internal_transactions} = insert_changes_list( - changes_list, + ordered_changes_list, for: InternalTransaction, returning: [:index, :transaction_hash], timestamps: timestamps @@ -1542,9 +1559,12 @@ defmodule Explorer.Chain do defp insert_logs(changes_list, named_arguments) when is_list(changes_list) and is_list(named_arguments) do timestamps = Keyword.fetch!(named_arguments, :timestamps) + # order so that row ShareLocks are grabbed in a consistent order + ordered_changes_list = Enum.sort_by(changes_list, &{&1.transaction_hash, &1.index}) + {:ok, logs} = insert_changes_list( - changes_list, + ordered_changes_list, conflict_target: [:transaction_hash, :index], on_conflict: :replace_all, for: Log, @@ -1559,9 +1579,12 @@ defmodule Explorer.Chain do defp insert_receipts(changes_list, named_arguments) when is_list(changes_list) and is_list(named_arguments) do timestamps = Keyword.fetch!(named_arguments, :timestamps) + # order so that row ShareLocks are grabbed in a consistent order + ordered_changes_list = Enum.sort_by(changes_list, & &1.transaction_hash) + {:ok, receipts} = insert_changes_list( - changes_list, + ordered_changes_list, conflict_target: :transaction_hash, on_conflict: :replace_all, for: Receipt, @@ -1584,9 +1607,12 @@ defmodule Explorer.Chain do defp insert_transactions(changes_list, named_arguments) when is_list(changes_list) and is_list(named_arguments) do timestamps = Keyword.fetch!(named_arguments, :timestamps) + # order so that row ShareLocks are grabbed in a consistent order + ordered_changes_list = Enum.sort_by(changes_list, & &1.hash) + {:ok, transactions} = insert_changes_list( - changes_list, + ordered_changes_list, conflict_target: :hash, on_conflict: :replace_all, for: Transaction, diff --git a/apps/explorer/lib/explorer/indexer/block_fetcher.ex b/apps/explorer/lib/explorer/indexer/block_fetcher.ex index b0573770aa..438723ddd4 100644 --- a/apps/explorer/lib/explorer/indexer/block_fetcher.ex +++ b/apps/explorer/lib/explorer/indexer/block_fetcher.ex @@ -9,10 +9,7 @@ defmodule Explorer.Indexer.BlockFetcher do alias Explorer.{Chain, Indexer, JSONRPC} - alias Explorer.Indexer.{ - Sequence, - BlockImporter - } + alias Explorer.Indexer.{AddressFetcher, Sequence} alias Explorer.JSONRPC.Transactions @@ -199,10 +196,10 @@ defmodule Explorer.Indexer.BlockFetcher do end defp insert(state, seq, range, params) do - case BlockImporter.import_blocks(params) do - :ok -> - :ok - + with {:ok, %{addresses: address_hashes}} <- Chain.import_blocks(params) do + :ok = AddressFetcher.async_fetch_balances(address_hashes) + :ok + else {:error, step, reason} -> debug(state, fn -> "failed to insert blocks during #{step} #{inspect(range)}: #{inspect(reason)}. Retrying" diff --git a/apps/explorer/lib/explorer/indexer/block_importer.ex b/apps/explorer/lib/explorer/indexer/block_importer.ex deleted file mode 100644 index 0364ab7263..0000000000 --- a/apps/explorer/lib/explorer/indexer/block_importer.ex +++ /dev/null @@ -1,36 +0,0 @@ -defmodule Explorer.Indexer.BlockImporter do - @moduledoc """ - Imports blocks to the chain. - - Batched block ranges are serialized through the importer to avoid - races and lock contention against conurrent address upserts. - """ - - use GenServer - - alias Explorer.Chain - alias Explorer.Indexer.AddressFetcher - - def import_blocks(blocks) do - GenServer.call(__MODULE__, {:import, blocks}) - end - - def start_link(opts) do - GenServer.start_link(__MODULE__, opts, name: __MODULE__) - end - - def init(_opts) do - {:ok, %{}} - end - - def handle_call({:import, blocks}, _from, state) do - case Chain.import_blocks(blocks) do - {:ok, %{addresses: address_hashes}} -> - :ok = AddressFetcher.async_fetch_balances(address_hashes) - {:reply, :ok, state} - - {:error, step, reason, _changes} -> - {:reply, {:error, step, reason}, state} - end - end -end diff --git a/apps/explorer/lib/explorer/indexer/supervisor.ex b/apps/explorer/lib/explorer/indexer/supervisor.ex index bedbdaac96..854f54706a 100644 --- a/apps/explorer/lib/explorer/indexer/supervisor.ex +++ b/apps/explorer/lib/explorer/indexer/supervisor.ex @@ -5,11 +5,7 @@ defmodule Explorer.Indexer.Supervisor do use Supervisor - alias Explorer.Indexer.{ - BlockFetcher, - BlockImporter, - AddressFetcher - } + alias Explorer.Indexer.{AddressFetcher, BlockFetcher} def start_link(opts) do Supervisor.start_link(__MODULE__, opts) @@ -19,9 +15,8 @@ defmodule Explorer.Indexer.Supervisor do def init(_opts) do children = [ {Task.Supervisor, name: Explorer.Indexer.TaskSupervisor}, - {BlockFetcher, []}, - {BlockImporter, []}, - {AddressFetcher, []} + {AddressFetcher, []}, + {BlockFetcher, []} ] Supervisor.init(children, strategy: :rest_for_one) diff --git a/apps/explorer/test/explorer/indexer/block_importer_test.exs b/apps/explorer/test/explorer/indexer/block_importer_test.exs deleted file mode 100644 index d5f6353525..0000000000 --- a/apps/explorer/test/explorer/indexer/block_importer_test.exs +++ /dev/null @@ -1,90 +0,0 @@ -defmodule Explorer.Indexer.BlockImporterTest do - # must be `async: false` due to use of named GenServer - use Explorer.DataCase, async: false - - alias Explorer.Indexer.BlockImporter - - setup do - start_supervised!({BlockImporter, []}) - - :ok - end - - test "import_blocks" do - assert :ok = - BlockImporter.import_blocks(%{ - blocks: [ - %{ - difficulty: 340_282_366_920_938_463_463_374_607_431_768_211_454, - gas_limit: 6_946_336, - gas_used: 50450, - hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", - miner_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", - nonce: 0, - number: 37, - parent_hash: "0xc37bbad7057945d1bf128c1ff009fb1ad632110bf6a000aac025a80f7766b66e", - size: 719, - timestamp: Timex.parse!("2017-12-15T21:06:30Z", "{ISO:Extended:Z}"), - total_difficulty: 12_590_447_576_074_723_148_144_860_474_975_121_280_509 - } - ], - internal_transactions: [ - %{ - call_type: "call", - from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", - gas: 4_677_320, - gas_used: 27770, - index: 0, - output: "0x", - to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", - trace_address: [], - transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", - type: "call", - value: 0 - } - ], - logs: [ - %{ - address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", - data: "0x000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", - first_topic: "0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22", - fourth_topic: nil, - index: 0, - second_topic: nil, - third_topic: nil, - transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", - type: "mined" - } - ], - receipts: [ - %{ - cumulative_gas_used: 50450, - gas_used: 50450, - status: :ok, - transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", - transaction_index: 0 - } - ], - transactions: [ - %{ - block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", - from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", - gas: 4_700_000, - gas_price: 100_000_000_000, - hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", - index: 0, - input: "0x10855269000000000000000000000000862d67cb0773ee3f8ce7ea89b328ffea861ab3ef", - nonce: 4, - public_key: - "0xe5d196ad4ceada719d9e592f7166d0c75700f6eab2e3c3de34ba751ea786527cb3f6eb96ad9fdfdb9989ff572df50f1c42ef800af9c5207a38b929aff969b5c9", - r: "0xa7f8f45cce375bb7af8750416e1b03e0473f93c256da2285d1134fc97a700e01", - s: "0x1f87a076f13824f4be8963e3dffd7300dae64d5f23c9a062af0c6ead347c135f", - standard_v: "0x1", - to_address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", - v: "0xbe", - value: 0 - } - ] - }) - end -end diff --git a/coveralls.json b/coveralls.json index 9ed1f11e62..a8b830b272 100644 --- a/coveralls.json +++ b/coveralls.json @@ -1,7 +1,7 @@ { "coverage_options": { "treat_no_relevant_lines_as_covered": true, - "minimum_coverage": 87.0 + "minimum_coverage": 88.1 }, "terminal_options": { "file_column_width": 120 From 60a2668641d22674853adf663928ed64e27b0b73 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 9 May 2018 14:37:27 -0500 Subject: [PATCH 56/77] Remove 60_000 Ecto.Repo timeout from config Set timeout only for batch inserts. --- apps/explorer/config/dev.exs | 4 +- apps/explorer/config/prod.exs | 3 +- apps/explorer/lib/explorer/chain.ex | 136 ++++++++++++++---- .../explorer/indexer/block_fetcher_test.exs | 12 +- 4 files changed, 119 insertions(+), 36 deletions(-) diff --git a/apps/explorer/config/dev.exs b/apps/explorer/config/dev.exs index 289a073dfe..20382f2fe4 100644 --- a/apps/explorer/config/dev.exs +++ b/apps/explorer/config/dev.exs @@ -7,8 +7,6 @@ config :explorer, Explorer.Repo, hostname: "localhost", loggers: [], pool_size: 20, - pool_timeout: 60_000, - # Default value of 15_000 causes timeouts around block 813200 - timeout: 60_000 + pool_timeout: 60_000 import_config "dev.secret.exs" diff --git a/apps/explorer/config/prod.exs b/apps/explorer/config/prod.exs index 62cf6f922b..f7cf3bd6fa 100644 --- a/apps/explorer/config/prod.exs +++ b/apps/explorer/config/prod.exs @@ -7,5 +7,4 @@ config :explorer, Explorer.Repo, pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"), ssl: String.equivalent?(System.get_env("ECTO_USE_SSL") || "true", "true"), prepare: :unnamed, - timeout: 60_000, - pool_timeout: 60_000 + timeout: 60_000 diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 1095ee73f2..727bf47f80 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -38,6 +38,7 @@ defmodule Explorer.Chain do @typep inserted_after_option :: {:inserted_after, DateTime.t()} @typep necessity_by_association_option :: {:necessity_by_association, necessity_by_association} @typep pagination_option :: {:pagination, pagination} + @typep timeout_option :: {:timeout, timeout} @typep timestamps :: %{inserted_at: %Ecto.DateTime{}, updated_at: %Ecto.DateTime{}} @typep timestamps_option :: {:timestamps, timestamps} @@ -110,6 +111,16 @@ defmodule Explorer.Chain do end end + # timeouts all in milliseconds + + @transaction_timeout 60_000 + @insert_addresses_timeout 60_000 + @insert_blocks_timeout 60_000 + @insert_internal_transactions_timeout 60_000 + @insert_logs_timeout 60_000 + @insert_receipts_timeout 60_000 + @insert_transactions_timeout 60_000 + @doc """ Updates `t:Explorer.Chain.Address.t/0` with `hash` of `address_hash` to have `fetched_balance` of `balance` in `t:map/0` `balances` of `address_hash` to `balance`. @@ -127,8 +138,8 @@ defmodule Explorer.Chain do :ok """ - @spec update_balances(%{(address_hash :: String.t()) => balance :: integer}) :: :ok | {:error, reason :: term} - def update_balances(balances) do + @spec update_balances(%{(address_hash :: String.t()) => balance :: integer}, [timeout_option]) :: :ok + def update_balances(balances, options \\ []) when is_list(options) do timestamps = timestamps() changes_list = @@ -147,7 +158,15 @@ defmodule Explorer.Chain do # MUST match order used in `insert_addresses/2` ordered_changes_list = sort_address_changes_list(changes_list) - {_, _} = Repo.safe_insert_all(Address, ordered_changes_list, conflict_target: :hash, on_conflict: :replace_all) + {_, _} = + Repo.safe_insert_all( + Address, + ordered_changes_list, + conflict_target: :hash, + on_conflict: :replace_all, + timeout: Keyword.get(options, :timeout, @insert_addresses_timeout) + ) + :ok end @@ -732,16 +751,34 @@ defmodule Explorer.Chain do * `t.Explorer.Chain.Receipt.t/0` * `t.Explorer.Chain.Log.t/0` + ## Options + + * `:timeout` - the timeout for the whole `c:Ecto.Repo.transaction/0` call. Defaults to `#{@transaction_timeout}` + milliseconds. + * `:insert_addresses_timeout` - the timeout for inserting all addresses found in the params lists across all types. + Defaults to `#{@insert_addresses_timeout}` milliseconds. + * `:insert_blocks_timeout` - the timeout for inserting all blocks. Defaults to `#{@insert_blocks_timeout}` + milliseconds. + * `:insert_internal_transactions_timeout` - the timeout for inserting all internal transactions. Defaults to + `#{@insert_internal_transactions_timeout}` milliseconds. + * `:insert_logs_timeout` - the timeout for inserting all logs. Defaults to `#{@insert_logs_timeout}` milliseconds. + * `:insert_receipts_timeout` - the timeout for inserting all receipts. Defaults to `#{@insert_receipts_timeout}` + milliseconds. + * `:insert_transactions_timeout` - the timeout for inserting all transactions found in the params lists across all types. + Defaults to `#{@insert_transactions_timeout}` milliseconds. """ - def import_blocks(%{ - blocks: blocks_params, - logs: logs_params, - internal_transactions: internal_transactions_params, - receipts: receipts_params, - transactions: transactions_params - }) + def import_blocks( + %{ + blocks: blocks_params, + logs: logs_params, + internal_transactions: internal_transactions_params, + receipts: receipts_params, + transactions: transactions_params + }, + options \\ [] + ) when is_list(blocks_params) and is_list(internal_transactions_params) and is_list(logs_params) and - is_list(receipts_params) and is_list(transactions_params) do + is_list(receipts_params) and is_list(transactions_params) and is_list(options) do with {:ok, ecto_schema_module_to_changes_list} <- ecto_schema_module_to_params_list_to_ecto_schema_module_to_changes_list(%{ Block => blocks_params, @@ -750,7 +787,7 @@ defmodule Explorer.Chain do Receipt => receipts_params, Transaction => transactions_params }) do - insert_ecto_schema_module_to_changes_list(ecto_schema_module_to_changes_list) + insert_ecto_schema_module_to_changes_list(ecto_schema_module_to_changes_list, options) end end @@ -1466,9 +1503,11 @@ defmodule Explorer.Chain do ) end - @spec insert_addresses([%{hash: Hash.Truncated.t()}], [timestamps_option, ...]) :: {:ok, [Hash.Truncated.t()]} + @spec insert_addresses([%{hash: Hash.Truncated.t()}], [timeout_option | timestamps_option]) :: + {:ok, [Hash.Truncated.t()]} defp insert_addresses(changes_list, named_arguments) when is_list(changes_list) and is_list(named_arguments) do timestamps = Keyword.fetch!(named_arguments, :timestamps) + timeout = Keyword.fetch!(named_arguments, :timeout) # order so that row ShareLocks are grabbed in a consistent order ordered_changes_list = sort_address_changes_list(changes_list) @@ -1478,6 +1517,7 @@ defmodule Explorer.Chain do conflict_target: :hash, on_conflict: [set: [balance_fetched_at: nil]], for: Address, + timeout: timeout, timestamps: timestamps ) @@ -1488,9 +1528,10 @@ defmodule Explorer.Chain do Enum.sort_by(changes_list, & &1.hash) end - @spec insert_blocks([map()], [timestamps_option]) :: {:ok, [Hash.t()]} | {:error, [Changeset.t()]} + @spec insert_blocks([map()], [timeout_option | timestamps_option]) :: {:ok, [Hash.t()]} | {:error, [Changeset.t()]} defp insert_blocks(changes_list, named_arguments) when is_list(changes_list) and is_list(named_arguments) do timestamps = Keyword.fetch!(named_arguments, :timestamps) + timeout = Keyword.fetch!(named_arguments, :timeout) # order so that row ShareLocks are grabbed in a consistent order ordered_changes_list = Enum.sort_by(changes_list, &{&1.number, &1.hash}) @@ -1501,6 +1542,7 @@ defmodule Explorer.Chain do conflict_target: :number, on_conflict: :replace_all, for: Block, + timeout: timeout, timestamps: timestamps ) @@ -1514,7 +1556,8 @@ defmodule Explorer.Chain do InternalTransaction => internal_transactions_changes, Receipt => receipts_changes, Transaction => transactions_changes - } = ecto_schema_module_to_changes_list + } = ecto_schema_module_to_changes_list, + options ) do address_hash_set = ecto_schema_module_to_changes_list_to_address_hash_set(ecto_schema_module_to_changes_list) addresses_changes = Address.hash_set_to_changes_list(address_hash_set) @@ -1522,15 +1565,49 @@ defmodule Explorer.Chain do timestamps = timestamps() Multi.new() - |> Multi.run(:addresses, fn _ -> insert_addresses(addresses_changes, timestamps: timestamps) end) - |> Multi.run(:blocks, fn _ -> insert_blocks(blocks_changes, timestamps: timestamps) end) - |> Multi.run(:transactions, fn _ -> insert_transactions(transactions_changes, timestamps: timestamps) end) + |> Multi.run(:addresses, fn _ -> + insert_addresses( + addresses_changes, + timeout: Keyword.get(options, :insert_addresses_timeout, @insert_addresses_timeout), + timestamps: timestamps + ) + end) + |> Multi.run(:blocks, fn _ -> + insert_blocks( + blocks_changes, + timeout: Keyword.get(options, :insert_blocks_timeout, @insert_blocks_timeout), + timestamps: timestamps + ) + end) + |> Multi.run(:transactions, fn _ -> + insert_transactions( + transactions_changes, + timeout: Keyword.get(options, :insert_transactions_timeout, @insert_transactions_timeout), + timestamps: timestamps + ) + end) |> Multi.run(:internal_transactions, fn _ -> - insert_internal_transactions(internal_transactions_changes, timestamps: timestamps) + insert_internal_transactions( + internal_transactions_changes, + timeout: Keyword.get(options, :insert_internal_transactions_timeout, @insert_internal_transactions_timeout), + timestamps: timestamps + ) end) - |> Multi.run(:receipts, fn _ -> insert_receipts(receipts_changes, timestamps: timestamps) end) - |> Multi.run(:logs, fn _ -> insert_logs(logs_changes, timestamps: timestamps) end) - |> Repo.transaction() + |> Multi.run(:receipts, fn _ -> + insert_receipts( + receipts_changes, + timeout: Keyword.get(options, :insert_receipts_timeout, @insert_receipts_timeout), + timestamps: timestamps + ) + end) + |> Multi.run(:logs, fn _ -> + insert_logs( + logs_changes, + timeout: Keyword.get(options, :insert_logs_timeout, @insert_logs_timeout), + timestamps: timestamps + ) + end) + |> Repo.transaction(timeout: Keyword.get(options, :transaction_timeout, @transaction_timeout)) end @spec insert_internal_transactions([map()], [timestamps_option]) :: @@ -1540,7 +1617,7 @@ defmodule Explorer.Chain do timestamps = Keyword.fetch!(named_arguments, :timestamps) # order so that row ShareLocks are grabbed in a consistent order - ordered_changes_list = Enum.sort_by(changes_list, & {&1.transaction_hash, &1.index}) + ordered_changes_list = Enum.sort_by(changes_list, &{&1.transaction_hash, &1.index}) {:ok, internal_transactions} = insert_changes_list( @@ -1554,10 +1631,11 @@ defmodule Explorer.Chain do for(internal_transaction <- internal_transactions, do: Map.take(internal_transaction, [:index, :transaction_hash]))} end - @spec insert_logs([map()], [timestamps_option]) :: + @spec insert_logs([map()], [timeout_option | timestamps_option]) :: {:ok, [%{index: non_neg_integer, transaction_hash: Hash.t()}]} | {:error, [Changeset.t()]} defp insert_logs(changes_list, named_arguments) when is_list(changes_list) and is_list(named_arguments) do timestamps = Keyword.fetch!(named_arguments, :timestamps) + timeout = Keyword.fetch!(named_arguments, :timeout) # order so that row ShareLocks are grabbed in a consistent order ordered_changes_list = Enum.sort_by(changes_list, &{&1.transaction_hash, &1.index}) @@ -1569,15 +1647,17 @@ defmodule Explorer.Chain do on_conflict: :replace_all, for: Log, returning: [:index, :transaction_hash], + timeout: timeout, timestamps: timestamps ) {:ok, for(log <- logs, do: Map.take(log, [:index, :transaction_hash]))} end - @spec insert_receipts([map()], [timestamps_option]) :: {:ok, [Hash.t()]} | {:error, [Changeset.t()]} + @spec insert_receipts([map()], [timeout_option | timestamps_option]) :: {:ok, [Hash.t()]} | {:error, [Changeset.t()]} defp insert_receipts(changes_list, named_arguments) when is_list(changes_list) and is_list(named_arguments) do timestamps = Keyword.fetch!(named_arguments, :timestamps) + timeout = Keyword.fetch!(named_arguments, :timeout) # order so that row ShareLocks are grabbed in a consistent order ordered_changes_list = Enum.sort_by(changes_list, & &1.transaction_hash) @@ -1589,6 +1669,7 @@ defmodule Explorer.Chain do on_conflict: :replace_all, for: Receipt, returning: [:transaction_hash], + timeout: timeout, timestamps: timestamps ) @@ -1603,9 +1684,11 @@ defmodule Explorer.Chain do {:ok, inserted} end - @spec insert_transactions([map()], [timestamps_option]) :: {:ok, [Hash.t()]} | {:error, [Changeset.t()]} + @spec insert_transactions([map()], [timeout_option | timestamps_option]) :: + {:ok, [Hash.t()]} | {:error, [Changeset.t()]} defp insert_transactions(changes_list, named_arguments) when is_list(changes_list) and is_list(named_arguments) do timestamps = Keyword.fetch!(named_arguments, :timestamps) + timeout = Keyword.fetch!(named_arguments, :timeout) # order so that row ShareLocks are grabbed in a consistent order ordered_changes_list = Enum.sort_by(changes_list, & &1.hash) @@ -1617,6 +1700,7 @@ defmodule Explorer.Chain do on_conflict: :replace_all, for: Transaction, returning: [:hash], + timeout: timeout, timestamps: timestamps ) diff --git a/apps/explorer/test/explorer/indexer/block_fetcher_test.exs b/apps/explorer/test/explorer/indexer/block_fetcher_test.exs index f40cc58fea..1e41a3d271 100644 --- a/apps/explorer/test/explorer/indexer/block_fetcher_test.exs +++ b/apps/explorer/test/explorer/indexer/block_fetcher_test.exs @@ -24,14 +24,15 @@ defmodule Explorer.Indexer.BlockFetcherTest do test "without debug_logs" do assert capture_log_at_level(:debug, fn -> - BlockFetcher.handle_info(:debug_count, %{debug_logs: false}) - end) == "" + BlockFetcher.handle_info(:debug_count, %{debug_logs: false}) + end) == "" end test "with debug_logs" do - log = capture_log_at_level(:debug, fn -> - BlockFetcher.handle_info(:debug_count, %{debug_logs: true}) - end) + log = + capture_log_at_level(:debug, fn -> + BlockFetcher.handle_info(:debug_count, %{debug_logs: true}) + end) assert log =~ "blocks: 4" assert log =~ "internal transactions: 3" @@ -44,6 +45,7 @@ defmodule Explorer.Indexer.BlockFetcherTest do defp capture_log_at_level(level, block) do logger_level_transaction(fn -> Logger.configure(level: level) + capture_log(fn -> block.() Process.sleep(10) From f8a06d27a85b6353edd181ac6198f8f2b1add567 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 9 May 2018 16:07:21 -0500 Subject: [PATCH 57/77] Test BlockFetcher.import_range --- .../lib/explorer/indexer/block_fetcher.ex | 18 +++++--- .../explorer/indexer/block_fetcher_test.exs | 42 ++++++++++++++++++- coveralls.json | 2 +- 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/apps/explorer/lib/explorer/indexer/block_fetcher.ex b/apps/explorer/lib/explorer/indexer/block_fetcher.ex index 438723ddd4..f500038c7b 100644 --- a/apps/explorer/lib/explorer/indexer/block_fetcher.ex +++ b/apps/explorer/lib/explorer/indexer/block_fetcher.ex @@ -196,16 +196,18 @@ defmodule Explorer.Indexer.BlockFetcher do end defp insert(state, seq, range, params) do - with {:ok, %{addresses: address_hashes}} <- Chain.import_blocks(params) do + with {:ok, %{addresses: address_hashes}} = ok <- Chain.import_blocks(params) do :ok = AddressFetcher.async_fetch_balances(address_hashes) - :ok + ok else - {:error, step, reason} -> + {:error, step, reason} = error -> debug(state, fn -> "failed to insert blocks during #{step} #{inspect(range)}: #{inspect(reason)}. Retrying" end) :ok = Sequence.inject_range(seq, range) + + error end end @@ -242,11 +244,13 @@ defmodule Explorer.Indexer.BlockFetcher do seq |> Sequence.build_stream() |> Task.async_stream(&import_range(&1, state, seq), Keyword.merge(task_opts, timeout: :infinity)) - |> Enum.each(fn {:ok, :ok} -> :ok end) + |> Stream.run() end - # Run at state.blocks_concurrency max_concurrency - defp import_range({block_start, block_end} = range, state, seq) do + # Run at state.blocks_concurrency max_concurrency when called by `stream_import/3` + # Only public for testing + @doc false + def import_range({block_start, block_end} = range, state, seq) do with {:blocks, {:ok, next, result}} <- {:blocks, JSONRPC.fetch_blocks_by_range(block_start, block_end)}, %{blocks: blocks, transactions: transactions} = result, cap_seq(seq, next, range, state), @@ -269,6 +273,8 @@ defmodule Explorer.Indexer.BlockFetcher do end) :ok = Sequence.inject_range(seq, range) + + {:error, step, reason} end end diff --git a/apps/explorer/test/explorer/indexer/block_fetcher_test.exs b/apps/explorer/test/explorer/indexer/block_fetcher_test.exs index 1e41a3d271..d630bb6f82 100644 --- a/apps/explorer/test/explorer/indexer/block_fetcher_test.exs +++ b/apps/explorer/test/explorer/indexer/block_fetcher_test.exs @@ -4,7 +4,9 @@ defmodule Explorer.Indexer.BlockFetcherTest do import ExUnit.CaptureLog - alias Explorer.Indexer.BlockFetcher + alias Explorer.Chain.{Address, Block} + alias Explorer.JSONRPC + alias Explorer.Indexer.{BlockFetcher, Sequence} @tag capture_log: true @@ -42,6 +44,44 @@ defmodule Explorer.Indexer.BlockFetcherTest do end end + describe "import_range/3" do + setup do + start_supervised!({JSONRPC, []}) + start_supervised!({Task.Supervisor, name: Explorer.Indexer.TaskSupervisor}) + + :ok + end + + test "with single element range that is valid imports one block" do + {:ok, sequence} = Sequence.start_link([], 0, 1) + + assert {:ok, + %{ + addresses: [ + %Explorer.Chain.Hash{ + byte_count: 20, + bytes: <<0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>> + } + ], + blocks: [ + %Explorer.Chain.Hash{ + byte_count: 32, + bytes: + <<91, 40, 193, 191, 211, 161, 82, 48, 201, 164, 107, 57, 156, 208, 249, 166, 146, 13, 67, 46, 133, + 56, 28, 198, 161, 64, 176, 110, 132, 16, 17, 47>> + } + ], + internal_transactions: [], + logs: [], + receipts: [], + transactions: [] + }} = BlockFetcher.import_range({0, 0}, %{debug_logs: false}, sequence) + + assert Repo.aggregate(Block, :count, :hash) == 1 + assert Repo.aggregate(Address, :count, :hash) == 1 + end + end + defp capture_log_at_level(level, block) do logger_level_transaction(fn -> Logger.configure(level: level) diff --git a/coveralls.json b/coveralls.json index a8b830b272..e8e5b48154 100644 --- a/coveralls.json +++ b/coveralls.json @@ -1,7 +1,7 @@ { "coverage_options": { "treat_no_relevant_lines_as_covered": true, - "minimum_coverage": 88.1 + "minimum_coverage": 90.4 }, "terminal_options": { "file_column_width": 120 From e0a34d8fbdf3d8a5ef1b8e99f843d6d1e0de3f1f Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 9 May 2018 16:38:24 -0500 Subject: [PATCH 58/77] Use block 37 to test that all schemas can be imported --- .../explorer/indexer/block_fetcher_test.exs | 112 ++++++++++++++++-- coveralls.json | 2 +- 2 files changed, 105 insertions(+), 9 deletions(-) diff --git a/apps/explorer/test/explorer/indexer/block_fetcher_test.exs b/apps/explorer/test/explorer/indexer/block_fetcher_test.exs index d630bb6f82..7776377980 100644 --- a/apps/explorer/test/explorer/indexer/block_fetcher_test.exs +++ b/apps/explorer/test/explorer/indexer/block_fetcher_test.exs @@ -4,12 +4,18 @@ defmodule Explorer.Indexer.BlockFetcherTest do import ExUnit.CaptureLog - alias Explorer.Chain.{Address, Block} + alias Explorer.Chain.{Address, Block, InternalTransaction, Log, Receipt, Transaction} alias Explorer.JSONRPC alias Explorer.Indexer.{BlockFetcher, Sequence} @tag capture_log: true + setup do + {:ok, state} = BlockFetcher.init(debug_logs: false) + + %{state: state} + end + describe "handle_info(:debug_count, state)" do setup do block = insert(:block) @@ -24,16 +30,16 @@ defmodule Explorer.Indexer.BlockFetcherTest do :ok end - test "without debug_logs" do + test "without debug_logs", %{state: state} do assert capture_log_at_level(:debug, fn -> - BlockFetcher.handle_info(:debug_count, %{debug_logs: false}) + BlockFetcher.handle_info(:debug_count, %{state | debug_logs: false}) end) == "" end - test "with debug_logs" do + test "with debug_logs", %{state: state} do log = capture_log_at_level(:debug, fn -> - BlockFetcher.handle_info(:debug_count, %{debug_logs: true}) + BlockFetcher.handle_info(:debug_count, %{state | debug_logs: true}) end) assert log =~ "blocks: 4" @@ -49,10 +55,12 @@ defmodule Explorer.Indexer.BlockFetcherTest do start_supervised!({JSONRPC, []}) start_supervised!({Task.Supervisor, name: Explorer.Indexer.TaskSupervisor}) - :ok + {:ok, state} = BlockFetcher.init(debug_logs: false) + + %{state: state} end - test "with single element range that is valid imports one block" do + test "with single element range that is valid imports one block", %{state: state} do {:ok, sequence} = Sequence.start_link([], 0, 1) assert {:ok, @@ -75,11 +83,99 @@ defmodule Explorer.Indexer.BlockFetcherTest do logs: [], receipts: [], transactions: [] - }} = BlockFetcher.import_range({0, 0}, %{debug_logs: false}, sequence) + }} = BlockFetcher.import_range({0, 0}, state, sequence) assert Repo.aggregate(Block, :count, :hash) == 1 assert Repo.aggregate(Address, :count, :hash) == 1 end + + test "can import range with all imported schemas", %{state: state} do + {:ok, sequence} = Sequence.start_link([], 0, 1) + + # 37 is determined using the following query: + # SELECT MIN(blocks.number) FROM + # (SELECT blocks.number + # FROM internal_transactions + # INNER JOIN transactions + # ON transactions.hash = internal_transactions.transaction_hash + # INNER JOIN blocks + # ON blocks.hash = transactions.block_hash + # INTERSECT + # SELECT blocks.number + # FROM logs + # INNER JOIN transactions + # ON transactions.hash = logs.transaction_hash + # INNER JOIN blocks + # ON blocks.hash = transactions.block_hash) as blocks + assert {:ok, + %{ + addresses: [ + %Explorer.Chain.Hash{ + byte_count: 20, + bytes: + <<139, 243, 141, 71, 100, 146, 144, 100, 242, 212, 211, 165, 101, 32, 167, 106, 179, 223, 65, 91>> + }, + %Explorer.Chain.Hash{ + byte_count: 20, + bytes: + <<232, 221, 197, 199, 162, 210, 240, 215, 169, 121, 132, 89, 192, 16, 79, 223, 94, 152, 122, 202>> + } + ], + blocks: [ + %Explorer.Chain.Hash{ + byte_count: 32, + bytes: + <<246, 180, 184, 200, 141, 243, 235, 210, 82, 236, 71, 99, 40, 51, 77, 192, 38, 207, 102, 96, 106, + 132, 251, 118, 155, 61, 60, 188, 204, 132, 113, 189>> + } + ], + internal_transactions: [ + %{ + index: 0, + transaction_hash: %Explorer.Chain.Hash{ + byte_count: 32, + bytes: + <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57, + 101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>> + } + } + ], + logs: [ + %{ + index: 0, + transaction_hash: %Explorer.Chain.Hash{ + byte_count: 32, + bytes: + <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57, + 101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>> + } + } + ], + receipts: [ + %Explorer.Chain.Hash{ + byte_count: 32, + bytes: + <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57, + 101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>> + } + ], + transactions: [ + %Explorer.Chain.Hash{ + byte_count: 32, + bytes: + <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57, + 101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>> + } + ] + }} = BlockFetcher.import_range({37, 37}, state, sequence) + + assert Repo.aggregate(Block, :count, :hash) == 1 + assert Repo.aggregate(Address, :count, :hash) == 2 + assert Repo.aggregate(InternalTransaction, :count, :id) == 1 + assert Repo.aggregate(Log, :count, :id) == 1 + assert Repo.aggregate(Receipt, :count, :transaction_hash) == 1 + assert Repo.aggregate(Transaction, :count, :hash) == 1 + end end defp capture_log_at_level(level, block) do diff --git a/coveralls.json b/coveralls.json index e8e5b48154..15c4d5110b 100644 --- a/coveralls.json +++ b/coveralls.json @@ -1,7 +1,7 @@ { "coverage_options": { "treat_no_relevant_lines_as_covered": true, - "minimum_coverage": 90.4 + "minimum_coverage": 92.2 }, "terminal_options": { "file_column_width": 120 From 8f35e93c0d6c8ebaf403ccec62d4da36a108efc3 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Wed, 9 May 2018 16:56:09 -0500 Subject: [PATCH 59/77] BlockFetcher.start_link genesis test --- .../explorer/indexer/block_fetcher_test.exs | 67 +++++++++++++------ coveralls.json | 2 +- 2 files changed, 49 insertions(+), 20 deletions(-) diff --git a/apps/explorer/test/explorer/indexer/block_fetcher_test.exs b/apps/explorer/test/explorer/indexer/block_fetcher_test.exs index 7776377980..21187c7c57 100644 --- a/apps/explorer/test/explorer/indexer/block_fetcher_test.exs +++ b/apps/explorer/test/explorer/indexer/block_fetcher_test.exs @@ -10,13 +10,41 @@ defmodule Explorer.Indexer.BlockFetcherTest do @tag capture_log: true - setup do - {:ok, state} = BlockFetcher.init(debug_logs: false) + # First block with all schemas to import + # 37 is determined using the following query: + # SELECT MIN(blocks.number) FROM + # (SELECT blocks.number + # FROM internal_transactions + # INNER JOIN transactions + # ON transactions.hash = internal_transactions.transaction_hash + # INNER JOIN blocks + # ON blocks.hash = transactions.block_hash + # INTERSECT + # SELECT blocks.number + # FROM logs + # INNER JOIN transactions + # ON transactions.hash = logs.transaction_hash + # INNER JOIN blocks + # ON blocks.hash = transactions.block_hash) as blocks + @first_full_block_number 37 + + describe "start_link/1" do + test "starts fetching blocks from Genesis" do + assert Repo.aggregate(Block, :count, :hash) == 0 + + start_supervised!(BlockFetcher) + + wait(fn -> + Repo.one!(from(block in Block, where: block.number == @first_full_block_number)) + end) - %{state: state} + assert Repo.aggregate(Block, :count, :hash) >= @first_full_block_number + end end describe "handle_info(:debug_count, state)" do + setup :state + setup do block = insert(:block) @@ -51,6 +79,8 @@ defmodule Explorer.Indexer.BlockFetcherTest do end describe "import_range/3" do + setup :state + setup do start_supervised!({JSONRPC, []}) start_supervised!({Task.Supervisor, name: Explorer.Indexer.TaskSupervisor}) @@ -92,21 +122,6 @@ defmodule Explorer.Indexer.BlockFetcherTest do test "can import range with all imported schemas", %{state: state} do {:ok, sequence} = Sequence.start_link([], 0, 1) - # 37 is determined using the following query: - # SELECT MIN(blocks.number) FROM - # (SELECT blocks.number - # FROM internal_transactions - # INNER JOIN transactions - # ON transactions.hash = internal_transactions.transaction_hash - # INNER JOIN blocks - # ON blocks.hash = transactions.block_hash - # INTERSECT - # SELECT blocks.number - # FROM logs - # INNER JOIN transactions - # ON transactions.hash = logs.transaction_hash - # INNER JOIN blocks - # ON blocks.hash = transactions.block_hash) as blocks assert {:ok, %{ addresses: [ @@ -167,7 +182,7 @@ defmodule Explorer.Indexer.BlockFetcherTest do 101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>> } ] - }} = BlockFetcher.import_range({37, 37}, state, sequence) + }} = BlockFetcher.import_range({@first_full_block_number, @first_full_block_number}, state, sequence) assert Repo.aggregate(Block, :count, :hash) == 1 assert Repo.aggregate(Address, :count, :hash) == 2 @@ -202,4 +217,18 @@ defmodule Explorer.Indexer.BlockFetcherTest do return end + + defp state(_) do + {:ok, state} = BlockFetcher.init(debug_logs: false) + + %{state: state} + end + + defp wait(producer) do + producer.() + rescue + Ecto.NoResultsError -> + Process.sleep(100) + wait(producer) + end end diff --git a/coveralls.json b/coveralls.json index 15c4d5110b..bc08a0ff35 100644 --- a/coveralls.json +++ b/coveralls.json @@ -1,7 +1,7 @@ { "coverage_options": { "treat_no_relevant_lines_as_covered": true, - "minimum_coverage": 92.2 + "minimum_coverage": 94.0 }, "terminal_options": { "file_column_width": 120 From 098985faf4d47deac2d3c617a7cfe833123776d3 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Fri, 11 May 2018 04:55:06 -0500 Subject: [PATCH 60/77] Use struct to prevent regression in poll_timer not existing in state Prevent regression in bug where transition to realtime does not work because poll_timer does not exist in state map so `%{state | poll_timer: ...}` fails at end of genesis task like: ``` [error] GenServer Explorer.Indexer.BlockFetcher terminating ** (KeyError) key :poll_timer not found (explorer) lib/explorer/indexer/block_fetcher.ex:283: Explorer.Indexer.BlockFetcher.schedule_next_realtime_fetch/1 (explorer) lib/explorer/indexer/block_fetcher.ex:121: Explorer.Indexer.BlockFetcher.handle_info/2 (stdlib) gen_server.erl:616: :gen_server.try_dispatch/4 (stdlib) gen_server.erl:686: :gen_server.handle_msg/6 (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3 Last message: {:DOWN, #Reference<0.2595141227.225443843.205604>, :process, #PID<0.10048.17>, :normal} State: %{blocks_batch_size: 10, blocks_concurrency: 10, debug_logs: true, genesis_task: #PID<0.10048.17>, internal_transactions_batch_size: 50, internal_transactions_concurrency: 8, realtime_interval: 10000, receipts_batch_size: 250, receipts_concurrency: 20} ``` --- .../lib/explorer/indexer/block_fetcher.ex | 95 +++++++++++-------- 1 file changed, 57 insertions(+), 38 deletions(-) diff --git a/apps/explorer/lib/explorer/indexer/block_fetcher.ex b/apps/explorer/lib/explorer/indexer/block_fetcher.ex index f500038c7b..7f58053c40 100644 --- a/apps/explorer/lib/explorer/indexer/block_fetcher.ex +++ b/apps/explorer/lib/explorer/indexer/block_fetcher.ex @@ -9,7 +9,7 @@ defmodule Explorer.Indexer.BlockFetcher do alias Explorer.{Chain, Indexer, JSONRPC} - alias Explorer.Indexer.{AddressFetcher, Sequence} + alias Explorer.Indexer.{AddressFetcher, BlockFetcher, Sequence} alias Explorer.JSONRPC.Transactions @@ -28,10 +28,27 @@ defmodule Explorer.Indexer.BlockFetcher do # milliseconds @block_rate 5_000 + @realtime_interval_per_block_rate 2 @receipts_batch_size 250 @receipts_concurrency 20 + defstruct debug_logs: @debug_logs, + blocks_batch_size: @blocks_batch_size, + blocks_concurrency: @blocks_concurrency, + genesis_task: nil, + internal_transactions_batch_size: @internal_transactions_batch_size, + internal_transactions_concurrency: @internal_transactions_concurrency, + poll_timer: nil, + realtime_interval: @block_rate * @realtime_interval_per_block_rate, + realtime_task: nil, + receipts_batch_size: @receipts_batch_size, + receipts_concurrency: @receipts_concurrency + + # This needs to match the documented options below for `start_link/1` + @allowed_option_names ~w(debug_logs blocks_batch_size blocks_concurrency block_rate internal_transactions_batch_size + internal_transactions_concurrrency receipts_batch_size receipts_concurrency)a + @doc """ Starts the server. @@ -40,7 +57,7 @@ defmodule Explorer.Indexer.BlockFetcher do Default options are pulled from application config under the `:explorer, :indexer` keyspace. The follow options can be overridden: - * `:debug_logs` - When `true` logs verbose index progress. Defaults `false`. + * `:debug_logs` - When `true` logs verbose index progress. Defaults `#{@debug_logs}`. * `:blocks_batch_size` - The number of blocks to request in one call to the JSONRPC. Defaults to `#{@blocks_batch_size}`. Block requests also include the transactions for those blocks. *These transactions are not paginated.* @@ -73,55 +90,57 @@ defmodule Explorer.Indexer.BlockFetcher do @impl GenServer def init(opts) do - opts = Keyword.merge(Application.fetch_env!(:explorer, :indexer), opts) + fields = + :explorer + |> Application.fetch_env!(:indexer) + |> Keyword.merge(opts) + |> Keyword.take(@allowed_option_names) + |> put_block_rate() + + state = struct!(%BlockFetcher{}, fields) send(self(), :catchup_index) :timer.send_interval(15_000, self(), :debug_count) - state = %{ - genesis_task: nil, - debug_logs: Keyword.get(opts, :debug_logs, @debug_logs), - realtime_interval: (opts[:block_rate] || @block_rate) * 2, - blocks_batch_size: Keyword.get(opts, :blocks_batch_size, @blocks_batch_size), - blocks_concurrency: Keyword.get(opts, :blocks_concurrency, @blocks_concurrency), - internal_transactions_batch_size: - Keyword.get(opts, :internal_transactions_batch_size, @internal_transactions_batch_size), - internal_transactions_concurrency: - Keyword.get(opts, :internal_transactions_concurrency, @internal_transactions_concurrency), - receipts_batch_size: Keyword.get(opts, :receipts_batch_size, @receipts_batch_size), - receipts_concurrency: Keyword.get(opts, :receipts_concurrency, @receipts_concurrency) - } - {:ok, state} end + defp put_block_rate(allowed_options) do + if Keyword.has_key?(allowed_options, :block_rate) do + {block_rate, passthrough_fields} = Keyword.pop(allowed_options, :block_rate) + Keyword.put(passthrough_fields, :realtime_interval, block_rate * @realtime_interval_per_block_rate) + else + allowed_options + end + end + @impl GenServer - def handle_info(:catchup_index, state) do + def handle_info(:catchup_index, %BlockFetcher{} = state) do {:ok, genesis_task} = Task.start_link(fn -> genesis_task(state) end) Process.monitor(genesis_task) - {:noreply, %{state | genesis_task: genesis_task}} + {:noreply, %BlockFetcher{state | genesis_task: genesis_task}} end - def handle_info(:realtime_index, state) do + def handle_info(:realtime_index, %BlockFetcher{} = state) do {:ok, realtime_task} = Task.start_link(fn -> realtime_task(state) end) Process.monitor(realtime_task) - {:noreply, %{state | realtime_task: realtime_task}} + {:noreply, %BlockFetcher{state | realtime_task: realtime_task}} end - def handle_info({:DOWN, _ref, :process, pid, :normal}, %{realtime_task: pid} = state) do - {:noreply, schedule_next_realtime_fetch(%{state | realtime_task: nil})} + def handle_info({:DOWN, _ref, :process, pid, :normal}, %BlockFetcher{realtime_task: pid} = state) do + {:noreply, schedule_next_realtime_fetch(%BlockFetcher{state | realtime_task: nil})} end - def handle_info({:DOWN, _ref, :process, pid, :normal}, %{genesis_task: pid} = state) do + def handle_info({:DOWN, _ref, :process, pid, :normal}, %BlockFetcher{genesis_task: pid} = state) do Logger.info(fn -> "Finished index from genesis. Transitioning to realtime index." end) - {:noreply, schedule_next_realtime_fetch(%{state | genesis_task: nil})} + {:noreply, schedule_next_realtime_fetch(%BlockFetcher{state | genesis_task: nil})} end - def handle_info(:debug_count, state) do + def handle_info(:debug_count, %BlockFetcher{} = state) do debug(state, fn -> """ @@ -143,14 +162,14 @@ defmodule Explorer.Indexer.BlockFetcher do :ok = Sequence.cap(seq) end - defp cap_seq(_seq, :more, {block_start, block_end}, state) do + defp cap_seq(_seq, :more, {block_start, block_end}, %BlockFetcher{} = state) do debug(state, fn -> "got blocks #{block_start} - #{block_end}" end) :ok end defp fetch_internal_transactions(_state, []), do: {:ok, []} - defp fetch_internal_transactions(state, hashes) do + defp fetch_internal_transactions(%BlockFetcher{} = state, hashes) do debug(state, fn -> "fetching internal transactions for #{length(hashes)} transactions" end) stream_opts = [max_concurrency: state.internal_transactions_concurrency, timeout: :infinity] @@ -166,7 +185,7 @@ defmodule Explorer.Indexer.BlockFetcher do defp fetch_transaction_receipts(_state, []), do: {:ok, %{logs: [], receipts: []}} - defp fetch_transaction_receipts(state, hashes) do + defp fetch_transaction_receipts(%BlockFetcher{} = state, hashes) do debug(state, fn -> "fetching #{length(hashes)} transaction receipts" end) stream_opts = [max_concurrency: state.receipts_concurrency, timeout: :infinity] @@ -185,7 +204,7 @@ defmodule Explorer.Indexer.BlockFetcher do end) end - defp genesis_task(state) do + defp genesis_task(%BlockFetcher{} = state) do {count, missing_ranges} = missing_block_numbers(state) current_block = Indexer.next_block_number() @@ -195,7 +214,7 @@ defmodule Explorer.Indexer.BlockFetcher do stream_import(state, seq, max_concurrency: state.blocks_concurrency) end - defp insert(state, seq, range, params) do + defp insert(%BlockFetcher{} = state, seq, range, params) do with {:ok, %{addresses: address_hashes}} = ok <- Chain.import_blocks(params) do :ok = AddressFetcher.async_fetch_balances(address_hashes) ok @@ -211,7 +230,7 @@ defmodule Explorer.Indexer.BlockFetcher do end end - defp missing_block_numbers(%{blocks_batch_size: blocks_batch_size}) do + defp missing_block_numbers(%BlockFetcher{blocks_batch_size: blocks_batch_size}) do {count, missing_ranges} = Chain.missing_block_numbers() chunked_ranges = @@ -235,7 +254,7 @@ defmodule Explorer.Indexer.BlockFetcher do {count, chunked_ranges} end - defp realtime_task(state) do + defp realtime_task(%BlockFetcher{} = state) do {:ok, seq} = Sequence.start_link([], Indexer.next_block_number(), 2) stream_import(state, seq, max_concurrency: 1) end @@ -250,7 +269,7 @@ defmodule Explorer.Indexer.BlockFetcher do # Run at state.blocks_concurrency max_concurrency when called by `stream_import/3` # Only public for testing @doc false - def import_range({block_start, block_end} = range, state, seq) do + def import_range({block_start, block_end} = range, %BlockFetcher{} = state, seq) do with {:blocks, {:ok, next, result}} <- {:blocks, JSONRPC.fetch_blocks_by_range(block_start, block_end)}, %{blocks: blocks, transactions: transactions} = result, cap_seq(seq, next, range, state), @@ -278,11 +297,11 @@ defmodule Explorer.Indexer.BlockFetcher do end end - defp schedule_next_realtime_fetch(state) do + defp schedule_next_realtime_fetch(%BlockFetcher{} = state) do timer = Process.send_after(self(), :realtime_index, state.realtime_interval) - %{state | poll_timer: timer} + %BlockFetcher{state | poll_timer: timer} end - defp debug(%{debug_logs: true}, func), do: Logger.debug(func) - defp debug(%{debug_logs: false}, _func), do: :noop + defp debug(%BlockFetcher{debug_logs: true}, func), do: Logger.debug(func) + defp debug(%BlockFetcher{debug_logs: false}, _func), do: :noop end From 61b9e18f8490bef853c1c3168b4e3e3d8f5fb81e Mon Sep 17 00:00:00 2001 From: Chris McCord Date: Fri, 11 May 2018 12:36:12 -0400 Subject: [PATCH 61/77] Rename DEBUG_LOGS to DEBUG_INDEXER --- apps/explorer/config/config.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index a229111292..c281af164d 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -7,7 +7,7 @@ use Mix.Config config :explorer, :indexer, block_rate: 5_000, - debug_logs: !!System.get_env("DEBUG_LOGS") + debug_logs: !!System.get_env("DEBUG_INDEXER") # General application configuration config :explorer, From 31eb7ead8972eb3798515b866d2803e8eded6eec Mon Sep 17 00:00:00 2001 From: Chris McCord Date: Fri, 11 May 2018 12:36:50 -0400 Subject: [PATCH 62/77] Use monitored, supervised task for improved isolation --- .../lib/explorer/indexer/block_fetcher.ex | 131 +++++++++--------- .../explorer/indexer/block_fetcher_test.exs | 2 + 2 files changed, 67 insertions(+), 66 deletions(-) diff --git a/apps/explorer/lib/explorer/indexer/block_fetcher.ex b/apps/explorer/lib/explorer/indexer/block_fetcher.ex index 7f58053c40..4a9bf92ea9 100644 --- a/apps/explorer/lib/explorer/indexer/block_fetcher.ex +++ b/apps/explorer/lib/explorer/indexer/block_fetcher.ex @@ -9,14 +9,15 @@ defmodule Explorer.Indexer.BlockFetcher do alias Explorer.{Chain, Indexer, JSONRPC} - alias Explorer.Indexer.{AddressFetcher, BlockFetcher, Sequence} + alias Explorer.Indexer.{AddressFetcher, Sequence} alias Explorer.JSONRPC.Transactions # dialyzer thinks that Logger.debug functions always have no_local_return @dialyzer {:nowarn_function, import_range: 3} - # These are all the *default* values for options. DO NOT use them directly in the code. Get options from `state`. + # These are all the *default* values for options. + # DO NOT use them directly in the code. Get options from `state`. @debug_logs false @@ -28,27 +29,10 @@ defmodule Explorer.Indexer.BlockFetcher do # milliseconds @block_rate 5_000 - @realtime_interval_per_block_rate 2 @receipts_batch_size 250 @receipts_concurrency 20 - defstruct debug_logs: @debug_logs, - blocks_batch_size: @blocks_batch_size, - blocks_concurrency: @blocks_concurrency, - genesis_task: nil, - internal_transactions_batch_size: @internal_transactions_batch_size, - internal_transactions_concurrency: @internal_transactions_concurrency, - poll_timer: nil, - realtime_interval: @block_rate * @realtime_interval_per_block_rate, - realtime_task: nil, - receipts_batch_size: @receipts_batch_size, - receipts_concurrency: @receipts_concurrency - - # This needs to match the documented options below for `start_link/1` - @allowed_option_names ~w(debug_logs blocks_batch_size blocks_concurrency block_rate internal_transactions_batch_size - internal_transactions_concurrrency receipts_batch_size receipts_concurrency)a - @doc """ Starts the server. @@ -90,57 +74,61 @@ defmodule Explorer.Indexer.BlockFetcher do @impl GenServer def init(opts) do - fields = - :explorer - |> Application.fetch_env!(:indexer) - |> Keyword.merge(opts) - |> Keyword.take(@allowed_option_names) - |> put_block_rate() - - state = struct!(%BlockFetcher{}, fields) - - send(self(), :catchup_index) + opts = Keyword.merge(Application.fetch_env!(:explorer, :indexer), opts) :timer.send_interval(15_000, self(), :debug_count) - {:ok, state} - end - - defp put_block_rate(allowed_options) do - if Keyword.has_key?(allowed_options, :block_rate) do - {block_rate, passthrough_fields} = Keyword.pop(allowed_options, :block_rate) - Keyword.put(passthrough_fields, :realtime_interval, block_rate * @realtime_interval_per_block_rate) - else - allowed_options - end + state = %{ + genesis_task: nil, + realtime_task: nil, + debug_logs: Keyword.get(opts, :debug_logs, @debug_logs), + realtime_interval: (opts[:block_rate] || @block_rate) * 2, + blocks_batch_size: Keyword.get(opts, :blocks_batch_size, @blocks_batch_size), + blocks_concurrency: Keyword.get(opts, :blocks_concurrency, @blocks_concurrency), + internal_transactions_batch_size: + Keyword.get(opts, :internal_transactions_batch_size, @internal_transactions_batch_size), + internal_transactions_concurrency: + Keyword.get(opts, :internal_transactions_concurrency, @internal_transactions_concurrency), + receipts_batch_size: Keyword.get(opts, :receipts_batch_size, @receipts_batch_size), + receipts_concurrency: Keyword.get(opts, :receipts_concurrency, @receipts_concurrency) + } + + {:ok, schedule_next_catchup_index(state)} end @impl GenServer - def handle_info(:catchup_index, %BlockFetcher{} = state) do - {:ok, genesis_task} = Task.start_link(fn -> genesis_task(state) end) - - Process.monitor(genesis_task) + def handle_info(:catchup_index, %{} = state) do + {:ok, genesis_task, _ref} = monitor_task(fn -> genesis_task(state) end) - {:noreply, %BlockFetcher{state | genesis_task: genesis_task}} + {:noreply, %{state | genesis_task: genesis_task}} end - def handle_info(:realtime_index, %BlockFetcher{} = state) do - {:ok, realtime_task} = Task.start_link(fn -> realtime_task(state) end) + def handle_info(:realtime_index, %{} = state) do + {:ok, realtime_task, _ref} = monitor_task(fn -> realtime_task(state) end) - Process.monitor(realtime_task) + {:noreply, %{state | realtime_task: realtime_task}} + end - {:noreply, %BlockFetcher{state | realtime_task: realtime_task}} + def handle_info({:DOWN, _ref, :process, pid, :normal}, %{realtime_task: pid} = state) do + {:noreply, schedule_next_realtime_fetch(%{state | realtime_task: nil})} end - def handle_info({:DOWN, _ref, :process, pid, :normal}, %BlockFetcher{realtime_task: pid} = state) do - {:noreply, schedule_next_realtime_fetch(%BlockFetcher{state | realtime_task: nil})} + def handle_info({:DOWN, _ref, :process, pid, _reason}, %{realtime_task: pid} = state) do + Logger.error(fn -> "realtime index stream exited. Restarting" end) + {:noreply, schedule_next_realtime_fetch(%{state | realtime_task: nil})} end - def handle_info({:DOWN, _ref, :process, pid, :normal}, %BlockFetcher{genesis_task: pid} = state) do + def handle_info({:DOWN, _ref, :process, pid, :normal}, %{genesis_task: pid} = state) do Logger.info(fn -> "Finished index from genesis. Transitioning to realtime index." end) - {:noreply, schedule_next_realtime_fetch(%BlockFetcher{state | genesis_task: nil})} + {:noreply, schedule_next_realtime_fetch(%{state | genesis_task: nil})} end - def handle_info(:debug_count, %BlockFetcher{} = state) do + def handle_info({:DOWN, _ref, :process, pid, _reason}, %{genesis_task: pid} = state) do + Logger.error(fn -> "gensis index stream exited. Restarting" end) + + {:noreply, schedule_next_catchup_index(%{state | genesis_task: nil})} + end + + def handle_info(:debug_count, %{} = state) do debug(state, fn -> """ @@ -162,14 +150,14 @@ defmodule Explorer.Indexer.BlockFetcher do :ok = Sequence.cap(seq) end - defp cap_seq(_seq, :more, {block_start, block_end}, %BlockFetcher{} = state) do + defp cap_seq(_seq, :more, {block_start, block_end}, %{} = state) do debug(state, fn -> "got blocks #{block_start} - #{block_end}" end) :ok end defp fetch_internal_transactions(_state, []), do: {:ok, []} - defp fetch_internal_transactions(%BlockFetcher{} = state, hashes) do + defp fetch_internal_transactions(%{} = state, hashes) do debug(state, fn -> "fetching internal transactions for #{length(hashes)} transactions" end) stream_opts = [max_concurrency: state.internal_transactions_concurrency, timeout: :infinity] @@ -185,7 +173,7 @@ defmodule Explorer.Indexer.BlockFetcher do defp fetch_transaction_receipts(_state, []), do: {:ok, %{logs: [], receipts: []}} - defp fetch_transaction_receipts(%BlockFetcher{} = state, hashes) do + defp fetch_transaction_receipts(%{} = state, hashes) do debug(state, fn -> "fetching #{length(hashes)} transaction receipts" end) stream_opts = [max_concurrency: state.receipts_concurrency, timeout: :infinity] @@ -204,7 +192,7 @@ defmodule Explorer.Indexer.BlockFetcher do end) end - defp genesis_task(%BlockFetcher{} = state) do + defp genesis_task(%{} = state) do {count, missing_ranges} = missing_block_numbers(state) current_block = Indexer.next_block_number() @@ -214,7 +202,7 @@ defmodule Explorer.Indexer.BlockFetcher do stream_import(state, seq, max_concurrency: state.blocks_concurrency) end - defp insert(%BlockFetcher{} = state, seq, range, params) do + defp insert(%{} = state, seq, range, params) do with {:ok, %{addresses: address_hashes}} = ok <- Chain.import_blocks(params) do :ok = AddressFetcher.async_fetch_balances(address_hashes) ok @@ -230,7 +218,7 @@ defmodule Explorer.Indexer.BlockFetcher do end end - defp missing_block_numbers(%BlockFetcher{blocks_batch_size: blocks_batch_size}) do + defp missing_block_numbers(%{blocks_batch_size: blocks_batch_size}) do {count, missing_ranges} = Chain.missing_block_numbers() chunked_ranges = @@ -254,7 +242,7 @@ defmodule Explorer.Indexer.BlockFetcher do {count, chunked_ranges} end - defp realtime_task(%BlockFetcher{} = state) do + defp realtime_task(%{} = state) do {:ok, seq} = Sequence.start_link([], Indexer.next_block_number(), 2) stream_import(state, seq, max_concurrency: 1) end @@ -269,7 +257,7 @@ defmodule Explorer.Indexer.BlockFetcher do # Run at state.blocks_concurrency max_concurrency when called by `stream_import/3` # Only public for testing @doc false - def import_range({block_start, block_end} = range, %BlockFetcher{} = state, seq) do + def import_range({block_start, block_end} = range, %{} = state, seq) do with {:blocks, {:ok, next, result}} <- {:blocks, JSONRPC.fetch_blocks_by_range(block_start, block_end)}, %{blocks: blocks, transactions: transactions} = result, cap_seq(seq, next, range, state), @@ -297,11 +285,22 @@ defmodule Explorer.Indexer.BlockFetcher do end end - defp schedule_next_realtime_fetch(%BlockFetcher{} = state) do - timer = Process.send_after(self(), :realtime_index, state.realtime_interval) - %BlockFetcher{state | poll_timer: timer} + defp schedule_next_catchup_index(state) do + send(self(), :catchup_index) + state + end + + defp schedule_next_realtime_fetch(state) do + Process.send_after(self(), :realtime_index, state.realtime_interval) + state + end + + defp monitor_task(task_func) do + {:ok, pid} = Task.Supervisor.start_child(Indexer.TaskSupervisor, task_func) + ref = Process.monitor(pid) + {:ok, pid, ref} end - defp debug(%BlockFetcher{debug_logs: true}, func), do: Logger.debug(func) - defp debug(%BlockFetcher{debug_logs: false}, _func), do: :noop + defp debug(%{debug_logs: true}, func), do: Logger.debug(func) + defp debug(%{debug_logs: false}, _func), do: :noop end diff --git a/apps/explorer/test/explorer/indexer/block_fetcher_test.exs b/apps/explorer/test/explorer/indexer/block_fetcher_test.exs index 21187c7c57..04c140e6c0 100644 --- a/apps/explorer/test/explorer/indexer/block_fetcher_test.exs +++ b/apps/explorer/test/explorer/indexer/block_fetcher_test.exs @@ -32,6 +32,8 @@ defmodule Explorer.Indexer.BlockFetcherTest do test "starts fetching blocks from Genesis" do assert Repo.aggregate(Block, :count, :hash) == 0 + start_supervised!({JSONRPC, []}) + start_supervised!({Task.Supervisor, name: Explorer.Indexer.TaskSupervisor}) start_supervised!(BlockFetcher) wait(fn -> From 0f3cd3f6a043fcd7c1a00fb84ca0d6b51a12f29c Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Mon, 14 May 2018 09:42:51 -0500 Subject: [PATCH 63/77] Disable credo rules handled by mix format Ports https://github.com/rrrene/credo/issues/505. --- .credo.exs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/.credo.exs b/.credo.exs index ed2e6ecbf1..e7430e9fc7 100644 --- a/.credo.exs +++ b/.credo.exs @@ -26,8 +26,6 @@ ~r"/_build/", ~r"/deps/", ~r"/node_modules/", - # `There are spaces around operators most of the time, but not here` disagrees with `mix format` - ~r"/apps/explorer/lib/explorer/chain/hash.ex", ~r"/apps/explorer_web/lib/explorer_web.ex" ] }, @@ -55,12 +53,23 @@ # {Credo.Check.Design.DuplicatedCode, false} # checks: [ + # outdated by formatter in Elixir 1.6. See https://github.com/rrrene/credo/issues/505 + {Credo.Check.Consistency.LineEndings, false}, + {Credo.Check.Consistency.SpaceAroundOperators, false}, + {Credo.Check.Consistency.SpaceInParentheses, false}, + {Credo.Check.Consistency.TabsOrSpaces, false}, + {Credo.Check.Readability.LargeNumbers, false}, + {Credo.Check.Readability.MaxLineLength, false}, + {Credo.Check.Readability.ParenthesesInCondition, false}, + {Credo.Check.Readability.RedundantBlankLines, false}, + {Credo.Check.Readability.Semicolons, false}, + {Credo.Check.Readability.SpaceAfterCommas, false}, + {Credo.Check.Readability.TrailingBlankLine, false}, + {Credo.Check.Readability.TrailingWhiteSpace, false}, + + # not handled by formatter {Credo.Check.Consistency.ExceptionNames}, - {Credo.Check.Consistency.LineEndings}, {Credo.Check.Consistency.ParameterPatternMatching}, - {Credo.Check.Consistency.SpaceAroundOperators}, - {Credo.Check.Consistency.SpaceInParentheses}, - {Credo.Check.Consistency.TabsOrSpaces}, # You can customize the priority of any check # Priority values are: `low, normal, high, higher` @@ -83,23 +92,14 @@ {Credo.Check.Design.TagTODO, exit_status: 0}, {Credo.Check.Design.TagFIXME}, {Credo.Check.Readability.FunctionNames}, - {Credo.Check.Readability.LargeNumbers}, - # mix format handles line length and using both hits some `mix format` bugs - {Credo.Check.Readability.MaxLineLength, false}, {Credo.Check.Readability.ModuleAttributeNames}, {Credo.Check.Readability.ModuleDoc}, {Credo.Check.Readability.ModuleNames}, {Credo.Check.Readability.ParenthesesOnZeroArityDefs}, - {Credo.Check.Readability.ParenthesesInCondition}, {Credo.Check.Readability.PredicateFunctionNames}, {Credo.Check.Readability.PreferImplicitTry}, - {Credo.Check.Readability.RedundantBlankLines}, {Credo.Check.Readability.StringSigils}, - {Credo.Check.Readability.TrailingBlankLine}, - {Credo.Check.Readability.TrailingWhiteSpace}, {Credo.Check.Readability.VariableNames}, - {Credo.Check.Readability.Semicolons}, - {Credo.Check.Readability.SpaceAfterCommas}, {Credo.Check.Refactor.DoubleBooleanNegation}, {Credo.Check.Refactor.CondStatements}, {Credo.Check.Refactor.CyclomaticComplexity}, From 053c317af14f617d0d8a1fa0092abf8c286046d8 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Mon, 14 May 2018 09:58:53 -0500 Subject: [PATCH 64/77] Test against binary, not iodata in doctests --- apps/explorer/lib/explorer/chain.ex | 71 +++++------------------- apps/explorer/lib/explorer/chain/hash.ex | 71 +++++------------------- 2 files changed, 30 insertions(+), 112 deletions(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 727bf47f80..01b1e7a287 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -368,65 +368,24 @@ defmodule Explorer.Chain do @doc """ Converts the `t:t/0` to string representation shown to users. - iex> Explorer.Chain.hash_to_iodata( - ...> %Explorer.Chain.Hash{ - ...> byte_count: 32, - ...> bytes: <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: - ...> big-integer-size(32)-unit(8)>> - ...> } - ...> ) - [ - "0x", - ['9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b'] - ] + iex> %Explorer.Chain.Hash{ + ...> byte_count: 32, + ...> bytes: <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: + ...> big-integer-size(32)-unit(8)>> + ...> } |> + ...> Explorer.Chain.hash_to_iodata() |> + ...> IO.iodata_to_binary() + "0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b" Always pads number, so that it is a valid format for casting. - iex> Explorer.Chain.hash_to_iodata( - ...> %Explorer.Chain.Hash{ - ...> byte_count: 32, - ...> bytes: <<0x1234567890abcdef :: big-integer-size(32)-unit(8)>> - ...> } - ...> ) - [ - "0x", - [ - [ - [ - [ - [['000', 48, 48, 48], '000', 48, 48, 48], - ['000', 48, 48, 48], - '000', - 48, - 48, - 48 - ], - [['000', 48, 48, 48], '000', 48, 48, 48], - ['000', 48, 48, 48], - '000', - 48, - 48, - 48 - ], - 49, - 50, - 51, - 52, - 53, - 54, - 55, - 56, - 57, - 48, - 97, - 98, - 99, - 100, - 101, - 102 - ] - ] - ] + iex> %Explorer.Chain.Hash{ + ...> byte_count: 32, + ...> bytes: <<0x1234567890abcdef :: big-integer-size(32)-unit(8)>> + ...> } |> + ...> Explorer.Chain.hash_to_iodata() |> + ...> IO.iodata_to_binary() + "0x0000000000000000000000000000000000000000000000001234567890abcdef" """ @spec hash_to_iodata(Hash.t()) :: iodata() diff --git a/apps/explorer/lib/explorer/chain/hash.ex b/apps/explorer/lib/explorer/chain/hash.ex index 78b6660095..7ff5364135 100644 --- a/apps/explorer/lib/explorer/chain/hash.ex +++ b/apps/explorer/lib/explorer/chain/hash.ex @@ -125,65 +125,24 @@ defmodule Explorer.Chain.Hash do @doc """ Converts the `t:t/0` to string representation shown to users. - iex> Explorer.Chain.Hash.to_iodata( - ...> %Explorer.Chain.Hash{ - ...> byte_count: 32, - ...> bytes: <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: - ...> big-integer-size(32)-unit(8)>> - ...> } - ...> ) - [ - "0x", - ['9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b'] - ] + iex> %Explorer.Chain.Hash{ + ...> byte_count: 32, + ...> bytes: <<0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b :: + ...> big-integer-size(32)-unit(8)>> + ...> } |> + ...> Explorer.Chain.Hash.to_iodata() |> + ...> IO.iodata_to_binary() + "0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b" Always pads number, so that it is a valid format for casting. - iex> Explorer.Chain.Hash.to_iodata( - ...> %Explorer.Chain.Hash{ - ...> byte_count: 32, - ...> bytes: <<0x1234567890abcdef :: big-integer-size(32)-unit(8)>> - ...> } - ...> ) - [ - "0x", - [ - [ - [ - [ - [['000', 48, 48, 48], '000', 48, 48, 48], - ['000', 48, 48, 48], - '000', - 48, - 48, - 48 - ], - [['000', 48, 48, 48], '000', 48, 48, 48], - ['000', 48, 48, 48], - '000', - 48, - 48, - 48 - ], - 49, - 50, - 51, - 52, - 53, - 54, - 55, - 56, - 57, - 48, - 97, - 98, - 99, - 100, - 101, - 102 - ] - ] - ] + iex> %Explorer.Chain.Hash{ + ...> byte_count: 32, + ...> bytes: <<0x1234567890abcdef :: big-integer-size(32)-unit(8)>> + ...> } |> + ...> Explorer.Chain.Hash.to_iodata() |> + ...> IO.iodata_to_binary() + "0x0000000000000000000000000000000000000000000000001234567890abcdef" """ @spec to_iodata(t) :: iodata() From 840c64abca05941e8df3e6390a08062dd77ecf84 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Mon, 14 May 2018 10:11:17 -0500 Subject: [PATCH 65/77] Short summart for Explorer.Chain.Receipt.Status moduledoc. --- apps/explorer/lib/explorer/chain/receipt/status.ex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/explorer/lib/explorer/chain/receipt/status.ex b/apps/explorer/lib/explorer/chain/receipt/status.ex index bb78880b93..a80fbf0685 100644 --- a/apps/explorer/lib/explorer/chain/receipt/status.ex +++ b/apps/explorer/lib/explorer/chain/receipt/status.ex @@ -1,5 +1,7 @@ defmodule Explorer.Chain.Receipt.Status do @moduledoc """ + Whether a transaction succeeded (`:ok`) or failed (`:error`). + Post-[Byzantium](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-609.md) status is `0x1` for success and `0x0` for failure, but instead of keeping track of just an integer and having to remember if its like C boolean (`0` for `false`, `1` for `true`) or a Posix exit code, let's represent it as native elixir - `:ok` for success and `:error` From 8e09990248bae326a6cb60226259bb79f1845823 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Mon, 14 May 2018 10:26:30 -0500 Subject: [PATCH 66/77] Indent Markdown lists 1 space for stand-alone lists 2 spaces only works with other prefixed text. --- apps/explorer/lib/explorer/chain.ex | 110 +++++++++--------- apps/explorer/lib/explorer/chain/address.ex | 14 +-- apps/explorer/lib/explorer/chain/block.ex | 32 ++--- apps/explorer/lib/explorer/chain/credit.ex | 8 +- apps/explorer/lib/explorer/chain/debit.ex | 8 +- .../explorer/chain/internal_transaction.ex | 32 ++--- .../chain/internal_transaction/call_type.ex | 12 +- .../chain/internal_transaction/type.ex | 10 +- apps/explorer/lib/explorer/chain/log.ex | 27 ++--- apps/explorer/lib/explorer/chain/receipt.ex | 16 +-- .../lib/explorer/chain/receipt/status.ex | 4 +- .../explorer/lib/explorer/chain/statistics.ex | 25 ++-- .../lib/explorer/chain/transaction.ex | 43 +++---- apps/explorer/lib/explorer/chain/wei.ex | 2 +- .../lib/explorer/exchange_rates/token.ex | 18 +-- apps/explorer/lib/explorer/jsonrpc.ex | 10 +- apps/explorer/lib/explorer/jsonrpc/block.ex | 60 +++++----- apps/explorer/lib/explorer/jsonrpc/log.ex | 16 +-- apps/explorer/lib/explorer/jsonrpc/receipt.ex | 27 ++--- .../lib/explorer/jsonrpc/transaction.ex | 45 +++---- .../lib/explorer/market/market_history.ex | 6 +- .../explorer_web/views/transaction_view.ex | 2 +- .../lib/explorer_web/views/wei_helpers.ex | 6 +- 23 files changed, 269 insertions(+), 264 deletions(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 01b1e7a287..7d7a3c776e 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -17,9 +17,9 @@ defmodule Explorer.Chain do @type direction :: :from | :to @typedoc """ - * `:optional` - the association is optional and only needs to be loaded if available - * `:required` - the association is required and MUST be loaded. If it is not available, then the parent struct - SHOULD NOT be returned. + * `:optional` - the association is optional and only needs to be loaded if available + * `:required` - the association is required and MUST be loaded. If it is not available, then the parent struct + SHOULD NOT be returned. """ @type necessity :: :optional | :required @@ -50,14 +50,14 @@ defmodule Explorer.Chain do ## Options - * `:direction` - if specified, will filter internal transactions by address type. If `:to` is specified, only internal - transactions where the "to" address matches will be returned. Likewise, if `:from` is specified, only internal - transactions where the "from" address matches will be returned. If :direction is omitted, internal transactions either - to or from the address will be returned. - * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is - `:required`, and the `t:Explorer.Chain.InternalTransaction.t/0` has no associated record for that association, then - the `t:Explorer.Chain.InternalTransaction.t/0` will not be included in the page `entries`. - * `:pagination` - pagination params to pass to scrivener. + * `:direction` - if specified, will filter internal transactions by address type. If `:to` is specified, only + internal transactions where the "to" address matches will be returned. Likewise, if `:from` is specified, only + internal transactions where the "from" address matches will be returned. If :direction is omitted, internal + transactions either to or from the address will be returned. + * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is + `:required`, and the `t:Explorer.Chain.InternalTransaction.t/0` has no associated record for that association, + then the `t:Explorer.Chain.InternalTransaction.t/0` will not be included in the page `entries`. + * `:pagination` - pagination params to pass to scrivener. """ def address_to_internal_transactions(%Address{hash: hash}, options \\ []) do @@ -81,14 +81,14 @@ defmodule Explorer.Chain do ## Options - * `:direction` - if specified, will filter transactions by address type. If `:to` is specified, only transactions + * `:direction` - if specified, will filter transactions by address type. If `:to` is specified, only transactions where the "to" address matches will be returned. Likewise, if `:from` is specified, only transactions where the "from" address matches will be returned. If :direction is omitted, transactions either to or from the address will be returned. - * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is + * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is `:required`, and the `t:Explorer.Chain.Transaction.t/0` has no associated record for that association, then the `t:Explorer.Chain.Transaction.t/0` will not be included in the page `entries`. - * `:pagination` - pagination params to pass to scrivener. + * `:pagination` - pagination params to pass to scrivener. """ @spec address_to_transactions(Address.t(), [ @@ -192,10 +192,10 @@ defmodule Explorer.Chain do ## Options - * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is + * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is `:required`, and the `t:Explorer.Chain.Transaction.t/0` has no associated record for that association, then the `t:Explorer.Chain.Transaction.t/0` will not be included in the page `entries`. - * `:pagination` - pagination params to pass to scrivener. + * `:pagination` - pagination params to pass to scrivener. """ @spec block_to_transactions(Block.t()) :: %Scrivener.Page{entries: [Transaction.t()]} @spec block_to_transactions(Block.t(), [necessity_by_association_option | pagination_option]) :: %Scrivener.Page{ @@ -413,7 +413,7 @@ defmodule Explorer.Chain do ## Options - * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is + * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is `:required`, and the `t:Explorer.Chain.Transaction.t/0` has no associated record for that association, then the `t:Explorer.Chain.Transaction.t/0` will not be included in the page `entries`. """ @@ -704,27 +704,27 @@ defmodule Explorer.Chain do ## Tree - * `t:Explorer.Chain.Block.t/0`s - * `t:Explorer.Chain.Transaction.t/0` - * `t.Explorer.Chain.InternalTransaction.t/0` - * `t.Explorer.Chain.Receipt.t/0` - * `t.Explorer.Chain.Log.t/0` + * `t:Explorer.Chain.Block.t/0`s + * `t:Explorer.Chain.Transaction.t/0` + * `t.Explorer.Chain.InternalTransaction.t/0` + * `t.Explorer.Chain.Receipt.t/0` + * `t.Explorer.Chain.Log.t/0` ## Options - * `:timeout` - the timeout for the whole `c:Ecto.Repo.transaction/0` call. Defaults to `#{@transaction_timeout}` - milliseconds. - * `:insert_addresses_timeout` - the timeout for inserting all addresses found in the params lists across all types. - Defaults to `#{@insert_addresses_timeout}` milliseconds. - * `:insert_blocks_timeout` - the timeout for inserting all blocks. Defaults to `#{@insert_blocks_timeout}` - milliseconds. - * `:insert_internal_transactions_timeout` - the timeout for inserting all internal transactions. Defaults to - `#{@insert_internal_transactions_timeout}` milliseconds. - * `:insert_logs_timeout` - the timeout for inserting all logs. Defaults to `#{@insert_logs_timeout}` milliseconds. - * `:insert_receipts_timeout` - the timeout for inserting all receipts. Defaults to `#{@insert_receipts_timeout}` - milliseconds. - * `:insert_transactions_timeout` - the timeout for inserting all transactions found in the params lists across all types. - Defaults to `#{@insert_transactions_timeout}` milliseconds. + * `:timeout` - the timeout for the whole `c:Ecto.Repo.transaction/0` call. Defaults to `#{@transaction_timeout}` + milliseconds. + * `:insert_addresses_timeout` - the timeout for inserting all addresses found in the params lists across all types. + Defaults to `#{@insert_addresses_timeout}` milliseconds. + * `:insert_blocks_timeout` - the timeout for inserting all blocks. Defaults to `#{@insert_blocks_timeout}` + milliseconds. + * `:insert_internal_transactions_timeout` - the timeout for inserting all internal transactions. Defaults to + `#{@insert_internal_transactions_timeout}` milliseconds. + * `:insert_logs_timeout` - the timeout for inserting all logs. Defaults to `#{@insert_logs_timeout}` milliseconds. + * `:insert_receipts_timeout` - the timeout for inserting all receipts. Defaults to `#{@insert_receipts_timeout}` + milliseconds. + * `:insert_transactions_timeout` - the timeout for inserting all transactions found in the params lists across all + types. Defaults to `#{@insert_transactions_timeout}` milliseconds. """ def import_blocks( %{ @@ -814,10 +814,10 @@ defmodule Explorer.Chain do ## Options - * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is - `:required`, and the `t:Explorer.Chain.Block.t/0` has no associated record for that association, then the - `t:Explorer.Chain.Block.t/0` will not be included in the page `entries`. - * `:pagination` - pagination params to pass to scrivener. + * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is + `:required`, and the `t:Explorer.Chain.Block.t/0` has no associated record for that association, then the + `t:Explorer.Chain.Block.t/0` will not be included in the page `entries`. + * `:pagination` - pagination params to pass to scrivener. """ @spec list_blocks([necessity_by_association_option | pagination_option]) :: %Scrivener.Page{ @@ -968,7 +968,7 @@ defmodule Explorer.Chain do ## Options - * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is + * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is `:required`, and the `t:Explorer.Chain.Block.t/0` has no associated record for that association, then the `t:Explorer.Chain.Block.t/0` will not be included in the page `entries`. @@ -1061,7 +1061,7 @@ defmodule Explorer.Chain do ## Options - * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is + * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is `:required`, and the `t:Explorer.Chain.InternalTransaction.t/0` has no associated record for that association, then the `t:Explorer.Chain.InternalTransaction.t/0` will not be included in the list. @@ -1131,10 +1131,10 @@ defmodule Explorer.Chain do ## Options - * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is + * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is `:required`, and the `t:Explorer.Chain.InternalTransaction.t/0` has no associated record for that association, then the `t:Explorer.Chain.InternalTransaction.t/0` will not be included in the list. - * `:pagination` - pagination params to pass to scrivener. + * `:pagination` - pagination params to pass to scrivener. """ @spec recent_pending_transactions([inserted_after_option | necessity_by_association_option]) :: %Scrivener.Page{ @@ -1264,10 +1264,10 @@ defmodule Explorer.Chain do ## Options - * `:pending` - * `nil` - count all transactions - * `true` - only count pending transactions - * `false` - only count collated transactions + * `:pending` + * `nil` - count all transactions + * `true` - only count pending transactions + * `false` - only count collated transactions """ @spec transaction_count([{:pending, boolean()}]) :: non_neg_integer() @@ -1282,10 +1282,10 @@ defmodule Explorer.Chain do ## Options - * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is + * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is `:required`, and the `t:Explorer.Chain.InternalTransaction.t/0` has no associated record for that association, then the `t:Explorer.Chain.InternalTransaction.t/0` will not be included in the list. - * `:pagination` - pagination params to pass to scrivener. + * `:pagination` - pagination params to pass to scrivener. """ @spec transaction_hash_to_internal_transactions(Hash.Full.t()) :: %Scrivener.Page{entries: [InternalTransaction.t()]} @@ -1312,10 +1312,10 @@ defmodule Explorer.Chain do ## Options - * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is + * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is `:required`, and the `t:Explorer.Chain.Log.t/0` has no associated record for that association, then the `t:Explorer.Chain.Log.t/0` will not be included in the page `entries`. - * `:pagination` - pagination params to pass to scrivener. + * `:pagination` - pagination params to pass to scrivener. """ @spec transaction_to_logs(Transaction.t(), [ @@ -1330,10 +1330,10 @@ defmodule Explorer.Chain do ## Returns - * `:failed` - the transaction failed without running out of gas - * `:pending` - the transaction has not be confirmed in a block yet - * `:out_of_gas` - the transaction failed because it ran out of gas - * `:success` - the transaction has been confirmed in a block + * `:failed` - the transaction failed without running out of gas + * `:pending` - the transaction has not be confirmed in a block yet + * `:out_of_gas` - the transaction failed because it ran out of gas + * `:success` - the transaction has been confirmed in a block """ @spec transaction_to_status(Transaction.t()) :: :failed | :pending | :out_of_gas | :success diff --git a/apps/explorer/lib/explorer/chain/address.ex b/apps/explorer/lib/explorer/chain/address.ex index c92bc0ca28..12fc8dbdd9 100644 --- a/apps/explorer/lib/explorer/chain/address.ex +++ b/apps/explorer/lib/explorer/chain/address.ex @@ -16,13 +16,13 @@ defmodule Explorer.Chain.Address do @type hash :: Hash.t() @typedoc """ - * `fetched_balance` - The last fetched balance from Parity - * `balance_fetched_at` - the last time `balance` was fetched - * `credit` - accumulation of all credits to the address `hash` - * `debit` - accumulation of all debits to the address `hash` - * `hash` - the hash of the address's public key - * `inserted_at` - when this address was inserted - * `updated_at` when this address was last updated + * `fetched_balance` - The last fetched balance from Parity + * `balance_fetched_at` - the last time `balance` was fetched + * `credit` - accumulation of all credits to the address `hash` + * `debit` - accumulation of all debits to the address `hash` + * `hash` - the hash of the address's public key + * `inserted_at` - when this address was inserted + * `updated_at` when this address was last updated """ @type t :: %__MODULE__{ fetched_balance: Wei.t(), diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index 2e0baf14f7..3e153525d0 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -25,22 +25,22 @@ defmodule Explorer.Chain.Block do @type block_number :: non_neg_integer() @typedoc """ - * `difficulty` - how hard the block was to mine. - * `gas_limit` - If the total number of gas used by the computation spawned by the transaction, including the original - message and any sub-messages that may be triggered, is less than or equal to the gas limit, then the transaction - processes. If the total gas exceeds the gas limit, then all changes are reverted, except that the transaction is - still valid and the fee can still be collected by the miner. - * `gas_used` - The actual `t:gas/0` used to mine/validate the transactions in the block. - * `hash` - the hash of the block. - * `miner` - the hash of the `t:Explorer.Chain.Address.t/0` of the miner. In Proof-of-Authority chains, this is the - validator. - * `nonce` - the hash of the generated proof-of-work. Not used in Proof-of-Authority chains. - * `number` - which block this is along the chain. - * `parent_hash` - the hash of the parent block, which should have the previous `number` - * `size` - The size of the block in bytes. - * `timestamp` - When the block was collated - * `total_diffficulty` - the total `difficulty` of the chain until this block. - * `transactions` - the `t:Explorer.Chain.Transaction.t/0` in this block. + * `difficulty` - how hard the block was to mine. + * `gas_limit` - If the total number of gas used by the computation spawned by the transaction, including the + original message and any sub-messages that may be triggered, is less than or equal to the gas limit, then the + transaction processes. If the total gas exceeds the gas limit, then all changes are reverted, except that the + transaction is still valid and the fee can still be collected by the miner. + * `gas_used` - The actual `t:gas/0` used to mine/validate the transactions in the block. + * `hash` - the hash of the block. + * `miner` - the hash of the `t:Explorer.Chain.Address.t/0` of the miner. In Proof-of-Authority chains, this is the + validator. + * `nonce` - the hash of the generated proof-of-work. Not used in Proof-of-Authority chains. + * `number` - which block this is along the chain. + * `parent_hash` - the hash of the parent block, which should have the previous `number` + * `size` - The size of the block in bytes. + * `timestamp` - When the block was collated + * `total_diffficulty` - the total `difficulty` of the chain until this block. + * `transactions` - the `t:Explorer.Chain.Transaction.t/0` in this block. """ @type t :: %__MODULE__{ difficulty: difficulty(), diff --git a/apps/explorer/lib/explorer/chain/credit.ex b/apps/explorer/lib/explorer/chain/credit.ex index be0a556be8..f31390d3de 100644 --- a/apps/explorer/lib/explorer/chain/credit.ex +++ b/apps/explorer/lib/explorer/chain/credit.ex @@ -10,10 +10,10 @@ defmodule Explorer.Chain.Credit do alias Explorer.Repo @typedoc """ - * `address` - address that was the `to_address` - * `address_hash` - foreign key for `address` - * `count` - the number of credits to `address` - * `value` - sum of all credit values. + * `address` - address that was the `to_address` + * `address_hash` - foreign key for `address` + * `count` - the number of credits to `address` + * `value` - sum of all credit values. """ @type t :: %__MODULE__{ address: %Ecto.Association.NotLoaded{} | Address.t(), diff --git a/apps/explorer/lib/explorer/chain/debit.ex b/apps/explorer/lib/explorer/chain/debit.ex index 510ce77a0d..f97cf336ef 100644 --- a/apps/explorer/lib/explorer/chain/debit.ex +++ b/apps/explorer/lib/explorer/chain/debit.ex @@ -10,10 +10,10 @@ defmodule Explorer.Chain.Debit do alias Explorer.Repo @typedoc """ - * `address` - address that was the `from_address` - * `address_hash` - foreign key for `address` - * `count` - the number of debits to `address` - * `value` - sum of all debit values. + * `address` - address that was the `from_address` + * `address_hash` - foreign key for `address` + * `count` - the number of debits to `address` + * `value` - sum of all debit values. """ @type t :: %__MODULE__{ address: %Ecto.Association.NotLoaded{} | Address.t(), diff --git a/apps/explorer/lib/explorer/chain/internal_transaction.ex b/apps/explorer/lib/explorer/chain/internal_transaction.ex index acadcbd922..7888dbbbc2 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction.ex @@ -7,22 +7,22 @@ defmodule Explorer.Chain.InternalTransaction do alias Explorer.Chain.InternalTransaction.{CallType, Type} @typedoc """ - * `call_type` - the type of call. `nil` when `type` is not `:call`. - * `error` - error message when `:call` `type` errors - * `from_address` - the source of the `value` - * `from_address_hash` - hash of the source of the `value` - * `gas` - the amount of gas allowed - * `gas_used` - the amount of gas used. `nil` when a call errors. - * `index` - the index of this internal transaction inside the `transaction` - * `input` - input bytes to the call - * `output` - output bytes from the call. `nil` when a call errors. - * `to_address` - the sink of the `value` - * `to_address_hash` - hash of the sink of the `value` - * `trace_address` - list of traces - * `transaction` - transaction in which this transaction occured - * `transaction_id` - foreign key for `transaction` - * `type` - type of internal transaction - * `value` - value of transfered from `from_address` to `to_address` + * `call_type` - the type of call. `nil` when `type` is not `:call`. + * `error` - error message when `:call` `type` errors + * `from_address` - the source of the `value` + * `from_address_hash` - hash of the source of the `value` + * `gas` - the amount of gas allowed + * `gas_used` - the amount of gas used. `nil` when a call errors. + * `index` - the index of this internal transaction inside the `transaction` + * `input` - input bytes to the call + * `output` - output bytes from the call. `nil` when a call errors. + * `to_address` - the sink of the `value` + * `to_address_hash` - hash of the sink of the `value` + * `trace_address` - list of traces + * `transaction` - transaction in which this transaction occured + * `transaction_id` - foreign key for `transaction` + * `type` - type of internal transaction + * `value` - value of transfered from `from_address` to `to_address` """ @type t :: %__MODULE__{ call_type: CallType.t() | nil, diff --git a/apps/explorer/lib/explorer/chain/internal_transaction/call_type.ex b/apps/explorer/lib/explorer/chain/internal_transaction/call_type.ex index 1ccf269fcc..8d354553ee 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction/call_type.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction/call_type.ex @@ -6,12 +6,12 @@ defmodule Explorer.Chain.InternalTransaction.CallType do @behaviour Ecto.Type @typedoc """ - * `:call` - call a function in a contract by jumping into the contract's context - * `:callcode` - * `:delegatecall` - Instead of jumping into the code as with `"call"`, and using the call's contract's context, use - the current contract's context with the delegated contract's code. There's some good chances for finding bugs - when fuzzing these if the memory layout differs between the current contract and the delegated contract. - * `:staticcall` + * `:call` - call a function in a contract by jumping into the contract's context + * `:callcode` + * `:delegatecall` - Instead of jumping into the code as with `"call"`, and using the call's contract's context, use + the current contract's context with the delegated contract's code. There's some good chances for finding bugs + when fuzzing these if the memory layout differs between the current contract and the delegated contract. + * `:staticcall` """ @type t :: :call | :callcode | :delegatecall | :staticcall diff --git a/apps/explorer/lib/explorer/chain/internal_transaction/type.ex b/apps/explorer/lib/explorer/chain/internal_transaction/type.ex index 8f7fe1f7ff..6180e69101 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction/type.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction/type.ex @@ -6,12 +6,12 @@ defmodule Explorer.Chain.InternalTransaction.Type do @behaviour Ecto.Type @typedoc """ - * `:call` - * `:create` - * `:suicide` - * `:reward` + * `:call` + * `:create` + * `:reward` + * `:suicide` """ - @type t :: :call | :create | :suicide | :reward + @type t :: :call | :create | :reward | :suicide @doc """ Casts `term` to `t:t/0` diff --git a/apps/explorer/lib/explorer/chain/log.ex b/apps/explorer/lib/explorer/chain/log.ex index 2751ef7f6c..94f7d7d459 100644 --- a/apps/explorer/lib/explorer/chain/log.ex +++ b/apps/explorer/lib/explorer/chain/log.ex @@ -9,19 +9,20 @@ defmodule Explorer.Chain.Log do @optional_attrs ~w(first_topic second_topic third_topic fourth_topic)a @typedoc """ - * `address` - address of contract that generate the event - * `address_hash` - foreign key for `address` - * `data` - non-indexed log parameters. - * `first_topic` - `topics[0]` - * `fourth_topic` - `topics[3]` - * `index` - index of the log entry in all logs for the `receipt` / `transaction` - * `receipt` - receipt for the `transaction` being mined in a block - * `second_topic` - `topics[1]` - * `transaction` - transaction for which `receipt` is - * `transaction_hash` - foreign key for `receipt`. **ALWAYS join throught `receipts` and not directly to `transaction` - to ensure that any `t:Explorer.Chain.Transaction.t/0` has a receipt before it has logs in that receipt.** - * `third_topic` - `topics[2]` - * `type` - type of event + * `address` - address of contract that generate the event + * `address_hash` - foreign key for `address` + * `data` - non-indexed log parameters. + * `first_topic` - `topics[0]` + * `fourth_topic` - `topics[3]` + * `index` - index of the log entry in all logs for the `receipt` / `transaction` + * `receipt` - receipt for the `transaction` being mined in a block + * `second_topic` - `topics[1]` + * `transaction` - transaction for which `receipt` is + * `transaction_hash` - foreign key for `receipt`. **ALWAYS join through `receipts` and not directly to + `transaction` to ensure that any `t:Explorer.Chain.Transaction.t/0` has a receipt before it has logs in that + receipt.** + * `third_topic` - `topics[2]` + * `type` - type of event """ @type t :: %__MODULE__{ address: %Ecto.Association.NotLoaded{} | Address.t(), diff --git a/apps/explorer/lib/explorer/chain/receipt.ex b/apps/explorer/lib/explorer/chain/receipt.ex index f0ff6f5a08..679b3850b1 100644 --- a/apps/explorer/lib/explorer/chain/receipt.ex +++ b/apps/explorer/lib/explorer/chain/receipt.ex @@ -11,14 +11,14 @@ defmodule Explorer.Chain.Receipt do @allowed_attrs @optional_attrs ++ @required_attrs @typedoc """ - * `cumulative_gas_used` - the cumulative gas used in `transaction`'s `t:Explorer.Chain.Block.t/0` before - `transaction`'s `index` - * `gas_used` - the gas used for just `transaction` - * `logs` - events that occurred while mining the `transaction` - * `status` - whether the transaction was successfully mined or failed - * `transaction` - the transaction for which this receipt is for - * `transaction_hash` - foreign key for `transaction` - * `transaction_index` - index of `transaction` in its `t:Explorer.Chain.Block.t/0`. + * `cumulative_gas_used` - the cumulative gas used in `transaction`'s `t:Explorer.Chain.Block.t/0` before + `transaction`'s `index` + * `gas_used` - the gas used for just `transaction` + * `logs` - events that occurred while mining the `transaction` + * `status` - whether the transaction was successfully mined or failed + * `transaction` - the transaction for which this receipt is for + * `transaction_hash` - foreign key for `transaction` + * `transaction_index` - index of `transaction` in its `t:Explorer.Chain.Block.t/0`. """ @type t :: %__MODULE__{ cumulative_gas_used: Gas.t(), diff --git a/apps/explorer/lib/explorer/chain/receipt/status.ex b/apps/explorer/lib/explorer/chain/receipt/status.ex index a80fbf0685..b8dfc1011d 100644 --- a/apps/explorer/lib/explorer/chain/receipt/status.ex +++ b/apps/explorer/lib/explorer/chain/receipt/status.ex @@ -11,8 +11,8 @@ defmodule Explorer.Chain.Receipt.Status do @behaviour Ecto.Type @typedoc """ - * `:ok` - transaction succeeded - * `:error` - transaction failed + * `:ok` - transaction succeeded + * `:error` - transaction failed """ @type t :: :ok | :error diff --git a/apps/explorer/lib/explorer/chain/statistics.ex b/apps/explorer/lib/explorer/chain/statistics.ex index e5b0f839fc..80bb55fb12 100644 --- a/apps/explorer/lib/explorer/chain/statistics.ex +++ b/apps/explorer/lib/explorer/chain/statistics.ex @@ -67,18 +67,19 @@ defmodule Explorer.Chain.Statistics do @type transactions_per_minute :: non_neg_integer() @typedoc """ - * `average_time` - the average time it took to mine/validate the last <= 100 `t:Explorer.Chain.Block.t/0` - * `block_velocity` - the number of `t:Explorer.Chain.Block.t/0` mined/validated in the last minute - * `blocks` - the last <= 5 `t:Explorer.Chain.Block.t/0` - * `lag` - the average time over the last hour between when the block was mined/validated - (`t:Explorer.Chain.Block.t/0` `timestamp`) and when it was inserted into the databasse - (`t:Explorer.Chain.Block.t/0` `inserted_at`) - * `number` - the latest `t:Explorer.Chain.Block.t/0` `number` - * `skipped_blocks` - the number of blocks that were mined/validated, but do not exist as `t:Explorer.Chain.Block.t/0` - * `timestamp` - when the last `t:Explorer.Chain.Block.t/0` was mined/validated - * `transaction_count` - the number of transactions confirmed in blocks that were mined/validated in the last day - * `transaction_velocity` - the number of `t:Explorer.Chain.Block.t/0` mined/validated in the last minute - * `transactions` - the last <= 5 `t:Explorer.Chain.Transaction.t/0` + * `average_time` - the average time it took to mine/validate the last <= 100 `t:Explorer.Chain.Block.t/0` + * `block_velocity` - the number of `t:Explorer.Chain.Block.t/0` mined/validated in the last minute + * `blocks` - the last <= 5 `t:Explorer.Chain.Block.t/0` + * `lag` - the average time over the last hour between when the block was mined/validated + (`t:Explorer.Chain.Block.t/0` `timestamp`) and when it was inserted into the databasse + (`t:Explorer.Chain.Block.t/0` `inserted_at`) + * `number` - the latest `t:Explorer.Chain.Block.t/0` `number` + * `skipped_blocks` - the number of blocks that were mined/validated, but do not exist as + `t:Explorer.Chain.Block.t/0` + * `timestamp` - when the last `t:Explorer.Chain.Block.t/0` was mined/validated + * `transaction_count` - the number of transactions confirmed in blocks that were mined/validated in the last day + * `transaction_velocity` - the number of `t:Explorer.Chain.Block.t/0` mined/validated in the last minute + * `transactions` - the last <= 5 `t:Explorer.Chain.Transaction.t/0` """ @type t :: %__MODULE__{ average_time: Duration.t(), diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index d4c1850422..3f4bd8e5de 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -71,27 +71,28 @@ defmodule Explorer.Chain.Transaction do @type wei_per_gas :: Wei.t() @typedoc """ - * `block` - the block in which this transaction was mined/validated. `nil` when transaction is pending. - * `block_hash` - `block` foreign key. `nil` when transaction is pending. - * `from_address` - the source of `value` - * `from_address_hash` - foreign key of `from_address` - * `gas` - Gas provided by the sender - * `gas_price` - How much the sender is willing to pay for `gas` - * `hash` - hash of contents of this transaction - * `index` - index of this transaction in `block`. `nil` when transaction is pending. - * `input`- data sent along with the transaction - * `internal_transactions` - transactions (value transfers) created while executing contract used for this transaction - * `nonce` - the number of transaction made by the sender prior to this one - * `public_key` - public key of the signer of the transaction - * `r` - the R field of the signature. The (r, s) is the normal output of an ECDSA signature, where r is computed as - the X coordinate of a point R, modulo the curve order n. - * `s` - The S field of the signature. The (r, s) is the normal output of an ECDSA signature, where r is computed as - the X coordinate of a point R, modulo the curve order n. - * `standard_v` - The standardized V field of the signature - * `to_address` - sink of `value` - * `to_address_hash` - `to_address` foreign key - * `v` - The V field of the signature. - * `value` - wei transferred from `from_address` to `to_address` + * `block` - the block in which this transaction was mined/validated. `nil` when transaction is pending. + * `block_hash` - `block` foreign key. `nil` when transaction is pending. + * `from_address` - the source of `value` + * `from_address_hash` - foreign key of `from_address` + * `gas` - Gas provided by the sender + * `gas_price` - How much the sender is willing to pay for `gas` + * `hash` - hash of contents of this transaction + * `index` - index of this transaction in `block`. `nil` when transaction is pending. + * `input`- data sent along with the transaction + * `internal_transactions` - transactions (value transfers) created while executing contract used for this + transaction + * `nonce` - the number of transaction made by the sender prior to this one + * `public_key` - public key of the signer of the transaction + * `r` - the R field of the signature. The (r, s) is the normal output of an ECDSA signature, where r is computed as + the X coordinate of a point R, modulo the curve order n. + * `s` - The S field of the signature. The (r, s) is the normal output of an ECDSA signature, where r is computed as + the X coordinate of a point R, modulo the curve order n. + * `standard_v` - The standardized V field of the signature + * `to_address` - sink of `value` + * `to_address_hash` - `to_address` foreign key + * `v` - The V field of the signature. + * `value` - wei transferred from `from_address` to `to_address` """ @type t :: %__MODULE__{ block: %Ecto.Association.NotLoaded{} | Block.t() | nil, diff --git a/apps/explorer/lib/explorer/chain/wei.ex b/apps/explorer/lib/explorer/chain/wei.ex index 0249efe72b..b7e27ac10a 100644 --- a/apps/explorer/lib/explorer/chain/wei.ex +++ b/apps/explorer/lib/explorer/chain/wei.ex @@ -89,7 +89,7 @@ defmodule Explorer.Chain.Wei do @typedoc """ Short for giga-wei - * 109 wei is one gwei + 109 wei is 1 gwei. """ @type gwei :: Decimal.t() diff --git a/apps/explorer/lib/explorer/exchange_rates/token.ex b/apps/explorer/lib/explorer/exchange_rates/token.ex index 6830d5236b..859b908a2f 100644 --- a/apps/explorer/lib/explorer/exchange_rates/token.ex +++ b/apps/explorer/lib/explorer/exchange_rates/token.ex @@ -6,15 +6,15 @@ defmodule Explorer.ExchangeRates.Token do @typedoc """ Represents an exchange rate for a given token. - * `:available_supply` - Available supply of a token - * `:btc_value` - The Bitcoin value of the currency - * `:id` - ID of a currency - * `:last_updated` - Timestamp of when the value was last updated - * `:market_cap_usd` - Market capitalization of the currency - * `:name` - Human-readable name of a ticker - * `:symbol` - Trading symbol used to represent a currency - * `:usd_value` - The USD value of the currency - * `:volume_24h_usd` - The volume from the last 24 hours in USD + * `:available_supply` - Available supply of a token + * `:btc_value` - The Bitcoin value of the currency + * `:id` - ID of a currency + * `:last_updated` - Timestamp of when the value was last updated + * `:market_cap_usd` - Market capitalization of the currency + * `:name` - Human-readable name of a ticker + * `:symbol` - Trading symbol used to represent a currency + * `:usd_value` - The USD value of the currency + * `:volume_24h_usd` - The volume from the last 24 hours in USD """ @type t :: %__MODULE__{ available_supply: Decimal.t(), diff --git a/apps/explorer/lib/explorer/jsonrpc.ex b/apps/explorer/lib/explorer/jsonrpc.ex index 9102de8170..c270a4993a 100644 --- a/apps/explorer/lib/explorer/jsonrpc.ex +++ b/apps/explorer/lib/explorer/jsonrpc.ex @@ -174,14 +174,14 @@ defmodule Explorer.JSONRPC do end @doc """ - 1. POSTs JSON `payload` to `url` - 2. Decodes the response - 3. Handles the response + 1. POSTs JSON `payload` to `url` + 2. Decodes the response + 3. Handles the response ## Returns - * Handled response - * `{:error, reason}` if POST failes + * Handled response + * `{:error, reason}` if POST failes """ def json_rpc(payload, url) do json = encode_json(payload) diff --git a/apps/explorer/lib/explorer/jsonrpc/block.ex b/apps/explorer/lib/explorer/jsonrpc/block.ex index c14edfb184..e26a2608f0 100644 --- a/apps/explorer/lib/explorer/jsonrpc/block.ex +++ b/apps/explorer/lib/explorer/jsonrpc/block.ex @@ -12,37 +12,37 @@ defmodule Explorer.JSONRPC.Block do @type elixir :: %{String.t() => non_neg_integer | DateTime.t() | String.t() | nil} @typedoc """ - * `"author"` - `t:Explorer.JSONRPC.address/0` that created the block. Aliased by `"miner"`. - * `"difficulty"` - `t:Explorer.JSONRPC.quantity/0`` of the difficulty for this block. - * `"extraData"` - the extra `t:Explorer.JSONRPC.data/0`` field of this block. - * `"gasLimit" - maximum gas `t:Explorer.JSONRPC.quantity/0`` in this block. - * `"gasUsed" - the total `t:Explorer.JSONRPC.quantity/0`` of gas used by all transactions in this block. - * `"hash"` - the `t:Explorer.JSONRPC.hash/0` of the block. - * `"logsBloom"` - `t:Explorer.JSONRPC.data/0`` for the [Bloom filter](https://en.wikipedia.org/wiki/Bloom_filter) for - the logs of the block. `nil` when block is pending. - * `"miner"` - `t:Explorer.JSONRPC.address/0` of the beneficiary to whom the mining rewards were given. Aliased by + * `"author"` - `t:Explorer.JSONRPC.address/0` that created the block. Aliased by `"miner"`. + * `"difficulty"` - `t:Explorer.JSONRPC.quantity/0`` of the difficulty for this block. + * `"extraData"` - the extra `t:Explorer.JSONRPC.data/0`` field of this block. + * `"gasLimit" - maximum gas `t:Explorer.JSONRPC.quantity/0`` in this block. + * `"gasUsed" - the total `t:Explorer.JSONRPC.quantity/0`` of gas used by all transactions in this block. + * `"hash"` - the `t:Explorer.JSONRPC.hash/0` of the block. + * `"logsBloom"` - `t:Explorer.JSONRPC.data/0`` for the [Bloom filter](https://en.wikipedia.org/wiki/Bloom_filter) + for the logs of the block. `nil` when block is pending. + * `"miner"` - `t:Explorer.JSONRPC.address/0` of the beneficiary to whom the mining rewards were given. Aliased by `"author"`. - * `"nonce"` - `t:Explorer.JSONRPC.nonce/0`. `nil` when its pending block. - * `"number"` - the block number `t:Explorer.JSONRPC.quantity/0`. `nil` when block is pending. - * `"parentHash" - the `t:Explorer.JSONRPC.hash/0` of the parent block. - * `"receiptsRoot"` - `t:Explorer.JSONRPC.hash/0` of the root of the receipts. - [trie](https://github.com/ethereum/wiki/wiki/Patricia-Tree) of the block. - * `"sealFields"` - UNKNOWN - * `"sha3Uncles"` - `t:Explorer.JSONRPC.hash/0` of the - [uncles](https://bitcoin.stackexchange.com/questions/39329/in-ethereum-what-is-an-uncle-block) data in the block. - * `"signature"` - UNKNOWN - * `"size"` - `t:Explorer.JSONRPC.quantity/0`` of bytes in this block - * `"stateRoot" - `t:Explorer.JSONRPC.hash/0` of the root of the final state - [trie](https://github.com/ethereum/wiki/wiki/Patricia-Tree) of the block. - * `"step"` - UNKNOWN - * `"timestamp"`: the unix timestamp as a `t:Explorer.JSONRPC.quantity/0`` for when the block was collated. - * `"totalDifficulty" - `t:Explorer.JSONRPC.quantity/0`` of the total difficulty of the chain until this block. - * `"transactions"` - `t:list/0` of `t:Explorer.JSONRPC.Transaction.t/0`. - * `"transactionsRoot" - `t:Explorer.JSONRPC.hash/0` of the root of the transaction - [trie](https://github.com/ethereum/wiki/wiki/Patricia-Tree) of the block. - * `uncles`: `t:list/0` of - [uncles](https://bitcoin.stackexchange.com/questions/39329/in-ethereum-what-is-an-uncle-block) - `t:Explorer.JSONRPC.hash/0`. + * `"nonce"` - `t:Explorer.JSONRPC.nonce/0`. `nil` when its pending block. + * `"number"` - the block number `t:Explorer.JSONRPC.quantity/0`. `nil` when block is pending. + * `"parentHash" - the `t:Explorer.JSONRPC.hash/0` of the parent block. + * `"receiptsRoot"` - `t:Explorer.JSONRPC.hash/0` of the root of the receipts. + [trie](https://github.com/ethereum/wiki/wiki/Patricia-Tree) of the block. + * `"sealFields"` - UNKNOWN + * `"sha3Uncles"` - `t:Explorer.JSONRPC.hash/0` of the + [uncles](https://bitcoin.stackexchange.com/questions/39329/in-ethereum-what-is-an-uncle-block) data in the block. + * `"signature"` - UNKNOWN + * `"size"` - `t:Explorer.JSONRPC.quantity/0`` of bytes in this block + * `"stateRoot" - `t:Explorer.JSONRPC.hash/0` of the root of the final state + [trie](https://github.com/ethereum/wiki/wiki/Patricia-Tree) of the block. + * `"step"` - UNKNOWN + * `"timestamp"`: the unix timestamp as a `t:Explorer.JSONRPC.quantity/0`` for when the block was collated. + * `"totalDifficulty" - `t:Explorer.JSONRPC.quantity/0`` of the total difficulty of the chain until this block. + * `"transactions"` - `t:list/0` of `t:Explorer.JSONRPC.Transaction.t/0`. + * `"transactionsRoot" - `t:Explorer.JSONRPC.hash/0` of the root of the transaction + [trie](https://github.com/ethereum/wiki/wiki/Patricia-Tree) of the block. + * `uncles`: `t:list/0` of + [uncles](https://bitcoin.stackexchange.com/questions/39329/in-ethereum-what-is-an-uncle-block) + `t:Explorer.JSONRPC.hash/0`. """ @type t :: %{String.t() => JSONRPC.data() | JSONRPC.hash() | JSONRPC.quantity() | nil} diff --git a/apps/explorer/lib/explorer/jsonrpc/log.ex b/apps/explorer/lib/explorer/jsonrpc/log.ex index d064a95545..c06a1f3c7d 100644 --- a/apps/explorer/lib/explorer/jsonrpc/log.ex +++ b/apps/explorer/lib/explorer/jsonrpc/log.ex @@ -9,14 +9,14 @@ defmodule Explorer.JSONRPC.Log do @type elixir :: %{String.t() => String.t() | [String.t()] | non_neg_integer()} @typedoc """ - * `"address"` - `t:Explorer.JSONRPC.address/0` from which event originated. - * `"blockHash"` - `t:Explorer.JSONRPC.hash/0` of the block this transaction is in. - * `"blockNumber"` - `t:Explorer.JSONRPC.quantity/0` for the block number this transaction is in. - * `"data"` - Data containing non-indexed log parameter - * `"logIndex"` - `t:Explorer.JSONRPC.quantity/0` of the event index positon in the block. - * `"topics"` - `t:list/0` of at most 4 32-byte topics. Topic 1-3 contains indexed parameters of the log. - * `"transactionHash"` - `t:Explorer.JSONRPC.hash/0` of the transaction - * `"transactionIndex"` - `t:Explorer.JSONRPC.quantity/0` for the index of the transaction in the block. + * `"address"` - `t:Explorer.JSONRPC.address/0` from which event originated. + * `"blockHash"` - `t:Explorer.JSONRPC.hash/0` of the block this transaction is in. + * `"blockNumber"` - `t:Explorer.JSONRPC.quantity/0` for the block number this transaction is in. + * `"data"` - Data containing non-indexed log parameter + * `"logIndex"` - `t:Explorer.JSONRPC.quantity/0` of the event index positon in the block. + * `"topics"` - `t:list/0` of at most 4 32-byte topics. Topic 1-3 contains indexed parameters of the log. + * `"transactionHash"` - `t:Explorer.JSONRPC.hash/0` of the transaction + * `"transactionIndex"` - `t:Explorer.JSONRPC.quantity/0` for the index of the transaction in the block. """ @type t :: %{String.t() => String.t() | [String.t()]} diff --git a/apps/explorer/lib/explorer/jsonrpc/receipt.ex b/apps/explorer/lib/explorer/jsonrpc/receipt.ex index 2d9018f721..bd38d9f135 100644 --- a/apps/explorer/lib/explorer/jsonrpc/receipt.ex +++ b/apps/explorer/lib/explorer/jsonrpc/receipt.ex @@ -13,19 +13,20 @@ defmodule Explorer.JSONRPC.Receipt do @type elixir :: %{String.t() => String.t() | non_neg_integer} @typedoc """ - * `"contractAddress"` - The contract `t:Explorer.JSONRPC.address/0` created, if the transaction was a contract - creation, otherwise `nil`. - * `"blockHash"` - `t:Explorer.JSONRPC.hash/0` of the block where `"transactionHash"` was in. - * `"blockNumber"` - The block number `t:Explorer.JSONRPC.quanity/0`. - * `"cumulativeGasUsed"` - `t:Explorer.JSONRPC.quantity/0` of gas used when this transaction was executed in the block. - * `"gasUsed"` - `t:Explorer.JSONRPC.quantity/0` of gas used by this specific transaction alone. - * `"logs"` - `t:list/0` of log objects, which this transaction generated. - * `"logsBloom"` - `t:Explorer.JSONRPC.data/0` of 256 Bytes for - [Bloom filter](https://en.wikipedia.org/wiki/Bloom_filter) for light clients to quickly retrieve related logs. - * `"root"` - `t:Explorer.JSONRPC.hash/0` of post-transaction stateroot (pre-Byzantium) - * `"status"` - `t:Explorer.JSONRPC.quantity/0` of either 1 (success) or 0 (failure) (post-Byzantium) - * `"transactionHash"` - `t:Explorer.JSONRPC.hash/0` the transaction. - * `"transactionIndex"` - `t:Explorer.JSONRPC.quantity/0` for the transaction index in the block. + * `"contractAddress"` - The contract `t:Explorer.JSONRPC.address/0` created, if the transaction was a contract + creation, otherwise `nil`. + * `"blockHash"` - `t:Explorer.JSONRPC.hash/0` of the block where `"transactionHash"` was in. + * `"blockNumber"` - The block number `t:Explorer.JSONRPC.quanity/0`. + * `"cumulativeGasUsed"` - `t:Explorer.JSONRPC.quantity/0` of gas used when this transaction was executed in the + block. + * `"gasUsed"` - `t:Explorer.JSONRPC.quantity/0` of gas used by this specific transaction alone. + * `"logs"` - `t:list/0` of log objects, which this transaction generated. + * `"logsBloom"` - `t:Explorer.JSONRPC.data/0` of 256 Bytes for + [Bloom filter](https://en.wikipedia.org/wiki/Bloom_filter) for light clients to quickly retrieve related logs. + * `"root"` - `t:Explorer.JSONRPC.hash/0` of post-transaction stateroot (pre-Byzantium) + * `"status"` - `t:Explorer.JSONRPC.quantity/0` of either 1 (success) or 0 (failure) (post-Byzantium) + * `"transactionHash"` - `t:Explorer.JSONRPC.hash/0` the transaction. + * `"transactionIndex"` - `t:Explorer.JSONRPC.quantity/0` for the transaction index in the block. """ @type t :: %{String.t() => JSONRPC.address() | JSONRPC.data() | JSONRPC.hash() | JSONRPC.quantity() | list | nil} diff --git a/apps/explorer/lib/explorer/jsonrpc/transaction.ex b/apps/explorer/lib/explorer/jsonrpc/transaction.ex index 573a95a08d..fd7e589ee4 100644 --- a/apps/explorer/lib/explorer/jsonrpc/transaction.ex +++ b/apps/explorer/lib/explorer/jsonrpc/transaction.ex @@ -15,28 +15,29 @@ defmodule Explorer.JSONRPC.Transaction do @type elixir :: %{String.t() => JSONRPC.address() | JSONRPC.hash() | String.t() | non_neg_integer() | nil} @typedoc """ - * `"blockHash"` - `t:Explorer.JSONRPC.hash/0` of the block this transaction is in. `nil` when transaction is pending. - * `"blockNumber"` - `t:Explorer.JSONRPC.quantity/0` for the block number this transaction is in. `nil` when - transaction is pending. - * `"chainId"` - the chain on which the transaction exists. - * `"condition"` - UNKNOWN - * `"creates"` - `t:Explorer.JSONRPC.address/0` of the created contract, if the transaction creates a contract. - * `"from"` - `t:Explorer.JSONRPC.address/0` of the sender. - * `"gas"` - `t:Explorer.JSONRPC.quantity/0` of gas provided by the sender. This is the max gas that may be used. - `gas * gasPrice` is the max fee in wei that the sender is willing to pay for the transaction to be executed. - * `"gasPrice"` - `t:Explorer.JSONRPC.quantity/0` of wei to pay per unit of gas used. - * `"hash"` - `t:Explorer.JSONRPC.hash/0` of the transaction - * `"input"` - `t:Explorer.JSONRPC.data/0` sent along with the transaction, such as input to the contract. - * `"nonce"` - `t:Explorer.JSONRPC.quantity/0` of transactions made by the sender prior to this one. - * `"publicKey"` - `t:Explorer.JSONRPC.hash/0` of the public key of the signer. - * `"r"` - `t:Explorer.JSONRPC.quantity/0` for the R field of the signature. - * `"raw"` - Raw transaction `t:Explorer.JSONRPC.data/0` - * `"standardV"` - `t:Explorer.JSONRPC.quantity/0` for the standardized V (`0` or `1`) field of the signature. - * `"to"` - `t:Explorer.JSONRPC.address/0` of the receiver. `nil` when it is a contract creation transaction. - * `"transactionIndex"` - `t:Explorer.JSONRPC.quantity/0` for the index of the transaction in the block. `nil` when - transaction is pending. - * `"v"` - `t:Explorer.JSONRPC.quantity/0` for the V field of the signature. - * `"value"` - `t:Explorer.JSONRPC.quantity/0` of wei transfered + * `"blockHash"` - `t:Explorer.JSONRPC.hash/0` of the block this transaction is in. `nil` when transaction is + pending. + * `"blockNumber"` - `t:Explorer.JSONRPC.quantity/0` for the block number this transaction is in. `nil` when + transaction is pending. + * `"chainId"` - the chain on which the transaction exists. + * `"condition"` - UNKNOWN + * `"creates"` - `t:Explorer.JSONRPC.address/0` of the created contract, if the transaction creates a contract. + * `"from"` - `t:Explorer.JSONRPC.address/0` of the sender. + * `"gas"` - `t:Explorer.JSONRPC.quantity/0` of gas provided by the sender. This is the max gas that may be used. + `gas * gasPrice` is the max fee in wei that the sender is willing to pay for the transaction to be executed. + * `"gasPrice"` - `t:Explorer.JSONRPC.quantity/0` of wei to pay per unit of gas used. + * `"hash"` - `t:Explorer.JSONRPC.hash/0` of the transaction + * `"input"` - `t:Explorer.JSONRPC.data/0` sent along with the transaction, such as input to the contract. + * `"nonce"` - `t:Explorer.JSONRPC.quantity/0` of transactions made by the sender prior to this one. + * `"publicKey"` - `t:Explorer.JSONRPC.hash/0` of the public key of the signer. + * `"r"` - `t:Explorer.JSONRPC.quantity/0` for the R field of the signature. + * `"raw"` - Raw transaction `t:Explorer.JSONRPC.data/0` + * `"standardV"` - `t:Explorer.JSONRPC.quantity/0` for the standardized V (`0` or `1`) field of the signature. + * `"to"` - `t:Explorer.JSONRPC.address/0` of the receiver. `nil` when it is a contract creation transaction. + * `"transactionIndex"` - `t:Explorer.JSONRPC.quantity/0` for the index of the transaction in the block. `nil` when + transaction is pending. + * `"v"` - `t:Explorer.JSONRPC.quantity/0` for the V field of the signature. + * `"value"` - `t:Explorer.JSONRPC.quantity/0` of wei transfered """ @type t :: %{String.t() => JSONRPC.address() | JSONRPC.hash() | JSONRPC.quantity() | String.t() | nil} diff --git a/apps/explorer/lib/explorer/market/market_history.ex b/apps/explorer/lib/explorer/market/market_history.ex index b6f6312422..deb00d00f9 100644 --- a/apps/explorer/lib/explorer/market/market_history.ex +++ b/apps/explorer/lib/explorer/market/market_history.ex @@ -13,9 +13,9 @@ defmodule Explorer.Market.MarketHistory do @typedoc """ The recorded values of the configured coin to USD for a single day. - * `:closing_price` - Closing price in USD. - * `:date` - The date in UTC. - * `:opening_price` - Opening price in USD. + * `:closing_price` - Closing price in USD. + * `:date` - The date in UTC. + * `:opening_price` - Opening price in USD. """ @type t :: %__MODULE__{ closing_price: Decimal.t(), diff --git a/apps/explorer_web/lib/explorer_web/views/transaction_view.ex b/apps/explorer_web/lib/explorer_web/views/transaction_view.ex index 25a038a586..7b492c10a9 100644 --- a/apps/explorer_web/lib/explorer_web/views/transaction_view.ex +++ b/apps/explorer_web/lib/explorer_web/views/transaction_view.ex @@ -114,7 +114,7 @@ defmodule ExplorerWeb.TransactionView do ## Options - * `:include_label` - Boolean. Defaults to true. Flag for displaying unit with value. + * `:include_label` - Boolean. Defaults to true. Flag for displaying unit with value. """ def value(%mod{value: value}, opts \\ []) when is_transaction_type(mod) do include_label? = Keyword.get(opts, :include_label, true) diff --git a/apps/explorer_web/lib/explorer_web/views/wei_helpers.ex b/apps/explorer_web/lib/explorer_web/views/wei_helpers.ex index a5670c4dbe..74f6a5f227 100644 --- a/apps/explorer_web/lib/explorer_web/views/wei_helpers.ex +++ b/apps/explorer_web/lib/explorer_web/views/wei_helpers.ex @@ -22,9 +22,9 @@ defmodule ExplorerWeb.WeiHelpers do The third argument allows for keyword options to be passed for formatting the converted number. - * `:fractional_digits` - Integer. Number of fractional digits to include - * `:include_unit_label` - Boolean (Defaults to `true`). Flag for if the unit - label should be included in the returned string + * `:fractional_digits` - Integer. Number of fractional digits to include + * `:include_unit_label` - Boolean (Defaults to `true`). Flag for if the unit + label should be included in the returned string ## Examples From 950e42d4d8cc9a1c432053cc37a71447b272b52c Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Mon, 14 May 2018 11:07:14 -0500 Subject: [PATCH 67/77] Consistently use child_spec --- apps/explorer/lib/explorer/application.ex | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index 3dfdb3feb1..eac2fa7964 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -5,8 +5,6 @@ defmodule Explorer.Application do use Application - import Supervisor.Spec, only: [supervisor: 3] - @impl Application def start(_type, _args) do # Children to start in all environments @@ -28,7 +26,7 @@ defmodule Explorer.Application do defp secondary_children(_) do [ Explorer.JSONRPC, - supervisor(Task.Supervisor, [[name: Explorer.TaskSupervisor]], id: Explorer.TaskSupervisor), + Supervisor.child_spec({Task.Supervisor, name: Explorer.TaskSupervisor}, id: Explorer.TaskSupervisor), Explorer.Indexer, Explorer.Chain.Statistics.Server, Explorer.ExchangeRates, From fbfd51000490845ef2d0f9abcb0702dc14aa38bc Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Mon, 14 May 2018 11:11:49 -0500 Subject: [PATCH 68/77] Remove phoenix_pubsub_redis --- apps/explorer/mix.exs | 7 +------ apps/explorer_web/mix.exs | 8 +------- mix.lock | 1 - 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index 8663ecc8cb..10117aa737 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -37,7 +37,7 @@ defmodule Explorer.Mixfile do def application do [ mod: {Explorer.Application, []}, - extra_applications: extra_applications(Mix.env()) + extra_applications: extra_applications() ] end @@ -46,11 +46,6 @@ defmodule Explorer.Mixfile do defp elixirc_paths(_), do: elixirc_paths() defp elixirc_paths, do: ["lib"] - # Specifies extra applications to start per environment - defp extra_applications(:prod), do: [:phoenix_pubsub_redis | extra_applications()] - - defp extra_applications(_), do: extra_applications() - defp extra_applications, do: [ :crontab, diff --git a/apps/explorer_web/mix.exs b/apps/explorer_web/mix.exs index a4d185c495..1021207a6e 100644 --- a/apps/explorer_web/mix.exs +++ b/apps/explorer_web/mix.exs @@ -37,7 +37,7 @@ defmodule ExplorerWeb.Mixfile do def application do [ mod: {ExplorerWeb.Application, []}, - extra_applications: extra_applications(Mix.env()) + extra_applications: extra_applications() ] end @@ -46,11 +46,6 @@ defmodule ExplorerWeb.Mixfile do defp elixirc_paths(_), do: elixirc_paths() defp elixirc_paths, do: ["lib"] - # Specifies extra applications to start per environment - defp extra_applications(:prod), do: [:phoenix_pubsub_redis | extra_applications()] - - defp extra_applications(_), do: extra_applications() - defp extra_applications, do: [ :scrivener_html, @@ -92,7 +87,6 @@ defmodule ExplorerWeb.Mixfile do {:phoenix_html, "~> 2.10"}, {:phoenix_live_reload, "~> 1.0", only: [:dev]}, {:phoenix_pubsub, "~> 1.0"}, - {:phoenix_pubsub_redis, "~> 2.1.0", only: [:prod]}, {:postgrex, ">= 0.0.0"}, {:scrivener_html, "~> 1.7"}, # Waiting on https://github.com/smeevil/set_locale/pull/9 diff --git a/mix.lock b/mix.lock index 633cf64a9a..5870e02311 100644 --- a/mix.lock +++ b/mix.lock @@ -49,7 +49,6 @@ "phoenix_html": {:hex, :phoenix_html, "2.10.5", "4f9df6b0fb7422a9440a73182a566cb9cbe0e3ffe8884ef9337ccf284fc1ef0a", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.1.3", "1d178429fc8950b12457d09c6afec247bfe1fcb6f36209e18fbb0221bdfe4d41", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2 or ~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.2", "bfa7fd52788b5eaa09cb51ff9fcad1d9edfeb68251add458523f839392f034c1", [:mix], [], "hexpm"}, - "phoenix_pubsub_redis": {:hex, :phoenix_pubsub_redis, "2.1.4", "aca57023215810af493e15f73aa71e51b0cd186747e38ec7a3ef4743bf13967f", [:mix], [{:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5.1 or ~> 1.6", [hex: :poolboy, repo: "hexpm", optional: false]}, {:redix, "~> 0.6.1", [hex: :redix, repo: "hexpm", optional: false]}, {:redix_pubsub, "~> 0.4.1", [hex: :redix_pubsub, repo: "hexpm", optional: false]}], "hexpm"}, "plug": {:hex, :plug, "1.4.4", "279b547662272cd835a8ca089717201dd3be51bb4705354eaf1b0346744acc82", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"}, From 5fee1443005e0cc6e64822a19860af7a8e80df2b Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Mon, 14 May 2018 11:26:06 -0500 Subject: [PATCH 69/77] Use Explorer.Indexer.Supervisor directly --- apps/explorer/lib/explorer/application.ex | 2 +- apps/explorer/lib/explorer/indexer.ex | 24 ------------------- .../lib/explorer/indexer/supervisor.ex | 2 +- 3 files changed, 2 insertions(+), 26 deletions(-) diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index eac2fa7964..bf6dbe652d 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -27,7 +27,7 @@ defmodule Explorer.Application do [ Explorer.JSONRPC, Supervisor.child_spec({Task.Supervisor, name: Explorer.TaskSupervisor}, id: Explorer.TaskSupervisor), - Explorer.Indexer, + Explorer.Indexer.Supervisor, Explorer.Chain.Statistics.Server, Explorer.ExchangeRates, Explorer.Market.History.Cataloger diff --git a/apps/explorer/lib/explorer/indexer.ex b/apps/explorer/lib/explorer/indexer.ex index 1e44b5fa69..ff85029299 100644 --- a/apps/explorer/lib/explorer/indexer.ex +++ b/apps/explorer/lib/explorer/indexer.ex @@ -5,30 +5,6 @@ defmodule Explorer.Indexer do alias Explorer.Chain - @doc """ - Options passed to `child_spec` are passed to `Explorer.Indexer.Supervisor.start_link/1` - - iex> Explorer.Indexer.child_spec([option: :value]) - %{ - id: Explorer.Indexer, - restart: :permanent, - shutdown: 5000, - start: {Explorer.Indexer.Supervisor, :start_link, - [[option: :value]]}, - type: :supervisor - } - - """ - def child_spec(opts) do - %{ - id: __MODULE__, - start: {Explorer.Indexer.Supervisor, :start_link, [opts]}, - restart: :permanent, - shutdown: 5000, - type: :supervisor - } - end - @doc """ The maximum `t:Explorer.Chain.Block.t/0` `number` that was indexed diff --git a/apps/explorer/lib/explorer/indexer/supervisor.ex b/apps/explorer/lib/explorer/indexer/supervisor.ex index 854f54706a..b57e2b0e05 100644 --- a/apps/explorer/lib/explorer/indexer/supervisor.ex +++ b/apps/explorer/lib/explorer/indexer/supervisor.ex @@ -8,7 +8,7 @@ defmodule Explorer.Indexer.Supervisor do alias Explorer.Indexer.{AddressFetcher, BlockFetcher} def start_link(opts) do - Supervisor.start_link(__MODULE__, opts) + Supervisor.start_link(__MODULE__, opts, name: __MODULE__) end @impl Supervisor From 7978c42e9037e375ce8375ff65f986291d0e7af4 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Mon, 14 May 2018 11:37:24 -0500 Subject: [PATCH 70/77] Compact aliases together --- apps/explorer/lib/explorer/indexer/block_fetcher.ex | 2 -- apps/explorer/test/support/data_case.ex | 4 ++-- apps/explorer_web/test/support/feature_case.ex | 6 +++--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/apps/explorer/lib/explorer/indexer/block_fetcher.ex b/apps/explorer/lib/explorer/indexer/block_fetcher.ex index 4a9bf92ea9..3af605b174 100644 --- a/apps/explorer/lib/explorer/indexer/block_fetcher.ex +++ b/apps/explorer/lib/explorer/indexer/block_fetcher.ex @@ -8,9 +8,7 @@ defmodule Explorer.Indexer.BlockFetcher do require Logger alias Explorer.{Chain, Indexer, JSONRPC} - alias Explorer.Indexer.{AddressFetcher, Sequence} - alias Explorer.JSONRPC.Transactions # dialyzer thinks that Logger.debug functions always have no_local_return diff --git a/apps/explorer/test/support/data_case.ex b/apps/explorer/test/support/data_case.ex index 3cf7d2a349..27e094626a 100644 --- a/apps/explorer/test/support/data_case.ex +++ b/apps/explorer/test/support/data_case.ex @@ -16,8 +16,6 @@ defmodule Explorer.DataCase do using do quote do - alias Explorer.Repo - use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney import Ecto @@ -25,6 +23,8 @@ defmodule Explorer.DataCase do import Ecto.Query import Explorer.DataCase import Explorer.Factory + + alias Explorer.Repo end end diff --git a/apps/explorer_web/test/support/feature_case.ex b/apps/explorer_web/test/support/feature_case.ex index 679c0d1022..4c5b91a0b2 100644 --- a/apps/explorer_web/test/support/feature_case.ex +++ b/apps/explorer_web/test/support/feature_case.ex @@ -8,14 +8,14 @@ defmodule ExplorerWeb.FeatureCase do quote do use Wallaby.DSL - alias Explorer.Repo - import Ecto import Ecto.Changeset import Ecto.Query - import ExplorerWeb.Router.Helpers import Explorer.Factory import ExplorerWeb.Factory + import ExplorerWeb.Router.Helpers + + alias Explorer.Repo end end From 785336f3226f1778b0a12259262f3ae2257c7fab Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Mon, 14 May 2018 11:43:27 -0500 Subject: [PATCH 71/77] Fix accidental delegate variable character deletion --- .../lib/explorer_web/views/address_transaction_view.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/explorer_web/lib/explorer_web/views/address_transaction_view.ex b/apps/explorer_web/lib/explorer_web/views/address_transaction_view.ex index 1c0adb894a..6519a61859 100644 --- a/apps/explorer_web/lib/explorer_web/views/address_transaction_view.ex +++ b/apps/explorer_web/lib/explorer_web/views/address_transaction_view.ex @@ -15,6 +15,6 @@ defmodule ExplorerWeb.AddressTransactionView do end end - defdelegate status(transacton), to: TransactionView + defdelegate status(transaction), to: TransactionView defdelegate to_address(transaction), to: TransactionView end From 71793e62518d127c62701d5bc1f947011c4b30b4 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Mon, 14 May 2018 13:05:12 -0500 Subject: [PATCH 72/77] Fix dialyzer warnings --- .../lib/explorer/exchange_rates/exchange_rates.ex | 2 +- apps/explorer/lib/explorer/market/market.ex | 2 +- apps/explorer/test/support/factory.ex | 11 +++++++++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex b/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex index 217929af3c..4a7fee903d 100644 --- a/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex +++ b/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex @@ -89,7 +89,7 @@ defmodule Explorer.ExchangeRates do @doc """ Returns a specific rate from the tracked tickers by symbol """ - @spec lookup(String.t()) :: Token.t() + @spec lookup(String.t()) :: Token.t() | nil def lookup(symbol) do if store() == :ets do case :ets.lookup(table_name(), symbol) do diff --git a/apps/explorer/lib/explorer/market/market.ex b/apps/explorer/lib/explorer/market/market.ex index 73ee59fb69..9ecc6f4cac 100644 --- a/apps/explorer/lib/explorer/market/market.ex +++ b/apps/explorer/lib/explorer/market/market.ex @@ -12,7 +12,7 @@ defmodule Explorer.Market do @doc """ Get most recent exchange rate for the given symbol. """ - @spec get_exchange_rate(String.t()) :: Token.t() + @spec get_exchange_rate(String.t()) :: Token.t() | nil def get_exchange_rate(symbol) do ExchangeRates.lookup(symbol) end diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index 6acda32703..19f3ce4a8f 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -74,8 +74,8 @@ defmodule Explorer.Factory do def market_history_factory do %MarketHistory{ - closing_price: Decimal.new(Enum.random(1..10_000) / 100), - opening_price: Decimal.new(Enum.random(1..10_000) / 100), + closing_price: price(), + opening_price: price(), date: Date.utc_today() } end @@ -164,4 +164,11 @@ defmodule Explorer.Factory do value: sequence("internal_transaction_value", &Decimal.new(&1)) } end + + defp price do + 1..10_000 + |> Enum.random() + |> Decimal.new() + |> Decimal.div(Decimal.new(100)) + end end From 75388c8eced49608d929cc5f03281ef5164937fd Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Mon, 14 May 2018 13:05:58 -0500 Subject: [PATCH 73/77] cd apps; mix new ethereum_jsonrpc --- apps/ethereum_jsonrpc/.formatter.exs | 4 +++ apps/ethereum_jsonrpc/.gitignore | 24 ++++++++++++++ apps/ethereum_jsonrpc/README.md | 21 ++++++++++++ apps/ethereum_jsonrpc/config/config.exs | 30 +++++++++++++++++ apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex | 18 ++++++++++ apps/ethereum_jsonrpc/mix.exs | 33 +++++++++++++++++++ .../test/ethereum_jsonrpc_test.exs | 8 +++++ apps/ethereum_jsonrpc/test/test_helper.exs | 1 + 8 files changed, 139 insertions(+) create mode 100644 apps/ethereum_jsonrpc/.formatter.exs create mode 100644 apps/ethereum_jsonrpc/.gitignore create mode 100644 apps/ethereum_jsonrpc/README.md create mode 100644 apps/ethereum_jsonrpc/config/config.exs create mode 100644 apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex create mode 100644 apps/ethereum_jsonrpc/mix.exs create mode 100644 apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs create mode 100644 apps/ethereum_jsonrpc/test/test_helper.exs diff --git a/apps/ethereum_jsonrpc/.formatter.exs b/apps/ethereum_jsonrpc/.formatter.exs new file mode 100644 index 0000000000..525446d406 --- /dev/null +++ b/apps/ethereum_jsonrpc/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/apps/ethereum_jsonrpc/.gitignore b/apps/ethereum_jsonrpc/.gitignore new file mode 100644 index 0000000000..770862cee7 --- /dev/null +++ b/apps/ethereum_jsonrpc/.gitignore @@ -0,0 +1,24 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where 3rd-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +ethereum_jsonrpc-*.tar + diff --git a/apps/ethereum_jsonrpc/README.md b/apps/ethereum_jsonrpc/README.md new file mode 100644 index 0000000000..adcc1c460d --- /dev/null +++ b/apps/ethereum_jsonrpc/README.md @@ -0,0 +1,21 @@ +# EthereumJsonrpc + +**TODO: Add description** + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `ethereum_jsonrpc` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:ethereum_jsonrpc, "~> 0.1.0"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at [https://hexdocs.pm/ethereum_jsonrpc](https://hexdocs.pm/ethereum_jsonrpc). + diff --git a/apps/ethereum_jsonrpc/config/config.exs b/apps/ethereum_jsonrpc/config/config.exs new file mode 100644 index 0000000000..828d6537f6 --- /dev/null +++ b/apps/ethereum_jsonrpc/config/config.exs @@ -0,0 +1,30 @@ +# This file is responsible for configuring your application +# and its dependencies with the aid of the Mix.Config module. +use Mix.Config + +# This configuration is loaded before any dependency and is restricted +# to this project. If another project depends on this project, this +# file won't be loaded nor affect the parent project. For this reason, +# if you want to provide default values for your application for +# 3rd-party users, it should be done in your "mix.exs" file. + +# You can configure your application as: +# +# config :ethereum_jsonrpc, key: :value +# +# and access this configuration in your application as: +# +# Application.get_env(:ethereum_jsonrpc, :key) +# +# You can also configure a 3rd-party app: +# +# config :logger, level: :info +# + +# It is also possible to import configuration files, relative to this +# directory. For example, you can emulate configuration per environment +# by uncommenting the line below and defining dev.exs, test.exs and such. +# Configuration from the imported file will override the ones defined +# here (which is why it is important to import them last). +# +# import_config "#{Mix.env}.exs" diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex new file mode 100644 index 0000000000..82c7b2b45a --- /dev/null +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex @@ -0,0 +1,18 @@ +defmodule EthereumJsonrpc do + @moduledoc """ + Documentation for EthereumJsonrpc. + """ + + @doc """ + Hello world. + + ## Examples + + iex> EthereumJsonrpc.hello + :world + + """ + def hello do + :world + end +end diff --git a/apps/ethereum_jsonrpc/mix.exs b/apps/ethereum_jsonrpc/mix.exs new file mode 100644 index 0000000000..0dc672b64b --- /dev/null +++ b/apps/ethereum_jsonrpc/mix.exs @@ -0,0 +1,33 @@ +defmodule EthereumJsonrpc.MixProject do + use Mix.Project + + def project do + [ + app: :ethereum_jsonrpc, + version: "0.1.0", + build_path: "../../_build", + config_path: "../../config/config.exs", + deps_path: "../../deps", + lockfile: "../../mix.lock", + elixir: "~> 1.6", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + # {:dep_from_hexpm, "~> 0.3.0"}, + # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}, + # {:sibling_app_in_umbrella, in_umbrella: true}, + ] + end +end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs new file mode 100644 index 0000000000..e681240eb4 --- /dev/null +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs @@ -0,0 +1,8 @@ +defmodule EthereumJsonrpcTest do + use ExUnit.Case + doctest EthereumJsonrpc + + test "greets the world" do + assert EthereumJsonrpc.hello() == :world + end +end diff --git a/apps/ethereum_jsonrpc/test/test_helper.exs b/apps/ethereum_jsonrpc/test/test_helper.exs new file mode 100644 index 0000000000..869559e709 --- /dev/null +++ b/apps/ethereum_jsonrpc/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() From 11bcecad9638f4f65b93a31adb5bb8b76dbecbce Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Mon, 14 May 2018 13:44:08 -0500 Subject: [PATCH 74/77] Explorer.JSONRPC -> EthereumJSONRPC Extract Explorer.JSONRPC to its own OTP application, apps/ethereum_jsonrpc. It is not ready to be a completely separate library because it knowns the internal params format used for Explorer.Chain. --- apps/ethereum_jsonrpc/README.md | 33 +- apps/ethereum_jsonrpc/config/config.exs | 30 +- apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex | 301 ++++++++++++++++- .../lib/ethereum_jsonrpc/application.ex | 16 + .../lib/ethereum_jsonrpc}/block.ex | 60 ++-- .../lib/ethereum_jsonrpc}/blocks.ex | 12 +- .../lib/ethereum_jsonrpc}/log.ex | 20 +- .../lib/ethereum_jsonrpc}/logs.ex | 4 +- .../lib/ethereum_jsonrpc}/parity.ex | 8 +- .../lib/ethereum_jsonrpc}/parity/trace.ex | 22 +- .../ethereum_jsonrpc}/parity/trace/action.ex | 12 +- .../ethereum_jsonrpc}/parity/trace/result.ex | 10 +- .../lib/ethereum_jsonrpc}/parity/traces.ex | 4 +- .../lib/ethereum_jsonrpc}/receipt.ex | 46 +-- .../lib/ethereum_jsonrpc}/receipts.ex | 12 +- .../lib/ethereum_jsonrpc}/transaction.ex | 63 ++-- .../lib/ethereum_jsonrpc}/transactions.ex | 10 +- apps/ethereum_jsonrpc/mix.exs | 44 ++- .../test/ethereum_jsonrpc/block_test.exs | 5 + .../test/ethereum_jsonrpc/blocks_test.exs | 5 + .../test/ethereum_jsonrpc/log_test.exs | 5 + .../parity/trace/action_test.exs | 5 + .../parity/trace/result_test.exs | 5 + .../ethereum_jsonrpc/parity/trace_test.exs | 5 + .../test/ethereum_jsonrpc/parity_test.exs | 5 + .../test/ethereum_jsonrpc/receipt_test.exs | 5 + .../test/ethereum_jsonrpc}/receipts_test.exs | 6 +- .../ethereum_jsonrpc/transaction_test.exs | 5 + .../ethereum_jsonrpc/transactions_test.exs | 5 + .../test/ethereum_jsonrpc_test.exs | 8 - apps/ethereum_jsonrpc/test/test_helper.exs | 6 + apps/explorer/config/config.exs | 5 - apps/explorer/lib/explorer/application.ex | 1 - .../lib/explorer/indexer/address_fetcher.ex | 11 +- .../lib/explorer/indexer/block_fetcher.ex | 11 +- apps/explorer/lib/explorer/jsonrpc.ex | 309 ------------------ apps/explorer/mix.exs | 2 + .../explorer/indexer/address_fetcher_test.exs | 2 - .../explorer/indexer/block_fetcher_test.exs | 3 - .../test/explorer/jsonrpc/block_test.exs | 5 - .../test/explorer/jsonrpc/blocks_test.exs | 5 - .../test/explorer/jsonrpc/log_test.exs | 5 - .../jsonrpc/parity/trace/action_test.exs | 5 - .../jsonrpc/parity/trace/result_test.exs | 5 - .../explorer/jsonrpc/parity/trace_test.exs | 5 - .../test/explorer/jsonrpc/parity_test.exs | 5 - .../test/explorer/jsonrpc/receipt_test.exs | 5 - .../explorer/jsonrpc/transaction_test.exs | 5 - .../explorer/jsonrpc/transactions_test.exs | 5 - coveralls.json | 2 +- 50 files changed, 595 insertions(+), 578 deletions(-) create mode 100644 apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/application.ex rename apps/{explorer/lib/explorer/jsonrpc => ethereum_jsonrpc/lib/ethereum_jsonrpc}/block.ex (90%) rename apps/{explorer/lib/explorer/jsonrpc => ethereum_jsonrpc/lib/ethereum_jsonrpc}/blocks.ex (98%) rename apps/{explorer/lib/explorer/jsonrpc => ethereum_jsonrpc/lib/ethereum_jsonrpc}/log.ex (85%) rename apps/{explorer/lib/explorer/jsonrpc => ethereum_jsonrpc/lib/ethereum_jsonrpc}/logs.ex (88%) rename apps/{explorer/lib/explorer/jsonrpc => ethereum_jsonrpc/lib/ethereum_jsonrpc}/parity.ex (97%) rename apps/{explorer/lib/explorer/jsonrpc => ethereum_jsonrpc/lib/ethereum_jsonrpc}/parity/trace.ex (98%) rename apps/{explorer/lib/explorer/jsonrpc => ethereum_jsonrpc/lib/ethereum_jsonrpc}/parity/trace/action.ex (93%) rename apps/{explorer/lib/explorer/jsonrpc => ethereum_jsonrpc/lib/ethereum_jsonrpc}/parity/trace/result.ex (91%) rename apps/{explorer/lib/explorer/jsonrpc => ethereum_jsonrpc/lib/ethereum_jsonrpc}/parity/traces.ex (85%) rename apps/{explorer/lib/explorer/jsonrpc => ethereum_jsonrpc/lib/ethereum_jsonrpc}/receipt.ex (78%) rename apps/{explorer/lib/explorer/jsonrpc => ethereum_jsonrpc/lib/ethereum_jsonrpc}/receipts.ex (97%) rename apps/{explorer/lib/explorer/jsonrpc => ethereum_jsonrpc/lib/ethereum_jsonrpc}/transaction.ex (70%) rename apps/{explorer/lib/explorer/jsonrpc => ethereum_jsonrpc/lib/ethereum_jsonrpc}/transactions.ex (98%) create mode 100644 apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs create mode 100644 apps/ethereum_jsonrpc/test/ethereum_jsonrpc/blocks_test.exs create mode 100644 apps/ethereum_jsonrpc/test/ethereum_jsonrpc/log_test.exs create mode 100644 apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/trace/action_test.exs create mode 100644 apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/trace/result_test.exs create mode 100644 apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/trace_test.exs create mode 100644 apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity_test.exs create mode 100644 apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipt_test.exs rename apps/{explorer/test/explorer/jsonrpc => ethereum_jsonrpc/test/ethereum_jsonrpc}/receipts_test.exs (92%) create mode 100644 apps/ethereum_jsonrpc/test/ethereum_jsonrpc/transaction_test.exs create mode 100644 apps/ethereum_jsonrpc/test/ethereum_jsonrpc/transactions_test.exs delete mode 100644 apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs delete mode 100644 apps/explorer/lib/explorer/jsonrpc.ex delete mode 100644 apps/explorer/test/explorer/jsonrpc/block_test.exs delete mode 100644 apps/explorer/test/explorer/jsonrpc/blocks_test.exs delete mode 100644 apps/explorer/test/explorer/jsonrpc/log_test.exs delete mode 100644 apps/explorer/test/explorer/jsonrpc/parity/trace/action_test.exs delete mode 100644 apps/explorer/test/explorer/jsonrpc/parity/trace/result_test.exs delete mode 100644 apps/explorer/test/explorer/jsonrpc/parity/trace_test.exs delete mode 100644 apps/explorer/test/explorer/jsonrpc/parity_test.exs delete mode 100644 apps/explorer/test/explorer/jsonrpc/receipt_test.exs delete mode 100644 apps/explorer/test/explorer/jsonrpc/transaction_test.exs delete mode 100644 apps/explorer/test/explorer/jsonrpc/transactions_test.exs diff --git a/apps/ethereum_jsonrpc/README.md b/apps/ethereum_jsonrpc/README.md index adcc1c460d..d546954ce5 100644 --- a/apps/ethereum_jsonrpc/README.md +++ b/apps/ethereum_jsonrpc/README.md @@ -1,21 +1,36 @@ -# EthereumJsonrpc +# EthereumJSONRPC -**TODO: Add description** +Ethereum JSONRPC client. + +## Configuration + +Configuration for parity URLs can be provided with the following mix +config: + +```elixir +config :ethereum_jsonrpc, + url: "https://sokol.poa.network", + trace_url: "https://sokol-trace.poa.network", + http: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]] +``` + +Note: the tracing node URL is provided separately from `:url`, +via `:trace_url`. The trace URL and is used for +`fetch_internal_transactions`, which is only a supported method on +tracing nodes. The `:http` option is passed directly to the HTTP +library (`HTTPoison`), which forwards the options down to `:hackney`. ## Installation -If [available in Hex](https://hex.pm/docs/publish), the package can be installed -by adding `ethereum_jsonrpc` to your list of dependencies in `mix.exs`: +The OTP application `:ethereum_jsonrpc` can be used in other umbrella +OTP applications by adding `ethereum_jsonrpc` to your list of +dependencies in `mix.exs`: ```elixir def deps do [ - {:ethereum_jsonrpc, "~> 0.1.0"} + {:ethereum_jsonrpc, in_umbrella: true} ] end ``` -Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) -and published on [HexDocs](https://hexdocs.pm). Once published, the docs can -be found at [https://hexdocs.pm/ethereum_jsonrpc](https://hexdocs.pm/ethereum_jsonrpc). - diff --git a/apps/ethereum_jsonrpc/config/config.exs b/apps/ethereum_jsonrpc/config/config.exs index 828d6537f6..a1c766314a 100644 --- a/apps/ethereum_jsonrpc/config/config.exs +++ b/apps/ethereum_jsonrpc/config/config.exs @@ -2,29 +2,7 @@ # and its dependencies with the aid of the Mix.Config module. use Mix.Config -# This configuration is loaded before any dependency and is restricted -# to this project. If another project depends on this project, this -# file won't be loaded nor affect the parent project. For this reason, -# if you want to provide default values for your application for -# 3rd-party users, it should be done in your "mix.exs" file. - -# You can configure your application as: -# -# config :ethereum_jsonrpc, key: :value -# -# and access this configuration in your application as: -# -# Application.get_env(:ethereum_jsonrpc, :key) -# -# You can also configure a 3rd-party app: -# -# config :logger, level: :info -# - -# It is also possible to import configuration files, relative to this -# directory. For example, you can emulate configuration per environment -# by uncommenting the line below and defining dev.exs, test.exs and such. -# Configuration from the imported file will override the ones defined -# here (which is why it is important to import them last). -# -# import_config "#{Mix.env}.exs" +config :ethereum_jsonrpc, + http: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]], + trace_url: "https://sokol-trace.poa.network", + url: "https://sokol.poa.network" diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex index 82c7b2b45a..5e282cd5d8 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex @@ -1,18 +1,303 @@ -defmodule EthereumJsonrpc do +defmodule EthereumJSONRPC do @moduledoc """ - Documentation for EthereumJsonrpc. + Ethereum JSONRPC client. + + ## Configuration + + Configuration for parity URLs can be provided with the following mix config: + + config :ethereum_jsonrpc, + url: "https://sokol.poa.network", + trace_url: "https://sokol-trace.poa.network", + http: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :ethereum_jsonrpc]] + + Note: the tracing node URL is provided separately from `:url`, via `:trace_url`. The trace URL and is used for + `fetch_internal_transactions`, which is only a supported method on tracing nodes. The `:http` option is passed + directly to the HTTP library (`HTTPoison`), which forwards the options down to `:hackney`. + """ + + require Logger + + alias EthereumJSONRPC.{Blocks, Parity, Receipts, Transactions} + + @typedoc """ + Truncated 20-byte [KECCAK-256](https://en.wikipedia.org/wiki/SHA-3) hash encoded as a hexadecimal number in a + `String.t`. + """ + @type address :: String.t() + + @typedoc """ + Binary data encoded as a single hexadecimal number in a `String.t` """ + @type data :: String.t() + + @typedoc """ + A full 32-byte [KECCAK-256](https://en.wikipedia.org/wiki/SHA-3) hash encoded as a hexadecimal number in a `String.t` + + ## Example + + "0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331" + + """ + @type hash :: String.t() + + @typedoc """ + 8 byte [KECCAK-256](https://en.wikipedia.org/wiki/SHA-3) hash of the proof-of-work. + """ + @type nonce :: String.t() + + @typedoc """ + A number encoded as a hexadecimal number in a `String.t` + + ## Example + + "0x1b4" + + """ + @type quantity :: String.t() + + @typedoc """ + Unix timestamp encoded as a hexadecimal number in a `String.t` + """ + @type timestamp :: String.t() @doc """ - Hello world. + Lists changes for a given filter subscription. + """ + def check_for_updates(filter_id) do + request = %{ + "id" => filter_id, + "jsonrpc" => "2.0", + "method" => "eth_getFilterChanges", + "params" => [filter_id] + } - ## Examples + json_rpc(request, config(:url)) + end - iex> EthereumJsonrpc.hello - :world + @doc """ + Fetches configuration for this module under `key` + Configuration can be set a compile time using `config` + + config :ethereume_jsonrpc, key, value + + Configuration can be set a runtime using `Application.put_env/3` + + Application.put_env(:ethereume_jsonrpc, key, value) + + """ + def config(key) do + Application.fetch_env!(:ethereum_jsonrpc, key) + end + + @doc """ + Fetches address balances by address hashes. + """ + def fetch_balances_by_hash(address_hashes) do + batched_requests = + for hash <- address_hashes do + %{ + "id" => hash, + "jsonrpc" => "2.0", + "method" => "eth_getBalance", + "params" => [hash, "latest"] + } + end + + batched_requests + |> json_rpc(config(:url)) + |> handle_balances() + end + + defp handle_balances({:ok, results}) do + native_results = + for response <- results, into: %{} do + {response["id"], hexadecimal_to_integer(response["result"])} + end + + {:ok, native_results} + end + + defp handle_balances({:error, _reason} = err), do: err + + @doc """ + Fetches blocks by block hashes. + + Transaction data is included for each block. """ - def hello do - :world + def fetch_blocks_by_hash(block_hashes) do + batched_requests = + for block_hash <- block_hashes do + %{ + "id" => block_hash, + "jsonrpc" => "2.0", + "method" => "eth_getBlockByHash", + "params" => [block_hash, true] + } + end + + batched_requests + |> json_rpc(config(:url)) + |> handle_get_block_by_number() + |> case do + {:ok, _next, results} -> {:ok, results} + {:error, reason} -> {:error, reason} + end end + + @doc """ + Fetches blocks by block number range. + """ + def fetch_blocks_by_range(block_start, block_end) do + block_start + |> build_batch_get_block_by_number(block_end) + |> json_rpc(config(:url)) + |> handle_get_block_by_number() + end + + @doc """ + Fetches internal transactions from client-specific API. + """ + def fetch_internal_transactions(hashes) when is_list(hashes) do + Parity.fetch_internal_transactions(hashes) + end + + def fetch_transaction_receipts(hashes) when is_list(hashes) do + Receipts.fetch(hashes) + end + + @doc """ + 1. POSTs JSON `payload` to `url` + 2. Decodes the response + 3. Handles the response + + ## Returns + + * Handled response + * `{:error, reason}` if POST failes + """ + def json_rpc(payload, url) do + json = encode_json(payload) + headers = [{"Content-Type", "application/json"}] + + case HTTPoison.post(url, json, headers, config(:http)) do + {:ok, %HTTPoison.Response{body: body, status_code: code}} -> + body |> decode_json(payload, url) |> handle_response(code) + + {:error, %HTTPoison.Error{reason: reason}} -> + {:error, reason} + end + end + + @doc """ + Creates a filter subscription that can be polled for retreiving new blocks. + """ + def listen_for_new_blocks do + id = DateTime.utc_now() |> DateTime.to_unix() + + request = %{ + "id" => id, + "jsonrpc" => "2.0", + "method" => "eth_newBlockFilter", + "params" => [] + } + + json_rpc(request, config(:url)) + end + + @doc """ + Converts `t:nonce/0` to `t:non_neg_integer/0` + """ + def nonce_to_integer(nonce) do + hexadecimal_to_integer(nonce) + end + + @doc """ + Converts `t:quantity/0` to `t:non_neg_integer/0`. + """ + def quantity_to_integer(quantity) do + hexadecimal_to_integer(quantity) + end + + @doc """ + Converts `t:timestamp/0` to `t:DateTime.t/0` + """ + def timestamp_to_datetime(timestamp) do + timestamp + |> hexadecimal_to_integer() + |> Timex.from_unix() + end + + defp build_batch_get_block_by_number(block_start, block_end) do + for current <- block_start..block_end do + %{ + "id" => current, + "jsonrpc" => "2.0", + "method" => "eth_getBlockByNumber", + "params" => [int_to_hash_string(current), true] + } + end + end + + defp encode_json(data), do: Jason.encode_to_iodata!(data) + + defp decode_json(body, posted_payload, url) do + Jason.decode!(body) + rescue + Jason.DecodeError -> + Logger.error(""" + failed to decode json payload: + + url: #{inspect(url)} + + body: #{inspect(body)} + + posted payload: #{inspect(posted_payload)} + + """) + + raise("bad jason") + end + + defp handle_get_block_by_number({:ok, results}) do + {blocks, next} = + Enum.reduce(results, {[], :more}, fn + %{"result" => nil}, {blocks, _} -> {blocks, :end_of_chain} + %{"result" => %{} = block}, {blocks, next} -> {[block | blocks], next} + end) + + elixir_blocks = Blocks.to_elixir(blocks) + elixir_transactions = Blocks.elixir_to_transactions(elixir_blocks) + blocks_params = Blocks.elixir_to_params(elixir_blocks) + transactions_params = Transactions.elixir_to_params(elixir_transactions) + + {:ok, next, + %{ + blocks: blocks_params, + transactions: transactions_params + }} + end + + defp handle_get_block_by_number({:error, reason}) do + {:error, reason} + end + + defp handle_response(resp, 200) do + case resp do + [%{} | _] = batch_resp -> {:ok, batch_resp} + %{"error" => error} -> {:error, error} + %{"result" => result} -> {:ok, result} + end + end + + defp handle_response(resp, _status) do + {:error, resp} + end + + defp hexadecimal_to_integer("0x" <> hexadecimal_digits) do + String.to_integer(hexadecimal_digits, 16) + end + + defp int_to_hash_string(number), do: "0x" <> Integer.to_string(number, 16) end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/application.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/application.ex new file mode 100644 index 0000000000..38c398e23c --- /dev/null +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/application.ex @@ -0,0 +1,16 @@ +defmodule EthereumJSONRPC.Application do + @moduledoc """ + Starts `:hackney_pool` `:ethereum_jsonrpc`. + """ + + use Application + + @impl Application + def start(_type, _args) do + children = [ + :hackney_pool.child_spec(:ethereum_jsonrpc, recv_timeout: 60_000, timeout: 60_000, max_connections: 1000) + ] + + Supervisor.start_link(children, strategy: :one_for_one, name: EthereumJSONRPC.Supervisor) + end +end diff --git a/apps/explorer/lib/explorer/jsonrpc/block.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex similarity index 90% rename from apps/explorer/lib/explorer/jsonrpc/block.ex rename to apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex index e26a2608f0..1fc387cbf1 100644 --- a/apps/explorer/lib/explorer/jsonrpc/block.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex @@ -1,55 +1,55 @@ -defmodule Explorer.JSONRPC.Block do +defmodule EthereumJSONRPC.Block do @moduledoc """ Block format as returned by [`eth_getBlockByHash`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbyhash) and [`eth_getBlockByNumber`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbynumber). """ - import Explorer.JSONRPC, only: [nonce_to_integer: 1, quantity_to_integer: 1, timestamp_to_datetime: 1] + import EthereumJSONRPC, only: [nonce_to_integer: 1, quantity_to_integer: 1, timestamp_to_datetime: 1] - alias Explorer.JSONRPC - alias Explorer.JSONRPC.Transactions + alias EthereumJSONRPC + alias EthereumJSONRPC.Transactions @type elixir :: %{String.t() => non_neg_integer | DateTime.t() | String.t() | nil} @typedoc """ - * `"author"` - `t:Explorer.JSONRPC.address/0` that created the block. Aliased by `"miner"`. - * `"difficulty"` - `t:Explorer.JSONRPC.quantity/0`` of the difficulty for this block. - * `"extraData"` - the extra `t:Explorer.JSONRPC.data/0`` field of this block. - * `"gasLimit" - maximum gas `t:Explorer.JSONRPC.quantity/0`` in this block. - * `"gasUsed" - the total `t:Explorer.JSONRPC.quantity/0`` of gas used by all transactions in this block. - * `"hash"` - the `t:Explorer.JSONRPC.hash/0` of the block. - * `"logsBloom"` - `t:Explorer.JSONRPC.data/0`` for the [Bloom filter](https://en.wikipedia.org/wiki/Bloom_filter) + * `"author"` - `t:EthereumJSONRPC.address/0` that created the block. Aliased by `"miner"`. + * `"difficulty"` - `t:EthereumJSONRPC.quantity/0`` of the difficulty for this block. + * `"extraData"` - the extra `t:EthereumJSONRPC.data/0`` field of this block. + * `"gasLimit" - maximum gas `t:EthereumJSONRPC.quantity/0`` in this block. + * `"gasUsed" - the total `t:EthereumJSONRPC.quantity/0`` of gas used by all transactions in this block. + * `"hash"` - the `t:EthereumJSONRPC.hash/0` of the block. + * `"logsBloom"` - `t:EthereumJSONRPC.data/0`` for the [Bloom filter](https://en.wikipedia.org/wiki/Bloom_filter) for the logs of the block. `nil` when block is pending. - * `"miner"` - `t:Explorer.JSONRPC.address/0` of the beneficiary to whom the mining rewards were given. Aliased by + * `"miner"` - `t:EthereumJSONRPC.address/0` of the beneficiary to whom the mining rewards were given. Aliased by `"author"`. - * `"nonce"` - `t:Explorer.JSONRPC.nonce/0`. `nil` when its pending block. - * `"number"` - the block number `t:Explorer.JSONRPC.quantity/0`. `nil` when block is pending. - * `"parentHash" - the `t:Explorer.JSONRPC.hash/0` of the parent block. - * `"receiptsRoot"` - `t:Explorer.JSONRPC.hash/0` of the root of the receipts. + * `"nonce"` - `t:EthereumJSONRPC.nonce/0`. `nil` when its pending block. + * `"number"` - the block number `t:EthereumJSONRPC.quantity/0`. `nil` when block is pending. + * `"parentHash" - the `t:EthereumJSONRPC.hash/0` of the parent block. + * `"receiptsRoot"` - `t:EthereumJSONRPC.hash/0` of the root of the receipts. [trie](https://github.com/ethereum/wiki/wiki/Patricia-Tree) of the block. * `"sealFields"` - UNKNOWN - * `"sha3Uncles"` - `t:Explorer.JSONRPC.hash/0` of the + * `"sha3Uncles"` - `t:EthereumJSONRPC.hash/0` of the [uncles](https://bitcoin.stackexchange.com/questions/39329/in-ethereum-what-is-an-uncle-block) data in the block. * `"signature"` - UNKNOWN - * `"size"` - `t:Explorer.JSONRPC.quantity/0`` of bytes in this block - * `"stateRoot" - `t:Explorer.JSONRPC.hash/0` of the root of the final state + * `"size"` - `t:EthereumJSONRPC.quantity/0`` of bytes in this block + * `"stateRoot" - `t:EthereumJSONRPC.hash/0` of the root of the final state [trie](https://github.com/ethereum/wiki/wiki/Patricia-Tree) of the block. * `"step"` - UNKNOWN - * `"timestamp"`: the unix timestamp as a `t:Explorer.JSONRPC.quantity/0`` for when the block was collated. - * `"totalDifficulty" - `t:Explorer.JSONRPC.quantity/0`` of the total difficulty of the chain until this block. - * `"transactions"` - `t:list/0` of `t:Explorer.JSONRPC.Transaction.t/0`. - * `"transactionsRoot" - `t:Explorer.JSONRPC.hash/0` of the root of the transaction + * `"timestamp"`: the unix timestamp as a `t:EthereumJSONRPC.quantity/0`` for when the block was collated. + * `"totalDifficulty" - `t:EthereumJSONRPC.quantity/0`` of the total difficulty of the chain until this block. + * `"transactions"` - `t:list/0` of `t:EthereumJSONRPC.Transaction.t/0`. + * `"transactionsRoot" - `t:EthereumJSONRPC.hash/0` of the root of the transaction [trie](https://github.com/ethereum/wiki/wiki/Patricia-Tree) of the block. * `uncles`: `t:list/0` of [uncles](https://bitcoin.stackexchange.com/questions/39329/in-ethereum-what-is-an-uncle-block) - `t:Explorer.JSONRPC.hash/0`. + `t:EthereumJSONRPC.hash/0`. """ - @type t :: %{String.t() => JSONRPC.data() | JSONRPC.hash() | JSONRPC.quantity() | nil} + @type t :: %{String.t() => EthereumJSONRPC.data() | EthereumJSONRPC.hash() | EthereumJSONRPC.quantity() | nil} @doc """ Converts `t:elixir/0` format to params used in `Explorer.Chain`. - iex> Explorer.JSONRPC.Block.elixir_to_params( + iex> EthereumJSONRPC.Block.elixir_to_params( ...> %{ ...> "author" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", ...> "difficulty" => 340282366920938463463374607431465537093, @@ -125,9 +125,9 @@ defmodule Explorer.JSONRPC.Block do end @doc """ - Get `t:Explorer.JSONRPC.Transactions.elixir/0` from `t:elixir/0` + Get `t:EthereumJSONRPC.Transactions.elixir/0` from `t:elixir/0` - iex> Explorer.JSONRPC.Block.elixir_to_transactions( + iex> EthereumJSONRPC.Block.elixir_to_transactions( ...> %{ ...> "author" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", ...> "difficulty" => 340282366920938463463374607431768211454, @@ -211,7 +211,7 @@ defmodule Explorer.JSONRPC.Block do @doc """ Decodes the stringly typed numerical fields to `t:non_neg_integer/0` and the timestamps to `t:DateTime.t/0` - iex> Explorer.JSONRPC.Block.to_elixir( + iex> EthereumJSONRPC.Block.to_elixir( ...> %{ ...> "author" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", ...> "difficulty" => "0xfffffffffffffffffffffffffffffffe", @@ -278,7 +278,7 @@ defmodule Explorer.JSONRPC.Block do end # double check that no new keys are being missed by requiring explicit match for passthrough - # `t:Explorer.JSONRPC.address/0` and `t:Explorer.JSONRPC.hash/0` pass through as `Explorer.Chain` can verify correct + # `t:EthereumJSONRPC.address/0` and `t:EthereumJSONRPC.hash/0` pass through as `Explorer.Chain` can verify correct # hash format defp entry_to_elixir({key, _} = entry) when key in ~w(author extraData hash logsBloom miner parentHash receiptsRoot sealFields sha3Uncles signature diff --git a/apps/explorer/lib/explorer/jsonrpc/blocks.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex similarity index 98% rename from apps/explorer/lib/explorer/jsonrpc/blocks.ex rename to apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex index c0bcb09ed7..74f9bc5bd0 100644 --- a/apps/explorer/lib/explorer/jsonrpc/blocks.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex @@ -1,10 +1,10 @@ -defmodule Explorer.JSONRPC.Blocks do +defmodule EthereumJSONRPC.Blocks do @moduledoc """ Blocks format as returned by [`eth_getBlockByHash`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbyhash) and [`eth_getBlockByNumber`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbynumber) from batch requests. """ - alias Explorer.JSONRPC.{Block, Transactions} + alias EthereumJSONRPC.{Block, Transactions} @type elixir :: [Block.elixir()] @type t :: [Block.t()] @@ -12,7 +12,7 @@ defmodule Explorer.JSONRPC.Blocks do @doc """ Converts `t:elixir/0` elements to params used by `Explorer.Chain.Block.changeset/2`. - iex> Explorer.JSONRPC.Blocks.elixir_to_params( + iex> EthereumJSONRPC.Blocks.elixir_to_params( ...> [ ...> %{ ...> "author" => "0x0000000000000000000000000000000000000000", @@ -64,9 +64,9 @@ defmodule Explorer.JSONRPC.Blocks do end @doc """ - Extracts the `t:Explorer.JSONRPC.Transactions.elixir/0` from the `t:elixir/0`. + Extracts the `t:EthereumJSONRPC.Transactions.elixir/0` from the `t:elixir/0`. - iex> Explorer.JSONRPC.Blocks.elixir_to_transactions([ + iex> EthereumJSONRPC.Blocks.elixir_to_transactions([ ...> %{ ...> "author" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", ...> "difficulty" => 340282366920938463463374607431768211454, @@ -150,7 +150,7 @@ defmodule Explorer.JSONRPC.Blocks do @doc """ Decodes the stringly typed numerical fields to `t:non_neg_integer/0` and the timestamps to `t:DateTime.t/0` - iex> Explorer.JSONRPC.Blocks.to_elixir( + iex> EthereumJSONRPC.Blocks.to_elixir( ...> [ ...> %{ ...> "author" => "0x0000000000000000000000000000000000000000", diff --git a/apps/explorer/lib/explorer/jsonrpc/log.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex similarity index 85% rename from apps/explorer/lib/explorer/jsonrpc/log.ex rename to apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex index c06a1f3c7d..34da3af457 100644 --- a/apps/explorer/lib/explorer/jsonrpc/log.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex @@ -1,29 +1,29 @@ -defmodule Explorer.JSONRPC.Log do +defmodule EthereumJSONRPC.Log do @moduledoc """ Log included in return from [`eth_getTransactionReceipt`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionreceipt). """ - import Explorer.JSONRPC, only: [quantity_to_integer: 1] + import EthereumJSONRPC, only: [quantity_to_integer: 1] @type elixir :: %{String.t() => String.t() | [String.t()] | non_neg_integer()} @typedoc """ - * `"address"` - `t:Explorer.JSONRPC.address/0` from which event originated. - * `"blockHash"` - `t:Explorer.JSONRPC.hash/0` of the block this transaction is in. - * `"blockNumber"` - `t:Explorer.JSONRPC.quantity/0` for the block number this transaction is in. + * `"address"` - `t:EthereumJSONRPC.address/0` from which event originated. + * `"blockHash"` - `t:EthereumJSONRPC.hash/0` of the block this transaction is in. + * `"blockNumber"` - `t:EthereumJSONRPC.quantity/0` for the block number this transaction is in. * `"data"` - Data containing non-indexed log parameter - * `"logIndex"` - `t:Explorer.JSONRPC.quantity/0` of the event index positon in the block. + * `"logIndex"` - `t:EthereumJSONRPC.quantity/0` of the event index positon in the block. * `"topics"` - `t:list/0` of at most 4 32-byte topics. Topic 1-3 contains indexed parameters of the log. - * `"transactionHash"` - `t:Explorer.JSONRPC.hash/0` of the transaction - * `"transactionIndex"` - `t:Explorer.JSONRPC.quantity/0` for the index of the transaction in the block. + * `"transactionHash"` - `t:EthereumJSONRPC.hash/0` of the transaction + * `"transactionIndex"` - `t:EthereumJSONRPC.quantity/0` for the index of the transaction in the block. """ @type t :: %{String.t() => String.t() | [String.t()]} @doc """ Converts `t:elixir/0` format to params used in `Explorer.Chain`. - iex> Explorer.JSONRPC.Log.elixir_to_params( + iex> EthereumJSONRPC.Log.elixir_to_params( ...> %{ ...> "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", ...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", @@ -71,7 +71,7 @@ defmodule Explorer.JSONRPC.Log do @doc """ Decodes the stringly typed numerical fields to `t:non_neg_integer/0`. - iex> Explorer.JSONRPC.Log.to_elixir( + iex> EthereumJSONRPC.Log.to_elixir( ...> %{ ...> "address" => "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", ...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", diff --git a/apps/explorer/lib/explorer/jsonrpc/logs.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/logs.ex similarity index 88% rename from apps/explorer/lib/explorer/jsonrpc/logs.ex rename to apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/logs.ex index 591ee635aa..7c4720f79b 100644 --- a/apps/explorer/lib/explorer/jsonrpc/logs.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/logs.ex @@ -1,10 +1,10 @@ -defmodule Explorer.JSONRPC.Logs do +defmodule EthereumJSONRPC.Logs do @moduledoc """ Collection of logs included in return from [`eth_getTransactionReceipt`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionreceipt). """ - alias Explorer.JSONRPC.Log + alias EthereumJSONRPC.Log @type elixir :: [Log.elixir()] @type t :: [Log.t()] diff --git a/apps/explorer/lib/explorer/jsonrpc/parity.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity.ex similarity index 97% rename from apps/explorer/lib/explorer/jsonrpc/parity.ex rename to apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity.ex index f5ecde2afd..7bebac6ba6 100644 --- a/apps/explorer/lib/explorer/jsonrpc/parity.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity.ex @@ -1,16 +1,16 @@ -defmodule Explorer.JSONRPC.Parity do +defmodule EthereumJSONRPC.Parity do @moduledoc """ Ethereum JSONRPC methods that are only supported by [Parity](https://wiki.parity.io/). """ - import Explorer.JSONRPC, only: [config: 1, json_rpc: 2] + import EthereumJSONRPC, only: [config: 1, json_rpc: 2] - alias Explorer.JSONRPC.Parity.Traces + alias EthereumJSONRPC.Parity.Traces @doc """ Fetches the `t:Explorer.Chain.InternalTransaction.changeset/2` params from the Parity trace URL. - iex> Explorer.JSONRPC.Parity.fetch_internal_transactions([ + iex> EthereumJSONRPC.Parity.fetch_internal_transactions([ ...> "0x0fa6f723216dba694337f9bb37d8870725655bdf2573526a39454685659e39b1" ...> ]) {:ok, diff --git a/apps/explorer/lib/explorer/jsonrpc/parity/trace.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/trace.ex similarity index 98% rename from apps/explorer/lib/explorer/jsonrpc/parity/trace.ex rename to apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/trace.ex index 43922fcc76..42851cb28f 100644 --- a/apps/explorer/lib/explorer/jsonrpc/parity/trace.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/trace.ex @@ -1,16 +1,16 @@ -defmodule Explorer.JSONRPC.Parity.Trace do +defmodule EthereumJSONRPC.Parity.Trace do @moduledoc """ Trace returned by [`trace_replayTransaction`](https://wiki.parity.io/JSONRPC-trace-module.html#trace_replaytransaction), which is an extension to the Ethereum JSONRPC standard that is only supported by [Parity](https://wiki.parity.io/). """ - alias Explorer.JSONRPC.Parity.Trace.{Action, Result} + alias EthereumJSONRPC.Parity.Trace.{Action, Result} @doc """ Create type traces are generated when a contract is created. - iex> Explorer.JSONRPC.Parity.Trace.elixir_to_params( + iex> EthereumJSONRPC.Parity.Trace.elixir_to_params( ...> %{ ...> "action" => %{ ...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", @@ -46,7 +46,7 @@ defmodule Explorer.JSONRPC.Parity.Trace do A create can fail due to a Bad Instruction in the `init` that is meant to form the `code` of the contract - iex> Explorer.JSONRPC.Parity.Trace.elixir_to_params( + iex> EthereumJSONRPC.Parity.Trace.elixir_to_params( ...> %{ ...> "action" => %{ ...> "from" => "0x78a42d3705fb3c26a4b54737a784bf064f0815fb", @@ -76,7 +76,7 @@ defmodule Explorer.JSONRPC.Parity.Trace do Call type traces are generated when a method is called. Calls are further divided by call type. - iex> Explorer.JSONRPC.Parity.Trace.elixir_to_params( + iex> EthereumJSONRPC.Parity.Trace.elixir_to_params( ...> %{ ...> "action" => %{ ...> "callType" => "call", @@ -113,7 +113,7 @@ defmodule Explorer.JSONRPC.Parity.Trace do Calls can error and be reverted - iex> Explorer.JSONRPC.Parity.Trace.elixir_to_params( + iex> EthereumJSONRPC.Parity.Trace.elixir_to_params( ...> %{ ...> "action" => %{ ...> "callType" => "call", @@ -153,7 +153,7 @@ defmodule Explorer.JSONRPC.Parity.Trace do | `"balance"` | `:value` | | `"refundAddress"` | `:to_address_hash` | - iex> Explorer.JSONRPC.Parity.Trace.elixir_to_params( + iex> EthereumJSONRPC.Parity.Trace.elixir_to_params( ...> %{ ...> "action" => %{ ...> "address" => "0xa7542d78b9a0be6147536887e0065f16182d294b", @@ -251,7 +251,7 @@ defmodule Explorer.JSONRPC.Parity.Trace do @doc """ Decodes the stringly typed numerical fields to `t:non_neg_integer/0`. - iex> Explorer.JSONRPC.Parity.Trace.to_elixir( + iex> EthereumJSONRPC.Parity.Trace.to_elixir( ...> %{ ...> "action" => %{ ...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", @@ -293,7 +293,7 @@ defmodule Explorer.JSONRPC.Parity.Trace do The caller must put `"index"` and `"transactionHash"` into the incoming map, as Parity itself does not include that information, but it is needed to locate the trace in history fully. - iex> Explorer.JSONRPC.Parity.Trace.to_elixir( + iex> EthereumJSONRPC.Parity.Trace.to_elixir( ...> %{ ...> "action" => %{ ...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", @@ -316,7 +316,7 @@ defmodule Explorer.JSONRPC.Parity.Trace do `"suicide"` `"type"` traces are different in that they have a `nil` `"result"`. This is because the `"result"` key is used to indicate success from Parity. - iex> Explorer.JSONRPC.Parity.Trace.to_elixir( + iex> EthereumJSONRPC.Parity.Trace.to_elixir( ...> %{ ...> "action" => %{ ...> "address" => "0xa7542d78b9a0be6147536887e0065f16182d294b", @@ -347,7 +347,7 @@ defmodule Explorer.JSONRPC.Parity.Trace do A call type trace can error and be reverted. - iex> Explorer.JSONRPC.Parity.Trace.to_elixir( + iex> EthereumJSONRPC.Parity.Trace.to_elixir( ...> %{ ...> "action" => %{ ...> "callType" => "call", diff --git a/apps/explorer/lib/explorer/jsonrpc/parity/trace/action.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/trace/action.ex similarity index 93% rename from apps/explorer/lib/explorer/jsonrpc/parity/trace/action.ex rename to apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/trace/action.ex index 7fd26ccf4b..67b675c83b 100644 --- a/apps/explorer/lib/explorer/jsonrpc/parity/trace/action.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/trace/action.ex @@ -1,14 +1,14 @@ -defmodule Explorer.JSONRPC.Parity.Trace.Action do +defmodule EthereumJSONRPC.Parity.Trace.Action do @moduledoc """ - The action that was peformed in a `t:Explorer.JSONRPC.Parity.Trace.t/0` + The action that was peformed in a `t:EthereumJSONRPC.Parity.Trace.t/0` """ - import Explorer.JSONRPC, only: [quantity_to_integer: 1] + import EthereumJSONRPC, only: [quantity_to_integer: 1] @doc """ Decodes the stringly typed numerical fields to `t:non_neg_integer/0`. - iex> Explorer.JSONRPC.Parity.Trace.Action.to_elixir( + iex> EthereumJSONRPC.Parity.Trace.Action.to_elixir( ...> %{ ...> "from" => "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", ...> "gas" => "0x462534", @@ -24,9 +24,9 @@ defmodule Explorer.JSONRPC.Parity.Trace.Action do } For a suicide, the `"balance"` is converted to a `t:non_neg_integer/0` while the `"address"` and `"refundAddress"` - `t:Explorer.JSONRPC.hash/0` pass through. + `t:EthereumJSONRPC.hash/0` pass through. - iex> Explorer.JSONRPC.Parity.Trace.Action.to_elixir( + iex> EthereumJSONRPC.Parity.Trace.Action.to_elixir( ...> %{ ...> "address" => "0xa7542d78b9a0be6147536887e0065f16182d294b", ...> "balance" => "0x0", diff --git a/apps/explorer/lib/explorer/jsonrpc/parity/trace/result.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/trace/result.ex similarity index 91% rename from apps/explorer/lib/explorer/jsonrpc/parity/trace/result.ex rename to apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/trace/result.ex index bf505a24df..23abb45188 100644 --- a/apps/explorer/lib/explorer/jsonrpc/parity/trace/result.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/trace/result.ex @@ -1,14 +1,14 @@ -defmodule Explorer.JSONRPC.Parity.Trace.Result do +defmodule EthereumJSONRPC.Parity.Trace.Result do @moduledoc """ - The result of performing the `t:Explorer.JSONRPC.Parity.Action.t/0` in a `t:Explorer.JSONRPC.Parity.Trace.t/0`. + The result of performing the `t:EthereumJSONRPC.Parity.Action.t/0` in a `t:EthereumJSONRPC.Parity.Trace.t/0`. """ - import Explorer.JSONRPC, only: [quantity_to_integer: 1] + import EthereumJSONRPC, only: [quantity_to_integer: 1] @doc """ Decodes the stringly typed numerical fields to `t:non_neg_integer/0`. - iex> Explorer.JSONRPC.Parity.Trace.Result.to_elixir( + iex> EthereumJSONRPC.Parity.Trace.Result.to_elixir( ...> %{ ...> "address" => "0xffc87239eb0267bc3ca2cd51d12fbf278e02ccb4", ...> "code" => "0x606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680630900f01014610067578063445df0ac146100a05780638da5cb5b146100c9578063fdacd5761461011e575b600080fd5b341561007257600080fd5b61009e600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610141565b005b34156100ab57600080fd5b6100b3610224565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc61022a565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b61013f600480803590602001909190505061024f565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415610220578190508073ffffffffffffffffffffffffffffffffffffffff1663fdacd5766001546040518263ffffffff167c010000000000000000000000000000000000000000000000000000000002815260040180828152602001915050600060405180830381600087803b151561020b57600080fd5b6102c65a03f1151561021c57600080fd5b5050505b5050565b60015481565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102ac57806001819055505b505600a165627a7a72305820a9c628775efbfbc17477a472413c01ee9b33881f550c59d21bee9928835c854b0029", @@ -23,7 +23,7 @@ defmodule Explorer.JSONRPC.Parity.Trace.Result do `nil` resultscan occur for suicide type traces. - iex> Explorer.JSONRPC.Parity.Trace.Result.to_elixir(nil) + iex> EthereumJSONRPC.Parity.Trace.Result.to_elixir(nil) nil """ diff --git a/apps/explorer/lib/explorer/jsonrpc/parity/traces.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/traces.ex similarity index 85% rename from apps/explorer/lib/explorer/jsonrpc/parity/traces.ex rename to apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/traces.ex index ccb7e4cce6..f18186fd25 100644 --- a/apps/explorer/lib/explorer/jsonrpc/parity/traces.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/traces.ex @@ -1,11 +1,11 @@ -defmodule Explorer.JSONRPC.Parity.Traces do +defmodule EthereumJSONRPC.Parity.Traces do @moduledoc """ Trace returned by [`trace_replayTransaction`](https://wiki.parity.io/JSONRPC-trace-module.html#trace_replaytransaction), which is an extension to the Ethereum JSONRPC standard that is only supported by [Parity](https://wiki.parity.io/). """ - alias Explorer.JSONRPC.Parity.Trace + alias EthereumJSONRPC.Parity.Trace def elixir_to_params(elixir) when is_list(elixir) do Enum.map(elixir, &Trace.elixir_to_params/1) diff --git a/apps/explorer/lib/explorer/jsonrpc/receipt.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex similarity index 78% rename from apps/explorer/lib/explorer/jsonrpc/receipt.ex rename to apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex index bd38d9f135..f1600184a4 100644 --- a/apps/explorer/lib/explorer/jsonrpc/receipt.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex @@ -1,37 +1,45 @@ -defmodule Explorer.JSONRPC.Receipt do +defmodule EthereumJSONRPC.Receipt do @moduledoc """ Receipts format as returned by [`eth_getTransactionReceipt`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionreceipt). """ - import Explorer.JSONRPC, only: [quantity_to_integer: 1] + import EthereumJSONRPC, only: [quantity_to_integer: 1] alias Explorer.Chain.Receipt.Status - alias Explorer.JSONRPC - alias Explorer.JSONRPC.Logs + alias EthereumJSONRPC + alias EthereumJSONRPC.Logs @type elixir :: %{String.t() => String.t() | non_neg_integer} @typedoc """ - * `"contractAddress"` - The contract `t:Explorer.JSONRPC.address/0` created, if the transaction was a contract + * `"contractAddress"` - The contract `t:EthereumJSONRPC.address/0` created, if the transaction was a contract creation, otherwise `nil`. - * `"blockHash"` - `t:Explorer.JSONRPC.hash/0` of the block where `"transactionHash"` was in. - * `"blockNumber"` - The block number `t:Explorer.JSONRPC.quanity/0`. - * `"cumulativeGasUsed"` - `t:Explorer.JSONRPC.quantity/0` of gas used when this transaction was executed in the + * `"blockHash"` - `t:EthereumJSONRPC.hash/0` of the block where `"transactionHash"` was in. + * `"blockNumber"` - The block number `t:EthereumJSONRPC.quanity/0`. + * `"cumulativeGasUsed"` - `t:EthereumJSONRPC.quantity/0` of gas used when this transaction was executed in the block. - * `"gasUsed"` - `t:Explorer.JSONRPC.quantity/0` of gas used by this specific transaction alone. + * `"gasUsed"` - `t:EthereumJSONRPC.quantity/0` of gas used by this specific transaction alone. * `"logs"` - `t:list/0` of log objects, which this transaction generated. - * `"logsBloom"` - `t:Explorer.JSONRPC.data/0` of 256 Bytes for + * `"logsBloom"` - `t:EthereumJSONRPC.data/0` of 256 Bytes for [Bloom filter](https://en.wikipedia.org/wiki/Bloom_filter) for light clients to quickly retrieve related logs. - * `"root"` - `t:Explorer.JSONRPC.hash/0` of post-transaction stateroot (pre-Byzantium) - * `"status"` - `t:Explorer.JSONRPC.quantity/0` of either 1 (success) or 0 (failure) (post-Byzantium) - * `"transactionHash"` - `t:Explorer.JSONRPC.hash/0` the transaction. - * `"transactionIndex"` - `t:Explorer.JSONRPC.quantity/0` for the transaction index in the block. + * `"root"` - `t:EthereumJSONRPC.hash/0` of post-transaction stateroot (pre-Byzantium) + * `"status"` - `t:EthereumJSONRPC.quantity/0` of either 1 (success) or 0 (failure) (post-Byzantium) + * `"transactionHash"` - `t:EthereumJSONRPC.hash/0` the transaction. + * `"transactionIndex"` - `t:EthereumJSONRPC.quantity/0` for the transaction index in the block. """ - @type t :: %{String.t() => JSONRPC.address() | JSONRPC.data() | JSONRPC.hash() | JSONRPC.quantity() | list | nil} + @type t :: %{ + String.t() => + EthereumJSONRPC.address() + | EthereumJSONRPC.data() + | EthereumJSONRPC.hash() + | EthereumJSONRPC.quantity() + | list + | nil + } @doc """ - Get `t:Explorer.JSONRPC.Logs.elixir/0` from `t:elixir/0` + Get `t:EthereumJSONRPC.Logs.elixir/0` from `t:elixir/0` """ @spec elixir_to_logs(elixir) :: Logs.elixir() def elixir_to_logs(%{"logs" => logs}), do: logs @@ -39,7 +47,7 @@ defmodule Explorer.JSONRPC.Receipt do @doc """ Converts `t:elixir/0` format to params used in `Explorer.Chain`. - iex> Explorer.JSONRPC.Receipt.elixir_to_params( + iex> EthereumJSONRPC.Receipt.elixir_to_params( ...> %{ ...> "blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", ...> "blockNumber" => 34, @@ -89,7 +97,7 @@ defmodule Explorer.JSONRPC.Receipt do @doc """ Decodes the stringly typed numerical fields to `t:non_neg_integer/0`. - iex> Explorer.JSONRPC.Receipt.to_elixir( + iex> EthereumJSONRPC.Receipt.to_elixir( ...> %{ ...> "blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", ...> "blockNumber" => "0x22", @@ -125,7 +133,7 @@ defmodule Explorer.JSONRPC.Receipt do end # double check that no new keys are being missed by requiring explicit match for passthrough - # `t:Explorer.JSONRPC.address/0` and `t:Explorer.JSONRPC.hash/0` pass through as `Explorer.Chain` can verify correct + # `t:EthereumJSONRPC.address/0` and `t:EthereumJSONRPC.hash/0` pass through as `Explorer.Chain` can verify correct # hash format defp entry_to_elixir({key, _} = entry) when key in ~w(blockHash contractAddress logsBloom root transactionHash), do: entry diff --git a/apps/explorer/lib/explorer/jsonrpc/receipts.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex similarity index 97% rename from apps/explorer/lib/explorer/jsonrpc/receipts.ex rename to apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex index 53098ca4cc..83a704c36f 100644 --- a/apps/explorer/lib/explorer/jsonrpc/receipts.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex @@ -1,13 +1,13 @@ -defmodule Explorer.JSONRPC.Receipts do +defmodule EthereumJSONRPC.Receipts do @moduledoc """ Receipts format as returned by [`eth_getTransactionReceipt`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionreceipt) from batch requests. """ - import Explorer.JSONRPC, only: [config: 1, json_rpc: 2] + import EthereumJSONRPC, only: [config: 1, json_rpc: 2] - alias Explorer.JSONRPC.{Logs, Receipt} + alias EthereumJSONRPC.{Logs, Receipt} @type elixir :: [Receipt.elixir()] @type t :: [Receipt.t()] @@ -15,7 +15,7 @@ defmodule Explorer.JSONRPC.Receipts do @doc """ Extracts logs from `t:elixir/0` - iex> Explorer.JSONRPC.Receipts.elixir_to_logs([ + iex> EthereumJSONRPC.Receipts.elixir_to_logs([ ...> %{ ...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", ...> "blockNumber" => 37, @@ -67,7 +67,7 @@ defmodule Explorer.JSONRPC.Receipts do @doc """ Converts each element of `t:elixir/0` to params used by `Explorer.Chain.Receipt.changeset/2`. - iex> Explorer.JSONRPC.Receipts.elixir_to_params([ + iex> EthereumJSONRPC.Receipts.elixir_to_params([ ...> %{ ...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", ...> "blockNumber" => 37, @@ -136,7 +136,7 @@ defmodule Explorer.JSONRPC.Receipts do @doc """ Converts stringly typed fields to native Elixir types. - iex> Explorer.JSONRPC.Receipts.to_elixir([ + iex> EthereumJSONRPC.Receipts.to_elixir([ ...> %{ ...> "blockHash" => "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", ...> "blockNumber" => "0x25", diff --git a/apps/explorer/lib/explorer/jsonrpc/transaction.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex similarity index 70% rename from apps/explorer/lib/explorer/jsonrpc/transaction.ex rename to apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex index fd7e589ee4..adb827d0ee 100644 --- a/apps/explorer/lib/explorer/jsonrpc/transaction.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex @@ -1,4 +1,4 @@ -defmodule Explorer.JSONRPC.Transaction do +defmodule EthereumJSONRPC.Transaction do @moduledoc """ Transaction format included in the return of [`eth_getBlockByHash`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbyhash) @@ -8,45 +8,50 @@ defmodule Explorer.JSONRPC.Transaction do and [`eth_getTransactionByBlockNumberAndIndex`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_gettransactionbyblocknumberandindex) """ - import Explorer.JSONRPC, only: [quantity_to_integer: 1] + import EthereumJSONRPC, only: [quantity_to_integer: 1] - alias Explorer.JSONRPC + alias EthereumJSONRPC - @type elixir :: %{String.t() => JSONRPC.address() | JSONRPC.hash() | String.t() | non_neg_integer() | nil} + @type elixir :: %{ + String.t() => EthereumJSONRPC.address() | EthereumJSONRPC.hash() | String.t() | non_neg_integer() | nil + } @typedoc """ - * `"blockHash"` - `t:Explorer.JSONRPC.hash/0` of the block this transaction is in. `nil` when transaction is + * `"blockHash"` - `t:EthereumJSONRPC.hash/0` of the block this transaction is in. `nil` when transaction is pending. - * `"blockNumber"` - `t:Explorer.JSONRPC.quantity/0` for the block number this transaction is in. `nil` when + * `"blockNumber"` - `t:EthereumJSONRPC.quantity/0` for the block number this transaction is in. `nil` when transaction is pending. * `"chainId"` - the chain on which the transaction exists. * `"condition"` - UNKNOWN - * `"creates"` - `t:Explorer.JSONRPC.address/0` of the created contract, if the transaction creates a contract. - * `"from"` - `t:Explorer.JSONRPC.address/0` of the sender. - * `"gas"` - `t:Explorer.JSONRPC.quantity/0` of gas provided by the sender. This is the max gas that may be used. + * `"creates"` - `t:EthereumJSONRPC.address/0` of the created contract, if the transaction creates a contract. + * `"from"` - `t:EthereumJSONRPC.address/0` of the sender. + * `"gas"` - `t:EthereumJSONRPC.quantity/0` of gas provided by the sender. This is the max gas that may be used. `gas * gasPrice` is the max fee in wei that the sender is willing to pay for the transaction to be executed. - * `"gasPrice"` - `t:Explorer.JSONRPC.quantity/0` of wei to pay per unit of gas used. - * `"hash"` - `t:Explorer.JSONRPC.hash/0` of the transaction - * `"input"` - `t:Explorer.JSONRPC.data/0` sent along with the transaction, such as input to the contract. - * `"nonce"` - `t:Explorer.JSONRPC.quantity/0` of transactions made by the sender prior to this one. - * `"publicKey"` - `t:Explorer.JSONRPC.hash/0` of the public key of the signer. - * `"r"` - `t:Explorer.JSONRPC.quantity/0` for the R field of the signature. - * `"raw"` - Raw transaction `t:Explorer.JSONRPC.data/0` - * `"standardV"` - `t:Explorer.JSONRPC.quantity/0` for the standardized V (`0` or `1`) field of the signature. - * `"to"` - `t:Explorer.JSONRPC.address/0` of the receiver. `nil` when it is a contract creation transaction. - * `"transactionIndex"` - `t:Explorer.JSONRPC.quantity/0` for the index of the transaction in the block. `nil` when + * `"gasPrice"` - `t:EthereumJSONRPC.quantity/0` of wei to pay per unit of gas used. + * `"hash"` - `t:EthereumJSONRPC.hash/0` of the transaction + * `"input"` - `t:EthereumJSONRPC.data/0` sent along with the transaction, such as input to the contract. + * `"nonce"` - `t:EthereumJSONRPC.quantity/0` of transactions made by the sender prior to this one. + * `"publicKey"` - `t:EthereumJSONRPC.hash/0` of the public key of the signer. + * `"r"` - `t:EthereumJSONRPC.quantity/0` for the R field of the signature. + * `"raw"` - Raw transaction `t:EthereumJSONRPC.data/0` + * `"standardV"` - `t:EthereumJSONRPC.quantity/0` for the standardized V (`0` or `1`) field of the signature. + * `"to"` - `t:EthereumJSONRPC.address/0` of the receiver. `nil` when it is a contract creation transaction. + * `"transactionIndex"` - `t:EthereumJSONRPC.quantity/0` for the index of the transaction in the block. `nil` when transaction is pending. - * `"v"` - `t:Explorer.JSONRPC.quantity/0` for the V field of the signature. - * `"value"` - `t:Explorer.JSONRPC.quantity/0` of wei transfered + * `"v"` - `t:EthereumJSONRPC.quantity/0` for the V field of the signature. + * `"value"` - `t:EthereumJSONRPC.quantity/0` of wei transfered """ - @type t :: %{String.t() => JSONRPC.address() | JSONRPC.hash() | JSONRPC.quantity() | String.t() | nil} + @type t :: %{ + String.t() => + EthereumJSONRPC.address() | EthereumJSONRPC.hash() | EthereumJSONRPC.quantity() | String.t() | nil + } @type params :: %{ - block_hash: JSONRPC.hash(), - from_address_hash: JSONRPC.address(), + block_hash: EthereumJSONRPC.hash(), + from_address_hash: EthereumJSONRPC.address(), gas: non_neg_integer(), gas_price: non_neg_integer(), - hash: JSONRPC.hash(), + hash: EthereumJSONRPC.hash(), index: non_neg_integer(), input: String.t(), nonce: non_neg_integer(), @@ -54,7 +59,7 @@ defmodule Explorer.JSONRPC.Transaction do r: non_neg_integer(), s: non_neg_integer(), standard_v: 0 | 1, - to_address_hash: JSONRPC.address(), + to_address_hash: EthereumJSONRPC.address(), v: non_neg_integer(), value: non_neg_integer() } @@ -97,9 +102,9 @@ defmodule Explorer.JSONRPC.Transaction do end @doc """ - Extracts `t:Explorer.JSONRPC.hash/0` from transaction `params` + Extracts `t:EthereumJSONRPC.hash/0` from transaction `params` - iex> Explorer.JSONRPC.Transaction.params_to_hash( + iex> EthereumJSONRPC.Transaction.params_to_hash( ...> %{ ...> block_hash: "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", ...> gas: 4700000, @@ -129,7 +134,7 @@ defmodule Explorer.JSONRPC.Transaction do end # double check that no new keys are being missed by requiring explicit match for passthrough - # `t:Explorer.JSONRPC.address/0` and `t:Explorer.JSONRPC.hash/0` pass through as `Explorer.Chain` can verify correct + # `t:EthereumJSONRPC.address/0` and `t:EthereumJSONRPC.hash/0` pass through as `Explorer.Chain` can verify correct # hash format # r s standardV and v pass through because they exceed postgres integer limits defp entry_to_elixir({key, value}) diff --git a/apps/explorer/lib/explorer/jsonrpc/transactions.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transactions.ex similarity index 98% rename from apps/explorer/lib/explorer/jsonrpc/transactions.ex rename to apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transactions.ex index 9551a537f3..9eb00c3ba4 100644 --- a/apps/explorer/lib/explorer/jsonrpc/transactions.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transactions.ex @@ -1,11 +1,11 @@ -defmodule Explorer.JSONRPC.Transactions do +defmodule EthereumJSONRPC.Transactions do @moduledoc """ List of transactions format as included in return from [`eth_getBlockByHash`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbyhash) and [`eth_getBlockByNumber`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbynumber). """ - alias Explorer.JSONRPC.Transaction + alias EthereumJSONRPC.Transaction @type elixir :: [Transaction.elixir()] @type t :: [Transaction.t()] @@ -13,7 +13,7 @@ defmodule Explorer.JSONRPC.Transactions do @doc """ Converts each entry in `elixir` to params used in `Explorer.Chain.Transaction.changeset/2`. - iex> Explorer.JSONRPC.Transactions.elixir_to_params( + iex> EthereumJSONRPC.Transactions.elixir_to_params( ...> [ ...> %{ ...> "blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", @@ -67,7 +67,7 @@ defmodule Explorer.JSONRPC.Transactions do @doc """ Extract just the `t:Explorer.Chain.Transaction.t/0` `hash` from `params` list elements. - iex> Explorer.JSONRPC.Transactions.params_to_hashes( + iex> EthereumJSONRPC.Transactions.params_to_hashes( ...> [ ...> %{ ...> block_hash: "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", @@ -98,7 +98,7 @@ defmodule Explorer.JSONRPC.Transactions do @doc """ Decodes stringly typed fields in entries in `transactions` - iex> Explorer.JSONRPC.Transactions.to_elixir([ + iex> EthereumJSONRPC.Transactions.to_elixir([ ...> %{ ...> "blockHash" => "0xe52d77084cab13a4e724162bcd8c6028e5ecfaa04d091ee476e96b9958ed6b47", ...> "blockNumber" => "0x22", diff --git a/apps/ethereum_jsonrpc/mix.exs b/apps/ethereum_jsonrpc/mix.exs index 0dc672b64b..975fd08b05 100644 --- a/apps/ethereum_jsonrpc/mix.exs +++ b/apps/ethereum_jsonrpc/mix.exs @@ -3,31 +3,63 @@ defmodule EthereumJsonrpc.MixProject do def project do [ + aliases: aliases(Mix.env()), app: :ethereum_jsonrpc, - version: "0.1.0", build_path: "../../_build", config_path: "../../config/config.exs", + deps: deps(), deps_path: "../../deps", - lockfile: "../../mix.lock", + dialyzer: [ + plt_add_deps: :transitive, + plt_add_apps: [:mix], + ignore_warnings: "../../.dialyzer-ignore" + ], elixir: "~> 1.6", + lockfile: "../../mix.lock", + preferred_cli_env: [ + coveralls: :test, + "coveralls.detail": :test, + "coveralls.post": :test, + "coveralls.html": :test, + dialyzer: :test + ], start_permanent: Mix.env() == :prod, - deps: deps() + test_coverage: [tool: ExCoveralls], + version: "0.1.0" ] end # Run "mix help compile.app" to learn about applications. def application do [ + mod: {EthereumJSONRPC.Application, []}, extra_applications: [:logger] ] end + defp aliases(env) do + env_aliases(env) + end + + defp env_aliases(:dev), do: [] + + defp env_aliases(_env), do: [compile: "compile --warnings-as-errors"] + # Run "mix help deps" to learn about dependencies. defp deps do [ - # {:dep_from_hexpm, "~> 0.3.0"}, - # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}, - # {:sibling_app_in_umbrella, in_umbrella: true}, + # Style Checking + {:credo, "0.9.2", only: [:dev, :test], runtime: false}, + # Static Type Checking + {:dialyxir, "~> 0.5", only: [:dev, :test], runtime: false}, + # Code coverage + {:excoveralls, "~> 0.8.1", only: [:test]}, + # JSONRPC HTTP Post calls + {:httpoison, "~> 1.0", override: true}, + # Decode/Encode JSON for JSONRPC + {:jason, "~> 1.0"}, + # Convert unix timestamps in JSONRPC to DateTimes + {:timex, "~> 3.1.24"} ] end end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs new file mode 100644 index 0000000000..3075dd4b74 --- /dev/null +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs @@ -0,0 +1,5 @@ +defmodule EthereumJSONRPC.BlockTest do + use ExUnit.Case, async: true + + doctest EthereumJSONRPC.Block +end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/blocks_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/blocks_test.exs new file mode 100644 index 0000000000..37626d801d --- /dev/null +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/blocks_test.exs @@ -0,0 +1,5 @@ +defmodule EthereumJSONRPC.BlocksTest do + use ExUnit.Case, async: true + + doctest EthereumJSONRPC.Blocks +end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/log_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/log_test.exs new file mode 100644 index 0000000000..a2c43064cf --- /dev/null +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/log_test.exs @@ -0,0 +1,5 @@ +defmodule EthereumJSONRPC.LogTest do + use ExUnit.Case, async: true + + doctest EthereumJSONRPC.Log +end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/trace/action_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/trace/action_test.exs new file mode 100644 index 0000000000..78597b57f1 --- /dev/null +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/trace/action_test.exs @@ -0,0 +1,5 @@ +defmodule EthereumJSONRPC.Parity.Trace.ActionTest do + use ExUnit.Case, async: true + + doctest EthereumJSONRPC.Parity.Trace.Action +end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/trace/result_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/trace/result_test.exs new file mode 100644 index 0000000000..f419f5045b --- /dev/null +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/trace/result_test.exs @@ -0,0 +1,5 @@ +defmodule EthereumJSONRPC.Parity.Trace.ResultTest do + use ExUnit.Case, async: true + + doctest EthereumJSONRPC.Parity.Trace.Result +end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/trace_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/trace_test.exs new file mode 100644 index 0000000000..75daf2eb04 --- /dev/null +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity/trace_test.exs @@ -0,0 +1,5 @@ +defmodule EthereumJSONRPC.Parity.TraceTest do + use ExUnit.Case, async: true + + doctest EthereumJSONRPC.Parity.Trace +end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity_test.exs new file mode 100644 index 0000000000..e953ad0146 --- /dev/null +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/parity_test.exs @@ -0,0 +1,5 @@ +defmodule EthereumJSONRPC.ParityTest do + use ExUnit.Case, async: true + + doctest EthereumJSONRPC.Parity +end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipt_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipt_test.exs new file mode 100644 index 0000000000..95b9a3abf4 --- /dev/null +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipt_test.exs @@ -0,0 +1,5 @@ +defmodule EthereumJSONRPC.ReceiptTest do + use ExUnit.Case, async: true + + doctest EthereumJSONRPC.Receipt +end diff --git a/apps/explorer/test/explorer/jsonrpc/receipts_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs similarity index 92% rename from apps/explorer/test/explorer/jsonrpc/receipts_test.exs rename to apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs index 46b887152c..fab742b008 100644 --- a/apps/explorer/test/explorer/jsonrpc/receipts_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs @@ -1,13 +1,13 @@ -defmodule Explorer.JSONRPC.ReceiptsTest do +defmodule EthereumJSONRPC.ReceiptsTest do use ExUnit.Case, async: true - alias Explorer.JSONRPC.Receipts + alias EthereumJSONRPC.Receipts doctest Receipts # These are integration tests that depend on the sokol chain being used. sokol can be used with the following config # - # config :explorer, Explorer.JSONRPC, + # config :explorer, EthereumJSONRPC, # trace_url: "https://sokol-trace.poa.network", # url: "https://sokol.poa.network" # diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/transaction_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/transaction_test.exs new file mode 100644 index 0000000000..ad7780181d --- /dev/null +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/transaction_test.exs @@ -0,0 +1,5 @@ +defmodule EthereumJSONRPC.TransactionTest do + use ExUnit.Case, async: true + + doctest EthereumJSONRPC.Transaction +end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/transactions_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/transactions_test.exs new file mode 100644 index 0000000000..765130a88a --- /dev/null +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/transactions_test.exs @@ -0,0 +1,5 @@ +defmodule EthereumJSONRPC.TransactionsTest do + use ExUnit.Case, async: true + + doctest EthereumJSONRPC.Transactions +end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs deleted file mode 100644 index e681240eb4..0000000000 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs +++ /dev/null @@ -1,8 +0,0 @@ -defmodule EthereumJsonrpcTest do - use ExUnit.Case - doctest EthereumJsonrpc - - test "greets the world" do - assert EthereumJsonrpc.hello() == :world - end -end diff --git a/apps/ethereum_jsonrpc/test/test_helper.exs b/apps/ethereum_jsonrpc/test/test_helper.exs index 869559e709..9fbe8ddc4c 100644 --- a/apps/ethereum_jsonrpc/test/test_helper.exs +++ b/apps/ethereum_jsonrpc/test/test_helper.exs @@ -1 +1,7 @@ +# https://github.com/CircleCI-Public/circleci-demo-elixir-phoenix/blob/a89de33a01df67b6773ac90adc74c34367a4a2d6/test/test_helper.exs#L1-L3 +junit_folder = Mix.Project.build_path() <> "/junit/#{Mix.Project.config()[:app]}" +File.mkdir_p!(junit_folder) +:ok = Application.put_env(:junit_formatter, :report_dir, junit_folder) + +ExUnit.configure(formatters: [JUnitFormatter, ExUnit.CLIFormatter]) ExUnit.start() diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index c281af164d..4f4b8ce0d2 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -14,11 +14,6 @@ config :explorer, ecto_repos: [Explorer.Repo], coin: "POA" -config :explorer, Explorer.JSONRPC, - http: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :eth]], - trace_url: "https://sokol-trace.poa.network", - url: "https://sokol.poa.network" - config :explorer, Explorer.Integrations.EctoLogger, query_time_ms_threshold: 2_000 config :explorer, Explorer.Repo, migration_timestamps: [type: :utc_datetime] diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index bf6dbe652d..2f5dc033c9 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -25,7 +25,6 @@ defmodule Explorer.Application do # Children to start when not testing defp secondary_children(_) do [ - Explorer.JSONRPC, Supervisor.child_spec({Task.Supervisor, name: Explorer.TaskSupervisor}, id: Explorer.TaskSupervisor), Explorer.Indexer.Supervisor, Explorer.Chain.Statistics.Server, diff --git a/apps/explorer/lib/explorer/indexer/address_fetcher.ex b/apps/explorer/lib/explorer/indexer/address_fetcher.ex index 91f69cd9b6..dd403decaa 100644 --- a/apps/explorer/lib/explorer/indexer/address_fetcher.ex +++ b/apps/explorer/lib/explorer/indexer/address_fetcher.ex @@ -5,12 +5,9 @@ defmodule Explorer.Indexer.AddressFetcher do use GenServer require Logger - alias Explorer.{Chain, JSONRPC} - - alias Explorer.Chain.{ - Address, - Hash - } + alias EthereumJSONRPC + alias Explorer.Chain + alias Explorer.Chain.{Address, Hash} @fetch_interval :timer.seconds(3) @max_batch_size 100 @@ -112,7 +109,7 @@ defmodule Explorer.Indexer.AddressFetcher do end defp do_fetch_addresses(address_hashes) do - JSONRPC.fetch_balances_by_hash(address_hashes) + EthereumJSONRPC.fetch_balances_by_hash(address_hashes) end defp take_batch(queue) do diff --git a/apps/explorer/lib/explorer/indexer/block_fetcher.ex b/apps/explorer/lib/explorer/indexer/block_fetcher.ex index 3af605b174..a32d8a11ac 100644 --- a/apps/explorer/lib/explorer/indexer/block_fetcher.ex +++ b/apps/explorer/lib/explorer/indexer/block_fetcher.ex @@ -7,9 +7,10 @@ defmodule Explorer.Indexer.BlockFetcher do require Logger - alias Explorer.{Chain, Indexer, JSONRPC} + alias EthereumJSONRPC + alias EthereumJSONRPC.Transactions + alias Explorer.{Chain, Indexer} alias Explorer.Indexer.{AddressFetcher, Sequence} - alias Explorer.JSONRPC.Transactions # dialyzer thinks that Logger.debug functions always have no_local_return @dialyzer {:nowarn_function, import_range: 3} @@ -161,7 +162,7 @@ defmodule Explorer.Indexer.BlockFetcher do hashes |> Enum.chunk_every(state.internal_transactions_batch_size) - |> Task.async_stream(&JSONRPC.fetch_internal_transactions(&1), stream_opts) + |> Task.async_stream(&EthereumJSONRPC.fetch_internal_transactions(&1), stream_opts) |> Enum.reduce_while({:ok, []}, fn {:ok, {:ok, internal_transactions}}, {:ok, acc} -> {:cont, {:ok, acc ++ internal_transactions}} {:ok, {:error, reason}}, {:ok, _acc} -> {:halt, {:error, reason}} @@ -177,7 +178,7 @@ defmodule Explorer.Indexer.BlockFetcher do hashes |> Enum.chunk_every(state.receipts_batch_size) - |> Task.async_stream(&JSONRPC.fetch_transaction_receipts(&1), stream_opts) + |> Task.async_stream(&EthereumJSONRPC.fetch_transaction_receipts(&1), stream_opts) |> Enum.reduce_while({:ok, %{logs: [], receipts: []}}, fn {:ok, {:ok, %{logs: logs, receipts: receipts}}}, {:ok, %{logs: acc_logs, receipts: acc_receipts}} -> {:cont, {:ok, %{logs: acc_logs ++ logs, receipts: acc_receipts ++ receipts}}} @@ -256,7 +257,7 @@ defmodule Explorer.Indexer.BlockFetcher do # Only public for testing @doc false def import_range({block_start, block_end} = range, %{} = state, seq) do - with {:blocks, {:ok, next, result}} <- {:blocks, JSONRPC.fetch_blocks_by_range(block_start, block_end)}, + with {:blocks, {:ok, next, result}} <- {:blocks, EthereumJSONRPC.fetch_blocks_by_range(block_start, block_end)}, %{blocks: blocks, transactions: transactions} = result, cap_seq(seq, next, range, state), transaction_hashes = Transactions.params_to_hashes(transactions), diff --git a/apps/explorer/lib/explorer/jsonrpc.ex b/apps/explorer/lib/explorer/jsonrpc.ex deleted file mode 100644 index c270a4993a..0000000000 --- a/apps/explorer/lib/explorer/jsonrpc.ex +++ /dev/null @@ -1,309 +0,0 @@ -defmodule Explorer.JSONRPC do - @moduledoc """ - Ethereum JSONRPC client. - - ## Configuration - - Configuration for parity URLs can be provided with the following mix config: - - config :explorer, Explorer.JSONRPC, - url: "https://sokol.poa.network", - trace_url: "https://sokol-trace.poa.network", - http: [recv_timeout: 60_000, timeout: 60_000, hackney: [pool: :eth]] - - Note: the tracing node URL is provided separately from `:url`, via `:trace_url`. The trace URL and is used for - `fetch_internal_transactions`, which is only a supported method on tracing nodes. The `:http` option is passed - directly to the HTTP library (`HTTPoison`), which forwards the options down to `:hackney`. - """ - - require Logger - - alias Explorer.JSONRPC.{Blocks, Parity, Receipts, Transactions} - - @typedoc """ - Truncated 20-byte [KECCAK-256](https://en.wikipedia.org/wiki/SHA-3) hash encoded as a hexadecimal number in a - `String.t`. - """ - @type address :: String.t() - - @typedoc """ - Binary data encoded as a single hexadecimal number in a `String.t` - """ - @type data :: String.t() - - @typedoc """ - A full 32-byte [KECCAK-256](https://en.wikipedia.org/wiki/SHA-3) hash encoded as a hexadecimal number in a `String.t` - - ## Example - - "0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331" - - """ - @type hash :: String.t() - - @typedoc """ - 8 byte [KECCAK-256](https://en.wikipedia.org/wiki/SHA-3) hash of the proof-of-work. - """ - @type nonce :: String.t() - - @typedoc """ - A number encoded as a hexadecimal number in a `String.t` - - ## Example - - "0x1b4" - - """ - @type quantity :: String.t() - - @typedoc """ - Unix timestamp encoded as a hexadecimal number in a `String.t` - """ - @type timestamp :: String.t() - - def child_spec(_opts) do - :hackney_pool.child_spec(:eth, recv_timeout: 60_000, timeout: 60_000, max_connections: 1000) - end - - @doc """ - Lists changes for a given filter subscription. - """ - def check_for_updates(filter_id) do - request = %{ - "id" => filter_id, - "jsonrpc" => "2.0", - "method" => "eth_getFilterChanges", - "params" => [filter_id] - } - - json_rpc(request, config(:url)) - end - - @doc """ - Fetches configuration for this module under `key` - - Configuration can be set a compile time using `config` - - config :explorer, Explorer.JSONRRPC, key: value - - Configuration can be set a runtime using `Application.put_env/3` - - Application.put_env(:explorer, Explorer.JSONRPC, key: value) - - """ - def config(key) do - :explorer - |> Application.fetch_env!(__MODULE__) - |> Keyword.fetch!(key) - end - - @doc """ - Fetches address balances by address hashes. - """ - def fetch_balances_by_hash(address_hashes) do - batched_requests = - for hash <- address_hashes do - %{ - "id" => hash, - "jsonrpc" => "2.0", - "method" => "eth_getBalance", - "params" => [hash, "latest"] - } - end - - batched_requests - |> json_rpc(config(:url)) - |> handle_balances() - end - - defp handle_balances({:ok, results}) do - native_results = - for response <- results, into: %{} do - {response["id"], hexadecimal_to_integer(response["result"])} - end - - {:ok, native_results} - end - - defp handle_balances({:error, _reason} = err), do: err - - @doc """ - Fetches blocks by block hashes. - - Transaction data is included for each block. - """ - def fetch_blocks_by_hash(block_hashes) do - batched_requests = - for block_hash <- block_hashes do - %{ - "id" => block_hash, - "jsonrpc" => "2.0", - "method" => "eth_getBlockByHash", - "params" => [block_hash, true] - } - end - - batched_requests - |> json_rpc(config(:url)) - |> handle_get_block_by_number() - |> case do - {:ok, _next, results} -> {:ok, results} - {:error, reason} -> {:error, reason} - end - end - - @doc """ - Fetches blocks by block number range. - """ - def fetch_blocks_by_range(block_start, block_end) do - block_start - |> build_batch_get_block_by_number(block_end) - |> json_rpc(config(:url)) - |> handle_get_block_by_number() - end - - @doc """ - Fetches internal transactions from client-specific API. - """ - def fetch_internal_transactions(hashes) when is_list(hashes) do - Parity.fetch_internal_transactions(hashes) - end - - def fetch_transaction_receipts(hashes) when is_list(hashes) do - Receipts.fetch(hashes) - end - - @doc """ - 1. POSTs JSON `payload` to `url` - 2. Decodes the response - 3. Handles the response - - ## Returns - - * Handled response - * `{:error, reason}` if POST failes - """ - def json_rpc(payload, url) do - json = encode_json(payload) - headers = [{"Content-Type", "application/json"}] - - case HTTPoison.post(url, json, headers, config(:http)) do - {:ok, %HTTPoison.Response{body: body, status_code: code}} -> - body |> decode_json(payload, url) |> handle_response(code) - - {:error, %HTTPoison.Error{reason: reason}} -> - {:error, reason} - end - end - - @doc """ - Creates a filter subscription that can be polled for retreiving new blocks. - """ - def listen_for_new_blocks do - id = DateTime.utc_now() |> DateTime.to_unix() - - request = %{ - "id" => id, - "jsonrpc" => "2.0", - "method" => "eth_newBlockFilter", - "params" => [] - } - - json_rpc(request, config(:url)) - end - - @doc """ - Converts `t:nonce/0` to `t:non_neg_integer/0` - """ - def nonce_to_integer(nonce) do - hexadecimal_to_integer(nonce) - end - - @doc """ - Converts `t:quantity/0` to `t:non_neg_integer/0`. - """ - def quantity_to_integer(quantity) do - hexadecimal_to_integer(quantity) - end - - @doc """ - Converts `t:timestamp/0` to `t:DateTime.t/0` - """ - def timestamp_to_datetime(timestamp) do - timestamp - |> hexadecimal_to_integer() - |> Timex.from_unix() - end - - defp build_batch_get_block_by_number(block_start, block_end) do - for current <- block_start..block_end do - %{ - "id" => current, - "jsonrpc" => "2.0", - "method" => "eth_getBlockByNumber", - "params" => [int_to_hash_string(current), true] - } - end - end - - defp encode_json(data), do: Jason.encode_to_iodata!(data) - - defp decode_json(body, posted_payload, url) do - Jason.decode!(body) - rescue - Jason.DecodeError -> - Logger.error(""" - failed to decode json payload: - - url: #{inspect(url)} - - body: #{inspect(body)} - - posted payload: #{inspect(posted_payload)} - - """) - - raise("bad jason") - end - - defp handle_get_block_by_number({:ok, results}) do - {blocks, next} = - Enum.reduce(results, {[], :more}, fn - %{"result" => nil}, {blocks, _} -> {blocks, :end_of_chain} - %{"result" => %{} = block}, {blocks, next} -> {[block | blocks], next} - end) - - elixir_blocks = Blocks.to_elixir(blocks) - elixir_transactions = Blocks.elixir_to_transactions(elixir_blocks) - blocks_params = Blocks.elixir_to_params(elixir_blocks) - transactions_params = Transactions.elixir_to_params(elixir_transactions) - - {:ok, next, - %{ - blocks: blocks_params, - transactions: transactions_params - }} - end - - defp handle_get_block_by_number({:error, reason}) do - {:error, reason} - end - - defp handle_response(resp, 200) do - case resp do - [%{} | _] = batch_resp -> {:ok, batch_resp} - %{"error" => error} -> {:error, error} - %{"result" => result} -> {:ok, result} - end - end - - defp handle_response(resp, _status) do - {:error, resp} - end - - defp hexadecimal_to_integer("0x" <> hexadecimal_digits) do - String.to_integer(hexadecimal_digits, 16) - end - - defp int_to_hash_string(number), do: "0x" <> Integer.to_string(number, 16) -end diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index 10117aa737..f35a39f30a 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -70,6 +70,8 @@ defmodule Explorer.Mixfile do # Code coverage {:excoveralls, "~> 0.8.1", only: [:test]}, {:exvcr, "~> 0.10", only: :test}, + # JSONRPC access to Parity for `Explorer.Indexer` + {:ethereum_jsonrpc, in_umbrella: true}, {:httpoison, "~> 1.0", override: true}, {:jason, "~> 1.0"}, {:junit_formatter, ">= 0.0.0", only: [:test], runtime: false}, diff --git a/apps/explorer/test/explorer/indexer/address_fetcher_test.exs b/apps/explorer/test/explorer/indexer/address_fetcher_test.exs index 7fafffc6fa..bdd4e6fb7e 100644 --- a/apps/explorer/test/explorer/indexer/address_fetcher_test.exs +++ b/apps/explorer/test/explorer/indexer/address_fetcher_test.exs @@ -4,7 +4,6 @@ defmodule Explorer.Indexer.AddressFetcherTest do use Explorer.DataCase, async: false alias Explorer.Chain.Address - alias Explorer.JSONRPC alias Explorer.Indexer.AddressFetcher @hash %Explorer.Chain.Hash{ @@ -13,7 +12,6 @@ defmodule Explorer.Indexer.AddressFetcherTest do } setup do - start_supervised!({JSONRPC, []}) start_supervised!({Task.Supervisor, name: Explorer.Indexer.TaskSupervisor}) :ok diff --git a/apps/explorer/test/explorer/indexer/block_fetcher_test.exs b/apps/explorer/test/explorer/indexer/block_fetcher_test.exs index 04c140e6c0..39f1e74eab 100644 --- a/apps/explorer/test/explorer/indexer/block_fetcher_test.exs +++ b/apps/explorer/test/explorer/indexer/block_fetcher_test.exs @@ -5,7 +5,6 @@ defmodule Explorer.Indexer.BlockFetcherTest do import ExUnit.CaptureLog alias Explorer.Chain.{Address, Block, InternalTransaction, Log, Receipt, Transaction} - alias Explorer.JSONRPC alias Explorer.Indexer.{BlockFetcher, Sequence} @tag capture_log: true @@ -32,7 +31,6 @@ defmodule Explorer.Indexer.BlockFetcherTest do test "starts fetching blocks from Genesis" do assert Repo.aggregate(Block, :count, :hash) == 0 - start_supervised!({JSONRPC, []}) start_supervised!({Task.Supervisor, name: Explorer.Indexer.TaskSupervisor}) start_supervised!(BlockFetcher) @@ -84,7 +82,6 @@ defmodule Explorer.Indexer.BlockFetcherTest do setup :state setup do - start_supervised!({JSONRPC, []}) start_supervised!({Task.Supervisor, name: Explorer.Indexer.TaskSupervisor}) {:ok, state} = BlockFetcher.init(debug_logs: false) diff --git a/apps/explorer/test/explorer/jsonrpc/block_test.exs b/apps/explorer/test/explorer/jsonrpc/block_test.exs deleted file mode 100644 index c36299629e..0000000000 --- a/apps/explorer/test/explorer/jsonrpc/block_test.exs +++ /dev/null @@ -1,5 +0,0 @@ -defmodule Explorer.JSONRPC.BlockTest do - use ExUnit.Case, async: true - - doctest Explorer.JSONRPC.Block -end diff --git a/apps/explorer/test/explorer/jsonrpc/blocks_test.exs b/apps/explorer/test/explorer/jsonrpc/blocks_test.exs deleted file mode 100644 index 87a2ba6e48..0000000000 --- a/apps/explorer/test/explorer/jsonrpc/blocks_test.exs +++ /dev/null @@ -1,5 +0,0 @@ -defmodule Explorer.JSONRPC.BlocksTest do - use ExUnit.Case, async: true - - doctest Explorer.JSONRPC.Blocks -end diff --git a/apps/explorer/test/explorer/jsonrpc/log_test.exs b/apps/explorer/test/explorer/jsonrpc/log_test.exs deleted file mode 100644 index f1d2a81704..0000000000 --- a/apps/explorer/test/explorer/jsonrpc/log_test.exs +++ /dev/null @@ -1,5 +0,0 @@ -defmodule Explorer.JSONRPC.LogTest do - use ExUnit.Case, async: true - - doctest Explorer.JSONRPC.Log -end diff --git a/apps/explorer/test/explorer/jsonrpc/parity/trace/action_test.exs b/apps/explorer/test/explorer/jsonrpc/parity/trace/action_test.exs deleted file mode 100644 index 1097bf0faa..0000000000 --- a/apps/explorer/test/explorer/jsonrpc/parity/trace/action_test.exs +++ /dev/null @@ -1,5 +0,0 @@ -defmodule Explorer.JSONRPC.Parity.Trace.ActionTest do - use ExUnit.Case, async: true - - doctest Explorer.JSONRPC.Parity.Trace.Action -end diff --git a/apps/explorer/test/explorer/jsonrpc/parity/trace/result_test.exs b/apps/explorer/test/explorer/jsonrpc/parity/trace/result_test.exs deleted file mode 100644 index 9df848f26f..0000000000 --- a/apps/explorer/test/explorer/jsonrpc/parity/trace/result_test.exs +++ /dev/null @@ -1,5 +0,0 @@ -defmodule Explorer.JSONRPC.Parity.Trace.ResultTest do - use ExUnit.Case, async: true - - doctest Explorer.JSONRPC.Parity.Trace.Result -end diff --git a/apps/explorer/test/explorer/jsonrpc/parity/trace_test.exs b/apps/explorer/test/explorer/jsonrpc/parity/trace_test.exs deleted file mode 100644 index c85e520239..0000000000 --- a/apps/explorer/test/explorer/jsonrpc/parity/trace_test.exs +++ /dev/null @@ -1,5 +0,0 @@ -defmodule Explorer.JSONRPC.Parity.TraceTest do - use ExUnit.Case, async: true - - doctest Explorer.JSONRPC.Parity.Trace -end diff --git a/apps/explorer/test/explorer/jsonrpc/parity_test.exs b/apps/explorer/test/explorer/jsonrpc/parity_test.exs deleted file mode 100644 index a13a32f946..0000000000 --- a/apps/explorer/test/explorer/jsonrpc/parity_test.exs +++ /dev/null @@ -1,5 +0,0 @@ -defmodule Explorer.JSONRPC.ParityTest do - use ExUnit.Case, async: true - - doctest Explorer.JSONRPC.Parity -end diff --git a/apps/explorer/test/explorer/jsonrpc/receipt_test.exs b/apps/explorer/test/explorer/jsonrpc/receipt_test.exs deleted file mode 100644 index 3316dc2c50..0000000000 --- a/apps/explorer/test/explorer/jsonrpc/receipt_test.exs +++ /dev/null @@ -1,5 +0,0 @@ -defmodule Explorer.JSONRPC.ReceiptTest do - use ExUnit.Case, async: true - - doctest Explorer.JSONRPC.Receipt -end diff --git a/apps/explorer/test/explorer/jsonrpc/transaction_test.exs b/apps/explorer/test/explorer/jsonrpc/transaction_test.exs deleted file mode 100644 index bb69efbfdd..0000000000 --- a/apps/explorer/test/explorer/jsonrpc/transaction_test.exs +++ /dev/null @@ -1,5 +0,0 @@ -defmodule Explorer.JSONRPC.TransactionTest do - use ExUnit.Case, async: true - - doctest Explorer.JSONRPC.Transaction -end diff --git a/apps/explorer/test/explorer/jsonrpc/transactions_test.exs b/apps/explorer/test/explorer/jsonrpc/transactions_test.exs deleted file mode 100644 index 5243096457..0000000000 --- a/apps/explorer/test/explorer/jsonrpc/transactions_test.exs +++ /dev/null @@ -1,5 +0,0 @@ -defmodule Explorer.JSONRPC.TransactionsTest do - use ExUnit.Case, async: true - - doctest Explorer.JSONRPC.Transactions -end diff --git a/coveralls.json b/coveralls.json index bc08a0ff35..1bb60fd89d 100644 --- a/coveralls.json +++ b/coveralls.json @@ -1,7 +1,7 @@ { "coverage_options": { "treat_no_relevant_lines_as_covered": true, - "minimum_coverage": 94.0 + "minimum_coverage": 91.2 }, "terminal_options": { "file_column_width": 120 From c496e5f43a977e9b243c3b419573a12ddd053008 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Mon, 14 May 2018 14:42:17 -0500 Subject: [PATCH 75/77] Remove double backticks not flagged by earmark --- .../lib/ethereum_jsonrpc/block.ex | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex index 1fc387cbf1..d7517279b0 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex @@ -13,12 +13,12 @@ defmodule EthereumJSONRPC.Block do @typedoc """ * `"author"` - `t:EthereumJSONRPC.address/0` that created the block. Aliased by `"miner"`. - * `"difficulty"` - `t:EthereumJSONRPC.quantity/0`` of the difficulty for this block. - * `"extraData"` - the extra `t:EthereumJSONRPC.data/0`` field of this block. - * `"gasLimit" - maximum gas `t:EthereumJSONRPC.quantity/0`` in this block. - * `"gasUsed" - the total `t:EthereumJSONRPC.quantity/0`` of gas used by all transactions in this block. + * `"difficulty"` - `t:EthereumJSONRPC.quantity/0` of the difficulty for this block. + * `"extraData"` - the extra `t:EthereumJSONRPC.data/0` field of this block. + * `"gasLimit" - maximum gas `t:EthereumJSONRPC.quantity/0` in this block. + * `"gasUsed" - the total `t:EthereumJSONRPC.quantity/0` of gas used by all transactions in this block. * `"hash"` - the `t:EthereumJSONRPC.hash/0` of the block. - * `"logsBloom"` - `t:EthereumJSONRPC.data/0`` for the [Bloom filter](https://en.wikipedia.org/wiki/Bloom_filter) + * `"logsBloom"` - `t:EthereumJSONRPC.data/0` for the [Bloom filter](https://en.wikipedia.org/wiki/Bloom_filter) for the logs of the block. `nil` when block is pending. * `"miner"` - `t:EthereumJSONRPC.address/0` of the beneficiary to whom the mining rewards were given. Aliased by `"author"`. @@ -31,12 +31,12 @@ defmodule EthereumJSONRPC.Block do * `"sha3Uncles"` - `t:EthereumJSONRPC.hash/0` of the [uncles](https://bitcoin.stackexchange.com/questions/39329/in-ethereum-what-is-an-uncle-block) data in the block. * `"signature"` - UNKNOWN - * `"size"` - `t:EthereumJSONRPC.quantity/0`` of bytes in this block + * `"size"` - `t:EthereumJSONRPC.quantity/0` of bytes in this block * `"stateRoot" - `t:EthereumJSONRPC.hash/0` of the root of the final state [trie](https://github.com/ethereum/wiki/wiki/Patricia-Tree) of the block. * `"step"` - UNKNOWN - * `"timestamp"`: the unix timestamp as a `t:EthereumJSONRPC.quantity/0`` for when the block was collated. - * `"totalDifficulty" - `t:EthereumJSONRPC.quantity/0`` of the total difficulty of the chain until this block. + * `"timestamp"`: the unix timestamp as a `t:EthereumJSONRPC.quantity/0` for when the block was collated. + * `"totalDifficulty" - `t:EthereumJSONRPC.quantity/0` of the total difficulty of the chain until this block. * `"transactions"` - `t:list/0` of `t:EthereumJSONRPC.Transaction.t/0`. * `"transactionsRoot" - `t:EthereumJSONRPC.hash/0` of the root of the transaction [trie](https://github.com/ethereum/wiki/wiki/Patricia-Tree) of the block. From 8758b5d1d7d2486d7b19d330861e60ab63924d43 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Mon, 14 May 2018 15:05:39 -0500 Subject: [PATCH 76/77] Don't ecto.drop before test as it breaks `mix test` in root directory. --- apps/explorer/mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index f35a39f30a..798df45bf5 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -97,7 +97,7 @@ defmodule Explorer.Mixfile do [ "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], "ecto.reset": ["ecto.drop", "ecto.setup"], - test: ["ecto.drop", "ecto.create --quiet", "ecto.migrate", "test"] + test: ["ecto.create --quiet", "ecto.migrate", "test"] ] ++ env_aliases(env) end From 659d92fc9865e1dd36913926670b9017528d3eb3 Mon Sep 17 00:00:00 2001 From: Luke Imhoff Date: Mon, 14 May 2018 15:50:39 -0500 Subject: [PATCH 77/77] Add missing backticks around :direction option name --- apps/explorer/lib/explorer/chain.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 7d7a3c776e..dbb3a2eec6 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -52,7 +52,7 @@ defmodule Explorer.Chain do * `:direction` - if specified, will filter internal transactions by address type. If `:to` is specified, only internal transactions where the "to" address matches will be returned. Likewise, if `:from` is specified, only - internal transactions where the "from" address matches will be returned. If :direction is omitted, internal + internal transactions where the "from" address matches will be returned. If `:direction` is omitted, internal transactions either to or from the address will be returned. * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is `:required`, and the `t:Explorer.Chain.InternalTransaction.t/0` has no associated record for that association, @@ -83,7 +83,7 @@ defmodule Explorer.Chain do * `:direction` - if specified, will filter transactions by address type. If `:to` is specified, only transactions where the "to" address matches will be returned. Likewise, if `:from` is specified, only transactions where the - "from" address matches will be returned. If :direction is omitted, transactions either to or from the address + "from" address matches will be returned. If `:direction` is omitted, transactions either to or from the address will be returned. * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is `:required`, and the `t:Explorer.Chain.Transaction.t/0` has no associated record for that association, then the