Merge branch 'master' of github.com:poanetwork/blockscout

pull/2765/head
YegorSan 5 years ago
commit 8309c971c2
  1. 11
      CHANGELOG.md
  2. 2
      README.md
  3. 4
      apps/block_scout_web/assets/css/_layout.scss
  4. 4
      apps/block_scout_web/assets/css/components/_address-overview.scss
  5. 64
      apps/block_scout_web/assets/css/theme/_dark-theme.scss
  6. 2
      apps/block_scout_web/assets/css/theme/_ether1_variables.scss
  7. 1
      apps/block_scout_web/assets/css/theme/_ethereum_classic_variables.scss
  8. 1
      apps/block_scout_web/assets/css/theme/_ethereum_variables.scss
  9. 1
      apps/block_scout_web/assets/css/theme/_goerli_variables.scss
  10. 1
      apps/block_scout_web/assets/css/theme/_kovan_variables.scss
  11. 1
      apps/block_scout_web/assets/css/theme/_poa_variables.scss
  12. 1
      apps/block_scout_web/assets/css/theme/_rinkeby_variables.scss
  13. 1
      apps/block_scout_web/assets/css/theme/_ropsten_variables.scss
  14. 2
      apps/block_scout_web/assets/css/theme/_rsk_variables.scss
  15. 2
      apps/block_scout_web/assets/css/theme/_social_variables.scss
  16. 2
      apps/block_scout_web/assets/css/theme/_sokol_variables.scss
  17. 1
      apps/block_scout_web/assets/css/theme/_tomochain_variables.scss
  18. 3
      apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex
  19. 5
      apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex
  20. 2
      apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex
  21. 2
      apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex
  22. 2
      apps/block_scout_web/lib/block_scout_web/templates/transaction_log/_logs.html.eex
  23. 5
      apps/block_scout_web/priv/gettext/default.pot
  24. 5
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  25. 2
      apps/block_scout_web/test/block_scout_web/controllers/block_controller_test.exs
  26. 2
      apps/block_scout_web/test/block_scout_web/controllers/chain_controller_test.exs
  27. 2
      apps/block_scout_web/test/support/conn_case.ex
  28. 2
      apps/block_scout_web/test/support/feature_case.ex
  29. 8
      apps/explorer/config/config.exs
  30. 8
      apps/explorer/lib/explorer/application.ex
  31. 67
      apps/explorer/lib/explorer/chain.ex
  32. 42
      apps/explorer/lib/explorer/chain/cache/pending_transactions.ex
  33. 48
      apps/explorer/lib/explorer/chain/cache/uncles.ex
  34. 83
      apps/explorer/lib/explorer/chain/import/runner/blocks.ex
  35. 109
      apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex
  36. 6
      apps/explorer/lib/explorer/chain/import/runner/internal_transactions_indexed_at_blocks.ex
  37. 17
      apps/explorer/test/explorer/chain/cache/pending_transactions_test.exs
  38. 28
      apps/explorer/test/explorer/chain/cache/uncles_test.exs
  39. 74
      apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs
  40. 2
      apps/explorer/test/support/data_case.ex
  41. 13
      apps/indexer/lib/indexer/block/fetcher.ex
  42. 3
      apps/indexer/lib/indexer/fetcher/pending_transaction.ex
  43. 9
      apps/indexer/lib/indexer/fetcher/uncle_block.ex
  44. 2
      apps/indexer/lib/indexer/temporary/uncles_without_index.ex

