Merge branch 'ag-docs1' of https://github.com/andogro/poa-explorer into ag-docs1

pull/2124/head
Andrew Gross 6 years ago
commit 8f73cea92d
No known key found for this signature in database
GPG Key ID: 0DD4DAD72164DFDB
  1. 32
      CHANGELOG.md
  2. 2
      README.md
  3. 8
      apps/block_scout_web/assets/css/components/_api.scss
  4. 4
      apps/block_scout_web/assets/css/components/_badge.scss
  5. 14
      apps/block_scout_web/assets/css/components/_tile.scss
  6. 20
      apps/block_scout_web/assets/css/components/_verify_other_explorers.scss
  7. 1
      apps/block_scout_web/assets/css/theme/_base_variables.scss
  8. 7
      apps/block_scout_web/assets/css/theme/_dai_variables.scss
  9. 5
      apps/block_scout_web/assets/css/theme/_ethereum_classic_variables.scss
  10. 5
      apps/block_scout_web/assets/css/theme/_goerli_variables.scss
  11. 9
      apps/block_scout_web/assets/css/theme/_kovan_variables.scss
  12. 5
      apps/block_scout_web/assets/css/theme/_lukso_variables.scss
  13. 7
      apps/block_scout_web/assets/css/theme/_neutral_variables.scss
  14. 7
      apps/block_scout_web/assets/css/theme/_poa_variables.scss
  15. 29
      apps/block_scout_web/assets/css/theme/_posdao_variables.scss
  16. 4
      apps/block_scout_web/assets/css/theme/_rsk_variables.scss
  17. 4
      apps/block_scout_web/assets/css/theme/_sokol_variables.scss
  18. 1
      apps/block_scout_web/assets/js/lib/currency.js
  19. 12
      apps/block_scout_web/assets/static/images/dai_logo.svg
  20. BIN
      apps/block_scout_web/assets/static/images/errors-img/goerli-page-not-found.png
  21. BIN
      apps/block_scout_web/assets/static/images/errors-img/goerli-page-not-found@2x.png
  22. BIN
      apps/block_scout_web/assets/static/images/goerli_logo.png
  23. BIN
      apps/block_scout_web/assets/static/images/posdao_logo.png
  24. BIN
      apps/block_scout_web/assets/static/images/posdao_logo_footer.png
  25. 21
      apps/block_scout_web/assets/static/images/posdao_logo_footer.svg
  26. 7
      apps/block_scout_web/lib/block_scout_web/channels/reward_channel.ex
  27. 5
      apps/block_scout_web/lib/block_scout_web/controller.ex
  28. 2
      apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex
  29. 43
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex
  30. 118
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/eth_controller.ex
  31. 8
      apps/block_scout_web/lib/block_scout_web/controllers/page_not_found_controller.ex
  32. 8
      apps/block_scout_web/lib/block_scout_web/controllers/pending_transaction_controller.ex
  33. 50
      apps/block_scout_web/lib/block_scout_web/etherscan.ex
  34. 4
      apps/block_scout_web/lib/block_scout_web/router.ex
  35. 2
      apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex
  36. 7
      apps/block_scout_web/lib/block_scout_web/templates/address_decompiled_contract/index.html.eex
  37. 2
      apps/block_scout_web/lib/block_scout_web/templates/common_components/_pagination_container.html.eex
  38. 2
      apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex
  39. 4
      apps/block_scout_web/lib/block_scout_web/templates/page_not_found/index.html.eex
  40. 4
      apps/block_scout_web/lib/block_scout_web/templates/transaction/_tile.html.eex
  41. 6
      apps/block_scout_web/lib/block_scout_web/views/address_decompiled_contract_view.ex
  42. 6
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex
  43. 36
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_rpc_view.ex
  44. 13
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_view.ex
  45. 5
      apps/block_scout_web/lib/block_scout_web/views/page_not_found.ex
  46. 12
      apps/block_scout_web/lib/block_scout_web/views/wei_helpers.ex
  47. 17
      apps/block_scout_web/priv/gettext/default.pot
  48. 19
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  49. 199
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs
  50. 4
      apps/block_scout_web/test/block_scout_web/controllers/block_controller_test.exs
  51. 4
      apps/block_scout_web/test/block_scout_web/controllers/chain_controller_test.exs
  52. 9
      apps/block_scout_web/test/block_scout_web/controllers/pending_transaction_controller_test.exs
  53. 4
      apps/block_scout_web/test/block_scout_web/features/viewing_chain_test.exs
  54. 8
      apps/block_scout_web/test/block_scout_web/views/address_decompiled_contract_view_test.exs
  55. 13
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex
  56. 2
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex
  57. 6
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/fetched_beneficiaries.ex
  58. 6
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs
  59. 18
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs
  60. 3
      apps/explorer/config/config.exs
  61. 18
      apps/explorer/lib/explorer/application.ex
  62. 181
      apps/explorer/lib/explorer/chain.ex
  63. 70
      apps/explorer/lib/explorer/chain/address.ex
  64. 44
      apps/explorer/lib/explorer/chain/net_version_cache.ex
  65. 37
      apps/explorer/lib/explorer/chain/transaction.ex
  66. 11
      apps/explorer/lib/explorer/chain/wei.ex
  67. 2
      apps/explorer/lib/explorer/counters/average_block_time.ex
  68. 4
      apps/explorer/lib/explorer/exchange_rates/source.ex
  69. 7
      apps/explorer/priv/repo/migrations/20190613065856_add_tx_hash_inserted_at_index.exs
  70. 21
      apps/explorer/test/explorer/chain/address_test.exs
  71. 4
      apps/explorer/test/explorer/chain/blocks_cache_test.exs
  72. 34
      apps/explorer/test/explorer/counters/average_block_time_test.exs
  73. 4
      apps/explorer/test/support/data_case.ex
  74. 8
      apps/indexer/lib/indexer/block/realtime/fetcher.ex
  75. 41
      apps/indexer/lib/indexer/fetcher/uncle_block.ex
  76. 33
      apps/indexer/lib/indexer/temporary/blocks_transactions_mismatch.ex
  77. 70
      apps/indexer/test/indexer/fetcher/uncle_block_test.exs
  78. 2
      bin/install_chrome_headless.sh
  79. 2
      docker/Dockerfile

