diff --git a/CHANGELOG.md b/CHANGELOG.md index a847de3b1d..fb0a5f0898 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,23 @@ ## Current ### 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 +- [#2151](https://github.com/poanetwork/blockscout/pull/2151) - hide dropdown menu then other networks list is empty ### 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 - [#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 @@ -12,7 +26,22 @@ - [#2090](https://github.com/poanetwork/blockscout/pull/2090) - updated some ETC theme colors - [#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 -- [#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 @@ -41,6 +70,7 @@ - [#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 - [#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 - [#2099](https://github.com/poanetwork/blockscout/pull/2099) - logs search input width diff --git a/README.md b/README.md index f53a43e897..e0e9ece96b 100644 --- a/README.md +++ b/README.md @@ -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) | | | | [SpringChain](https://explorer.springrole.com/) | | | | [Kotti Testnet](https://kottiexplorer.ethernode.io/) | +| | | [Loom](http://plasma-blockexplorer.dappchains.com/) | +| | | [Tenda](https://tenda.network) | ### Visual Interface diff --git a/apps/block_scout_web/assets/css/components/_api.scss b/apps/block_scout_web/assets/css/components/_api.scss index 6fc2bc9a97..820e1e8f01 100644 --- a/apps/block_scout_web/assets/css/components/_api.scss +++ b/apps/block_scout_web/assets/css/components/_api.scss @@ -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-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-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 { color: $api-doc-list-item-title-color; - font-size: 17px; - font-weight: 700; + font-size: 15px; + font-weight: 400; line-height: 1.2; margin: 0 0 15px; } diff --git a/apps/block_scout_web/assets/css/components/_badge.scss b/apps/block_scout_web/assets/css/components/_badge.scss index 2891a6e4ff..9fad8b41dd 100644 --- a/apps/block_scout_web/assets/css/components/_badge.scss +++ b/apps/block_scout_web/assets/css/components/_badge.scss @@ -2,8 +2,8 @@ $badge-success-color: #15bba6 !default; $badge-success-background-color: rgba($badge-success-color, 0.1) !default; $badge-danger-color: #ed9966 !default; $badge-danger-background-color: rgba($badge-danger-color, 0.1) !default; -$badge-neutral-color: #333 !default; -$badge-neutral-background-color: #e9e9e9 !default; +$badge-neutral-color: $secondary !default; +$badge-neutral-background-color: rgba($secondary, .1) !default; .badge { color: $white; diff --git a/apps/block_scout_web/assets/css/components/_tile.scss b/apps/block_scout_web/assets/css/components/_tile.scss index d5658647c5..c10fa2f9b6 100644 --- a/apps/block_scout_web/assets/css/components/_tile.scss +++ b/apps/block_scout_web/assets/css/components/_tile.scss @@ -4,7 +4,7 @@ $tile-type-reorg-color: $purple !default; $tile-type-emission-reward-color: $lilac !default; $tile-type-transaction-color: $blue !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-unique-token-color: $orange !default; $tile-type-unique-token-image-color: $orange !default; @@ -104,6 +104,18 @@ $tile-body-a-color: #5959d8 !default; 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 { background-color: #f6f7f9; font-size: 12px; diff --git a/apps/block_scout_web/assets/css/components/_verify_other_explorers.scss b/apps/block_scout_web/assets/css/components/_verify_other_explorers.scss index a3ea681eda..ce8cbdb926 100644 --- a/apps/block_scout_web/assets/css/components/_verify_other_explorers.scss +++ b/apps/block_scout_web/assets/css/components/_verify_other_explorers.scss @@ -18,9 +18,13 @@ line-height: 1.25; display: inline-flex; margin-bottom: 12px; - @media (min-width: 1200px) { + width: 100%; + @media (min-width: 768px) { margin-right: 10px; + } + @media (min-width: 1200px) { margin-bottom: 0; + width: auto; } } } @@ -31,6 +35,8 @@ flex-grow: 2; @media (min-width: 768px) { flex-direction: row; + position: relative; + padding-right: 44px; } } @@ -41,6 +47,7 @@ flex-grow: 2; @media (min-width: 768px) { margin-top: 0; + max-width: 188px; } @media (min-width: 1200px) { min-width: 145px; @@ -76,7 +83,7 @@ } .exp-content { - padding: 6px 9px 4px 9px; + padding: 6px 9px 5px 9px; h3, div { font-size: 10px; line-height: 1; @@ -117,7 +124,7 @@ display: inline-flex; align-items: center; justify-content: center; - border: 1px solid $secondary; + border: 1px solid $btn-line-color; border-radius: 2px; margin-top: 10px; transition: .1s ease-in; @@ -125,12 +132,15 @@ @media (min-width: 768px) { margin-left: 10px; margin-top: 0; + position: absolute; + top: 0; + right: 0; } svg path { - fill: $secondary; + fill: $btn-line-color; } &:hover { - background-color: $secondary; + background-color: $btn-line-color; svg path { fill: #fff; } diff --git a/apps/block_scout_web/assets/css/theme/_base_variables.scss b/apps/block_scout_web/assets/css/theme/_base_variables.scss index 3e97eb61b3..9f1b82c887 100644 --- a/apps/block_scout_web/assets/css/theme/_base_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_base_variables.scss @@ -47,6 +47,7 @@ $yellow: #ffc107 !default; $green: #20b760 !default; $teal: #009097 !default; $cyan: #90e1d8 !default; +$dark-purple: #923dc3; $colors: () !default; $colors: map-merge( diff --git a/apps/block_scout_web/assets/css/theme/_dai_variables.scss b/apps/block_scout_web/assets/css/theme/_dai_variables.scss index 1e19866bbe..50cbcfa290 100644 --- a/apps/block_scout_web/assets/css/theme/_dai_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_dai_variables.scss @@ -57,4 +57,9 @@ $card-tab-active: $secondary; $dashboard-banner-gradient-end ); } -} \ No newline at end of file +} + +// Badges +$badge-neutral-color: #20446e; +$badge-neutral-background-color: rgba(#20446e, .1); +$api-text-monospace-color: #20446e; \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/theme/_ethereum_classic_variables.scss b/apps/block_scout_web/assets/css/theme/_ethereum_classic_variables.scss index 9bec5a1243..68feab4dfc 100644 --- a/apps/block_scout_web/assets/css/theme/_ethereum_classic_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_ethereum_classic_variables.scss @@ -70,3 +70,8 @@ $card-tab-active: $tertiary; filter: brightness(0) invert(1); } } + +// Badges +$badge-neutral-color: $tertiary; +$badge-neutral-background-color: rgba($tertiary, .1); +$api-text-monospace-color: $tertiary; \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/theme/_goerli_variables.scss b/apps/block_scout_web/assets/css/theme/_goerli_variables.scss index 3ab3e4d089..7900dd4c3b 100644 --- a/apps/block_scout_web/assets/css/theme/_goerli_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_goerli_variables.scss @@ -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; diff --git a/apps/block_scout_web/assets/css/theme/_kovan_variables.scss b/apps/block_scout_web/assets/css/theme/_kovan_variables.scss index 821232bbd5..351f046726 100644 --- a/apps/block_scout_web/assets/css/theme/_kovan_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_kovan_variables.scss @@ -67,4 +67,11 @@ $card-tab-active: $tertiary; $dashboard-banner-gradient-end ); } -} \ No newline at end of file +} + +// 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; \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/theme/_lukso_variables.scss b/apps/block_scout_web/assets/css/theme/_lukso_variables.scss index cfa415b7ae..65f9a73b5f 100644 --- a/apps/block_scout_web/assets/css/theme/_lukso_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_lukso_variables.scss @@ -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; diff --git a/apps/block_scout_web/assets/css/theme/_neutral_variables.scss b/apps/block_scout_web/assets/css/theme/_neutral_variables.scss index 8b6676a856..8a0a62d26e 100644 --- a/apps/block_scout_web/assets/css/theme/_neutral_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_neutral_variables.scss @@ -59,4 +59,9 @@ $card-tab-active: $primary; $dashboard-banner-gradient-end ); } -} \ No newline at end of file +} + +// Badges +$badge-neutral-color: $primary; +$badge-neutral-background-color: rgba($primary, .1); +$api-text-monospace-color: $primary; \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/theme/_poa_variables.scss b/apps/block_scout_web/assets/css/theme/_poa_variables.scss index 8b6676a856..8a0a62d26e 100644 --- a/apps/block_scout_web/assets/css/theme/_poa_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_poa_variables.scss @@ -59,4 +59,9 @@ $card-tab-active: $primary; $dashboard-banner-gradient-end ); } -} \ No newline at end of file +} + +// Badges +$badge-neutral-color: $primary; +$badge-neutral-background-color: rgba($primary, .1); +$api-text-monospace-color: $primary; \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/theme/_posdao_variables.scss b/apps/block_scout_web/assets/css/theme/_posdao_variables.scss deleted file mode 100644 index 9d92ca2adc..0000000000 --- a/apps/block_scout_web/assets/css/theme/_posdao_variables.scss +++ /dev/null @@ -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; \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/theme/_rsk_variables.scss b/apps/block_scout_web/assets/css/theme/_rsk_variables.scss index e7536db13c..76029e1a34 100644 --- a/apps/block_scout_web/assets/css/theme/_rsk_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_rsk_variables.scss @@ -61,3 +61,7 @@ $card-tab-active: $secondary; filter: brightness(0) invert(1); } } + +// Badges +$badge-neutral-color: #1a323b; +$badge-neutral-background-color: rgba(#1a323b, .1); diff --git a/apps/block_scout_web/assets/css/theme/_sokol_variables.scss b/apps/block_scout_web/assets/css/theme/_sokol_variables.scss index 2d80e64f03..71822bdc5a 100644 --- a/apps/block_scout_web/assets/css/theme/_sokol_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_sokol_variables.scss @@ -65,3 +65,7 @@ $card-tab-active: $sub-accent-color; ); } } + +// Badges +$badge-neutral-color: $tertiary; +$badge-neutral-background-color: rgba($tertiary, .1); \ No newline at end of file diff --git a/apps/block_scout_web/assets/js/lib/currency.js b/apps/block_scout_web/assets/js/lib/currency.js index 99c6cfe1f1..9b7493f1ad 100644 --- a/apps/block_scout_web/assets/js/lib/currency.js +++ b/apps/block_scout_web/assets/js/lib/currency.js @@ -18,6 +18,7 @@ function formatCurrencyValue (value, symbol) { if (value < 0.000001) return `${window.localized['Less than']} ${symbol}0.000001` if (value < 1) return `${symbol}${numeral(value).format('0.000000')}` 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')}` } diff --git a/apps/block_scout_web/assets/static/images/dai_logo.svg b/apps/block_scout_web/assets/static/images/dai_logo.svg index cddb632a3e..3ce9609c84 100644 --- a/apps/block_scout_web/assets/static/images/dai_logo.svg +++ b/apps/block_scout_web/assets/static/images/dai_logo.svg @@ -1 +1,11 @@ - \ No newline at end of file + + + + + + + + + + + diff --git a/apps/block_scout_web/assets/static/images/errors-img/goerli-page-not-found.png b/apps/block_scout_web/assets/static/images/errors-img/goerli-page-not-found.png new file mode 100644 index 0000000000..8758824715 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/goerli-page-not-found.png differ diff --git a/apps/block_scout_web/assets/static/images/errors-img/goerli-page-not-found@2x.png b/apps/block_scout_web/assets/static/images/errors-img/goerli-page-not-found@2x.png new file mode 100644 index 0000000000..19557af509 Binary files /dev/null and b/apps/block_scout_web/assets/static/images/errors-img/goerli-page-not-found@2x.png differ diff --git a/apps/block_scout_web/assets/static/images/goerli_logo.png b/apps/block_scout_web/assets/static/images/goerli_logo.png deleted file mode 100644 index 910a7b2362..0000000000 Binary files a/apps/block_scout_web/assets/static/images/goerli_logo.png and /dev/null differ diff --git a/apps/block_scout_web/assets/static/images/posdao_logo.png b/apps/block_scout_web/assets/static/images/posdao_logo.png deleted file mode 100755 index 535b0ab6ac..0000000000 Binary files a/apps/block_scout_web/assets/static/images/posdao_logo.png and /dev/null differ diff --git a/apps/block_scout_web/assets/static/images/posdao_logo_footer.png b/apps/block_scout_web/assets/static/images/posdao_logo_footer.png deleted file mode 100755 index 1b49d8eff2..0000000000 Binary files a/apps/block_scout_web/assets/static/images/posdao_logo_footer.png and /dev/null differ diff --git a/apps/block_scout_web/assets/static/images/posdao_logo_footer.svg b/apps/block_scout_web/assets/static/images/posdao_logo_footer.svg deleted file mode 100644 index 9a26bec27f..0000000000 --- a/apps/block_scout_web/assets/static/images/posdao_logo_footer.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/apps/block_scout_web/lib/block_scout_web/channels/reward_channel.ex b/apps/block_scout_web/lib/block_scout_web/channels/reward_channel.ex index 5176182d34..6f6ff3a70b 100644 --- a/apps/block_scout_web/lib/block_scout_web/channels/reward_channel.ex +++ b/apps/block_scout_web/lib/block_scout_web/channels/reward_channel.ex @@ -11,9 +11,10 @@ defmodule BlockScoutWeb.RewardChannel do intercept(["new_reward"]) def join("rewards:" <> address_hash, _params, socket) do - {:ok, hash} = Chain.string_to_address_hash(address_hash) - {:ok, address} = Chain.hash_to_address(hash) - {:ok, %{}, assign(socket, :current_address, address)} + with {:ok, hash} <- Chain.string_to_address_hash(address_hash), + {:ok, address} <- Chain.hash_to_address(hash) do + {:ok, %{}, assign(socket, :current_address, address)} + end end def handle_out("new_reward", %{emission_funds: emission_funds, validator: validator}, socket) do diff --git a/apps/block_scout_web/lib/block_scout_web/controller.ex b/apps/block_scout_web/lib/block_scout_web/controller.ex index fe2851c197..cf545f5260 100644 --- a/apps/block_scout_web/lib/block_scout_web/controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controller.ex @@ -12,8 +12,9 @@ defmodule BlockScoutWeb.Controller do def not_found(conn) do conn |> put_status(:not_found) - |> put_view(BlockScoutWeb.ErrorView) - |> render("404.html") + |> put_view(BlockScoutWeb.PageNotFoundView) + |> render(:index) + |> halt() end def unprocessable_entity(conn) do diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex index da22b8c042..6742d01b60 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex @@ -28,7 +28,7 @@ defmodule BlockScoutWeb.AddressTransactionController 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), - {:ok, address} <- Chain.hash_to_address(address_hash) do + {:ok, address} <- Chain.hash_to_address(address_hash, [:names], false) do options = @transaction_necessity_by_association |> put_in([:necessity_by_association, :block], :required) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex index be10089bad..3150d3bd60 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/address_controller.ex @@ -20,6 +20,35 @@ defmodule BlockScoutWeb.API.RPC.AddressController do |> render(:listaccounts, %{accounts: accounts}) 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 with {:address_param, {:ok, address_param}} <- fetch_address(params), {:format, {:ok, address_hashes}} <- to_address_hashes(address_param) do @@ -217,6 +246,20 @@ defmodule BlockScoutWeb.API.RPC.AddressController do {:required_params, result} 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 result = with {:ok, contract_address_hash} <- to_address_hash(params, "contractaddress"), diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/eth_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/eth_controller.ex new file mode 100644 index 0000000000..693772ed8c --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/eth_controller.ex @@ -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 diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/page_not_found_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/page_not_found_controller.ex new file mode 100644 index 0000000000..e453e5463b --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/controllers/page_not_found_controller.ex @@ -0,0 +1,8 @@ +defmodule BlockScoutWeb.PageNotFoundController do + use BlockScoutWeb, :controller + + def index(conn, _params) do + conn + |> render("index.html") + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/pending_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/pending_transaction_controller.ex index 95ffe8aa22..1128f04616 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/pending_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/pending_transaction_controller.ex @@ -13,7 +13,8 @@ defmodule BlockScoutWeb.PendingTransactionController do [ necessity_by_association: %{ [from_address: :names] => :optional, - [to_address: :names] => :optional + [to_address: :names] => :optional, + [created_contract_address: :names] => :optional } ], paging_options(params) @@ -51,10 +52,7 @@ defmodule BlockScoutWeb.PendingTransactionController do end def index(conn, _params) do - render(conn, "index.html", - current_path: current_path(conn), - pending_transaction_count: Chain.pending_transaction_count() - ) + render(conn, "index.html", current_path: current_path(conn)) end defp get_pending_transactions_and_next_page(options) do diff --git a/apps/block_scout_web/lib/block_scout_web/etherscan.ex b/apps/block_scout_web/lib/block_scout_web/etherscan.ex index 74a9fa761c..2191a86fa2 100644 --- a/apps/block_scout_web/lib/block_scout_web/etherscan.ex +++ b/apps/block_scout_web/lib/block_scout_web/etherscan.ex @@ -100,6 +100,12 @@ defmodule BlockScoutWeb.Etherscan do "result" => [] } + @account_eth_get_balance_example_value %{ + "jsonrpc" => "2.0", + "result" => "0x0234c8a3397aab58", + "id" => 1 + } + @account_tokentx_example_value %{ "status" => "1", "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 %{ name: "balance", description: """ @@ -2203,6 +2252,7 @@ defmodule BlockScoutWeb.Etherscan do @account_module %{ name: "account", actions: [ + @account_eth_get_balance_action, @account_balance_action, @account_balancemulti_action, @account_txlist_action, diff --git a/apps/block_scout_web/lib/block_scout_web/router.ex b/apps/block_scout_web/lib/block_scout_web/router.ex index aa19725811..786c77cdff 100644 --- a/apps/block_scout_web/lib/block_scout_web/router.ex +++ b/apps/block_scout_web/lib/block_scout_web/router.ex @@ -32,6 +32,8 @@ defmodule BlockScoutWeb.Router do alias BlockScoutWeb.API.RPC + post("/eth_rpc", EthController, :eth_request) + forward("/", RPCTranslator, %{ "block" => RPC.BlockController, "account" => RPC.AddressController, @@ -245,5 +247,7 @@ defmodule BlockScoutWeb.Router do get("/chain_blocks", ChainController, :chain_blocks, as: :chain_blocks) get("/api_docs", APIDocsController, :index) + + get("/:page", PageNotFoundController, :index) end end diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex index 270450c2c0..c2e62e2004 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_tile.html.eex @@ -39,7 +39,7 @@ - <% if @total_supply do %> + <%= if @total_supply do %> (<%= balance_percentage(@address, @total_supply) %>) <% end %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_decompiled_contract/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_decompiled_contract/index.html.eex index 173744603c..6cb4ccf6ab 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_decompiled_contract/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_decompiled_contract/index.html.eex @@ -2,7 +2,8 @@ <%= render BlockScoutWeb.AddressView, "overview.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 %>