@ -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,8 +12,14 @@
- [#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
@ -30,6 +38,7 @@
- [#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

@ -32,7 +32,7 @@ Currently available full-featured block explorers (Etherscan, Etherchain, Blockc
|--------------------------------------------------------|-------------------------------------------------------|------------------------------------------------------|----------------------------------------------------------------|
| [Callisto](https://blockscout.com/callisto/mainnet) | [Goerli Testnet](https://blockscout.com/eth/goerli) | [ARTIS](https://explorer.sigma1.artis.network) | [Celo Testnet](https://alfajores-blockscout.celo-testnet.org/) |
| [Ethereum Classic](https://blockscout.com/etc/mainnet) | [Kovan Testnet](https://blockscout.com/eth/kovan) | [Ether-1](https://blocks.ether1.wattpool.net/) | [Matic Testnet](https://explorer.testnet2.matic.network/) |
| [Ethereum Mainnet](https://blockscout.com/eth/mainnet) | [LUKSO L14 Testnet](https://blockscout.com/lukso/l14) | [Kotti Testnet](https://kottiexplorer.ethernode.io/) | |
| [Ethereum Mainnet](https://blockscout.com/eth/mainnet) | [LUKSO L14 Testnet](https://blockscout.com/lukso/l14) | [Kotti Testnet](https://kottiexplorer.ethernode.io/) | [Mordor Testnet](https://mordorexplorer.ethernode.io/) |
| [POA Core Network](https://blockscout.com/poa/core) | [POA Sokol Testnet](https://blockscout.com/poa/sokol) | [Fuse Network](https://explorer.fuse.io/) | |
| [RSK](https://blockscout.com/rsk/mainnet) | [Rinkeby Testnet](https://blockscout.com/eth/rinkeby) | [Oasis Labs](https://blockexplorer.oasiscloud.io/) | |
| [xDai Chain](https://blockscout.com/poa/dai) | [Ropsten Testnet](https://blockscout.com/eth/ropsten) | [Petrichor](https://explorer.petrachor.com/) | |

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

@ -79,4 +79,8 @@
&:last-child {
margin-bottom: 0;
}
}
.logs-decoded {
line-height: 25px!important;
}

@ -792,35 +792,37 @@ $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: #35335d!important;
background-color: #3f426c!important;
}
.dark-theme-applied .dropdown-item:hover:before {
content: "|";
height: 50px;
width: 50%;
opacity: 1;
background: none;
right: 17%;
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: "|";
height: 50px;
width: 50%;
opacity: 1;
background: none;
left: 24%;
top: 14%;
color: $dark-primary;
}
@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: "|";
height: 50px;
width: 50%;
opacity: 1;
background: none;
right: 17%;
color: $dark-primary;
position: relative;
}
.dark-theme-applied .navbar.navbar-primary .navbar-nav .nav-link:hover:before
{
content: "|";
height: 50px;
width: 50%;
opacity: 1;
background: none;
left: 24%;
top: 14%;
color: $dark-primary;
}
}

@ -37,7 +37,7 @@ $btn-line-bg: #fff; // button bg
$btn-line-color: #4b021e; // button border and font color && hover bg color
$btn-copy-color: #4b021e; // btn copy
$btn-qr-color: #4b021e; // btn qr-code
$btn-contract-color: #4b021e;
//links & tile
.tile a { color: $tertiary !important; } // links color for badges
.tile-type-block {

@ -35,6 +35,7 @@ $btn-line-color: $tertiary; // button border and font color && hover bg color
$btn-copy-color: $tertiary; // btn copy
$btn-qr-color: $tertiary; // btn qr-code
$btn-address-card-icon-color: $tertiary; // btn address color
$btn-contract-color: $tertiary;
//links & tile
$tile-body-a-color: $tertiary;

@ -38,6 +38,7 @@ $btn-line-color: $secondary; // button border and font color && hover bg color
$btn-copy-color: $secondary; // btn copy
$btn-qr-color: $secondary; // btn qr-code
$btn-address-card-icon-color: $secondary; // btn address color
$btn-contract-color: $secondary;
//links & tile
$tile-body-a-color: $secondary;

@ -44,6 +44,7 @@ $btn-line-color: $sub-accent-color; // button border and font color && hover bg
$btn-copy-color: $sub-accent-color; // btn copy
$btn-qr-color: $sub-accent-color; // btn qr-code
$btn-address-card-icon-color: $sub-accent-color; // btn address color
$btn-contract-color: $sub-accent-color;
//links & tile
$tile-body-a-color: $sub-accent-color;

@ -39,6 +39,7 @@ $btn-line-color: $tertiary; // button border and font color && hover bg color
$btn-copy-color: $tertiary; // btn copy
$btn-qr-color: $tertiary; // btn qr-code
$btn-address-card-icon-color: $tertiary; // btn address color
$btn-contract-color: $tertiary;
//links & tile
$tile-body-a-color: $tertiary;

@ -41,6 +41,7 @@ $btn-line-color: $primary; // button border and font color && hover bg color
$btn-copy-color: $primary; // btn copy
$btn-qr-color: $primary; // btn qr-code
$btn-address-card-icon-color: $primary; // btn address color
$btn-contract-color: $primary;
//links & tile
$tile-body-a-color: $primary;

@ -38,6 +38,7 @@ $btn-line-color: $secondary; // button border and font color && hover bg color
$btn-copy-color: $secondary; // btn copy
$btn-qr-color: $secondary; // btn qr-code
$btn-address-card-icon-color: $secondary; // btn address color
$btn-contract-color: $secondary;
//links & tile
$tile-body-a-color: $secondary;

@ -38,6 +38,7 @@ $btn-line-color: $secondary; // button border and font color && hover bg color
$btn-copy-color: $secondary; // btn copy
$btn-qr-color: $secondary; // btn qr-code
$btn-address-card-icon-color: $secondary; // btn address color
$btn-contract-color: $secondary;
//links & tile
$tile-body-a-color: $secondary;

@ -39,7 +39,7 @@ $btn-line-color: $secondary; // button border and font color && hover bg color
$btn-copy-color: $secondary; // btn copy
$btn-qr-color: $secondary; // btn qr-code
$btn-address-card-icon-color: $secondary; // btn address color
$btn-contract-color: $secondary;
// card
$card-background-1: $secondary;
$card-tab-active: $secondary;

@ -5,4 +5,4 @@ $tertiary: #53a9ff;
$footer-background-color: $primary;
$footer-title-color: #fff;
$footer-text-color: #fff;
$footer-item-disc-color: $secondary;
$footer-item-disc-color: $secondary;

@ -50,7 +50,7 @@ $btn-line-color: $sub-accent-color; // button border and font color && hover bg
$btn-copy-color: $sub-accent-color; // btn copy
$btn-qr-color: $sub-accent-color; // btn qr-code
$btn-address-card-icon-color: $sub-accent-color; // btn address color
$btn-contract-color: $sub-accent-color;
//links & tile
$tile-body-a-color: $sub-accent-color;
$tile-type-block-color: $sub-accent-color;

@ -1,3 +1,4 @@
$primary: #211841;
$secondary: #f16950;
$tertiary: #8b84bc;
$btn-contract-color: $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),

@ -27,7 +27,7 @@
document.getElementById("navbar-logo").style.filter = "brightness(0) invert(1)";
}
</script>
<button class="navbar-toggler" id="toggleButton" onclick="switchVisible(); type="button" value="Click" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="<%= gettext("Toggle navigation") %>">
<button class="navbar-toggler" id="toggleButton" type="button" value="Click" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="<%= gettext("Toggle navigation") %>">
<span class="navbar-toggler-icon" id="toggleImage1" style="width="26px;"></span>
<span class="navbar-toggler-icon-1" id="toggleImage2"> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 47.971 47.971" style="enable-background:new 0 0 47.971 47.971; width: 17px;
transform: translate(0px, -1.5px);" xml:space="preserve"> <g><g>

@ -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),

@ -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,20 +1346,43 @@ 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
case Blocks.take_enough(paging_options.page_size) do
nil ->
elements = fetch_blocks(block_type, paging_options, necessity_by_association)
cond do
block_type == "Block" && !paging_options.key ->
block_from_cache(block_type, paging_options, necessity_by_association)
Blocks.update(elements)
block_type == "Uncle" && !paging_options.key ->
uncles_from_cache(block_type, paging_options, necessity_by_association)
elements
true ->
fetch_blocks(block_type, paging_options, necessity_by_association)
end
end
blocks ->
blocks
end
else
fetch_blocks(block_type, paging_options, necessity_by_association)
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)
Blocks.update(elements)
elements
blocks ->
blocks
end
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
)

@ -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
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) when is_list(changes_list) do
Multi.new()
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()}

@ -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}}
@ -196,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)

Loading…
Cancel
Save