@ -1,9 +1,23 @@
## Current ## Current
### Features ### Features
- [#2109](https://github.com/poanetwork/blockscout/pull/2109) - use bigger updates instead of `Multi` transactions in BlocksTransactionsMismatch
- [#2075](https://github.com/poanetwork/blockscout/pull/2075) - add blocks cache - [#2075](https://github.com/poanetwork/blockscout/pull/2075) - add blocks cache
- [#2151](https://github.com/poanetwork/blockscout/pull/2151) - hide dropdown menu then other networks list is empty
### Fixes ### Fixes
- [#2179](https://github.com/poanetwork/blockscout/pull/2179) - fix docker build error
- [#2165](https://github.com/poanetwork/blockscout/pull/2165) - sort blocks by timestamp when calculating average block time
- [#2175](https://github.com/poanetwork/blockscout/pull/2175) - fix coinmarketcap response errors
- [#2164](https://github.com/poanetwork/blockscout/pull/2164) - fix large numbers in balance view card
- [#2155](https://github.com/poanetwork/blockscout/pull/2155) - fix pending transaction query
- [#2183](https://github.com/poanetwork/blockscout/pull/2183) - tile content aligning for mobile resolution fix, dai logo fix
- [#2162](https://github.com/poanetwork/blockscout/pull/2162) - contract creation tile color changed
- [#2144](https://github.com/poanetwork/blockscout/pull/2144) - 'page not found' images path fixed for goerli
- [#2142](https://github.com/poanetwork/blockscout/pull/2142) - Removed posdao theme and logo, added 'page not found' image for goerli
- [#2138](https://github.com/poanetwork/blockscout/pull/2138) - badge colors issue, api titles issue
- [#2129](https://github.com/poanetwork/blockscout/pull/2129) - Fix for width of explorer elements
- [#2121](https://github.com/poanetwork/blockscout/pull/2121) - Binding of 404 page
- [#2120](https://github.com/poanetwork/blockscout/pull/2120) - footer links and socials focus color issue - [#2120](https://github.com/poanetwork/blockscout/pull/2120) - footer links and socials focus color issue
- [#2113](https://github.com/poanetwork/blockscout/pull/2113) - renewed logos for rsk, dai, blockscout; themes color changes for lukso; error images for lukso - [#2113](https://github.com/poanetwork/blockscout/pull/2113) - renewed logos for rsk, dai, blockscout; themes color changes for lukso; error images for lukso
- [#2112](https://github.com/poanetwork/blockscout/pull/2112) - themes color improvements, dropdown color issue - [#2112](https://github.com/poanetwork/blockscout/pull/2112) - themes color improvements, dropdown color issue
@ -12,7 +26,22 @@
- [#2090](https://github.com/poanetwork/blockscout/pull/2090) - updated some ETC theme colors - [#2090](https://github.com/poanetwork/blockscout/pull/2090) - updated some ETC theme colors
- [#2096](https://github.com/poanetwork/blockscout/pull/2096) - RSK theme fixes - [#2096](https://github.com/poanetwork/blockscout/pull/2096) - RSK theme fixes
- [#2093](https://github.com/poanetwork/blockscout/pull/2093) - detect token transfer type for deprecated erc721 spec - [#2093](https://github.com/poanetwork/blockscout/pull/2093) - detect token transfer type for deprecated erc721 spec
- [#2108](https://github.com/poanetwork/blockscout/pull/2108) - fixe uncle fetching without full transactions - [#2111](https://github.com/poanetwork/blockscout/pull/2111) - improve address transaction controller
- [#2108](https://github.com/poanetwork/blockscout/pull/2108) - fix uncle fetching without full transactions
- [#2128](https://github.com/poanetwork/blockscout/pull/2128) - add new function clause for uncle errors
- [#2123](https://github.com/poanetwork/blockscout/pull/2123) - fix coins percentage view
- [#2119](https://github.com/poanetwork/blockscout/pull/2119) - fix map logging
- [#2130](https://github.com/poanetwork/blockscout/pull/2130) - fix navigation
- [#2147](https://github.com/poanetwork/blockscout/pull/2147) - add rsk format of checksum
- [#2149](https://github.com/poanetwork/blockscout/pull/2149) - remove pending transaction count
- [#2169](https://github.com/poanetwork/blockscout/pull/2169) - add more validator reward types for xDai
- [#2173](https://github.com/poanetwork/blockscout/pull/2173) - handle correctly empty transactions
- [#2174](https://github.com/poanetwork/blockscout/pull/2174) - fix reward channel joining
- [#2186](https://github.com/poanetwork/blockscout/pull/2186) - fix net version test
### Chore
- [#2127](https://github.com/poanetwork/blockscout/pull/2127) - use previouse chromedriver version
- [#2118](https://github.com/poanetwork/blockscout/pull/2118) - show only the last decompiled contract
### Chore ### Chore
@ -41,6 +70,7 @@
- [#2037](https://github.com/poanetwork/blockscout/pull/2037) - add address logs search functionality - [#2037](https://github.com/poanetwork/blockscout/pull/2037) - add address logs search functionality
- [#2012](https://github.com/poanetwork/blockscout/pull/2012) - make all pages pagination async - [#2012](https://github.com/poanetwork/blockscout/pull/2012) - make all pages pagination async
- [#2064](https://github.com/poanetwork/blockscout/pull/2064) - feat: add fields to tx apis, small cleanups - [#2064](https://github.com/poanetwork/blockscout/pull/2064) - feat: add fields to tx apis, small cleanups
- [#2100](https://github.com/poanetwork/blockscout/pull/2100) - feat: eth_get_balance rpc endpoint
### Fixes ### Fixes
- [#2099](https://github.com/poanetwork/blockscout/pull/2099) - logs search input width - [#2099](https://github.com/poanetwork/blockscout/pull/2099) - logs search input width

@ -54,6 +54,8 @@ Currently available block explorers (i.e. Etherscan and Etherchain) are closed s
| [xDai Chain](https://blockscout.com/poa/dai) | | [SafeChain](https://explorer.safechain.io) | | [xDai Chain](https://blockscout.com/poa/dai) | | [SafeChain](https://explorer.safechain.io) |
| | | [SpringChain](https://explorer.springrole.com/) | | | | [SpringChain](https://explorer.springrole.com/) |
| | | [Kotti Testnet](https://kottiexplorer.ethernode.io/) | | | | [Kotti Testnet](https://kottiexplorer.ethernode.io/) |
| | | [Loom](http://plasma-blockexplorer.dappchains.com/) |
| | | [Tenda](https://tenda.network) |
### Visual Interface ### Visual Interface

@ -1,7 +1,7 @@
$api-text-monospace-color: $primary !default; $api-text-monospace-color: $secondary !default;
$api-text-monospace-background: rgba($api-text-monospace-color, 0.1) !default; $api-text-monospace-background: rgba($api-text-monospace-color, 0.1) !default;
$api-anchors-list-background-color: #f6f7f9 !default; $api-anchors-list-background-color: #f6f7f9 !default;
$api-doc-list-item-title-color: $primary !default; $api-doc-list-item-title-color: #333 !default;
$api-doc-list-item-view-more-color: $api-doc-list-item-title-color !default; $api-doc-list-item-view-more-color: $api-doc-list-item-title-color !default;
.api-text-monospace { .api-text-monospace {
@ -89,8 +89,8 @@ $api-doc-list-item-view-more-color: $api-doc-list-item-title-color !default;
.api-doc-list-item-title { .api-doc-list-item-title {
color: $api-doc-list-item-title-color; color: $api-doc-list-item-title-color;
font-size: 17px; font-size: 15px;
font-weight: 700; font-weight: 400;
line-height: 1.2; line-height: 1.2;
margin: 0 0 15px; margin: 0 0 15px;
} }

@ -2,8 +2,8 @@ $badge-success-color: #15bba6 !default;
$badge-success-background-color: rgba($badge-success-color, 0.1) !default; $badge-success-background-color: rgba($badge-success-color, 0.1) !default;
$badge-danger-color: #ed9966 !default; $badge-danger-color: #ed9966 !default;
$badge-danger-background-color: rgba($badge-danger-color, 0.1) !default; $badge-danger-background-color: rgba($badge-danger-color, 0.1) !default;
$badge-neutral-color: #333 !default; $badge-neutral-color: $secondary !default;
$badge-neutral-background-color: #e9e9e9 !default; $badge-neutral-background-color: rgba($secondary, .1) !default;
.badge { .badge {
color: $white; color: $white;

@ -4,7 +4,7 @@ $tile-type-reorg-color: $purple !default;
$tile-type-emission-reward-color: $lilac !default; $tile-type-emission-reward-color: $lilac !default;
$tile-type-transaction-color: $blue !default; $tile-type-transaction-color: $blue !default;
$tile-type-contract-call-color: $green !default; $tile-type-contract-call-color: $green !default;
$tile-type-contract-creation-color: $pink !default; $tile-type-contract-creation-color: $dark-purple !default;
$tile-type-token-transfer-color: $orange !default; $tile-type-token-transfer-color: $orange !default;
$tile-type-unique-token-color: $orange !default; $tile-type-unique-token-color: $orange !default;
$tile-type-unique-token-image-color: $orange !default; $tile-type-unique-token-image-color: $orange !default;
@ -104,6 +104,18 @@ $tile-body-a-color: #5959d8 !default;
padding: 0 5px; padding: 0 5px;
} }
.tile-transaction-type-block {
.tile-status-label {
padding: 0;
}
}
.tile-bottom {
@media (max-width: 767px) {
justify-content: flex-start !important;
}
}
.tile-bottom-contents { .tile-bottom-contents {
background-color: #f6f7f9; background-color: #f6f7f9;
font-size: 12px; font-size: 12px;

@ -18,9 +18,13 @@
line-height: 1.25; line-height: 1.25;
display: inline-flex; display: inline-flex;
margin-bottom: 12px; margin-bottom: 12px;
@media (min-width: 1200px) { width: 100%;
@media (min-width: 768px) {
margin-right: 10px; margin-right: 10px;
}
@media (min-width: 1200px) {
margin-bottom: 0; margin-bottom: 0;
width: auto;
} }
} }
} }
@ -31,6 +35,8 @@
flex-grow: 2; flex-grow: 2;
@media (min-width: 768px) { @media (min-width: 768px) {
flex-direction: row; flex-direction: row;
position: relative;
padding-right: 44px;
} }
} }
@ -41,6 +47,7 @@
flex-grow: 2; flex-grow: 2;
@media (min-width: 768px) { @media (min-width: 768px) {
margin-top: 0; margin-top: 0;
max-width: 188px;
} }
@media (min-width: 1200px) { @media (min-width: 1200px) {
min-width: 145px; min-width: 145px;
@ -76,7 +83,7 @@
} }
.exp-content { .exp-content {
padding: 6px 9px 4px 9px; padding: 6px 9px 5px 9px;
h3, div { h3, div {
font-size: 10px; font-size: 10px;
line-height: 1; line-height: 1;
@ -117,7 +124,7 @@
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border: 1px solid $secondary; border: 1px solid $btn-line-color;
border-radius: 2px; border-radius: 2px;
margin-top: 10px; margin-top: 10px;
transition: .1s ease-in; transition: .1s ease-in;
@ -125,12 +132,15 @@
@media (min-width: 768px) { @media (min-width: 768px) {
margin-left: 10px; margin-left: 10px;
margin-top: 0; margin-top: 0;
position: absolute;
top: 0;
right: 0;
} }
svg path { svg path {
fill: $secondary; fill: $btn-line-color;
} }
&:hover { &:hover {
background-color: $secondary; background-color: $btn-line-color;
svg path { svg path {
fill: #fff; fill: #fff;
} }

@ -47,6 +47,7 @@ $yellow: #ffc107 !default;
$green: #20b760 !default; $green: #20b760 !default;
$teal: #009097 !default; $teal: #009097 !default;
$cyan: #90e1d8 !default; $cyan: #90e1d8 !default;
$dark-purple: #923dc3;
$colors: () !default; $colors: () !default;
$colors: map-merge( $colors: map-merge(

@ -57,4 +57,9 @@ $card-tab-active: $secondary;
$dashboard-banner-gradient-end $dashboard-banner-gradient-end
); );
} }
} }
// Badges
$badge-neutral-color: #20446e;
$badge-neutral-background-color: rgba(#20446e, .1);
$api-text-monospace-color: #20446e;

@ -70,3 +70,8 @@ $card-tab-active: $tertiary;
filter: brightness(0) invert(1); filter: brightness(0) invert(1);
} }
} }
// Badges
$badge-neutral-color: $tertiary;
$badge-neutral-background-color: rgba($tertiary, .1);
$api-text-monospace-color: $tertiary;

@ -73,3 +73,8 @@ $card-tab-active: $sub-accent-color;
); );
} }
} }
// Badges
$badge-neutral-color: $sub-accent-color;
$badge-neutral-background-color: rgba($sub-accent-color, .1);
$api-text-monospace-color: $sub-accent-color;

@ -67,4 +67,11 @@ $card-tab-active: $tertiary;
$dashboard-banner-gradient-end $dashboard-banner-gradient-end
); );
} }
} }
// Badges
$badge-success-color: #15bba6;
$badge-success-background-color: rgba(#15bba6, .1);
$badge-neutral-color: $tertiary;
$badge-neutral-background-color: rgba($tertiary, .1);
$api-text-monospace-color: $tertiary;

@ -146,3 +146,8 @@ $dashboard-banner-network-plain-container-height: 150px;
} }
} }
} }
// Badges
$badge-neutral-color: $tertiary;
$badge-neutral-background-color: rgba($tertiary, .1);
$api-text-monospace-color: $tertiary;

@ -59,4 +59,9 @@ $card-tab-active: $primary;
$dashboard-banner-gradient-end $dashboard-banner-gradient-end
); );
} }
} }
// Badges
$badge-neutral-color: $primary;
$badge-neutral-background-color: rgba($primary, .1);
$api-text-monospace-color: $primary;

@ -59,4 +59,9 @@ $card-tab-active: $primary;
$dashboard-banner-gradient-end $dashboard-banner-gradient-end
); );
} }
} }
// Badges
$badge-neutral-color: $primary;
$badge-neutral-background-color: rgba($primary, .1);
$api-text-monospace-color: $primary;

@ -1,29 +0,0 @@
$primary: #15bba6;
$secondary: #17314f;
$tertiary: #00ff00;
$header-links-color-active: #333;
$dashboard-banner-gradient-start: $secondary;
$dashboard-banner-gradient-end: #1e4168;
$dashboard-line-color-market: $primary;
$tile-type-block-border-color: $secondary;
$tile-type-block-color: #333;
$footer-background-color: #173250;
$footer-text-color: #909dac;
$navbar-logo-height: auto;
$navbar-logo-width: 100px;
$footer-logo-height: auto;
$footer-logo-width: 100px;
$card-background-1: $secondary;
$card-background-1-text-color: #fff;
$btn-copy-color: $secondary;
$btn-qr-color: $secondary;
$btn-dropdown-line-color: $secondary;

@ -61,3 +61,7 @@ $card-tab-active: $secondary;
filter: brightness(0) invert(1); filter: brightness(0) invert(1);
} }
} }
// Badges
$badge-neutral-color: #1a323b;
$badge-neutral-background-color: rgba(#1a323b, .1);

@ -65,3 +65,7 @@ $card-tab-active: $sub-accent-color;
); );
} }
} }
// Badges
$badge-neutral-color: $tertiary;
$badge-neutral-background-color: rgba($tertiary, .1);

@ -18,6 +18,7 @@ function formatCurrencyValue (value, symbol) {
if (value < 0.000001) return `${window.localized['Less than']} ${symbol}0.000001` if (value < 0.000001) return `${window.localized['Less than']} ${symbol}0.000001`
if (value < 1) return `${symbol}${numeral(value).format('0.000000')}` if (value < 1) return `${symbol}${numeral(value).format('0.000000')}`
if (value < 100000) return `${symbol}${numeral(value).format('0,0.00')}` if (value < 100000) return `${symbol}${numeral(value).format('0,0.00')}`
if (value > 1000000000) return `${symbol}${numeral(value).format('0.000e+0')}`
return `${symbol}${numeral(value).format('0,0')}` return `${symbol}${numeral(value).format('0,0')}`
} }

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

@ -1,21 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="111" height="36">
<defs>
<linearGradient id="a" x1="0%" x2="0%" y1="100%" y2="0%">
<stop offset="0%" stop-color="#FFF" stop-opacity="0"/>
<stop offset="100%" stop-color="#FFF"/>
</linearGradient>
<linearGradient id="b" x1="0%" x2="86.603%" y1="50%" y2="0%">
<stop offset="0%" stop-color="#FFF"/>
<stop offset="100%" stop-color="#FFF" stop-opacity="0"/>
</linearGradient>
</defs>
<path fill="#FFF" fill-rule="evenodd" d="M16.001 36L.005 26.999V8.998L16.001-.003l15.996 9.001v18.001L16.001 36zm10.074-23.52L15.994 6.83 5.912 12.48v11.301l10.082 5.65 10.081-5.65V12.48z" opacity=".4"/>
<path fill="none" d="M0 8.934l5.914 3.688v10.499L0 19.433V8.934z" opacity=".6"/>
<path fill="url(#a)" d="M0 8.934l5.914 3.688v10.499L0 19.433V8.934z"/>
<path fill="none" d="M31.992 8.934l-5.915 3.688v10.499l5.915-3.688V8.934z" opacity=".6"/>
<path fill="url(#a)" d="M31.992 8.934l-5.915 3.688v10.499l5.915-3.688V8.934z"/>
<path fill="none" d="M15.861 29.431l10.216-5.541v6.569L15.861 36v-6.569z" opacity=".6"/>
<path fill="url(#b)" d="M15.861 29.431l10.216-5.541v6.569L15.861 36v-6.569z"/>
<path fill="#FFF" fill-rule="evenodd" d="M16.494 25.007l-6.505-3.504v-7.007l6.505-3.504L23 14.496v7.007l-6.506 3.504z" opacity=".8"/>
<path fill="#FFF" fill-rule="evenodd" d="M16.987 17.727l6.014-3.74v7.281l-6.014 3.74v-7.281zM109.56 21.291c-.437.875-1.058 1.547-1.864 2.016-.805.47-1.752.704-2.84.704-1.098 0-2.05-.234-2.856-.704-.805-.469-1.426-1.141-1.864-2.016-.437-.874-.656-1.898-.656-3.072 0-1.173.216-2.194.648-3.064a4.664 4.664 0 0 1 1.864-2.008c.811-.469 1.766-.704 2.864-.704 1.099 0 2.051.235 2.856.704.806.47 1.424 1.139 1.856 2.008.432.87.648 1.891.648 3.064 0 1.174-.218 2.198-.656 3.072zm-2.28-6.136c-.581-.709-1.389-1.064-2.424-1.064-1.045 0-1.858.355-2.44 1.064-.581.71-.872 1.731-.872 3.064 0 1.334.291 2.358.872 3.072.582.715 1.395 1.072 2.44 1.072 1.035 0 1.843-.357 2.424-1.072.582-.714.872-1.738.872-3.072 0-1.333-.29-2.354-.872-3.064zm-9.624 8.776a.91.91 0 0 1-.48-.136.91.91 0 0 1-.352-.424l-.96-2.144h-5.632l-.96 2.144a.898.898 0 0 1-.352.424.932.932 0 0 1-.496.136c-.245 0-.469-.085-.672-.256a.787.787 0 0 1-.304-.624c0-.138.032-.277.096-.416l4.416-9.488c.096-.213.243-.376.44-.488.198-.112.408-.168.632-.168.224 0 .435.056.632.168.198.112.344.275.44.488l4.432 9.488a.989.989 0 0 1 .096.416.791.791 0 0 1-.296.624 1.007 1.007 0 0 1-.68.256zm-4.608-9.056l-2.112 4.752h4.224l-2.112-4.752zm-12.352 8.992h-3.168c-.309 0-.552-.085-.728-.256-.176-.17-.264-.41-.264-.72v-9.328c0-.309.088-.549.264-.72.176-.17.419-.256.728-.256h3.168c1.846 0 3.278.494 4.296 1.48 1.019.987 1.528 2.371 1.528 4.152 0 1.792-.509 3.182-1.528 4.168-1.018.987-2.45 1.48-4.296 1.48zm-.128-9.6H78.6v7.92h1.968c2.592 0 3.888-1.322 3.888-3.968 0-2.634-1.296-3.952-3.888-3.952zm-10.079 3.152c.949.214 1.72.451 2.312.712.592.262 1.045.595 1.36 1 .314.406.472.912.472 1.52a2.9 2.9 0 0 1-.552 1.752c-.368.507-.886.902-1.552 1.184-.667.283-1.432.424-2.296.424a8.061 8.061 0 0 1-2.328-.328c-.731-.218-1.326-.514-1.784-.888-.246-.181-.368-.437-.368-.768a.9.9 0 0 1 .2-.584c.133-.165.29-.248.472-.248.192 0 .41.075.656.224.458.331.938.571 1.44.72a5.79 5.79 0 0 0 1.664.224c.778 0 1.376-.136 1.792-.408.416-.272.624-.669.624-1.192 0-.405-.195-.717-.584-.936-.39-.218-1.038-.429-1.944-.632-.939-.202-1.702-.437-2.288-.704-.587-.266-1.03-.608-1.328-1.024-.299-.416-.448-.944-.448-1.584 0-.661.186-1.253.56-1.776.373-.522.893-.93 1.56-1.224.666-.293 1.416-.44 2.248-.44 1.525 0 2.768.406 3.728 1.216.138.118.237.232.296.344a.919.919 0 0 1 .088.424.903.903 0 0 1-.2.584c-.134.166-.291.248-.472.248a.88.88 0 0 1-.296-.048 2.281 2.281 0 0 1-.36-.176 5.445 5.445 0 0 0-1.272-.712c-.411-.154-.915-.232-1.512-.232-.726 0-1.299.147-1.72.44-.422.294-.632.702-.632 1.224 0 .438.186.774.56 1.008.373.235 1.008.454 1.904.656zm-8.76 5.888c-.806.47-1.752.704-2.84.704-1.099 0-2.051-.234-2.856-.704-.806-.469-1.427-1.141-1.864-2.016-.438-.874-.656-1.898-.656-3.072 0-1.173.216-2.194.648-3.064a4.658 4.658 0 0 1 1.864-2.008c.81-.469 1.765-.704 2.864-.704 1.098 0 2.05.235 2.856.704a4.677 4.677 0 0 1 1.856 2.008c.432.87.648 1.891.648 3.064 0 1.174-.219 2.198-.656 3.072-.438.875-1.059 1.547-1.864 2.016zm-.416-8.152c-.582-.709-1.39-1.064-2.424-1.064-1.046 0-1.859.355-2.44 1.064-.582.71-.872 1.731-.872 3.064 0 1.334.29 2.358.872 3.072.581.715 1.394 1.072 2.44 1.072 1.034 0 1.842-.357 2.424-1.072.581-.714.872-1.738.872-3.072 0-1.333-.291-2.354-.872-3.064zm-13.032 4.328h-2.864v3.44c0 .32-.094.574-.28.76-.187.187-.44.28-.76.28-.31 0-.558-.093-.744-.28-.187-.186-.28-.44-.28-.76v-9.36c0-.309.088-.549.264-.72.176-.17.418-.256.728-.256h3.936c1.205 0 2.144.302 2.816.904.672.603 1.008 1.448 1.008 2.536s-.336 1.936-1.008 2.544c-.672.608-1.611.912-2.816.912zm-.256-5.296h-2.608v3.712h2.608c1.418 0 2.128-.618 2.128-1.856 0-1.237-.71-1.856-2.128-1.856z"/>
</svg>

Before

Width:  |  Height:  |  Size: 4.9 KiB

@ -11,9 +11,10 @@ defmodule BlockScoutWeb.RewardChannel do
intercept(["new_reward"]) intercept(["new_reward"])
def join("rewards:" <> address_hash, _params, socket) do def join("rewards:" <> address_hash, _params, socket) do
{:ok, hash} = Chain.string_to_address_hash(address_hash) with {:ok, hash} <- Chain.string_to_address_hash(address_hash),
{:ok, address} = Chain.hash_to_address(hash) {:ok, address} <- Chain.hash_to_address(hash) do
{:ok, %{}, assign(socket, :current_address, address)} {:ok, %{}, assign(socket, :current_address, address)}
end
end end
def handle_out("new_reward", %{emission_funds: emission_funds, validator: validator}, socket) do def handle_out("new_reward", %{emission_funds: emission_funds, validator: validator}, socket) do

@ -12,8 +12,9 @@ defmodule BlockScoutWeb.Controller do
def not_found(conn) do def not_found(conn) do
conn conn
|> put_status(:not_found) |> put_status(:not_found)
|> put_view(BlockScoutWeb.ErrorView) |> put_view(BlockScoutWeb.PageNotFoundView)
|> render("404.html") |> render(:index)
|> halt()
end end
def unprocessable_entity(conn) do def unprocessable_entity(conn) do

@ -28,7 +28,7 @@ defmodule BlockScoutWeb.AddressTransactionController do
def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do {:ok, address} <- Chain.hash_to_address(address_hash, [:names], false) do
options = options =
@transaction_necessity_by_association @transaction_necessity_by_association
|> put_in([:necessity_by_association, :block], :required) |> put_in([:necessity_by_association, :block], :required)

@ -20,6 +20,35 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
|> render(:listaccounts, %{accounts: accounts}) |> render(:listaccounts, %{accounts: accounts})
end end
def eth_get_balance(conn, params) do
with {:address_param, {:ok, address_param}} <- fetch_address(params),
{:block_param, {:ok, block}} <- {:block_param, fetch_block_param(params)},
{:format, {:ok, address_hash}} <- to_address_hash(address_param),
{:balance, {:ok, balance}} <- {:balance, Chain.get_balance_as_of_block(address_hash, block)} do
render(conn, :eth_get_balance, %{balance: Wei.hex_format(balance)})
else
{:address_param, :error} ->
conn
|> put_status(400)
|> render(:eth_get_balance_error, %{message: "Query parameter 'address' is required"})
{:format, :error} ->
conn
|> put_status(400)
|> render(:eth_get_balance_error, %{error: "Invalid address hash"})
{:block_param, :error} ->
conn
|> put_status(400)
|> render(:eth_get_balance_error, %{error: "Invalid block"})
{:balance, {:error, :not_found}} ->
conn
|> put_status(404)
|> render(:eth_get_balance_error, %{error: "Balance not found"})
end
end
def balance(conn, params, template \\ :balance) do def balance(conn, params, template \\ :balance) do
with {:address_param, {:ok, address_param}} <- fetch_address(params), with {:address_param, {:ok, address_param}} <- fetch_address(params),
{:format, {:ok, address_hashes}} <- to_address_hashes(address_param) do {:format, {:ok, address_hashes}} <- to_address_hashes(address_param) do
@ -217,6 +246,20 @@ defmodule BlockScoutWeb.API.RPC.AddressController do
{:required_params, result} {:required_params, result}
end end
defp fetch_block_param(%{"block" => "latest"}), do: {:ok, :latest}
defp fetch_block_param(%{"block" => "earliest"}), do: {:ok, :earliest}
defp fetch_block_param(%{"block" => "pending"}), do: {:ok, :pending}
defp fetch_block_param(%{"block" => string_integer}) when is_bitstring(string_integer) do
case Integer.parse(string_integer) do
{integer, ""} -> {:ok, integer}
_ -> :error
end
end
defp fetch_block_param(%{"block" => _block}), do: :error
defp fetch_block_param(_), do: {:ok, :latest}
defp to_valid_format(params, :tokenbalance) do defp to_valid_format(params, :tokenbalance) do
result = result =
with {:ok, contract_address_hash} <- to_address_hash(params, "contractaddress"), with {:ok, contract_address_hash} <- to_address_hash(params, "contractaddress"),

@ -0,0 +1,118 @@
defmodule BlockScoutWeb.API.RPC.EthController do
use BlockScoutWeb, :controller
alias Explorer.Chain
alias Explorer.Chain.Wei
def eth_request(%{body_params: %{"_json" => requests}} = conn, _) when is_list(requests) do
responses = responses(requests)
conn
|> put_status(200)
|> render("responses.json", %{responses: responses})
end
def eth_request(%{body_params: %{"_json" => request}} = conn, _) do
[response] = responses([request])
conn
|> put_status(200)
|> render("response.json", %{response: response})
end
def eth_request(conn, request) do
# In the case that the JSON body is sent up w/o a json content type,
# Phoenix encodes it as a single key value pair, with the value being
# nil and the body being the key (as in a CURL request w/ no content type header)
decoded_request =
with [{single_key, nil}] <- Map.to_list(request),
{:ok, decoded} <- Jason.decode(single_key) do
decoded
else
_ -> request
end
[response] = responses([decoded_request])
conn
|> put_status(200)
|> render("response.json", %{response: response})
end
defp responses(requests) do
Enum.map(requests, fn request ->
with {:id, {:ok, id}} <- {:id, Map.fetch(request, "id")},
{:request, {:ok, result}} <- {:request, do_eth_request(request)} do
format_success(result, id)
else
{:id, :error} -> format_error("id is a required field", 0)
{:request, {:error, message}} -> format_error(message, Map.get(request, "id"))
end
end)
end
defp format_success(result, id) do
%{result: result, id: id}
end
defp format_error(message, id) do
%{error: message, id: id}
end
defp do_eth_request(%{"jsonrpc" => rpc_version}) when rpc_version != "2.0" do
{:error, "invalid rpc version"}
end
defp do_eth_request(%{"jsonrpc" => "2.0", "method" => method, "params" => params})
when is_list(params) do
with {:ok, action} <- get_action(method),
true <- :erlang.function_exported(__MODULE__, action, Enum.count(params)) do
apply(__MODULE__, action, params)
else
_ ->
{:error, "Action not found."}
end
end
defp do_eth_request(%{"params" => _params, "method" => _}) do
{:error, "Invalid params. Params must be a list."}
end
defp do_eth_request(_) do
{:error, "Method, params, and jsonrpc, are all required parameters."}
end
def eth_get_balance(address_param, block_param \\ nil) do
with {:address, {:ok, address}} <- {:address, Chain.string_to_address_hash(address_param)},
{:block, {:ok, block}} <- {:block, block_param(block_param)},
{:balance, {:ok, balance}} <- {:balance, Chain.get_balance_as_of_block(address, block)} do
{:ok, Wei.hex_format(balance)}
else
{:address, :error} ->
{:error, "Query parameter 'address' is invalid"}
{:block, :error} ->
{:error, "Query parameter 'block' is invalid"}
{:balance, {:error, :not_found}} ->
{:error, "Balance not found"}
end
end
defp get_action("eth_getBalance"), do: {:ok, :eth_get_balance}
defp get_action(_), do: :error
defp block_param("latest"), do: {:ok, :latest}
defp block_param("earliest"), do: {:ok, :earliest}
defp block_param("pending"), do: {:ok, :pending}
defp block_param(string_integer) when is_bitstring(string_integer) do
case Integer.parse(string_integer) do
{integer, ""} -> {:ok, integer}
_ -> :error
end
end
defp block_param(nil), do: {:ok, :latest}
defp block_param(_), do: :error
end

@ -0,0 +1,8 @@
defmodule BlockScoutWeb.PageNotFoundController do
use BlockScoutWeb, :controller
def index(conn, _params) do
conn
|> render("index.html")
end
end

@ -13,7 +13,8 @@ defmodule BlockScoutWeb.PendingTransactionController do
[ [
necessity_by_association: %{ necessity_by_association: %{
[from_address: :names] => :optional, [from_address: :names] => :optional,
[to_address: :names] => :optional [to_address: :names] => :optional,
[created_contract_address: :names] => :optional
} }
], ],
paging_options(params) paging_options(params)
@ -51,10 +52,7 @@ defmodule BlockScoutWeb.PendingTransactionController do
end end
def index(conn, _params) do def index(conn, _params) do
render(conn, "index.html", render(conn, "index.html", current_path: current_path(conn))
current_path: current_path(conn),
pending_transaction_count: Chain.pending_transaction_count()
)
end end
defp get_pending_transactions_and_next_page(options) do defp get_pending_transactions_and_next_page(options) do

@ -100,6 +100,12 @@ defmodule BlockScoutWeb.Etherscan do
"result" => [] "result" => []
} }
@account_eth_get_balance_example_value %{
"jsonrpc" => "2.0",
"result" => "0x0234c8a3397aab58",
"id" => 1
}
@account_tokentx_example_value %{ @account_tokentx_example_value %{
"status" => "1", "status" => "1",
"message" => "OK", "message" => "OK",
@ -1028,6 +1034,49 @@ defmodule BlockScoutWeb.Etherscan do
} }
} }
@account_eth_get_balance_action %{
name: "eth_get_balance",
description:
"Mimics Ethereum JSON RPC's eth_getBalance. Returns the balance as of the provided block (defaults to latest)",
required_params: [
%{
key: "address",
placeholder: "addressHash",
type: "string",
description: "The address of the account."
}
],
optional_params: [
%{
key: "block",
placeholder: "block",
type: "string",
description: """
Either the block number as a string, or one of latest, earliest or pending
latest will be the latest balance in a *consensus* block.
earliest will be the first recorded balance for the address.
pending will be the latest balance in consensus *or* nonconcensus blocks.
"""
}
],
responses: [
%{
code: "200",
description: "successful operation",
example_value: Jason.encode!(@account_eth_get_balance_example_value),
model: %{
name: "Result",
fields: %{
jsonrpc: @jsonrpc_version_type,
id: @id_type,
result: @hex_number_type
}
}
}
]
}
@account_balance_action %{ @account_balance_action %{
name: "balance", name: "balance",
description: """ description: """
@ -2203,6 +2252,7 @@ defmodule BlockScoutWeb.Etherscan do
@account_module %{ @account_module %{
name: "account", name: "account",
actions: [ actions: [
@account_eth_get_balance_action,
@account_balance_action, @account_balance_action,
@account_balancemulti_action, @account_balancemulti_action,
@account_txlist_action, @account_txlist_action,

@ -32,6 +32,8 @@ defmodule BlockScoutWeb.Router do
alias BlockScoutWeb.API.RPC alias BlockScoutWeb.API.RPC
post("/eth_rpc", EthController, :eth_request)
forward("/", RPCTranslator, %{ forward("/", RPCTranslator, %{
"block" => RPC.BlockController, "block" => RPC.BlockController,
"account" => RPC.AddressController, "account" => RPC.AddressController,
@ -245,5 +247,7 @@ defmodule BlockScoutWeb.Router do
get("/chain_blocks", ChainController, :chain_blocks, as: :chain_blocks) get("/chain_blocks", ChainController, :chain_blocks, as: :chain_blocks)
get("/api_docs", APIDocsController, :index) get("/api_docs", APIDocsController, :index)
get("/:page", PageNotFoundController, :index)
end end
end end

@ -39,7 +39,7 @@
</span> </span>
<!-- percentage of coins from total supply --> <!-- percentage of coins from total supply -->
<span class="ml-0 ml-md-2"> <span class="ml-0 ml-md-2">
<% if @total_supply do %> <%= if @total_supply do %>
(<%= balance_percentage(@address, @total_supply) %>) (<%= balance_percentage(@address, @total_supply) %>)
<% end %> <% end %>
</span> </span>

@ -2,7 +2,8 @@
<%= render BlockScoutWeb.AddressView, "overview.html", assigns %> <%= render BlockScoutWeb.AddressView, "overview.html", assigns %>
<div class="card"> <div class="card">
<%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %> <%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %>
<%= for contract <- sort_contracts_by_version(@address.decompiled_smart_contracts) do %> <% contract = last_decompiled_contract_version(@address.decompiled_smart_contracts) %>
<%= if contract do %>
<div class="card-body"> <div class="card-body">
<h3><%= gettext "Decompiler version" %></h3> <h3><%= gettext "Decompiler version" %></h3>
<div class="tile tile-muted"> <div class="tile tile-muted">
@ -21,6 +22,10 @@
</div> </div>
</section> </section>
</div> </div>
<% else %>
<div class="tile tile-muted text-center">
<%= gettext "There is no decompilded contracts for this address." %>
</div>
<% end %> <% end %>
</div> </div>
</section> </section>

@ -1,4 +1,4 @@
<div class='pagination-container <%= if assigns[:position] == "top" do %>position-top<% end %> <%= if assigns[:position] == "bottom" do %>position-bottom<% end %>' <%= if !assigns[:next_page_path] do %>disabled<% end %>> <div class='pagination-container <%= if assigns[:position] == "top" do %>position-top<% end %> <%= if assigns[:position] == "bottom" do %>position-bottom<% end %>'>
<%= if false do %> <%= if false do %>
<!-- Pagination limit --> <!-- Pagination limit -->
<div class="pagination-limit"> <div class="pagination-limit">

@ -77,7 +77,7 @@
</div> </div>
</li> </li>
<li class="nav-item dropdown nav-item-networks"> <li class="nav-item dropdown nav-item-networks">
<a class="nav-link topnav-nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <a class="nav-link topnav-nav-link <%= if dropdown_nets() == [], do: "disabled", else: "dropdown-toggle" %>" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="nav-link-icon"> <span class="nav-link-icon">
<%= render BlockScoutWeb.IconsView, "_active_icon.html" %> <%= render BlockScoutWeb.IconsView, "_active_icon.html" %>
</span> </span>

@ -4,8 +4,8 @@
<img alt="Page Not Found" src="/images/errors-img/poa-page-not-found.png" srcset="/images/errors-img/poa-page-not-found@2x.png 2x"> <img alt="Page Not Found" src="/images/errors-img/poa-page-not-found.png" srcset="/images/errors-img/poa-page-not-found@2x.png 2x">
</div> </div>
<div class="block-not-found-content"> <div class="block-not-found-content">
<h1 class="card-title error-title">Lorem Ipsum Dolor</h1> <h1 class="card-title error-title">Page not found</h1>
<p class="error-descr">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua</p> <p class="error-descr">The requested path was not found on BlockScout.</p>
<a class="error-btn btn-line" href="/">Back Home</a> <a class="error-btn btn-line" href="/">Back Home</a>
</div> </div>
</div> </div>

@ -19,7 +19,7 @@
<!-- Content --> <!-- Content -->
<div class="col-md-7 col-lg-8 d-flex flex-column pr-2 pr-sm-2 pr-md-0"> <div class="col-md-7 col-lg-8 d-flex flex-column pr-2 pr-sm-2 pr-md-0">
<%= render "_link.html", transaction_hash: @transaction.hash %> <%= render "_link.html", transaction_hash: @transaction.hash %>
<span class="text-nowrap"> <span>
<%= @transaction |> BlockScoutWeb.AddressView.address_partial_selector(:from, assigns[:current_address]) |> BlockScoutWeb.RenderHelpers.render_partial() %> <%= @transaction |> BlockScoutWeb.AddressView.address_partial_selector(:from, assigns[:current_address]) |> BlockScoutWeb.RenderHelpers.render_partial() %>
&rarr; &rarr;
<%= @transaction |> BlockScoutWeb.AddressView.address_partial_selector(:to, assigns[:current_address]) |> BlockScoutWeb.RenderHelpers.render_partial() %> <%= @transaction |> BlockScoutWeb.AddressView.address_partial_selector(:to, assigns[:current_address]) |> BlockScoutWeb.RenderHelpers.render_partial() %>
@ -58,7 +58,7 @@
<% end %> <% end %>
</div> </div>
<!-- Block info --> <!-- Block info -->
<div class="col-md-3 col-lg-2 d-flex flex-row flex-md-column flex-nowrap justify-content-center text-md-right mt-3 mt-md-0"> <div class="col-md-3 col-lg-2 d-flex flex-row flex-md-column flex-nowrap justify-content-center text-md-right mt-3 mt-md-0 tile-bottom">
<span class="mr-2 mr-md-0 order-1"> <span class="mr-2 mr-md-0 order-1">
<%= @transaction |> block_number() |> BlockScoutWeb.RenderHelpers.render_partial() %> <%= @transaction |> block_number() |> BlockScoutWeb.RenderHelpers.render_partial() %>
</span> </span>

@ -230,10 +230,8 @@ defmodule BlockScoutWeb.AddressDecompiledContractView do
end) end)
end end
def sort_contracts_by_version(decompiled_contracts) do def last_decompiled_contract_version(decompiled_contracts) do
decompiled_contracts Enum.max_by(decompiled_contracts, & &1.decompiler_version)
|> Enum.sort_by(& &1.decompiler_version)
|> Enum.reverse()
end end
defp add_line_numbers(code) do defp add_line_numbers(code) do

@ -1,7 +1,7 @@
defmodule BlockScoutWeb.API.RPC.AddressView do defmodule BlockScoutWeb.API.RPC.AddressView do
use BlockScoutWeb, :view use BlockScoutWeb, :view
alias BlockScoutWeb.API.RPC.RPCView alias BlockScoutWeb.API.RPC.{EthRPCView, RPCView}
def render("listaccounts.json", %{accounts: accounts}) do def render("listaccounts.json", %{accounts: accounts}) do
accounts = Enum.map(accounts, &prepare_account/1) accounts = Enum.map(accounts, &prepare_account/1)
@ -51,6 +51,10 @@ defmodule BlockScoutWeb.API.RPC.AddressView do
RPCView.render("show.json", data: data) RPCView.render("show.json", data: data)
end end
def render("eth_get_balance_error.json", %{error: message}) do
EthRPCView.render("error.json", %{error: message, id: 0})
end
def render("error.json", assigns) do def render("error.json", assigns) do
RPCView.render("error.json", assigns) RPCView.render("error.json", assigns)
end end

@ -17,16 +17,48 @@ defmodule BlockScoutWeb.API.RPC.EthRPCView do
} }
end end
def render("response.json", %{response: %{error: error, id: id}}) do
%__MODULE__{
error: error,
id: id
}
end
def render("response.json", %{response: %{result: result, id: id}}) do
%__MODULE__{
result: result,
id: id
}
end
def render("responses.json", %{responses: responses}) do
Enum.map(responses, fn
%{error: error, id: id} ->
%__MODULE__{
error: error,
id: id
}
%{result: result, id: id} ->
%__MODULE__{
result: result,
id: id
}
end)
end
defimpl Poison.Encoder, for: BlockScoutWeb.API.RPC.EthRPCView do defimpl Poison.Encoder, for: BlockScoutWeb.API.RPC.EthRPCView do
def encode(%BlockScoutWeb.API.RPC.EthRPCView{result: result, id: id, error: error}, _options) when is_nil(error) do def encode(%BlockScoutWeb.API.RPC.EthRPCView{result: result, id: id, error: error}, _options) when is_nil(error) do
result = Poison.encode!(result)
""" """
{"jsonrpc":"2.0","result":"#{result}","id":#{id}} {"jsonrpc":"2.0","result":#{result},"id":#{id}}
""" """
end end
def encode(%BlockScoutWeb.API.RPC.EthRPCView{id: id, error: error}, _options) do def encode(%BlockScoutWeb.API.RPC.EthRPCView{id: id, error: error}, _options) do
""" """
{"jsonrpc":"2.0","error": #{error},"id": #{id}} {"jsonrpc":"2.0","error": "#{error}","id": #{id}}
""" """
end end
end end

@ -0,0 +1,13 @@
defmodule BlockScoutWeb.API.RPC.EthView do
use BlockScoutWeb, :view
alias BlockScoutWeb.API.RPC.EthRPCView
def render("responses.json", %{responses: responses}) do
EthRPCView.render("responses.json", %{responses: responses})
end
def render("response.json", %{response: response}) do
EthRPCView.render("response.json", %{response: response})
end
end

@ -0,0 +1,5 @@
defmodule BlockScoutWeb.PageNotFoundView do
use BlockScoutWeb, :view
@dialyzer :no_match
end

@ -57,13 +57,19 @@ defmodule BlockScoutWeb.WeiHelpers do
converted_value = converted_value =
wei wei
|> Wei.to(unit) |> Wei.to(unit)
|> Cldr.Number.to_string!(format: "#,##0.##################")
formatted_value =
if Decimal.cmp(converted_value, 1_000_000_000_000) == :gt do
Cldr.Number.to_string!(converted_value, format: "0.###E+0")
else
Cldr.Number.to_string!(converted_value, format: "#,##0.##################")
end
if Keyword.get(options, :include_unit_label, true) do if Keyword.get(options, :include_unit_label, true) do
display_unit = display_unit(unit) display_unit = display_unit(unit)
"#{converted_value} #{display_unit}" "#{formatted_value} #{display_unit}"
else else
converted_value formatted_value
end end
end end

@ -378,7 +378,7 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20 #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20
#: lib/block_scout_web/templates/transaction/_tile.html.eex:30 #: lib/block_scout_web/templates/transaction/_tile.html.eex:30
#: lib/block_scout_web/templates/transaction/overview.html.eex:192 #: lib/block_scout_web/templates/transaction/overview.html.eex:192
#: lib/block_scout_web/views/wei_helpers.ex:72 #: lib/block_scout_web/views/wei_helpers.ex:78
msgid "Ether" msgid "Ether"
msgstr "" msgstr ""
@ -436,7 +436,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/block_view.ex:20 #: lib/block_scout_web/views/block_view.ex:20
#: lib/block_scout_web/views/wei_helpers.ex:71 #: lib/block_scout_web/views/wei_helpers.ex:77
msgid "Gwei" msgid "Gwei"
msgstr "" msgstr ""
@ -982,7 +982,7 @@ msgid "Wallet addresses"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/wei_helpers.ex:70 #: lib/block_scout_web/views/wei_helpers.ex:76
msgid "Wei" msgid "Wei"
msgstr "" msgstr ""
@ -1490,7 +1490,7 @@ msgid "EVM Version"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:16 #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:17
msgid "Copy Decompiled Contract Code" msgid "Copy Decompiled Contract Code"
msgstr "" msgstr ""
@ -1505,12 +1505,12 @@ msgid "Decompiled code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:14 #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:15
msgid "Decompiled contract code" msgid "Decompiled contract code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:7 #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:8
msgid "Decompiler version" msgid "Decompiler version"
msgstr "" msgstr ""
@ -1697,3 +1697,8 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/overview.html.eex:178 #: lib/block_scout_web/templates/transaction/overview.html.eex:178
msgid " Token Transfer" msgid " Token Transfer"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:27
msgid "There is no decompilded contracts for this address."
msgstr ""

@ -378,7 +378,7 @@ msgstr ""
#: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20 #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:20
#: lib/block_scout_web/templates/transaction/_tile.html.eex:30 #: lib/block_scout_web/templates/transaction/_tile.html.eex:30
#: lib/block_scout_web/templates/transaction/overview.html.eex:192 #: lib/block_scout_web/templates/transaction/overview.html.eex:192
#: lib/block_scout_web/views/wei_helpers.ex:72 #: lib/block_scout_web/views/wei_helpers.ex:78
msgid "Ether" msgid "Ether"
msgstr "POA" msgstr "POA"
@ -436,7 +436,7 @@ msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/block_view.ex:20 #: lib/block_scout_web/views/block_view.ex:20
#: lib/block_scout_web/views/wei_helpers.ex:71 #: lib/block_scout_web/views/wei_helpers.ex:77
msgid "Gwei" msgid "Gwei"
msgstr "" msgstr ""
@ -982,7 +982,7 @@ msgid "Wallet addresses"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/wei_helpers.ex:70 #: lib/block_scout_web/views/wei_helpers.ex:76
msgid "Wei" msgid "Wei"
msgstr "" msgstr ""
@ -1490,7 +1490,7 @@ msgid "EVM Version"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:16 #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:17
msgid "Copy Decompiled Contract Code" msgid "Copy Decompiled Contract Code"
msgstr "" msgstr ""
@ -1505,12 +1505,12 @@ msgid "Decompiled code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:14 #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:15
msgid "Decompiled contract code" msgid "Decompiled contract code"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:7 #: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:8
msgid "Decompiler version" msgid "Decompiler version"
msgstr "" msgstr ""
@ -1693,7 +1693,12 @@ msgstr ""
msgid "New Smart Contract Verification" msgid "New Smart Contract Verification"
msgstr "" msgstr ""
#, elixir-format, fuzzy #, elixir-format
#: lib/block_scout_web/templates/transaction/overview.html.eex:178 #: lib/block_scout_web/templates/transaction/overview.html.eex:178
msgid " Token Transfer" msgid " Token Transfer"
msgstr "" msgstr ""
#, elixir-format, fuzzy
#: lib/block_scout_web/templates/address_decompiled_contract/index.html.eex:27
msgid "There is no decompilded contracts for this address."
msgstr ""

@ -0,0 +1,199 @@
defmodule BlockScoutWeb.API.RPC.EthControllerTest do
use BlockScoutWeb.ConnCase, async: false
alias Explorer.Counters.{AddressesWithBalanceCounter, AverageBlockTime}
alias Indexer.Fetcher.CoinBalanceOnDemand
setup do
mocked_json_rpc_named_arguments = [
transport: EthereumJSONRPC.Mox,
transport_options: []
]
start_supervised!({Task.Supervisor, name: Indexer.TaskSupervisor})
start_supervised!(AverageBlockTime)
start_supervised!({CoinBalanceOnDemand, [mocked_json_rpc_named_arguments, [name: CoinBalanceOnDemand]]})
start_supervised!(AddressesWithBalanceCounter)
Application.put_env(:explorer, AverageBlockTime, enabled: true)
on_exit(fn ->
Application.put_env(:explorer, AverageBlockTime, enabled: false)
end)
:ok
end
defp params(api_params, params), do: Map.put(api_params, "params", params)
describe "eth_get_balance" do
setup do
%{
api_params: %{
"method" => "eth_getBalance",
"jsonrpc" => "2.0",
"id" => 0
}
}
end
test "with an invalid address", %{conn: conn, api_params: api_params} do
assert response =
conn
|> post("/api/eth_rpc", params(api_params, ["badHash"]))
|> json_response(200)
assert %{"error" => "Query parameter 'address' is invalid"} = response
end
test "with a valid address that has no balance", %{conn: conn, api_params: api_params} do
address = insert(:address)
assert response =
conn
|> post("/api/eth_rpc", params(api_params, [to_string(address.hash)]))
|> json_response(200)
assert %{"error" => "Balance not found"} = response
end
test "with a valid address that has a balance", %{conn: conn, api_params: api_params} do
block = insert(:block)
address = insert(:address)
insert(:fetched_balance, block_number: block.number, address_hash: address.hash, value: 1)
assert response =
conn
|> post("/api/eth_rpc", params(api_params, [to_string(address.hash)]))
|> json_response(200)
assert %{"result" => "0x1"} = response
end
test "with a valid address that has no earliest balance", %{conn: conn, api_params: api_params} do
block = insert(:block, number: 1)
address = insert(:address)
insert(:fetched_balance, block_number: block.number, address_hash: address.hash, value: 1)
assert response =
conn
|> post("/api/eth_rpc", params(api_params, [to_string(address.hash), "earliest"]))
|> json_response(200)
assert response["error"] == "Balance not found"
end
test "with a valid address that has an earliest balance", %{conn: conn, api_params: api_params} do
block = insert(:block, number: 0)
address = insert(:address)
insert(:fetched_balance, block_number: block.number, address_hash: address.hash, value: 1)
assert response =
conn
|> post("/api/eth_rpc", params(api_params, [to_string(address.hash), "earliest"]))
|> json_response(200)
assert response["result"] == "0x1"
end
test "with a valid address and no pending balance", %{conn: conn, api_params: api_params} do
block = insert(:block, number: 1, consensus: true)
address = insert(:address)
insert(:fetched_balance, block_number: block.number, address_hash: address.hash, value: 1)
assert response =
conn
|> post("/api/eth_rpc", params(api_params, [to_string(address.hash), "pending"]))
|> json_response(200)
assert response["error"] == "Balance not found"
end
test "with a valid address and a pending balance", %{conn: conn, api_params: api_params} do
block = insert(:block, number: 1, consensus: false)
address = insert(:address)
insert(:fetched_balance, block_number: block.number, address_hash: address.hash, value: 1)
assert response =
conn
|> post("/api/eth_rpc", params(api_params, [to_string(address.hash), "pending"]))
|> json_response(200)
assert response["result"] == "0x1"
end
test "with a valid address and a pending balance after a consensus block", %{conn: conn, api_params: api_params} do
insert(:block, number: 1, consensus: true)
block = insert(:block, number: 2, consensus: false)
address = insert(:address)
insert(:fetched_balance, block_number: block.number, address_hash: address.hash, value: 1)
assert response =
conn
|> post("/api/eth_rpc", params(api_params, [to_string(address.hash), "pending"]))
|> json_response(200)
assert response["result"] == "0x1"
end
test "with a block provided", %{conn: conn, api_params: api_params} do
address = insert(:address)
insert(:fetched_balance, block_number: 1, address_hash: address.hash, value: 1)
insert(:fetched_balance, block_number: 2, address_hash: address.hash, value: 2)
insert(:fetched_balance, block_number: 3, address_hash: address.hash, value: 3)
assert response =
conn
|> post("/api/eth_rpc", params(api_params, [to_string(address.hash), "2"]))
|> json_response(200)
assert response["result"] == "0x2"
end
test "with a block provided and no balance", %{conn: conn, api_params: api_params} do
address = insert(:address)
insert(:fetched_balance, block_number: 3, address_hash: address.hash, value: 3)
assert response =
conn
|> post("/api/eth_rpc", params(api_params, [to_string(address.hash), "2"]))
|> json_response(200)
assert response["error"] == "Balance not found"
end
test "with a batch of requests", %{conn: conn} do
address = insert(:address)
insert(:fetched_balance, block_number: 1, address_hash: address.hash, value: 1)
insert(:fetched_balance, block_number: 2, address_hash: address.hash, value: 2)
insert(:fetched_balance, block_number: 3, address_hash: address.hash, value: 3)
params = [
%{"id" => 0, "params" => [to_string(address.hash), "1"], "jsonrpc" => "2.0", "method" => "eth_getBalance"},
%{"id" => 1, "params" => [to_string(address.hash), "2"], "jsonrpc" => "2.0", "method" => "eth_getBalance"},
%{"id" => 2, "params" => [to_string(address.hash), "3"], "jsonrpc" => "2.0", "method" => "eth_getBalance"}
]
assert response =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/eth_rpc", Jason.encode!(params))
|> json_response(200)
assert [
%{"id" => 0, "result" => "0x1"},
%{"id" => 1, "result" => "0x2"},
%{"id" => 2, "result" => "0x3"}
] = response
end
end
end

@ -3,8 +3,8 @@ defmodule BlockScoutWeb.BlockControllerTest do
alias Explorer.Chain.Block alias Explorer.Chain.Block
setup do setup do
Supervisor.terminate_child(Explorer.Supervisor, ConCache) Supervisor.terminate_child(Explorer.Supervisor, {ConCache, :blocks})
Supervisor.restart_child(Explorer.Supervisor, ConCache) Supervisor.restart_child(Explorer.Supervisor, {ConCache, :blocks})
:ok :ok
end end

@ -9,8 +9,8 @@ defmodule BlockScoutWeb.ChainControllerTest do
alias Explorer.Counters.AddressesWithBalanceCounter alias Explorer.Counters.AddressesWithBalanceCounter
setup do setup do
Supervisor.terminate_child(Explorer.Supervisor, ConCache) Supervisor.terminate_child(Explorer.Supervisor, {ConCache, :blocks})
Supervisor.restart_child(Explorer.Supervisor, ConCache) Supervisor.restart_child(Explorer.Supervisor, {ConCache, :blocks})
start_supervised!(AddressesWithBalanceCounter) start_supervised!(AddressesWithBalanceCounter)
AddressesWithBalanceCounter.consolidate() AddressesWithBalanceCounter.consolidate()

@ -37,15 +37,6 @@ defmodule BlockScoutWeb.PendingTransactionControllerTest do
refute hd(json_response(conn, 200)["items"]) =~ to_string(dropped_replaced.hash) refute hd(json_response(conn, 200)["items"]) =~ to_string(dropped_replaced.hash)
end end
test "returns a count of pending transactions", %{conn: conn} do
insert(:transaction)
conn = get(conn, pending_transaction_path(BlockScoutWeb.Endpoint, :index))
assert html_response(conn, 200)
assert 1 == conn.assigns.pending_transaction_count
end
test "works when there are no transactions", %{conn: conn} do test "works when there are no transactions", %{conn: conn} do
conn = get(conn, pending_transaction_path(conn, :index)) conn = get(conn, pending_transaction_path(conn, :index))

@ -10,8 +10,8 @@ defmodule BlockScoutWeb.ViewingChainTest do
alias Explorer.Counters.AddressesWithBalanceCounter alias Explorer.Counters.AddressesWithBalanceCounter
setup do setup do
Supervisor.terminate_child(Explorer.Supervisor, ConCache) Supervisor.terminate_child(Explorer.Supervisor, {ConCache, :blocks})
Supervisor.restart_child(Explorer.Supervisor, ConCache) Supervisor.restart_child(Explorer.Supervisor, {ConCache, :blocks})
Enum.map(401..404, &insert(:block, number: &1)) Enum.map(401..404, &insert(:block, number: &1))

@ -96,15 +96,15 @@ defmodule BlockScoutWeb.AddressDecompiledContractViewTest do
end end
end end
describe "sort_contracts_by_version/1" do describe "last_decompiled_contract_version/1" do
test "sorts contracts in lexicographical order" do test "returns last version" do
contract2 = insert(:decompiled_smart_contract, decompiler_version: "v2") contract2 = insert(:decompiled_smart_contract, decompiler_version: "v2")
contract1 = insert(:decompiled_smart_contract, decompiler_version: "v1") contract1 = insert(:decompiled_smart_contract, decompiler_version: "v1")
contract3 = insert(:decompiled_smart_contract, decompiler_version: "v3") contract3 = insert(:decompiled_smart_contract, decompiler_version: "v3")
result = AddressDecompiledContractView.sort_contracts_by_version([contract2, contract1, contract3]) result = AddressDecompiledContractView.last_decompiled_contract_version([contract2, contract1, contract3])
assert result == [contract3, contract2, contract1] assert result == contract3
end end
end end
end end

@ -249,6 +249,19 @@ defmodule EthereumJSONRPC do
|> fetch_blocks_by_params(&Block.ByNephew.request/1, json_rpc_named_arguments) |> fetch_blocks_by_params(&Block.ByNephew.request/1, json_rpc_named_arguments)
end end
@spec fetch_net_version(json_rpc_named_arguments) :: {:ok, non_neg_integer()} | {:error, reason :: term}
def fetch_net_version(json_rpc_named_arguments) do
result =
%{id: 0, method: "net_version", params: []}
|> request()
|> json_rpc(json_rpc_named_arguments)
case result do
{:ok, bin_number} -> {:ok, String.to_integer(bin_number)}
other -> other
end
end
@doc """ @doc """
Fetches block number by `t:tag/0`. Fetches block number by `t:tag/0`.

@ -319,6 +319,8 @@ defmodule EthereumJSONRPC.Block do
@spec elixir_to_transactions(elixir) :: Transactions.elixir() @spec elixir_to_transactions(elixir) :: Transactions.elixir()
def elixir_to_transactions(%{"transactions" => transactions}), do: transactions def elixir_to_transactions(%{"transactions" => transactions}), do: transactions
def elixir_to_transactions(_), do: []
@doc """ @doc """
Get `t:EthereumJSONRPC.Uncles.elixir/0` from `t:elixir/0`. Get `t:EthereumJSONRPC.Uncles.elixir/0` from `t:elixir/0`.

@ -174,6 +174,12 @@ defmodule EthereumJSONRPC.Parity.FetchedBeneficiaries do
defp get_address_type(reward_type, index) when reward_type == "external" and index == 2, do: :validator defp get_address_type(reward_type, index) when reward_type == "external" and index == 2, do: :validator
defp get_address_type(reward_type, index) when reward_type == "external" and index == 3, do: :validator defp get_address_type(reward_type, index) when reward_type == "external" and index == 3, do: :validator
defp get_address_type(reward_type, index) when reward_type == "external" and index == 4, do: :validator defp get_address_type(reward_type, index) when reward_type == "external" and index == 4, do: :validator
defp get_address_type(reward_type, index) when reward_type == "external" and index == 5, do: :validator
defp get_address_type(reward_type, index) when reward_type == "external" and index == 6, do: :validator
defp get_address_type(reward_type, index) when reward_type == "external" and index == 7, do: :validator
defp get_address_type(reward_type, index) when reward_type == "external" and index == 8, do: :validator
defp get_address_type(reward_type, index) when reward_type == "external" and index == 9, do: :validator
defp get_address_type(reward_type, index) when reward_type == "external" and index == 10, do: :validator
defp get_address_type(reward_type, _index) when reward_type == "block", do: :validator defp get_address_type(reward_type, _index) when reward_type == "block", do: :validator
defp get_address_type(reward_type, _index) when reward_type == "uncle", do: :uncle defp get_address_type(reward_type, _index) when reward_type == "uncle", do: :uncle
end end

@ -56,4 +56,10 @@ defmodule EthereumJSONRPC.BlockTest do
} }
end end
end end
describe "elixir_to_transactions/1" do
test "converts to empty list if there is not transaction key" do
assert Block.elixir_to_transactions(%{}) == []
end
end
end end

@ -886,6 +886,24 @@ defmodule EthereumJSONRPCTest do
end end
end end
describe "fetch_net_version/1" do
test "fetches net version", %{json_rpc_named_arguments: json_rpc_named_arguments} do
expected_version =
case Keyword.fetch!(json_rpc_named_arguments, :variant) do
EthereumJSONRPC.Parity -> 77
_variant -> 1
end
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options ->
{:ok, "#{expected_version}"}
end)
end
assert {:ok, ^expected_version} = EthereumJSONRPC.fetch_net_version(json_rpc_named_arguments)
end
end
defp clear_mailbox do defp clear_mailbox do
receive do receive do
_ -> clear_mailbox() _ -> clear_mailbox()

@ -80,7 +80,8 @@ if System.get_env("SOURCE_MODULE") == "TransactionAndLog" do
end end
config :explorer, config :explorer,
solc_bin_api_url: "https://solc-bin.ethereum.org" solc_bin_api_url: "https://solc-bin.ethereum.org",
checksum_function: System.get_env("CHECKSUM_FUNCTION") && String.to_atom(System.get_env("CHECKSUM_FUNCTION"))
config :logger, :explorer, config :logger, :explorer,
# keep synced with `config/config.exs` # keep synced with `config/config.exs`

@ -6,7 +6,7 @@ defmodule Explorer.Application do
use Application use Application
alias Explorer.Admin alias Explorer.Admin
alias Explorer.Chain.{BlockCountCache, BlockNumberCache, BlocksCache, TransactionCountCache} alias Explorer.Chain.{BlockCountCache, BlockNumberCache, BlocksCache, NetVersionCache, TransactionCountCache}
alias Explorer.Repo.PrometheusLogger alias Explorer.Repo.PrometheusLogger
@impl Application @impl Application
@ -31,7 +31,8 @@ defmodule Explorer.Application do
{Admin.Recovery, [[], [name: Admin.Recovery]]}, {Admin.Recovery, [[], [name: Admin.Recovery]]},
{TransactionCountCache, [[], []]}, {TransactionCountCache, [[], []]},
{BlockCountCache, []}, {BlockCountCache, []},
{ConCache, [name: BlocksCache.cache_name(), ttl_check_interval: false]} con_cache_child_spec(BlocksCache.cache_name()),
con_cache_child_spec(NetVersionCache.cache_name())
] ]
children = base_children ++ configurable_children() children = base_children ++ configurable_children()
@ -79,4 +80,17 @@ defmodule Explorer.Application do
http: HTTPoison http: HTTPoison
] ]
end end
defp con_cache_child_spec(name) do
Supervisor.child_spec(
{
ConCache,
[
name: name,
ttl_check_interval: false
]
},
id: {ConCache, name}
)
end
end end

@ -227,60 +227,31 @@ defmodule Explorer.Chain do
transaction_hashes_from_token_transfers = transaction_hashes_from_token_transfers =
TokenTransfer.where_any_address_fields_match(direction, address_hash, paging_options) TokenTransfer.where_any_address_fields_match(direction, address_hash, paging_options)
token_transfers_query = transactions_list =
transaction_hashes_from_token_transfers
|> Transaction.where_transaction_hashes_match()
|> join_associations(necessity_by_association)
|> order_by([transaction], desc: transaction.block_number, desc: transaction.index)
|> Transaction.preload_token_transfers(address_hash)
base_query =
paging_options paging_options
|> fetch_transactions() |> fetch_transactions()
|> Transaction.where_transaction_matches(transaction_hashes_from_token_transfers, direction, address_hash)
|> join_associations(necessity_by_association) |> join_associations(necessity_by_association)
|> Transaction.preload_token_transfers(address_hash) |> Transaction.preload_token_transfers(address_hash)
|> Repo.all()
from_address_query = if Application.get_env(:block_scout_web, BlockScoutWeb.Chain)[:has_emission_funds] do
base_query address_hash
|> where([t], t.from_address_hash == ^address_hash) |> Reward.fetch_emission_rewards_tuples(paging_options)
|> Enum.concat(transactions_list)
to_address_query = |> Enum.sort_by(fn item ->
base_query case item do
|> where([t], t.to_address_hash == ^address_hash) {%Reward{} = emission_reward, _} ->
{-emission_reward.block.number, 1}
created_contract_query =
base_query item ->
|> where([t], t.created_contract_address_hash == ^address_hash) {-item.block_number, -item.index}
queries =
[token_transfers_query] ++
case direction do
:from -> [from_address_query]
:to -> [to_address_query, created_contract_query]
_ -> [from_address_query, to_address_query, created_contract_query]
end end
end)
rewards_list = |> Enum.take(paging_options.page_size)
if Application.get_env(:block_scout_web, BlockScoutWeb.Chain)[:has_emission_funds] do else
Reward.fetch_emission_rewards_tuples(address_hash, paging_options) transactions_list
else end
[]
end
queries
|> Stream.flat_map(&Repo.all/1)
|> Stream.uniq_by(& &1.hash)
|> Stream.concat(rewards_list)
|> Enum.sort_by(fn item ->
case item do
{%Reward{} = emission_reward, _} ->
{-emission_reward.block.number, 1}
item ->
{-item.block_number, -item.index}
end
end)
|> Enum.take(paging_options.page_size)
end end
@spec address_to_logs(Address.t(), Keyword.t()) :: [ @spec address_to_logs(Address.t(), Keyword.t()) :: [
@ -703,25 +674,32 @@ defmodule Explorer.Chain do
iex> Explorer.Chain.hash_to_address(hash) iex> Explorer.Chain.hash_to_address(hash)
{:error, :not_found} {:error, :not_found}
Optionally accepts:
- a list of bindings to preload, just like `Ecto.Query.preload/3`
- a boolean to also fetch the `has_decompiled_code?` virtual field or not
""" """
@spec hash_to_address(Hash.Address.t()) :: {:ok, Address.t()} | {:error, :not_found} @spec hash_to_address(Hash.Address.t(), [Macro.t()], boolean()) :: {:ok, Address.t()} | {:error, :not_found}
def hash_to_address(%Hash{byte_count: unquote(Hash.Address.byte_count())} = hash) do def hash_to_address(
query = %Hash{byte_count: unquote(Hash.Address.byte_count())} = hash,
from( preloads \\ [
address in Address,
preload: [
:contracts_creation_internal_transaction, :contracts_creation_internal_transaction,
:names, :names,
:smart_contract, :smart_contract,
:token, :token,
:contracts_creation_transaction :contracts_creation_transaction
], ],
query_decompiled_code_flag \\ true
) do
query =
from(
address in Address,
preload: ^preloads,
where: address.hash == ^hash where: address.hash == ^hash
) )
query_with_decompiled_flag = with_decompiled_code_flag(query, hash) query
|> with_decompiled_code_flag(hash, query_decompiled_code_flag)
query_with_decompiled_flag
|> Repo.one() |> Repo.one()
|> case do |> case do
nil -> {:error, :not_found} nil -> {:error, :not_found}
@ -824,6 +802,86 @@ defmodule Explorer.Chain do
Repo.all(query) Repo.all(query)
end end
@doc """
Returns the balance of the given address and block combination.
Returns `{:error, :not_found}` if there is no address by that hash present.
Returns `{:error, :no_balance}` if there is no balance for that address at that block.
"""
@spec get_balance_as_of_block(Hash.Address.t(), integer | :earliest | :latest | :pending) ::
{:ok, Wei.t()} | {:error, :no_balance} | {:error, :not_found}
def get_balance_as_of_block(address, block) when is_integer(block) do
coin_balance_query =
from(coin_balance in CoinBalance,
where: coin_balance.address_hash == ^address,
where: not is_nil(coin_balance.value),
where: coin_balance.block_number <= ^block,
order_by: [desc: coin_balance.block_number],
limit: 1,
select: coin_balance.value
)
case Repo.one(coin_balance_query) do
nil -> {:error, :not_found}
coin_balance -> {:ok, coin_balance}
end
end
def get_balance_as_of_block(address, :latest) do
case max_consensus_block_number() do
{:ok, latest_block_number} ->
get_balance_as_of_block(address, latest_block_number)
{:error, :not_found} ->
{:error, :not_found}
end
end
def get_balance_as_of_block(address, :earliest) do
query =
from(coin_balance in CoinBalance,
where: coin_balance.address_hash == ^address,
where: not is_nil(coin_balance.value),
where: coin_balance.block_number == 0,
limit: 1,
select: coin_balance.value
)
case Repo.one(query) do
nil -> {:error, :not_found}
coin_balance -> {:ok, coin_balance}
end
end
def get_balance_as_of_block(address, :pending) do
query =
case max_consensus_block_number() do
{:ok, latest_block_number} ->
from(coin_balance in CoinBalance,
where: coin_balance.address_hash == ^address,
where: not is_nil(coin_balance.value),
where: coin_balance.block_number > ^latest_block_number,
order_by: [desc: coin_balance.block_number],
limit: 1,
select: coin_balance.value
)
{:error, :not_found} ->
from(coin_balance in CoinBalance,
where: coin_balance.address_hash == ^address,
where: not is_nil(coin_balance.value),
order_by: [desc: coin_balance.block_number],
limit: 1,
select: coin_balance.value
)
end
case Repo.one(query) do
nil -> {:error, :not_found}
coin_balance -> {:ok, coin_balance}
end
end
@spec list_ordered_addresses(non_neg_integer(), non_neg_integer()) :: [Address.t()] @spec list_ordered_addresses(non_neg_integer(), non_neg_integer()) :: [Address.t()]
def list_ordered_addresses(offset, limit) do def list_ordered_addresses(offset, limit) do
query = query =
@ -1907,7 +1965,6 @@ defmodule Explorer.Chain do
|> page_pending_transaction(paging_options) |> page_pending_transaction(paging_options)
|> limit(^paging_options.page_size) |> limit(^paging_options.page_size)
|> pending_transactions_query() |> pending_transactions_query()
|> where([transaction], is_nil(transaction.error) or transaction.error != "dropped/replaced")
|> order_by([transaction], desc: transaction.inserted_at, desc: transaction.hash) |> order_by([transaction], desc: transaction.inserted_at, desc: transaction.hash)
|> join_associations(necessity_by_association) |> join_associations(necessity_by_association)
|> preload([{:token_transfers, [:token, :from_address, :to_address]}]) |> preload([{:token_transfers, [:token, :from_address, :to_address]}])
@ -1916,7 +1973,7 @@ defmodule Explorer.Chain do
defp pending_transactions_query(query) do defp pending_transactions_query(query) do
from(transaction in query, from(transaction in query,
where: is_nil(transaction.block_hash) where: is_nil(transaction.block_hash) and (is_nil(transaction.error) or transaction.error != "dropped/replaced")
) )
end end
@ -3081,7 +3138,11 @@ defmodule Explorer.Chain do
defp staking_pool_filter(query, _), do: query defp staking_pool_filter(query, _), do: query
defp with_decompiled_code_flag(query, hash) do defp with_decompiled_code_flag(query, hash, use_option \\ true)
defp with_decompiled_code_flag(query, _hash, false), do: query
defp with_decompiled_code_flag(query, hash, true) do
has_decompiled_code_query = has_decompiled_code_query =
from(decompiled_contract in DecompiledSmartContract, from(decompiled_contract in DecompiledSmartContract,
where: decompiled_contract.address_hash == ^hash, where: decompiled_contract.address_hash == ^hash,

@ -16,6 +16,7 @@ defmodule Explorer.Chain.Address do
DecompiledSmartContract, DecompiledSmartContract,
Hash, Hash,
InternalTransaction, InternalTransaction,
NetVersionCache,
SmartContract, SmartContract,
Token, Token,
Transaction, Transaction,
@ -130,6 +131,21 @@ defmodule Explorer.Chain.Address do
end end
def checksum(hash, iodata?) do def checksum(hash, iodata?) do
checksum_formatted =
case Application.get_env(:explorer, :checksum_function) || :eth do
:eth -> eth_checksum(hash)
:rsk -> rsk_checksum(hash)
end
if iodata? do
["0x" | checksum_formatted]
else
to_string(["0x" | checksum_formatted])
end
end
# https://github.com/rsksmart/RSKIPs/blob/master/IPs/RSKIP60.md
def eth_checksum(hash) do
string_hash = string_hash =
hash hash
|> to_string() |> to_string()
@ -137,26 +153,46 @@ defmodule Explorer.Chain.Address do
match_byte_stream = stream_every_four_bytes_of_sha256(string_hash) match_byte_stream = stream_every_four_bytes_of_sha256(string_hash)
checksum_formatted = string_hash
string_hash |> stream_binary()
|> stream_binary() |> Stream.zip(match_byte_stream)
|> Stream.zip(match_byte_stream) |> Enum.map(fn
|> Enum.map(fn {digit, _} when digit in '0123456789' ->
{digit, _} when digit in '0123456789' -> digit
digit
{alpha, 1} -> {alpha, 1} ->
alpha - 32 alpha - 32
{alpha, _} -> {alpha, _} ->
alpha alpha
end) end)
end
if iodata? do def rsk_checksum(hash) do
["0x" | checksum_formatted] chain_id = NetVersionCache.version()
else
to_string(["0x" | checksum_formatted]) string_hash =
end hash
|> to_string()
|> String.trim_leading("0x")
prefix = "#{chain_id}0x"
match_byte_stream = stream_every_four_bytes_of_sha256("#{prefix}#{string_hash}")
string_hash
|> stream_binary()
|> Stream.zip(match_byte_stream)
|> Enum.map(fn
{digit, _} when digit in '0123456789' ->
digit
{alpha, 1} ->
alpha - 32
{alpha, _} ->
alpha
end)
end end
defp stream_every_four_bytes_of_sha256(value) do defp stream_every_four_bytes_of_sha256(value) do

@ -0,0 +1,44 @@
defmodule Explorer.Chain.NetVersionCache do
@moduledoc """
Caches chain version.
"""
@cache_name :net_version
@key :version
@spec version() :: non_neg_integer() | {:error, any()}
def version do
cached_value = fetch_from_cache()
if is_nil(cached_value) do
fetch_from_node()
else
cached_value
end
end
def cache_name do
@cache_name
end
defp fetch_from_cache do
ConCache.get(@cache_name, @key)
end
defp cache_value(value) do
ConCache.put(@cache_name, @key, value)
end
defp fetch_from_node do
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
case EthereumJSONRPC.fetch_net_version(json_rpc_named_arguments) do
{:ok, value} ->
cache_value(value)
value
other ->
other
end
end
end

@ -5,7 +5,7 @@ defmodule Explorer.Chain.Transaction do
require Logger require Logger
import Ecto.Query, only: [from: 2, order_by: 3, preload: 3, subquery: 1, where: 3] import Ecto.Query, only: [from: 2, preload: 3, subquery: 1, where: 3]
alias ABI.FunctionSelector alias ABI.FunctionSelector
@ -549,15 +549,40 @@ defmodule Explorer.Chain.Transaction do
end end
@doc """ @doc """
Builds a query that will check for transactions within the hashes params. Modifies a query to filter for transactions whose hash is in a list or that are
linked to the given address_hash through a direction.
Be careful to not pass a large list, because this will lead to performance Be careful to not pass a large list, because this will lead to performance
problems. problems.
""" """
def where_transaction_hashes_match(transaction_hashes) do def where_transaction_matches(query, transaction_hashes, :from, address_hash) do
Transaction where(
|> where([t], t.hash == fragment("ANY (?)", ^transaction_hashes)) query,
|> order_by([transaction], desc: transaction.block_number, desc: transaction.index) [t],
t.hash in ^transaction_hashes or
t.from_address_hash == ^address_hash
)
end
def where_transaction_matches(query, transaction_hashes, :to, address_hash) do
where(
query,
[t],
t.hash in ^transaction_hashes or
t.to_address_hash == ^address_hash or
t.created_contract_address_hash == ^address_hash
)
end
def where_transaction_matches(query, transaction_hashes, _direction, address_hash) do
where(
query,
[t],
t.hash in ^transaction_hashes or
t.from_address_hash == ^address_hash or
t.to_address_hash == ^address_hash or
t.created_contract_address_hash == ^address_hash
)
end end
@collated_fields ~w(block_number cumulative_gas_used gas_used index)a @collated_fields ~w(block_number cumulative_gas_used gas_used index)a

@ -113,6 +113,17 @@ defmodule Explorer.Chain.Wei do
@wei_per_ether Decimal.new(1_000_000_000_000_000_000) @wei_per_ether Decimal.new(1_000_000_000_000_000_000)
@wei_per_gwei Decimal.new(1_000_000_000) @wei_per_gwei Decimal.new(1_000_000_000)
@spec hex_format(Wei.t()) :: String.t()
def hex_format(%Wei{value: decimal}) do
hex =
decimal
|> Decimal.to_integer()
|> Integer.to_string(16)
|> String.downcase()
"0x" <> hex
end
@doc """ @doc """
Sums two Wei values. Sums two Wei values.

@ -66,7 +66,7 @@ defmodule Explorer.Counters.AverageBlockTime do
from(block in Block, from(block in Block,
limit: 100, limit: 100,
offset: 0, offset: 0,
order_by: [desc: block.number], order_by: [desc: block.number, desc: block.timestamp],
select: {block.number, block.timestamp} select: {block.number, block.timestamp}
) )

@ -28,7 +28,7 @@ defmodule Explorer.ExchangeRates.Source do
true -> fetch_exchange_rates_from_paginable_source(source, page + 1) true -> fetch_exchange_rates_from_paginable_source(source, page + 1)
end end
{:ok, %Response{body: body, status_code: status_code}} when status_code in 400..499 -> {:ok, %Response{body: body, status_code: status_code}} when status_code in 400..502 ->
{:error, decode_json(body)["error"]} {:error, decode_json(body)["error"]}
{:error, %Error{reason: reason}} -> {:error, %Error{reason: reason}} ->
@ -65,6 +65,8 @@ defmodule Explorer.ExchangeRates.Source do
def decode_json(data) do def decode_json(data) do
Jason.decode!(data) Jason.decode!(data)
rescue
_ -> data
end end
def to_decimal(nil), do: nil def to_decimal(nil), do: nil

@ -0,0 +1,7 @@
defmodule Explorer.Repo.Migrations.AddTxHashInsertedAtIndex do
use Ecto.Migration
def change do
create(index(:transactions, [:hash, :inserted_at]))
end
end

@ -1,6 +1,8 @@
defmodule Explorer.Chain.AddressTest do defmodule Explorer.Chain.AddressTest do
use Explorer.DataCase use Explorer.DataCase
import Mox
alias Explorer.Chain.Address alias Explorer.Chain.Address
alias Explorer.Repo alias Explorer.Repo
@ -28,6 +30,12 @@ defmodule Explorer.Chain.AddressTest do
end end
describe "Phoenix.HTML.Safe.to_iodata/1" do describe "Phoenix.HTML.Safe.to_iodata/1" do
setup do
Application.put_env(:explorer, :checksum_function, :eth)
:ok
end
defp str(value) do defp str(value) do
to_string(insert(:address, hash: value)) to_string(insert(:address, hash: value))
end end
@ -39,5 +47,18 @@ defmodule Explorer.Chain.AddressTest do
assert str("0xdbf03b407c01e7cd3cbea99509d93f8dddc8c6fb") == "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB" assert str("0xdbf03b407c01e7cd3cbea99509d93f8dddc8c6fb") == "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB"
assert str("0xd1220a0cf47c7b9be7a2e6ba89f429762e7b9adb") == "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb" assert str("0xd1220a0cf47c7b9be7a2e6ba89f429762e7b9adb") == "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb"
end end
test "returns the checksum rsk formatted address" do
expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options ->
{:ok, "30"}
end)
Application.put_env(:explorer, :checksum_function, :rsk)
assert str("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed") == "0x5aaEB6053f3e94c9b9a09f33669435E7ef1bEAeD"
assert str("0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359") == "0xFb6916095cA1Df60bb79ce92cE3EA74c37c5d359"
assert str("0xdbf03b407c01e7cd3cbea99509d93f8dddc8c6fb") == "0xDBF03B407c01E7CD3cBea99509D93F8Dddc8C6FB"
assert str("0xd1220a0cf47c7b9be7a2e6ba89f429762e7b9adb") == "0xD1220A0Cf47c7B9BE7a2e6ba89F429762E7B9adB"
end
end end
end end

@ -5,8 +5,8 @@ defmodule Explorer.Chain.BlocksCacheTest do
alias Explorer.Repo alias Explorer.Repo
setup do setup do
Supervisor.terminate_child(Explorer.Supervisor, ConCache) Supervisor.terminate_child(Explorer.Supervisor, {ConCache, :blocks})
Supervisor.restart_child(Explorer.Supervisor, ConCache) Supervisor.restart_child(Explorer.Supervisor, {ConCache, :blocks})
:ok :ok
end end

@ -3,7 +3,9 @@ defmodule Explorer.Counters.AverageBlockTimeTest do
doctest Explorer.Counters.AverageBlockTimeDurationFormat doctest Explorer.Counters.AverageBlockTimeDurationFormat
alias Explorer.Chain.Block
alias Explorer.Counters.AverageBlockTime alias Explorer.Counters.AverageBlockTime
alias Explorer.Repo
setup do setup do
start_supervised!(AverageBlockTime) start_supervised!(AverageBlockTime)
@ -24,5 +26,37 @@ defmodule Explorer.Counters.AverageBlockTimeTest do
test "without blocks duration is 0" do test "without blocks duration is 0" do
assert AverageBlockTime.average_block_time() == Timex.Duration.parse!("PT0S") assert AverageBlockTime.average_block_time() == Timex.Duration.parse!("PT0S")
end end
test "considers both uncles and consensus blocks" do
block_number = 99_999_999
first_timestamp = Timex.now()
insert(:block, number: block_number, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 3))
insert(:block, number: block_number, consensus: false, timestamp: Timex.shift(first_timestamp, seconds: 9))
insert(:block, number: block_number, consensus: false, timestamp: Timex.shift(first_timestamp, seconds: 6))
assert Repo.aggregate(Block, :count, :hash) == 3
AverageBlockTime.refresh()
assert AverageBlockTime.average_block_time() == Timex.Duration.parse!("PT3S")
end
test "when there are no uncles sorts by block number" do
block_number = 99_999_999
first_timestamp = Timex.now()
insert(:block, number: block_number, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 3))
insert(:block, number: block_number + 2, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 9))
insert(:block, number: block_number + 1, consensus: true, timestamp: Timex.shift(first_timestamp, seconds: 6))
assert Repo.aggregate(Block, :count, :hash) == 3
AverageBlockTime.refresh()
assert AverageBlockTime.average_block_time() == Timex.Duration.parse!("PT3S")
end
end end
end end

@ -40,8 +40,8 @@ defmodule Explorer.DataCase do
end end
Explorer.Chain.BlockNumberCache.setup() Explorer.Chain.BlockNumberCache.setup()
Supervisor.terminate_child(Explorer.Supervisor, ConCache) Supervisor.terminate_child(Explorer.Supervisor, {ConCache, :blocks})
Supervisor.restart_child(Explorer.Supervisor, ConCache) Supervisor.restart_child(Explorer.Supervisor, {ConCache, :blocks})
:ok :ok
end end

@ -140,7 +140,7 @@ defmodule Indexer.Block.Realtime.Fetcher do
%__MODULE__{state | subscription: subscription} %__MODULE__{state | subscription: subscription}
{:error, reason} -> {:error, reason} ->
Logger.debug(fn -> ["Could not connect to websocket: ", reason, ". Continuing with polling."] end) Logger.debug(fn -> ["Could not connect to websocket: #{inspect(reason)}. Continuing with polling."] end)
state state
end end
end end
@ -201,6 +201,12 @@ defmodule Indexer.Block.Realtime.Fetcher do
end end
end end
def import(_, _) do
Logger.warn("Empty parameters were provided for realtime fetcher")
{:ok, []}
end
defp start_fetch_and_import(number, block_fetcher, previous_number, max_number_seen) do defp start_fetch_and_import(number, block_fetcher, previous_number, max_number_seen) do
start_at = determine_start_at(number, previous_number, max_number_seen) start_at = determine_start_at(number, previous_number, max_number_seen)

@ -104,18 +104,18 @@ defmodule Indexer.Fetcher.UncleBlock do
{nephew_hash_bytes, index} {nephew_hash_bytes, index}
end end
defp run_blocks(%Blocks{blocks_params: []}, _, original_entries), do: {:retry, original_entries} def run_blocks(%Blocks{blocks_params: []}, _, original_entries), do: {:retry, original_entries}
defp run_blocks( def run_blocks(
%Blocks{ %Blocks{
blocks_params: blocks_params, blocks_params: blocks_params,
transactions_params: transactions_params, transactions_params: transactions_params,
block_second_degree_relations_params: block_second_degree_relations_params, block_second_degree_relations_params: block_second_degree_relations_params,
errors: errors errors: errors
}, },
block_fetcher, block_fetcher,
original_entries original_entries
) do ) do
addresses_params = Addresses.extract_addresses(%{blocks: blocks_params, transactions: transactions_params}) addresses_params = Addresses.extract_addresses(%{blocks: blocks_params, transactions: transactions_params})
case Block.Fetcher.import(block_fetcher, %{ case Block.Fetcher.import(block_fetcher, %{
@ -235,7 +235,17 @@ defmodule Indexer.Fetcher.UncleBlock do
Enum.map(errors, &error_to_entry/1) Enum.map(errors, &error_to_entry/1)
end end
defp error_to_entry(%{data: %{hash: hash}}) when is_binary(hash), do: hash defp error_to_entry(%{data: %{hash: hash, index: index}}) when is_binary(hash) do
{:ok, %Hash{bytes: nephew_hash_bytes}} = Hash.Full.cast(hash)
{nephew_hash_bytes, index}
end
defp error_to_entry(%{data: %{nephew_hash: hash, index: index}}) when is_binary(hash) do
{:ok, %Hash{bytes: nephew_hash_bytes}} = Hash.Full.cast(hash)
{nephew_hash_bytes, index}
end
defp errors_to_iodata(errors) when is_list(errors) do defp errors_to_iodata(errors) when is_list(errors) do
errors_to_iodata(errors, []) errors_to_iodata(errors, [])
@ -251,4 +261,9 @@ defmodule Indexer.Fetcher.UncleBlock do
when is_integer(code) and is_binary(message) and is_binary(hash) do when is_integer(code) and is_binary(message) and is_binary(hash) do
[hash, ": (", to_string(code), ") ", message, ?\n] [hash, ": (", to_string(code), ") ", message, ?\n]
end end
defp error_to_iodata(%{code: code, message: message, data: %{nephew_hash: hash}})
when is_integer(code) and is_binary(message) and is_binary(hash) do
[hash, ": (", to_string(code), ") ", message, ?\n]
end
end end

@ -13,7 +13,6 @@ defmodule Indexer.Temporary.BlocksTransactionsMismatch do
import Ecto.Query import Ecto.Query
alias Ecto.Multi
alias EthereumJSONRPC.Blocks alias EthereumJSONRPC.Blocks
alias Explorer.Chain.Block alias Explorer.Chain.Block
alias Explorer.Repo alias Explorer.Repo
@ -23,13 +22,14 @@ defmodule Indexer.Temporary.BlocksTransactionsMismatch do
@defaults [ @defaults [
flush_interval: :timer.seconds(3), flush_interval: :timer.seconds(3),
max_batch_size: 10, max_batch_size: 50,
max_concurrency: 1, max_concurrency: 1,
task_supervisor: Indexer.Temporary.BlocksTransactionsMismatch.TaskSupervisor, task_supervisor: Indexer.Temporary.BlocksTransactionsMismatch.TaskSupervisor,
metadata: [fetcher: :blocks_transactions_mismatch] metadata: [fetcher: :blocks_transactions_mismatch]
] ]
@doc false @doc false
# credo:disable-for-next-line Credo.Check.Design.DuplicatedCode
def child_spec([init_options, gen_server_options]) when is_list(init_options) do def child_spec([init_options, gen_server_options]) when is_list(init_options) do
{state, mergeable_init_options} = Keyword.pop(init_options, :json_rpc_named_arguments) {state, mergeable_init_options} = Keyword.pop(init_options, :json_rpc_named_arguments)
@ -99,17 +99,26 @@ defmodule Indexer.Temporary.BlocksTransactionsMismatch do
Map.has_key?(found_blocks_map, to_string(block.hash)) Map.has_key?(found_blocks_map, to_string(block.hash))
end) end)
{:ok, _} = {matching_blocks_data, unmatching_blocks_data} =
found_blocks_data Enum.split_with(found_blocks_data, fn {block, trans_num} ->
|> Enum.reduce(Multi.new(), fn {block, trans_num}, multi -> found_blocks_map[to_string(block.hash)] == trans_num
changes = %{
refetch_needed: false,
consensus: found_blocks_map[to_string(block.hash)] == trans_num
}
Multi.update(multi, block.hash, Block.changeset(block, changes))
end) end)
|> Repo.transaction()
unless Enum.empty?(matching_blocks_data) do
hashes = Enum.map(matching_blocks_data, fn {block, _trans_num} -> block.hash end)
Block
|> where([block], block.hash in ^hashes)
|> Repo.update_all(set: [refetch_needed: false])
end
unless Enum.empty?(unmatching_blocks_data) do
hashes = Enum.map(unmatching_blocks_data, fn {block, _trans_num} -> block.hash end)
Block
|> where([block], block.hash in ^hashes)
|> Repo.update_all(set: [refetch_needed: false, consensus: false])
end
if Enum.empty?(missing_blocks_data) do if Enum.empty?(missing_blocks_data) do
:ok :ok

@ -6,7 +6,9 @@ defmodule Indexer.Fetcher.UncleBlockTest do
import EthereumJSONRPC, only: [integer_to_quantity: 1] import EthereumJSONRPC, only: [integer_to_quantity: 1]
alias EthereumJSONRPC.Blocks
alias Explorer.Chain alias Explorer.Chain
alias Explorer.Chain.Hash
alias Indexer.Block alias Indexer.Block
alias Indexer.Fetcher.UncleBlock alias Indexer.Fetcher.UncleBlock
@ -138,6 +140,74 @@ defmodule Indexer.Fetcher.UncleBlockTest do
end end
end end
describe "run/2" do
test "retries failed request", %{json_rpc_named_arguments: json_rpc_named_arguments} do
%Hash{bytes: block_hash_bytes} = block_hash()
entries = [{block_hash_bytes, 0}]
EthereumJSONRPC.Mox
|> expect(:json_rpc, fn [
%{
id: id,
method: "eth_getUncleByBlockHashAndIndex"
}
],
_ ->
{:ok,
[
%{
id: id,
error: %{
code: 404,
data: %{index: 0, nephew_hash: "0xa0814f0478fe90c82852f812fd74c96df148654c326d2600d836e6908ebb62b4"},
message: "Not Found"
}
}
]}
end)
assert {:retry, ^entries} =
UncleBlock.run(entries, %Block.Fetcher{json_rpc_named_arguments: json_rpc_named_arguments})
end
end
describe "run_blocks/2" do
test "converts errors to entries for retry", %{json_rpc_named_arguments: json_rpc_named_arguments} do
miner_hash =
address_hash()
|> to_string()
block_number = 1
index = 0
hash = "0xa0814f0478fe90c82852f812fd74c96df148654c326d2600d836e6908ebb62b4"
params = %Blocks{
errors: [
%{
code: 404,
data: %{index: index, nephew_hash: hash},
message: "Not Found"
}
],
blocks_params: [%{miner_hash: miner_hash, number: block_number}]
}
assert {:retry, [{bin_hash, ^index}]} =
UncleBlock.run_blocks(
params,
%Block.Fetcher{
json_rpc_named_arguments: json_rpc_named_arguments,
callback_module: Indexer.Block.Realtime.Fetcher
},
[]
)
assert Hash.Full.cast(bin_hash) == Hash.Full.cast(hash)
end
end
defp wait(producer) do defp wait(producer) do
producer.() producer.()
rescue rescue

@ -1,6 +1,6 @@
export DISPLAY=:99.0 export DISPLAY=:99.0
sh -e /etc/init.d/xvfb start sh -e /etc/init.d/xvfb start
export CHROMEDRIVER_VERSION=`curl -s http://chromedriver.storage.googleapis.com/LATEST_RELEASE` export CHROMEDRIVER_VERSION=74.0.3729.6
curl -L -O "http://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip" curl -L -O "http://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip"
unzip chromedriver_linux64.zip unzip chromedriver_linux64.zip
sudo chmod +x chromedriver sudo chmod +x chromedriver

@ -33,7 +33,7 @@ RUN cd apps/block_scout_web/assets/ && \
RUN cd apps/explorer/ && \ RUN cd apps/explorer/ && \
npm install && \ npm install && \
apk del --force-broken-world alpine-sdk gmp-dev automake libtool inotify-tools autoconf python apk update && apk del --force-broken-world alpine-sdk gmp-dev automake libtool inotify-tools autoconf python
# RUN mix do ecto.drop --force, ecto.create, ecto.migrate # RUN mix do ecto.drop --force, ecto.create, ecto.migrate

Loading…
Cancel
Save