<%= gettext "Decompiler version" %>

@@ -21,6 +22,10 @@
+ <% else %> +
+ <%= gettext "There is no decompilded contracts for this address." %> +
<% end %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_pagination_container.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_pagination_container.html.eex index 2f9f470eed..f9992c6d86 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_pagination_container.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_pagination_container.html.eex @@ -1,4 +1,4 @@ -
disabled<% end %>> +
<%= if false do %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex index de88489330..73a92c7cfc 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_topnav.html.eex @@ -77,7 +77,7 @@
-

Lorem Ipsum Dolor

-

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua

+

Page not found

+

The requested path was not found on BlockScout.

Back Home
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tile.html.eex index e8d52b25f4..d6882801ad 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tile.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_tile.html.eex @@ -19,7 +19,7 @@
<%= render "_link.html", transaction_hash: @transaction.hash %> - + <%= @transaction |> BlockScoutWeb.AddressView.address_partial_selector(:from, 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 %>
-
+
<%= @transaction |> block_number() |> BlockScoutWeb.RenderHelpers.render_partial() %> diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_decompiled_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_decompiled_contract_view.ex index b92d356479..438f344199 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_decompiled_contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_decompiled_contract_view.ex @@ -230,10 +230,8 @@ defmodule BlockScoutWeb.AddressDecompiledContractView do end) end - def sort_contracts_by_version(decompiled_contracts) do - decompiled_contracts - |> Enum.sort_by(& &1.decompiler_version) - |> Enum.reverse() + def last_decompiled_contract_version(decompiled_contracts) do + Enum.max_by(decompiled_contracts, & &1.decompiler_version) end defp add_line_numbers(code) do diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex index 7aaf26dba0..248dc942c7 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex @@ -1,7 +1,7 @@ defmodule BlockScoutWeb.API.RPC.AddressView do use BlockScoutWeb, :view - alias BlockScoutWeb.API.RPC.RPCView + alias BlockScoutWeb.API.RPC.{EthRPCView, RPCView} def render("listaccounts.json", %{accounts: accounts}) do accounts = Enum.map(accounts, &prepare_account/1) @@ -51,6 +51,10 @@ defmodule BlockScoutWeb.API.RPC.AddressView do RPCView.render("show.json", data: data) 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 RPCView.render("error.json", assigns) end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_rpc_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_rpc_view.ex index 39eb5ae9d1..5dda92d3d5 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_rpc_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_rpc_view.ex @@ -17,16 +17,48 @@ defmodule BlockScoutWeb.API.RPC.EthRPCView do } 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 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 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 diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_view.ex new file mode 100644 index 0000000000..739f3ac8a7 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/eth_view.ex @@ -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 diff --git a/apps/block_scout_web/lib/block_scout_web/views/page_not_found.ex b/apps/block_scout_web/lib/block_scout_web/views/page_not_found.ex new file mode 100644 index 0000000000..b5a18f0434 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/page_not_found.ex @@ -0,0 +1,5 @@ +defmodule BlockScoutWeb.PageNotFoundView do + use BlockScoutWeb, :view + + @dialyzer :no_match +end diff --git a/apps/block_scout_web/lib/block_scout_web/views/wei_helpers.ex b/apps/block_scout_web/lib/block_scout_web/views/wei_helpers.ex index 3488e60522..5cdbff8c0c 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/wei_helpers.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/wei_helpers.ex @@ -57,13 +57,19 @@ defmodule BlockScoutWeb.WeiHelpers do converted_value = wei |> 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 display_unit = display_unit(unit) - "#{converted_value} #{display_unit}" + "#{formatted_value} #{display_unit}" else - converted_value + formatted_value end end diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index e9763e2287..4e05e8266f 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -378,7 +378,7 @@ msgstr "" #: 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/overview.html.eex:192 -#: lib/block_scout_web/views/wei_helpers.ex:72 +#: lib/block_scout_web/views/wei_helpers.ex:78 msgid "Ether" msgstr "" @@ -436,7 +436,7 @@ msgstr "" #, elixir-format #: 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" msgstr "" @@ -982,7 +982,7 @@ msgid "Wallet addresses" msgstr "" #, elixir-format -#: lib/block_scout_web/views/wei_helpers.ex:70 +#: lib/block_scout_web/views/wei_helpers.ex:76 msgid "Wei" msgstr "" @@ -1490,7 +1490,7 @@ msgid "EVM Version" msgstr "" #, 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" msgstr "" @@ -1505,12 +1505,12 @@ msgid "Decompiled code" msgstr "" #, 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" msgstr "" #, 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" msgstr "" @@ -1697,3 +1697,8 @@ msgstr "" #: lib/block_scout_web/templates/transaction/overview.html.eex:178 msgid " Token Transfer" 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 "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 654a848278..142aec2b37 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -378,7 +378,7 @@ msgstr "" #: 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/overview.html.eex:192 -#: lib/block_scout_web/views/wei_helpers.ex:72 +#: lib/block_scout_web/views/wei_helpers.ex:78 msgid "Ether" msgstr "POA" @@ -436,7 +436,7 @@ msgstr "" #, elixir-format #: 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" msgstr "" @@ -982,7 +982,7 @@ msgid "Wallet addresses" msgstr "" #, elixir-format -#: lib/block_scout_web/views/wei_helpers.ex:70 +#: lib/block_scout_web/views/wei_helpers.ex:76 msgid "Wei" msgstr "" @@ -1490,7 +1490,7 @@ msgid "EVM Version" msgstr "" #, 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" msgstr "" @@ -1505,12 +1505,12 @@ msgid "Decompiled code" msgstr "" #, 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" msgstr "" #, 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" msgstr "" @@ -1693,7 +1693,12 @@ msgstr "" msgid "New Smart Contract Verification" msgstr "" -#, elixir-format, fuzzy +#, elixir-format #: lib/block_scout_web/templates/transaction/overview.html.eex:178 msgid " Token Transfer" 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 "" diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs new file mode 100644 index 0000000000..b26becee2f --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs @@ -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 diff --git a/apps/block_scout_web/test/block_scout_web/controllers/block_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/block_controller_test.exs index 9dea0841f9..e8c4af7de8 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/block_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/block_controller_test.exs @@ -3,8 +3,8 @@ defmodule BlockScoutWeb.BlockControllerTest do alias Explorer.Chain.Block setup do - Supervisor.terminate_child(Explorer.Supervisor, ConCache) - Supervisor.restart_child(Explorer.Supervisor, ConCache) + Supervisor.terminate_child(Explorer.Supervisor, {ConCache, :blocks}) + Supervisor.restart_child(Explorer.Supervisor, {ConCache, :blocks}) :ok end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/chain_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/chain_controller_test.exs index 701a991c55..f902f15ad4 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/chain_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/chain_controller_test.exs @@ -9,8 +9,8 @@ defmodule BlockScoutWeb.ChainControllerTest do alias Explorer.Counters.AddressesWithBalanceCounter setup do - Supervisor.terminate_child(Explorer.Supervisor, ConCache) - Supervisor.restart_child(Explorer.Supervisor, ConCache) + Supervisor.terminate_child(Explorer.Supervisor, {ConCache, :blocks}) + Supervisor.restart_child(Explorer.Supervisor, {ConCache, :blocks}) start_supervised!(AddressesWithBalanceCounter) AddressesWithBalanceCounter.consolidate() diff --git a/apps/block_scout_web/test/block_scout_web/controllers/pending_transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/pending_transaction_controller_test.exs index d535b309f8..d632154294 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/pending_transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/pending_transaction_controller_test.exs @@ -37,15 +37,6 @@ defmodule BlockScoutWeb.PendingTransactionControllerTest do refute hd(json_response(conn, 200)["items"]) =~ to_string(dropped_replaced.hash) 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 conn = get(conn, pending_transaction_path(conn, :index)) diff --git a/apps/block_scout_web/test/block_scout_web/features/viewing_chain_test.exs b/apps/block_scout_web/test/block_scout_web/features/viewing_chain_test.exs index 1add7ed9d5..a32e4f5da3 100644 --- a/apps/block_scout_web/test/block_scout_web/features/viewing_chain_test.exs +++ b/apps/block_scout_web/test/block_scout_web/features/viewing_chain_test.exs @@ -10,8 +10,8 @@ defmodule BlockScoutWeb.ViewingChainTest do alias Explorer.Counters.AddressesWithBalanceCounter setup do - Supervisor.terminate_child(Explorer.Supervisor, ConCache) - Supervisor.restart_child(Explorer.Supervisor, ConCache) + Supervisor.terminate_child(Explorer.Supervisor, {ConCache, :blocks}) + Supervisor.restart_child(Explorer.Supervisor, {ConCache, :blocks}) Enum.map(401..404, &insert(:block, number: &1)) diff --git a/apps/block_scout_web/test/block_scout_web/views/address_decompiled_contract_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/address_decompiled_contract_view_test.exs index 334ea36baa..abac26beae 100644 --- a/apps/block_scout_web/test/block_scout_web/views/address_decompiled_contract_view_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/address_decompiled_contract_view_test.exs @@ -96,15 +96,15 @@ defmodule BlockScoutWeb.AddressDecompiledContractViewTest do end end - describe "sort_contracts_by_version/1" do - test "sorts contracts in lexicographical order" do + describe "last_decompiled_contract_version/1" do + test "returns last version" do contract2 = insert(:decompiled_smart_contract, decompiler_version: "v2") contract1 = insert(:decompiled_smart_contract, decompiler_version: "v1") 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 diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex index 24fc593d85..71994da56c 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex @@ -249,6 +249,19 @@ defmodule EthereumJSONRPC do |> fetch_blocks_by_params(&Block.ByNephew.request/1, json_rpc_named_arguments) 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 """ Fetches block number by `t:tag/0`. diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex index 6be34eeee1..1c52a6e6c9 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex @@ -319,6 +319,8 @@ defmodule EthereumJSONRPC.Block do @spec elixir_to_transactions(elixir) :: Transactions.elixir() def elixir_to_transactions(%{"transactions" => transactions}), do: transactions + def elixir_to_transactions(_), do: [] + @doc """ Get `t:EthereumJSONRPC.Uncles.elixir/0` from `t:elixir/0`. diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/fetched_beneficiaries.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/fetched_beneficiaries.ex index 98174be69a..fea1a4c8aa 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/fetched_beneficiaries.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/parity/fetched_beneficiaries.ex @@ -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 == 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 == 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 == "uncle", do: :uncle end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs index 1cf16c6f47..23e1e442c5 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs @@ -56,4 +56,10 @@ defmodule EthereumJSONRPC.BlockTest do } 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 diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs index 5f82b6a43f..3a6bb6f046 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs @@ -886,6 +886,24 @@ defmodule EthereumJSONRPCTest do 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 receive do _ -> clear_mailbox() diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index 94a1d79c6d..9a7d9f4296 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -80,7 +80,8 @@ if System.get_env("SOURCE_MODULE") == "TransactionAndLog" do end 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, # keep synced with `config/config.exs` diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index 984695b3b4..98a71c7bac 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -6,7 +6,7 @@ defmodule Explorer.Application do use Application alias Explorer.Admin - alias Explorer.Chain.{BlockCountCache, BlockNumberCache, BlocksCache, TransactionCountCache} + alias Explorer.Chain.{BlockCountCache, BlockNumberCache, BlocksCache, NetVersionCache, TransactionCountCache} alias Explorer.Repo.PrometheusLogger @impl Application @@ -31,7 +31,8 @@ defmodule Explorer.Application do {Admin.Recovery, [[], [name: Admin.Recovery]]}, {TransactionCountCache, [[], []]}, {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() @@ -79,4 +80,17 @@ defmodule Explorer.Application do http: HTTPoison ] end + + defp con_cache_child_spec(name) do + Supervisor.child_spec( + { + ConCache, + [ + name: name, + ttl_check_interval: false + ] + }, + id: {ConCache, name} + ) + end end diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 4edc94887a..c40c2a492b 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -227,60 +227,31 @@ defmodule Explorer.Chain do transaction_hashes_from_token_transfers = TokenTransfer.where_any_address_fields_match(direction, address_hash, paging_options) - token_transfers_query = - 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 = + transactions_list = paging_options |> fetch_transactions() + |> Transaction.where_transaction_matches(transaction_hashes_from_token_transfers, direction, address_hash) |> join_associations(necessity_by_association) |> Transaction.preload_token_transfers(address_hash) + |> Repo.all() - from_address_query = - base_query - |> where([t], t.from_address_hash == ^address_hash) - - to_address_query = - base_query - |> where([t], t.to_address_hash == ^address_hash) - - created_contract_query = - base_query - |> where([t], t.created_contract_address_hash == ^address_hash) - - 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] + if Application.get_env(:block_scout_web, BlockScoutWeb.Chain)[:has_emission_funds] do + address_hash + |> Reward.fetch_emission_rewards_tuples(paging_options) + |> Enum.concat(transactions_list) + |> Enum.sort_by(fn item -> + case item do + {%Reward{} = emission_reward, _} -> + {-emission_reward.block.number, 1} + + item -> + {-item.block_number, -item.index} end - - rewards_list = - if Application.get_env(:block_scout_web, BlockScoutWeb.Chain)[:has_emission_funds] do - Reward.fetch_emission_rewards_tuples(address_hash, paging_options) - else - [] - 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) + |> Enum.take(paging_options.page_size) + else + transactions_list + end end @spec address_to_logs(Address.t(), Keyword.t()) :: [ @@ -703,25 +674,32 @@ defmodule Explorer.Chain do iex> Explorer.Chain.hash_to_address(hash) {: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} - def hash_to_address(%Hash{byte_count: unquote(Hash.Address.byte_count())} = hash) do - query = - from( - address in Address, - preload: [ + @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, + preloads \\ [ :contracts_creation_internal_transaction, :names, :smart_contract, :token, :contracts_creation_transaction ], + query_decompiled_code_flag \\ true + ) do + query = + from( + address in Address, + preload: ^preloads, where: address.hash == ^hash ) - query_with_decompiled_flag = with_decompiled_code_flag(query, hash) - - query_with_decompiled_flag + query + |> with_decompiled_code_flag(hash, query_decompiled_code_flag) |> Repo.one() |> case do nil -> {:error, :not_found} @@ -824,6 +802,86 @@ defmodule Explorer.Chain do Repo.all(query) 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()] def list_ordered_addresses(offset, limit) do query = @@ -1907,7 +1965,6 @@ defmodule Explorer.Chain do |> page_pending_transaction(paging_options) |> limit(^paging_options.page_size) |> pending_transactions_query() - |> where([transaction], is_nil(transaction.error) or transaction.error != "dropped/replaced") |> order_by([transaction], desc: transaction.inserted_at, desc: transaction.hash) |> join_associations(necessity_by_association) |> preload([{:token_transfers, [:token, :from_address, :to_address]}]) @@ -1916,7 +1973,7 @@ defmodule Explorer.Chain do defp pending_transactions_query(query) do 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 @@ -3081,7 +3138,11 @@ defmodule Explorer.Chain do 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 = from(decompiled_contract in DecompiledSmartContract, where: decompiled_contract.address_hash == ^hash, diff --git a/apps/explorer/lib/explorer/chain/address.ex b/apps/explorer/lib/explorer/chain/address.ex index a619d70546..28b67f1d66 100644 --- a/apps/explorer/lib/explorer/chain/address.ex +++ b/apps/explorer/lib/explorer/chain/address.ex @@ -16,6 +16,7 @@ defmodule Explorer.Chain.Address do DecompiledSmartContract, Hash, InternalTransaction, + NetVersionCache, SmartContract, Token, Transaction, @@ -130,6 +131,21 @@ defmodule Explorer.Chain.Address do end 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 = hash |> to_string() @@ -137,26 +153,46 @@ defmodule Explorer.Chain.Address do match_byte_stream = stream_every_four_bytes_of_sha256(string_hash) - checksum_formatted = - string_hash - |> stream_binary() - |> Stream.zip(match_byte_stream) - |> Enum.map(fn - {digit, _} when digit in '0123456789' -> - digit + string_hash + |> stream_binary() + |> Stream.zip(match_byte_stream) + |> Enum.map(fn + {digit, _} when digit in '0123456789' -> + digit - {alpha, 1} -> - alpha - 32 + {alpha, 1} -> + alpha - 32 - {alpha, _} -> - alpha - end) + {alpha, _} -> + alpha + end) + end - if iodata? do - ["0x" | checksum_formatted] - else - to_string(["0x" | checksum_formatted]) - end + def rsk_checksum(hash) do + chain_id = NetVersionCache.version() + + string_hash = + 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 defp stream_every_four_bytes_of_sha256(value) do diff --git a/apps/explorer/lib/explorer/chain/net_version_cache.ex b/apps/explorer/lib/explorer/chain/net_version_cache.ex new file mode 100644 index 0000000000..c3df467e0c --- /dev/null +++ b/apps/explorer/lib/explorer/chain/net_version_cache.ex @@ -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 diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index de99198181..19f04dee2d 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -5,7 +5,7 @@ defmodule Explorer.Chain.Transaction do 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 @@ -549,15 +549,40 @@ defmodule Explorer.Chain.Transaction do end @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 problems. """ - def where_transaction_hashes_match(transaction_hashes) do - Transaction - |> where([t], t.hash == fragment("ANY (?)", ^transaction_hashes)) - |> order_by([transaction], desc: transaction.block_number, desc: transaction.index) + def where_transaction_matches(query, transaction_hashes, :from, address_hash) do + where( + query, + [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 @collated_fields ~w(block_number cumulative_gas_used gas_used index)a diff --git a/apps/explorer/lib/explorer/chain/wei.ex b/apps/explorer/lib/explorer/chain/wei.ex index 4b12d3deb0..c1c434aa44 100644 --- a/apps/explorer/lib/explorer/chain/wei.ex +++ b/apps/explorer/lib/explorer/chain/wei.ex @@ -113,6 +113,17 @@ defmodule Explorer.Chain.Wei do @wei_per_ether Decimal.new(1_000_000_000_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 """ Sums two Wei values. diff --git a/apps/explorer/lib/explorer/counters/average_block_time.ex b/apps/explorer/lib/explorer/counters/average_block_time.ex index 27a524c22a..6db9caf8f6 100644 --- a/apps/explorer/lib/explorer/counters/average_block_time.ex +++ b/apps/explorer/lib/explorer/counters/average_block_time.ex @@ -66,7 +66,7 @@ defmodule Explorer.Counters.AverageBlockTime do from(block in Block, limit: 100, offset: 0, - order_by: [desc: block.number], + order_by: [desc: block.number, desc: block.timestamp], select: {block.number, block.timestamp} ) diff --git a/apps/explorer/lib/explorer/exchange_rates/source.ex b/apps/explorer/lib/explorer/exchange_rates/source.ex index 584f7bf147..41b87c1e29 100644 --- a/apps/explorer/lib/explorer/exchange_rates/source.ex +++ b/apps/explorer/lib/explorer/exchange_rates/source.ex @@ -28,7 +28,7 @@ defmodule Explorer.ExchangeRates.Source do true -> fetch_exchange_rates_from_paginable_source(source, page + 1) 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, %Error{reason: reason}} -> @@ -65,6 +65,8 @@ defmodule Explorer.ExchangeRates.Source do def decode_json(data) do Jason.decode!(data) + rescue + _ -> data end def to_decimal(nil), do: nil diff --git a/apps/explorer/priv/repo/migrations/20190613065856_add_tx_hash_inserted_at_index.exs b/apps/explorer/priv/repo/migrations/20190613065856_add_tx_hash_inserted_at_index.exs new file mode 100644 index 0000000000..4596b1e4d4 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20190613065856_add_tx_hash_inserted_at_index.exs @@ -0,0 +1,7 @@ +defmodule Explorer.Repo.Migrations.AddTxHashInsertedAtIndex do + use Ecto.Migration + + def change do + create(index(:transactions, [:hash, :inserted_at])) + end +end diff --git a/apps/explorer/test/explorer/chain/address_test.exs b/apps/explorer/test/explorer/chain/address_test.exs index 22710e88bb..213913c766 100644 --- a/apps/explorer/test/explorer/chain/address_test.exs +++ b/apps/explorer/test/explorer/chain/address_test.exs @@ -1,6 +1,8 @@ defmodule Explorer.Chain.AddressTest do use Explorer.DataCase + import Mox + alias Explorer.Chain.Address alias Explorer.Repo @@ -28,6 +30,12 @@ defmodule Explorer.Chain.AddressTest do end describe "Phoenix.HTML.Safe.to_iodata/1" do + setup do + Application.put_env(:explorer, :checksum_function, :eth) + + :ok + end + defp str(value) do to_string(insert(:address, hash: value)) end @@ -39,5 +47,18 @@ defmodule Explorer.Chain.AddressTest do assert str("0xdbf03b407c01e7cd3cbea99509d93f8dddc8c6fb") == "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB" assert str("0xd1220a0cf47c7b9be7a2e6ba89f429762e7b9adb") == "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb" 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 diff --git a/apps/explorer/test/explorer/chain/blocks_cache_test.exs b/apps/explorer/test/explorer/chain/blocks_cache_test.exs index f030502dc3..687d2ff30a 100644 --- a/apps/explorer/test/explorer/chain/blocks_cache_test.exs +++ b/apps/explorer/test/explorer/chain/blocks_cache_test.exs @@ -5,8 +5,8 @@ defmodule Explorer.Chain.BlocksCacheTest do alias Explorer.Repo setup do - Supervisor.terminate_child(Explorer.Supervisor, ConCache) - Supervisor.restart_child(Explorer.Supervisor, ConCache) + Supervisor.terminate_child(Explorer.Supervisor, {ConCache, :blocks}) + Supervisor.restart_child(Explorer.Supervisor, {ConCache, :blocks}) :ok end diff --git a/apps/explorer/test/explorer/counters/average_block_time_test.exs b/apps/explorer/test/explorer/counters/average_block_time_test.exs index 63c47042c7..0f7c328bb3 100644 --- a/apps/explorer/test/explorer/counters/average_block_time_test.exs +++ b/apps/explorer/test/explorer/counters/average_block_time_test.exs @@ -3,7 +3,9 @@ defmodule Explorer.Counters.AverageBlockTimeTest do doctest Explorer.Counters.AverageBlockTimeDurationFormat + alias Explorer.Chain.Block alias Explorer.Counters.AverageBlockTime + alias Explorer.Repo setup do start_supervised!(AverageBlockTime) @@ -24,5 +26,37 @@ defmodule Explorer.Counters.AverageBlockTimeTest do test "without blocks duration is 0" do assert AverageBlockTime.average_block_time() == Timex.Duration.parse!("PT0S") 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 diff --git a/apps/explorer/test/support/data_case.ex b/apps/explorer/test/support/data_case.ex index ebaf91cf3d..1386a55d36 100644 --- a/apps/explorer/test/support/data_case.ex +++ b/apps/explorer/test/support/data_case.ex @@ -40,8 +40,8 @@ defmodule Explorer.DataCase do end Explorer.Chain.BlockNumberCache.setup() - Supervisor.terminate_child(Explorer.Supervisor, ConCache) - Supervisor.restart_child(Explorer.Supervisor, ConCache) + Supervisor.terminate_child(Explorer.Supervisor, {ConCache, :blocks}) + Supervisor.restart_child(Explorer.Supervisor, {ConCache, :blocks}) :ok end diff --git a/apps/indexer/lib/indexer/block/realtime/fetcher.ex b/apps/indexer/lib/indexer/block/realtime/fetcher.ex index 8778677f33..8edd87738d 100644 --- a/apps/indexer/lib/indexer/block/realtime/fetcher.ex +++ b/apps/indexer/lib/indexer/block/realtime/fetcher.ex @@ -140,7 +140,7 @@ defmodule Indexer.Block.Realtime.Fetcher do %__MODULE__{state | subscription: subscription} {: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 end end @@ -201,6 +201,12 @@ defmodule Indexer.Block.Realtime.Fetcher do 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 start_at = determine_start_at(number, previous_number, max_number_seen) diff --git a/apps/indexer/lib/indexer/fetcher/uncle_block.ex b/apps/indexer/lib/indexer/fetcher/uncle_block.ex index ba36ffb2f9..213604a73e 100644 --- a/apps/indexer/lib/indexer/fetcher/uncle_block.ex +++ b/apps/indexer/lib/indexer/fetcher/uncle_block.ex @@ -104,18 +104,18 @@ defmodule Indexer.Fetcher.UncleBlock do {nephew_hash_bytes, index} end - defp run_blocks(%Blocks{blocks_params: []}, _, original_entries), do: {:retry, original_entries} - - defp run_blocks( - %Blocks{ - blocks_params: blocks_params, - transactions_params: transactions_params, - block_second_degree_relations_params: block_second_degree_relations_params, - errors: errors - }, - block_fetcher, - original_entries - ) do + def run_blocks(%Blocks{blocks_params: []}, _, original_entries), do: {:retry, original_entries} + + def run_blocks( + %Blocks{ + blocks_params: blocks_params, + transactions_params: transactions_params, + block_second_degree_relations_params: block_second_degree_relations_params, + errors: errors + }, + block_fetcher, + original_entries + ) do addresses_params = Addresses.extract_addresses(%{blocks: blocks_params, transactions: transactions_params}) case Block.Fetcher.import(block_fetcher, %{ @@ -235,7 +235,17 @@ defmodule Indexer.Fetcher.UncleBlock do Enum.map(errors, &error_to_entry/1) 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 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 [hash, ": (", to_string(code), ") ", message, ?\n] 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 diff --git a/apps/indexer/lib/indexer/temporary/blocks_transactions_mismatch.ex b/apps/indexer/lib/indexer/temporary/blocks_transactions_mismatch.ex index 49b33b7c44..486789f675 100644 --- a/apps/indexer/lib/indexer/temporary/blocks_transactions_mismatch.ex +++ b/apps/indexer/lib/indexer/temporary/blocks_transactions_mismatch.ex @@ -13,7 +13,6 @@ defmodule Indexer.Temporary.BlocksTransactionsMismatch do import Ecto.Query - alias Ecto.Multi alias EthereumJSONRPC.Blocks alias Explorer.Chain.Block alias Explorer.Repo @@ -23,13 +22,14 @@ defmodule Indexer.Temporary.BlocksTransactionsMismatch do @defaults [ flush_interval: :timer.seconds(3), - max_batch_size: 10, + max_batch_size: 50, max_concurrency: 1, task_supervisor: Indexer.Temporary.BlocksTransactionsMismatch.TaskSupervisor, metadata: [fetcher: :blocks_transactions_mismatch] ] @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 {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)) end) - {:ok, _} = - found_blocks_data - |> Enum.reduce(Multi.new(), fn {block, trans_num}, multi -> - changes = %{ - refetch_needed: false, - consensus: found_blocks_map[to_string(block.hash)] == trans_num - } - - Multi.update(multi, block.hash, Block.changeset(block, changes)) + {matching_blocks_data, unmatching_blocks_data} = + Enum.split_with(found_blocks_data, fn {block, trans_num} -> + found_blocks_map[to_string(block.hash)] == trans_num 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 :ok diff --git a/apps/indexer/test/indexer/fetcher/uncle_block_test.exs b/apps/indexer/test/indexer/fetcher/uncle_block_test.exs index 224349d781..f3350b8de7 100644 --- a/apps/indexer/test/indexer/fetcher/uncle_block_test.exs +++ b/apps/indexer/test/indexer/fetcher/uncle_block_test.exs @@ -6,7 +6,9 @@ defmodule Indexer.Fetcher.UncleBlockTest do import EthereumJSONRPC, only: [integer_to_quantity: 1] + alias EthereumJSONRPC.Blocks alias Explorer.Chain + alias Explorer.Chain.Hash alias Indexer.Block alias Indexer.Fetcher.UncleBlock @@ -138,6 +140,74 @@ defmodule Indexer.Fetcher.UncleBlockTest do 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 producer.() rescue diff --git a/bin/install_chrome_headless.sh b/bin/install_chrome_headless.sh index 9721e84ea3..d27bb6f9d8 100755 --- a/bin/install_chrome_headless.sh +++ b/bin/install_chrome_headless.sh @@ -1,6 +1,6 @@ export DISPLAY=:99.0 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" unzip chromedriver_linux64.zip sudo chmod +x chromedriver diff --git a/docker/Dockerfile b/docker/Dockerfile index 7694358e72..73501a9b9d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -33,7 +33,7 @@ RUN cd apps/block_scout_web/assets/ && \ RUN cd apps/explorer/ && \ 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