Merge branch 'master' into ab-erc-721-coin-instance-page

pull/2642/head
Victor Baranov 5 years ago committed by GitHub
commit d8071728b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 14
      CHANGELOG.md
  2. 4
      apps/block_scout_web/assets/css/_layout.scss
  3. 4
      apps/block_scout_web/assets/css/components/_address-overview.scss
  4. 20
      apps/block_scout_web/assets/css/theme/_dark-theme.scss
  5. 3
      apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex
  6. 5
      apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex
  7. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex
  8. 2
      apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex
  9. 2
      apps/block_scout_web/lib/block_scout_web/templates/transaction_log/_logs.html.eex
  10. 2
      apps/block_scout_web/mix.exs
  11. 5
      apps/block_scout_web/priv/gettext/default.pot
  12. 5
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  13. 2
      apps/block_scout_web/test/block_scout_web/controllers/block_controller_test.exs
  14. 2
      apps/block_scout_web/test/block_scout_web/controllers/chain_controller_test.exs
  15. 2
      apps/block_scout_web/test/support/conn_case.ex
  16. 2
      apps/block_scout_web/test/support/feature_case.ex
  17. 8
      apps/explorer/config/config.exs
  18. 8
      apps/explorer/lib/explorer/application.ex
  19. 51
      apps/explorer/lib/explorer/chain.ex
  20. 42
      apps/explorer/lib/explorer/chain/cache/pending_transactions.ex
  21. 48
      apps/explorer/lib/explorer/chain/cache/uncles.ex
  22. 83
      apps/explorer/lib/explorer/chain/import/runner/blocks.ex
  23. 109
      apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex
  24. 6
      apps/explorer/lib/explorer/chain/import/runner/internal_transactions_indexed_at_blocks.ex
  25. 15
      apps/explorer/lib/explorer/smart_contract/verifier.ex
  26. 17
      apps/explorer/test/explorer/chain/cache/pending_transactions_test.exs
  27. 28
      apps/explorer/test/explorer/chain/cache/uncles_test.exs
  28. 72
      apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs
  29. 28
      apps/explorer/test/explorer/smart_contract/verifier_test.exs
  30. 2
      apps/explorer/test/support/data_case.ex
  31. 15
      apps/indexer/lib/indexer/block/fetcher.ex
  32. 3
      apps/indexer/lib/indexer/fetcher/pending_transaction.ex
  33. 9
      apps/indexer/lib/indexer/fetcher/uncle_block.ex
  34. 2
      apps/indexer/lib/indexer/temporary/uncles_without_index.ex
  35. 2
      mix.lock

@ -1,7 +1,9 @@
## Current
### Features
- [#2642](https://github.com/poanetwork/blockscout/pull/2642) - add ERC721 coin instance page
- [#2733](https://github.com/poanetwork/blockscout/pull/2733) - Add cache for first page of uncles
- [#2735](https://github.com/poanetwork/blockscout/pull/2735) - Add pending transactions cache
- [#2726](https://github.com/poanetwork/blockscout/pull/2726) - Remove internal_transaction block_number setting from blocks runner
- [#2717](https://github.com/poanetwork/blockscout/pull/2717) - Improve speed of nonconsensus data removal
- [#2679](https://github.com/poanetwork/blockscout/pull/2679) - added fixed height for card chain blocks and card chain transactions
- [#2678](https://github.com/poanetwork/blockscout/pull/2678) - fixed dashboard banner height bug
@ -10,9 +12,17 @@
- [#2666](https://github.com/poanetwork/blockscout/pull/2666) - fetch token counters in parallel
- [#2665](https://github.com/poanetwork/blockscout/pull/2665) - new menu layout for mobile devices
- [#2663](https://github.com/poanetwork/blockscout/pull/2663) - Fetch address counters in parallel
- [#2642](https://github.com/poanetwork/blockscout/pull/2642) - add ERC721 coin instance page
### Fixes
- [#2750](https://github.com/poanetwork/blockscout/pull/2750) - fixed contract buttons color for NFT token instance on each theme
- [#2746](https://github.com/poanetwork/blockscout/pull/2746) - fixed wrong alignment in logs decoded view
- [#2745](https://github.com/poanetwork/blockscout/pull/2745) - optimize addresses page
- [#2742](https://github.com/poanetwork/blockscout/pull/2742) -
fixed menu hovers in dark mode desktop view
- [#2737](https://github.com/poanetwork/blockscout/pull/2737) - switched hardcoded subnetwork value to elixir expression for mobile menu
- [#2736](https://github.com/poanetwork/blockscout/pull/2736) - do not update cache if no blocks were inserted
- [#2731](https://github.com/poanetwork/blockscout/pull/2731) - fix library verification
- [#2718](https://github.com/poanetwork/blockscout/pull/2718) - Include all addresses taking part in transactions in wallets' addresses counter
- [#2709](https://github.com/poanetwork/blockscout/pull/2709) - Fix stuck label and value for uncle block height
- [#2707](https://github.com/poanetwork/blockscout/pull/2707) - fix for dashboard banner chart legend items
@ -28,10 +38,12 @@
- [#2671](https://github.com/poanetwork/blockscout/pull/2671) - fixed buttons color at smart contract section
- [#2660](https://github.com/poanetwork/blockscout/pull/2660) - set correct last value for coin balances chart data
- [#2619](https://github.com/poanetwork/blockscout/pull/2619) - Enforce DB transaction's order to prevent deadlocks
- [#2738](https://github.com/poanetwork/blockscout/pull/2738) - do not fail block `internal_transactions_indexed_at` field update
### Chore
- [#2724](https://github.com/poanetwork/blockscout/pull/2724) - fix ci by commenting a line in hackney library
- [#2708](https://github.com/poanetwork/blockscout/pull/2708) - add log index to logs view
- [#2723](https://github.com/poanetwork/blockscout/pull/2723) - get rid of ex_json_schema warnings
## 2.0.4-beta

@ -8,3 +8,7 @@
background-color: #fbfafc;
}
}
.logs-hash {
line-height: 24px !important;
}

@ -80,3 +80,7 @@
margin-bottom: 0;
}
}
.logs-decoded {
line-height: 25px!important;
}

@ -790,9 +790,19 @@ $labels-dark: #8a8dba; // header nav, labels
}
.dark-theme-applied .dropdown-item.active, .dark-theme-applied .dropdown-item:hover, .dark-theme-applied .dropdown-item:focus {
background-image: none;
width: 100%;
background-color: #3f426c!important;
}
@media (max-width: 991.98px) {
.dark-theme-applied .navbar.navbar-primary .navbar-nav .nav-link:hover,
.dark-theme-applied .navbar.navbar-primary .navbar-nav .nav-link.activeLink,
.dark-theme-applied .navbar.navbar-primary .navbar-nav .nav-link:focus {
background-image: none;
width: 100%;
background-color: #35335d!important;
color: white;
border: none;
}
.dark-theme-applied .dropdown-item:hover:before {
content: "|";
@ -804,15 +814,6 @@ $labels-dark: #8a8dba; // header nav, labels
color: $dark-primary;
position: relative;
}
.dark-theme-applied .navbar.navbar-primary .navbar-nav .nav-link:hover,
.dark-theme-applied .navbar.navbar-primary .navbar-nav .nav-link.activeLink,
.dark-theme-applied .navbar.navbar-primary .navbar-nav .nav-link:focus {
background-image: none;
width: 100%;
background-color: #35335d!important;
color: white;
border: none;
}
.dark-theme-applied .navbar.navbar-primary .navbar-nav .nav-link:hover:before
{
content: "|";
@ -824,3 +825,4 @@ $labels-dark: #8a8dba; // header nav, labels
top: 14%;
color: $dark-primary;
}
}

@ -44,8 +44,7 @@ defmodule BlockScoutWeb.AddressController do
index: index,
exchange_rate: exchange_rate,
total_supply: total_supply,
tx_count: tx_count,
validation_count: validation_count(address.hash)
tx_count: tx_count
)
end)

@ -29,11 +29,6 @@
<span data-test="transaction_count">
<%= @tx_count %>
</span> <%= gettext "Transactions sent" %>
<% if validator?(@address) do %>
<span data-test="validation_count">
<%= @validation_count %>
</span> <%= gettext "Validations" %>
<% end %>
</span>
</td>
</tr>

@ -17,7 +17,7 @@
<dl class="row">
<dt class="col-md-2"> <%= gettext "Transaction" %> </dt>
<dd class="col-md-10">
<h3 class="">
<h3 class="logs-decoded">
<%= link(
@log.transaction,
to: transaction_path(@conn, :show, @log.transaction),

@ -16,7 +16,7 @@
<span class="nav-link-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="6" height="6">
<circle cx="3" cy="3" r="3" fill="#80d6a1"></circle>
</svg> </span>Sokol Testnet</a>
</svg> </span><%= subnetwork_title() %></a>
<button class="new-button" id="dark-mode-changer">
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="16">
<path fill="#a3a9b5" fill-rule="evenodd" d="M14.88 11.578a.544.544 0 0 0-.599-.166 5.7 5.7 0 0 1-1.924.321c-3.259 0-5.91-2.632-5.91-5.866 0-1.947.968-3.759 2.59-4.849a.534.534 0 0 0-.225-.97A5.289 5.289 0 0 0 8.059 0C3.615 0 0 3.588 0 8s3.615 8 8.059 8c2.82 0 5.386-1.423 6.862-3.806a.533.533 0 0 0-.041-.616z"></path>

@ -18,7 +18,7 @@
<dl class="row">
<dt class="col-lg-2"> <%= gettext "Address" %> </dt>
<dd class="col-lg-10">
<h3 class="">
<h3 class="logs-hash">
<%= link(
@log.address,
to: address_path(@conn, :show, @log.address),

@ -131,7 +131,7 @@ defmodule BlockScoutWeb.Mixfile do
# `:cowboy` `~> 2.0` and Phoenix 1.4 compatibility
{:wobserver, "~> 0.2.0", github: "poanetwork/wobserver", branch: "support-https"},
{:phoenix_form_awesomplete, "~> 0.1.4"},
{:ex_json_schema, "~> 0.6.1"}
{:ex_json_schema, "~> 0.6.2"}
]
end

@ -1635,11 +1635,6 @@ msgstr ""
msgid "Validated Transactions"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tile.html.eex:35
msgid "Validations"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:30
msgid "Validator Creation Date"

@ -1635,11 +1635,6 @@ msgstr ""
msgid "Validated Transactions"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_tile.html.eex:35
msgid "Validations"
msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:30
msgid "Validator Creation Date"

@ -5,6 +5,8 @@ defmodule BlockScoutWeb.BlockControllerTest do
setup do
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Uncles.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Uncles.child_id())
:ok
end

@ -11,6 +11,8 @@ defmodule BlockScoutWeb.ChainControllerTest do
setup do
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Blocks.child_id())
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Uncles.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Uncles.child_id())
start_supervised!(AddressesCounter)
AddressesCounter.consolidate()

@ -44,6 +44,8 @@ defmodule BlockScoutWeb.ConnCase do
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Transactions.child_id())
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Accounts.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Accounts.child_id())
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.PendingTransactions.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.PendingTransactions.child_id())
{:ok, conn: Phoenix.ConnTest.build_conn()}
end

@ -31,6 +31,8 @@ defmodule BlockScoutWeb.FeatureCase do
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Transactions.child_id())
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Accounts.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Accounts.child_id())
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.PendingTransactions.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.PendingTransactions.child_id())
metadata = Phoenix.Ecto.SQL.Sandbox.metadata_for(Explorer.Repo, self())
{:ok, session} = Wallaby.start_session(metadata: metadata)

@ -145,6 +145,14 @@ config :explorer, Explorer.Chain.Cache.Accounts,
ttl_check_interval: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(1), else: false),
global_ttl: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(5))
config :explorer, Explorer.Chain.Cache.PendingTransactions,
ttl_check_interval: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(1), else: false),
global_ttl: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(5))
config :explorer, Explorer.Chain.Cache.Uncles,
ttl_check_interval: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(1), else: false),
global_ttl: if(System.get_env("DISABLE_INDEXER") == "true", do: :timer.seconds(5))
# 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"

@ -13,8 +13,10 @@ defmodule Explorer.Application do
BlockNumber,
Blocks,
NetVersion,
PendingTransactions,
TransactionCount,
Transactions
Transactions,
Uncles
}
alias Explorer.Chain.Supply.RSK
@ -51,7 +53,9 @@ defmodule Explorer.Application do
con_cache_child_spec(MarketHistoryCache.cache_name()),
con_cache_child_spec(RSK.cache_name(), ttl_check_interval: :timer.minutes(1), global_ttl: :timer.minutes(30)),
Transactions,
Accounts
Accounts,
PendingTransactions,
Uncles
]
children = base_children ++ configurable_children()

@ -54,8 +54,10 @@ defmodule Explorer.Chain do
BlockCount,
BlockNumber,
Blocks,
PendingTransactions,
TransactionCount,
Transactions
Transactions,
Uncles
}
alias Explorer.Chain.Import.Runner
@ -1344,7 +1346,19 @@ defmodule Explorer.Chain do
paging_options = Keyword.get(options, :paging_options) || @default_paging_options
block_type = Keyword.get(options, :block_type, "Block")
if block_type == "Block" && !paging_options.key do
cond do
block_type == "Block" && !paging_options.key ->
block_from_cache(block_type, paging_options, necessity_by_association)
block_type == "Uncle" && !paging_options.key ->
uncles_from_cache(block_type, paging_options, necessity_by_association)
true ->
fetch_blocks(block_type, paging_options, necessity_by_association)
end
end
defp block_from_cache(block_type, paging_options, necessity_by_association) do
case Blocks.take_enough(paging_options.page_size) do
nil ->
elements = fetch_blocks(block_type, paging_options, necessity_by_association)
@ -1356,8 +1370,19 @@ defmodule Explorer.Chain do
blocks ->
blocks
end
else
fetch_blocks(block_type, paging_options, necessity_by_association)
end
def uncles_from_cache(block_type, paging_options, necessity_by_association) do
case Uncles.take_enough(paging_options.page_size) do
nil ->
elements = fetch_blocks(block_type, paging_options, necessity_by_association)
Uncles.update(elements)
elements
blocks ->
blocks
end
end
@ -2216,6 +2241,24 @@ defmodule Explorer.Chain do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
if is_nil(paging_options.key) do
paging_options.page_size
|> PendingTransactions.take_enough()
|> case do
nil ->
pending_transactions = fetch_recent_pending_transactions(paging_options, necessity_by_association)
PendingTransactions.update(pending_transactions)
pending_transactions
pending_transactions ->
pending_transactions
end
else
fetch_recent_pending_transactions(paging_options, necessity_by_association)
end
end
defp fetch_recent_pending_transactions(paging_options, necessity_by_association) do
Transaction
|> page_pending_transaction(paging_options)
|> limit(^paging_options.page_size)

@ -0,0 +1,42 @@
defmodule Explorer.Chain.Cache.PendingTransactions do
@moduledoc """
Caches the latest pending transactions
"""
alias Explorer.Chain.Transaction
use Explorer.Chain.OrderedCache,
name: :pending_transactions,
max_size: 51,
preloads: [
:block,
created_contract_address: :names,
from_address: :names,
to_address: :names,
token_transfers: :token,
token_transfers: :from_address,
token_transfers: :to_address
],
ttl_check_interval: Application.get_env(:explorer, __MODULE__)[:ttl_check_interval],
global_ttl: Application.get_env(:explorer, __MODULE__)[:global_ttl]
@type element :: Transaction.t()
@type id :: {non_neg_integer(), non_neg_integer()}
def element_to_id(%Transaction{inserted_at: inserted_at, hash: hash}) do
{inserted_at, hash}
end
def update_pending(transactions) when is_nil(transactions), do: :ok
def update_pending(transactions) do
transactions
|> Enum.filter(&pending?(&1))
|> update()
end
defp pending?(transaction) do
is_nil(transaction.block_hash) and (is_nil(transaction.error) or transaction.error != "dropped/replaced")
end
end

@ -0,0 +1,48 @@
defmodule Explorer.Chain.Cache.Uncles do
@moduledoc """
Caches the last known uncles
"""
alias Explorer.Chain.Block
alias Explorer.Repo
use Explorer.Chain.OrderedCache,
name: :uncles,
max_size: 60,
ids_list_key: "uncle_numbers",
preload: :transactions,
preload: [miner: :names],
preload: :rewards,
preload: :nephews,
ttl_check_interval: Application.get_env(:explorer, __MODULE__)[:ttl_check_interval],
global_ttl: Application.get_env(:explorer, __MODULE__)[:global_ttl]
import Ecto.Query
@type element :: Block.t()
@type id :: non_neg_integer()
def element_to_id(%Block{number: number}), do: number
def update_from_second_degree_relations(second_degree_relations) when is_nil(second_degree_relations), do: :ok
def update_from_second_degree_relations(second_degree_relations) do
uncle_hashes =
second_degree_relations
|> Enum.map(& &1.uncle_hash)
|> Enum.uniq()
query =
from(
block in Block,
where: block.consensus == false and block.hash in ^uncle_hashes,
inner_join: nephews in assoc(block, :nephews),
preload: [nephews: block]
)
query
|> Repo.all()
|> update()
end
end

@ -56,7 +56,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
# Note, needs to be executed after `lose_consensus` for lock acquisition
insert(repo, changes_list, insert_options)
end)
|> Multi.run(:uncle_fetched_block_second_degree_relations, fn repo, %{blocks: blocks} when is_list(blocks) ->
|> Multi.run(:uncle_fetched_block_second_degree_relations, fn repo, _ ->
update_block_second_degree_relations(repo, hashes, %{
timeout:
options[Runner.Block.SecondDegreeRelations.option_key()][:timeout] ||
@ -86,15 +86,9 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
|> Multi.run(:remove_nonconsensus_logs, fn repo, %{derive_transaction_forks: transactions} ->
remove_nonconsensus_logs(repo, transactions, insert_options)
end)
|> Multi.run(:acquire_internal_transactions, fn repo, %{derive_transaction_forks: transactions} ->
acquire_internal_transactions(repo, hashes, transactions)
end)
|> Multi.run(:remove_nonconsensus_internal_transactions, fn repo, %{derive_transaction_forks: transactions} ->
remove_nonconsensus_internal_transactions(repo, transactions, insert_options)
end)
|> Multi.run(:internal_transaction_transaction_block_number, fn repo, _ ->
update_internal_transaction_block_number(repo, hashes)
end)
|> Multi.run(:acquire_contract_address_tokens, fn repo, _ ->
acquire_contract_address_tokens(repo, consensus_block_numbers)
end)
@ -139,26 +133,6 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
Tokens.acquire_contract_address_tokens(repo, contract_address_hashes)
end
defp acquire_internal_transactions(repo, hashes, forked_transaction_hashes) do
query =
from(internal_transaction in InternalTransaction,
join: transaction in Transaction,
on: internal_transaction.transaction_hash == transaction.hash,
where: transaction.block_hash in ^hashes,
or_where: transaction.hash in ^forked_transaction_hashes,
select: {internal_transaction.transaction_hash, internal_transaction.index},
# Enforce InternalTransaction ShareLocks order (see docs: sharelocks.md)
order_by: [
internal_transaction.transaction_hash,
internal_transaction.index
],
# NOTE: find a better way to know the alias that ecto gives to token
lock: "FOR UPDATE OF i0"
)
{:ok, repo.all(query)}
end
defp fork_transactions(%{
repo: repo,
timeout: timeout,
@ -168,11 +142,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
query =
from(
transaction in where_forked(blocks_changes),
select: %{
block_hash: transaction.block_hash,
index: transaction.index,
hash: transaction.hash
},
select: transaction,
# Enforce Transaction ShareLocks order (see docs: sharelocks.md)
order_by: [asc: :hash],
lock: "FOR UPDATE"
@ -196,11 +166,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
updated_at: ^updated_at
]
],
select: %{
block_hash: s.block_hash,
index: s.index,
hash: s.hash
}
select: s
)
{_num, transactions} = repo.update_all(update_query, [], timeout: timeout)
@ -379,13 +345,27 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
defp remove_nonconsensus_internal_transactions(repo, forked_transaction_hashes, %{timeout: timeout}) do
query =
from(internal_transaction in InternalTransaction,
from(
internal_transaction in InternalTransaction,
where: internal_transaction.transaction_hash in ^forked_transaction_hashes,
select: map(internal_transaction, [:transaction_hash, :index])
select: %{transaction_hash: internal_transaction.transaction_hash},
# Enforce InternalTransaction ShareLocks order (see docs: sharelocks.md)
order_by: [
internal_transaction.transaction_hash,
internal_transaction.index
],
lock: "FOR UPDATE"
)
# ShareLocks order already enforced by `acquire_internal_transactions` (see docs: sharelocks.md)
{_count, deleted_internal_transactions} = repo.delete_all(query, timeout: timeout)
delete_query =
from(
i in InternalTransaction,
join: s in subquery(query),
on: i.transaction_hash == s.transaction_hash,
select: map(i, [:transaction_hash, :index])
)
{_count, deleted_internal_transactions} = repo.delete_all(delete_query, timeout: timeout)
{:ok, deleted_internal_transactions}
rescue
@ -632,7 +612,8 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
b in Block.SecondDegreeRelation,
join: s in subquery(query),
on: b.nephew_hash == s.nephew_hash and b.uncle_hash == s.uncle_hash,
update: [set: [uncle_fetched_at: ^updated_at]]
update: [set: [uncle_fetched_at: ^updated_at]],
select: map(b, [:nephew_hash, :uncle_hash, :index])
)
try do
@ -645,24 +626,6 @@ defmodule Explorer.Chain.Import.Runner.Blocks do
end
end
defp update_internal_transaction_block_number(repo, blocks_hashes) when is_list(blocks_hashes) do
query =
from(
internal_transaction in InternalTransaction,
join: transaction in Transaction,
on: internal_transaction.transaction_hash == transaction.hash,
join: block in Block,
on: block.hash == transaction.block_hash,
where: block.hash in ^blocks_hashes,
update: [set: [block_number: block.number]]
)
# ShareLocks order already enforced by `acquire_internal_transactions` (see docs: sharelocks.md)
{total, _} = repo.update_all(query, [])
{:ok, total}
end
defp where_forked(blocks_changes) when is_list(blocks_changes) do
initial = from(t in Transaction, where: false)

@ -4,9 +4,10 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
"""
require Ecto.Query
require Logger
alias Ecto.{Changeset, Multi, Repo}
alias Explorer.Chain.{Hash, Import, InternalTransaction, Transaction}
alias Explorer.Chain.{Block, Hash, Import, InternalTransaction, Transaction}
alias Explorer.Chain.Import.Runner
import Ecto.Query, only: [from: 2]
@ -52,32 +53,43 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
|> Multi.run(:acquire_transactions, fn repo, _ ->
acquire_transactions(repo, changes_list)
end)
|> Multi.run(:internal_transactions, fn repo, _ ->
insert(repo, changes_list, insert_options)
|> Multi.run(:internal_transactions, fn repo, %{acquire_transactions: transactions} ->
insert(repo, changes_list, transactions, insert_options)
end)
|> Multi.run(:internal_transactions_indexed_at_transactions, fn repo, %{acquire_transactions: transaction_hashes} ->
update_transactions(repo, transaction_hashes, update_transactions_options)
|> Multi.run(:internal_transactions_indexed_at_transactions, fn repo, %{acquire_transactions: transactions} ->
update_transactions(repo, transactions, update_transactions_options)
end)
|> Multi.run(
:remove_consensus_of_missing_transactions_blocks,
fn repo, %{internal_transactions: inserted} = results_map ->
# NOTE: for this to work it has to follow the runner `internal_transactions_indexed_at_blocks`
block_hashes = Map.get(results_map, :internal_transactions_indexed_at_blocks, [])
remove_consensus_of_missing_transactions_blocks(repo, block_hashes, changes_list, inserted)
end
)
end
@impl Runner
def timeout, do: @timeout
@spec insert(Repo.t(), [map], %{
@spec insert(Repo.t(), [map], [Transaction.t()], %{
optional(:on_conflict) => Runner.on_conflict(),
required(:timeout) => timeout,
required(:timestamps) => Import.timestamps()
}) ::
{:ok, [%{index: non_neg_integer, transaction_hash: Hash.t()}]}
| {:error, [Changeset.t()]}
defp insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options)
defp insert(repo, changes_list, transactions, %{timeout: timeout, timestamps: timestamps} = options)
when is_list(changes_list) do
on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0)
# Enforce InternalTransaction ShareLocks order (see docs: sharelocks.md)
ordered_changes_list = Enum.sort_by(changes_list, &{&1.transaction_hash, &1.index})
transactions_map = Map.new(transactions, &{&1.hash, &1})
final_changes_list = reject_pending_transactions(ordered_changes_list, repo)
final_changes_list =
changes_list
# Enforce InternalTransaction ShareLocks order (see docs: sharelocks.md)
|> Enum.sort_by(&{&1.transaction_hash, &1.index})
|> reject_missing_transactions(transactions_map)
{:ok, internal_transactions} =
Import.insert_changes_list(
@ -86,16 +98,12 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
conflict_target: [:transaction_hash, :index],
for: InternalTransaction,
on_conflict: on_conflict,
returning: [:transaction_hash, :index],
returning: true,
timeout: timeout,
timestamps: timestamps
)
{:ok,
for(
internal_transaction <- internal_transactions,
do: Map.take(internal_transaction, [:id, :index, :transaction_hash])
)}
{:ok, internal_transactions}
end
defp default_on_conflict do
@ -158,26 +166,28 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
from(
t in Transaction,
where: t.hash in ^transaction_hashes,
# do not consider pending transactions
where: not is_nil(t.block_hash),
select: t.hash,
select: map(t, [:hash, :block_hash, :block_number]),
# Enforce Transaction ShareLocks order (see docs: sharelocks.md)
order_by: t.hash,
lock: "FOR UPDATE"
)
hashes = repo.all(query)
{:ok, hashes}
{:ok, repo.all(query)}
end
defp update_transactions(repo, transaction_hashes, %{
defp update_transactions(repo, transactions, %{
timeout: timeout,
timestamps: timestamps
})
when is_list(transaction_hashes) do
when is_list(transactions) do
transaction_hashes = Enum.map(transactions, & &1.hash)
update_query =
from(
t in Transaction,
# pending transactions are already excluded by `acquire_transactions`
where: t.hash in ^transaction_hashes,
# ShareLocks order already enforced by `acquire_transactions` (see docs: sharelocks.md)
update: [
@ -214,22 +224,51 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
end
end
defp reject_pending_transactions(ordered_changes_list, repo) do
transaction_hashes =
ordered_changes_list
|> Enum.map(& &1.transaction_hash)
|> Enum.dedup()
# If not using Parity this is not relevant
defp remove_consensus_of_missing_transactions_blocks(_, [], _, _), do: {:ok, []}
query =
from(t in Transaction,
where: t.hash in ^transaction_hashes,
where: is_nil(t.block_hash),
select: t.hash
defp remove_consensus_of_missing_transactions_blocks(repo, block_hashes, changes_list, inserted) do
inserted_block_numbers = MapSet.new(inserted, & &1.block_number)
missing_transactions_block_numbers =
changes_list
|> MapSet.new(& &1.block_number)
|> MapSet.difference(inserted_block_numbers)
|> MapSet.to_list()
update_query =
from(
b in Block,
where: b.number in ^missing_transactions_block_numbers,
where: b.hash in ^block_hashes,
# ShareLocks order already enforced by `internal_transactions_indexed_at_blocks` (see docs: sharelocks.md)
update: [set: [consensus: false, internal_transactions_indexed_at: nil]]
)
pending_transactions = repo.all(query)
try do
{_num, result} = repo.update_all(update_query, [])
Logger.debug(fn ->
[
"consensus removed from blocks with numbers: ",
inspect(missing_transactions_block_numbers),
" because of missing transactions"
]
end)
{:ok, result}
rescue
postgrex_error in Postgrex.Error ->
{:error, %{exception: postgrex_error, missing_transactions_block_numbers: missing_transactions_block_numbers}}
end
end
ordered_changes_list
|> Enum.reject(fn %{transaction_hash: hash} -> Enum.member?(pending_transactions, hash) end)
defp reject_missing_transactions(ordered_changes_list, transactions_map) do
Enum.reject(ordered_changes_list, fn %{transaction_hash: hash} ->
transactions_map
|> Map.get(hash, %{})
|> Map.get(:block_hash)
|> is_nil()
end)
end
end

@ -65,12 +65,10 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsIndexedAtBlocks do
lock: "FOR UPDATE"
)
block_count = Enum.count(block_numbers)
try do
{^block_count, result} =
{_, result} =
repo.update_all(
from(b in Block, join: s in subquery(query), on: b.hash == s.hash),
from(b in Block, join: s in subquery(query), on: b.hash == s.hash, select: b.hash),
[set: [internal_transactions_indexed_at: timestamps.updated_at]],
timeout: timeout
)

@ -69,7 +69,8 @@ defmodule Explorer.SmartContract.Verifier do
blockchain_bytecode_without_whisper = extract_bytecode(blockchain_bytecode)
cond do
generated_bytecode != blockchain_bytecode_without_whisper ->
generated_bytecode != blockchain_bytecode_without_whisper &&
!try_library_verification(generated_bytecode, blockchain_bytecode_without_whisper) ->
{:error, :generated_bytecode}
has_constructor_with_params?(abi) &&
@ -81,6 +82,18 @@ defmodule Explorer.SmartContract.Verifier do
end
end
# 730000000000000000000000000000000000000000 - default library address that returned by the compiler
defp try_library_verification(
"730000000000000000000000000000000000000000" <> bytecode,
<<_address::binary-size(42)>> <> bytecode
) do
true
end
defp try_library_verification(_, _) do
false
end
@doc """
In order to discover the bytecode we need to remove the `swarm source` from
the hash.

@ -0,0 +1,17 @@
defmodule Explorer.Chain.Cache.PendingTransactionsTest do
use Explorer.DataCase
alias Explorer.Chain.Cache.PendingTransactions
describe "update_pending/1" do
test "adds a new pending transaction" do
transaction = insert(:transaction, block_hash: nil, error: nil)
PendingTransactions.update([transaction])
transaction_hash = transaction.hash
assert [%{hash: transaction_hash}] = PendingTransactions.all()
end
end
end

@ -0,0 +1,28 @@
defmodule Explorer.Chain.Cache.UnclesTest do
use Explorer.DataCase
alias Explorer.Chain.Cache.Uncles
alias Explorer.Repo
setup do
Supervisor.terminate_child(Explorer.Supervisor, Uncles.child_id())
Supervisor.restart_child(Explorer.Supervisor, Uncles.child_id())
:ok
end
describe "update_from_second_degree_relations/1" do
test "fetches an uncle from a second_degree_relation and adds it to the cache" do
block = insert(:block)
uncle = insert(:block, consensus: false)
uncle_hash = uncle.hash
second_degree_relation = insert(:block_second_degree_relation, uncle_hash: uncle_hash, nephew: block)
Uncles.update_from_second_degree_relations([second_degree_relation])
assert [%{hash: uncle_hash}] = Uncles.all()
end
end
end

@ -2,7 +2,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do
use Explorer.DataCase
alias Ecto.Multi
alias Explorer.Chain.{Data, Wei, Transaction, InternalTransaction}
alias Explorer.Chain.{Block, Data, Wei, Transaction, InternalTransaction}
alias Explorer.Chain.Import.Runner.InternalTransactions
describe "run/1" do
@ -42,10 +42,78 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do
assert is_nil(Repo.get(Transaction, pending.hash).block_hash)
end
test "removes consensus to blocks where transactions are missing" do
empty_block = insert(:block)
pending = insert(:transaction)
assert is_nil(pending.block_hash)
full_block = insert(:block)
inserted = insert(:transaction) |> with_block(full_block)
assert full_block.hash == inserted.block_hash
index = 0
pending_transaction_changes =
pending.hash
|> make_internal_transaction_changes(index, nil)
|> Map.put(:block_number, empty_block.number)
transaction_changes =
inserted.hash
|> make_internal_transaction_changes(index, nil)
|> Map.put(:block_number, full_block.number)
multi =
Multi.new()
|> Multi.run(:internal_transactions_indexed_at_blocks, fn _, _ -> {:ok, [empty_block.hash, full_block.hash]} end)
assert {:ok, _} = run_internal_transactions([pending_transaction_changes, transaction_changes], multi)
assert from(i in InternalTransaction, where: i.transaction_hash == ^pending.hash) |> Repo.one() |> is_nil()
assert %{consensus: false} = Repo.get(Block, empty_block.hash)
assert from(i in InternalTransaction, where: i.transaction_hash == ^inserted.hash) |> Repo.one() |> is_nil() ==
false
assert %{consensus: true} = Repo.get(Block, full_block.hash)
end
defp run_internal_transactions(changes_list) when is_list(changes_list) do
test "does not remove consensus when block is empty and no transactions are missing" do
empty_block = insert(:block)
full_block = insert(:block)
inserted = insert(:transaction) |> with_block(full_block)
assert full_block.hash == inserted.block_hash
index = 0
transaction_changes =
inserted.hash
|> make_internal_transaction_changes(index, nil)
|> Map.put(:block_number, full_block.number)
multi =
Multi.new()
|> Multi.run(:internal_transactions_indexed_at_blocks, fn _, _ -> {:ok, [empty_block.hash, full_block.hash]} end)
assert {:ok, _} = run_internal_transactions([transaction_changes], multi)
assert %{consensus: true} = Repo.get(Block, empty_block.hash)
assert from(i in InternalTransaction, where: i.transaction_hash == ^inserted.hash) |> Repo.one() |> is_nil() ==
false
assert %{consensus: true} = Repo.get(Block, full_block.hash)
end
end
defp run_internal_transactions(changes_list, multi \\ Multi.new()) when is_list(changes_list) do
multi
|> InternalTransactions.run(changes_list, %{
timeout: :infinity,
timestamps: %{inserted_at: DateTime.utc_now(), updated_at: DateTime.utc_now()}

@ -144,6 +144,34 @@ defmodule Explorer.SmartContract.VerifierTest do
assert abi != nil
end
test "verifies a library" do
bytecode =
"0x7349f540c22cba15c47a08c235e20081474201a742301460806040526004361060335760003560e01c8063c2985578146038575b600080fd5b603e60b0565b6040805160208082528351818301528351919283929083019185019080838360005b8381101560765781810151838201526020016060565b50505050905090810190601f16801560a25780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b604080518082019091526003815262666f6f60e81b60208201529056fea265627a7a72315820174b282a3ef3b9778d79fbc2e4c36bc939c54dfaaaa51d3122ee6e648093844c64736f6c634300050b0032"
contract_address = insert(:contract_address, contract_code: bytecode)
code = """
pragma solidity 0.5.11;
library Foo {
function foo() external pure returns (string memory) {
return "foo";
}
}
"""
params = %{
"contract_source_code" => code,
"compiler_version" => "v0.5.11+commit.c082d0b4",
"evm_version" => "default",
"name" => "Foo",
"optimization" => true
}
assert {:ok, %{abi: abi}} = Verifier.evaluate_authenticity(contract_address.hash, params)
assert abi != nil
end
test "verifies smart contract compiled with Solidity 0.5.9 (includes new metadata in bytecode) with constructor args" do
path = File.cwd!() <> "/test/support/fixture/smart_contract/solidity_0.5.9_smart_contract.sol"
contract = File.read!(path)

@ -47,6 +47,8 @@ defmodule Explorer.DataCase do
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Transactions.child_id())
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.Accounts.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.Accounts.child_id())
Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.PendingTransactions.child_id())
Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.PendingTransactions.child_id())
:ok
end

@ -13,7 +13,7 @@ defmodule Indexer.Block.Fetcher do
alias Explorer.Chain
alias Explorer.Chain.{Address, Block, Hash, Import, Transaction}
alias Explorer.Chain.Cache.Blocks, as: BlocksCache
alias Explorer.Chain.Cache.{Accounts, BlockNumber, Transactions}
alias Explorer.Chain.Cache.{Accounts, BlockNumber, PendingTransactions, Transactions, Uncles}
alias Indexer.Block.Fetcher.Receipts
alias Indexer.Fetcher.{
@ -175,8 +175,9 @@ defmodule Indexer.Block.Fetcher do
) do
result = {:ok, %{inserted: inserted, errors: blocks_errors}}
update_block_cache(inserted[:blocks])
update_transactions_cache(inserted[:transactions])
update_transactions_cache(inserted[:transactions], inserted[:fork_transactions])
update_addresses_cache(inserted[:addresses])
update_uncles_cache(inserted[:block_second_degree_relations])
result
else
{step, {:error, reason}} -> {:error, {step, reason}}
@ -184,6 +185,8 @@ defmodule Indexer.Block.Fetcher do
end
end
defp update_block_cache([]), do: :ok
defp update_block_cache(blocks) when is_list(blocks) do
{min_block, max_block} = Enum.min_max_by(blocks, & &1.number)
@ -194,12 +197,18 @@ defmodule Indexer.Block.Fetcher do
defp update_block_cache(_), do: :ok
defp update_transactions_cache(transactions) do
defp update_transactions_cache(transactions, forked_transactions) do
Transactions.update(transactions)
PendingTransactions.update_pending(transactions)
PendingTransactions.update_pending(forked_transactions)
end
defp update_addresses_cache(addresses), do: Accounts.drop(addresses)
defp update_uncles_cache(updated_relations) do
Uncles.update_from_second_degree_relations(updated_relations)
end
def import(
%__MODULE__{broadcast: broadcast, callback_module: callback_module} = state,
options

@ -14,7 +14,7 @@ defmodule Indexer.Fetcher.PendingTransaction do
alias Ecto.Changeset
alias Explorer.Chain
alias Explorer.Chain.Cache.Accounts
alias Explorer.Chain.Cache.{Accounts, PendingTransactions}
alias Indexer.Fetcher.PendingTransaction
alias Indexer.Transform.Addresses
@ -151,6 +151,7 @@ defmodule Indexer.Fetcher.PendingTransaction do
}) do
{:ok, imported} ->
Accounts.drop(imported[:addresses])
PendingTransactions.update_pending(imported[:transactions])
:ok
{:error, [%Changeset{} | _] = changesets} ->

@ -12,7 +12,7 @@ defmodule Indexer.Fetcher.UncleBlock do
alias Ecto.Changeset
alias EthereumJSONRPC.Blocks
alias Explorer.Chain
alias Explorer.Chain.Cache.Accounts
alias Explorer.Chain.Cache.{Accounts, PendingTransactions, Uncles}
alias Explorer.Chain.Hash
alias Indexer.{Block, BufferedTask, Tracer}
alias Indexer.Fetcher.UncleBlock
@ -129,6 +129,7 @@ defmodule Indexer.Fetcher.UncleBlock do
}) do
{:ok, imported} ->
Accounts.drop(imported[:addresses])
Uncles.update_from_second_degree_relations(imported[:block_second_degree_relations])
retry(errors)
{:error, {:import = step, [%Changeset{} | _] = changesets}} ->
@ -155,7 +156,7 @@ defmodule Indexer.Fetcher.UncleBlock do
@impl Block.Fetcher
def import(_, options) when is_map(options) do
with {:ok, %{block_second_degree_relations: block_second_degree_relations}} = ok <-
with {:ok, %{block_second_degree_relations: block_second_degree_relations} = imported} <-
options
|> Map.drop(@ignored_options)
|> uncle_blocks()
@ -168,8 +169,10 @@ defmodule Indexer.Fetcher.UncleBlock do
# * TokenBalance.async_fetch is not called because it uses block numbers from consensus, not uncles
UncleBlock.async_fetch_blocks(block_second_degree_relations)
PendingTransactions.update_pending(imported[:transactions])
PendingTransactions.update_pending(imported[:fork_transactions])
ok
{:ok, imported}
end
end

@ -15,6 +15,7 @@ defmodule Indexer.Temporary.UnclesWithoutIndex do
alias EthereumJSONRPC.Blocks
alias Explorer.{Chain, Repo}
alias Explorer.Chain.Block.SecondDegreeRelation
alias Explorer.Chain.Cache.Uncles
alias Indexer.{BufferedTask, Tracer}
alias Indexer.Fetcher.UncleBlock
@ -99,6 +100,7 @@ defmodule Indexer.Temporary.UnclesWithoutIndex do
case Chain.import(%{block_second_degree_relations: %{params: block_second_degree_relations_params}}) do
{:ok, %{block_second_degree_relations: block_second_degree_relations}} ->
UncleBlock.async_fetch_blocks(block_second_degree_relations)
Uncles.update_from_second_degree_relations(block_second_degree_relations)
retry(errors)

@ -39,7 +39,7 @@
"ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.6.4", "5b1ac8451f889576bb29dee70412de1170974298727ab944aa4d17e91bdd3472", [:mix], [{:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.6", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, "~> 2.3", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"ex_cldr_units": {:hex, :ex_cldr_units, "2.5.1", "0e65067a22a7c5146266c313d6333c2700868c32aa6d536f47c6c0d84aac3ac1", [:mix], [{:ex_cldr, "~> 2.6", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_lists, "~> 2.2", [hex: :ex_cldr_lists, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.6", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.19.3", "3c7b0f02851f5fc13b040e8e925051452e41248f685e40250d7e40b07b9f8c10", [:mix], [{:earmark, "~> 1.2", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
"ex_json_schema": {:hex, :ex_json_schema, "0.6.1", "b57c0588385b8262b80f19d33d9b9b71fcd60d247691abf2635b57a03ec0ad44", [:mix], [], "hexpm"},
"ex_json_schema": {:hex, :ex_json_schema, "0.6.2", "de23d80478215987469c81688208fe0ff440ee0e0e6ae2268fcadbb2ff35df9d", [:mix], [], "hexpm"},
"ex_machina": {:hex, :ex_machina, "2.3.0", "92a5ad0a8b10ea6314b876a99c8c9e3f25f4dde71a2a835845b136b9adaf199a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm"},
"ex_rlp": {:hex, :ex_rlp, "0.5.2", "7f4ce7bd55e543c054ce6d49629b01e9833c3462e3d547952be89865f39f2c58", [:mix], [], "hexpm"},
"ex_utils": {:hex, :ex_utils, "0.1.7", "2c133e0bcdc49a858cf8dacf893308ebc05bc5fba501dc3d2935e65365ec0bf3", [:mix], [], "hexpm"},

Loading…
Cancel
Save