diff --git a/.circleci/config.yml b/.circleci/config.yml
index 53e5680e78..f410162b48 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -3,7 +3,7 @@ jobs:
build:
docker:
# Ensure .tool-versions matches
- - image: circleci/elixir:1.8.1-node-browsers
+ - image: circleci/elixir:1.9.0-node-browsers
environment:
MIX_ENV: test
# match POSTGRES_PASSWORD for postgres image below
@@ -129,7 +129,7 @@ jobs:
check_formatted:
docker:
# Ensure .tool-versions matches
- - image: circleci/elixir:1.8.1
+ - image: circleci/elixir:1.9.0
environment:
MIX_ENV: test
@@ -143,7 +143,7 @@ jobs:
credo:
docker:
# Ensure .tool-versions matches
- - image: circleci/elixir:1.8.1
+ - image: circleci/elixir:1.9.0
environment:
MIX_ENV: test
@@ -177,7 +177,7 @@ jobs:
dialyzer:
docker:
# Ensure .tool-versions matches
- - image: circleci/elixir:1.8.1
+ - image: circleci/elixir:1.9.0
environment:
MIX_ENV: test
@@ -191,9 +191,9 @@ jobs:
- restore_cache:
keys:
- - v7-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }}
- - v7-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }}
- - v7-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}
+ - v8-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }}
+ - v8-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }}
+ - v8-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}
- run:
name: Unpack PLT cache
@@ -213,15 +213,15 @@ jobs:
cp ~/.mix/dialyxir*.plt plts/
- save_cache:
- key: v7-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }}
+ key: v8-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.lock" }}
paths:
- plts
- save_cache:
- key: v7-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }}
+ key: v8-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}-{{ checksum "mix.exs" }}
paths:
- plts
- save_cache:
- key: v7-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}
+ key: v8-mix-dialyzer-{{ checksum "OTP_VERSION.lock" }}-{{ checksum "ELIXIR_VERSION.lock" }}
paths:
- plts
@@ -247,7 +247,7 @@ jobs:
gettext:
docker:
# Ensure .tool-versions matches
- - image: circleci/elixir:1.8.1
+ - image: circleci/elixir:1.9.0
environment:
MIX_ENV: test
@@ -286,7 +286,7 @@ jobs:
release:
docker:
# Ensure .tool-versions matches
- - image: circleci/elixir:1.8.1
+ - image: circleci/elixir:1.9.0
environment:
MIX_ENV: prod
@@ -298,7 +298,7 @@ jobs:
- run: mix local.hex --force
- run: mix local.rebar --force
- - run: mix release --verbose --env prod
+ - run: MIX_ENV=prod mix release
- run:
name: Collecting artifacts
command: |
@@ -312,7 +312,7 @@ jobs:
sobelow:
docker:
# Ensure .tool-versions matches
- - image: circleci/elixir:1.8.1
+ - image: circleci/elixir:1.9.0
environment:
MIX_ENV: test
@@ -336,7 +336,7 @@ jobs:
test_geth_http_websocket:
docker:
# Ensure .tool-versions matches
- - image: circleci/elixir:1.8.1-node-browsers
+ - image: circleci/elixir:1.9.0-node-browsers
environment:
MIX_ENV: test
# match POSTGRES_PASSWORD for postgres image below
@@ -390,7 +390,7 @@ jobs:
test_geth_mox:
docker:
# Ensure .tool-versions matches
- - image: circleci/elixir:1.8.1-node-browsers
+ - image: circleci/elixir:1.9.0-node-browsers
environment:
MIX_ENV: test
# match POSTGRES_PASSWORD for postgres image below
@@ -444,7 +444,7 @@ jobs:
test_parity_http_websocket:
docker:
# Ensure .tool-versions matches
- - image: circleci/elixir:1.8.1-node-browsers
+ - image: circleci/elixir:1.9.0-node-browsers
environment:
MIX_ENV: test
# match POSTGRES_PASSWORD for postgres image below
@@ -498,7 +498,7 @@ jobs:
test_parity_mox:
docker:
# Ensure .tool-versions matches
- - image: circleci/elixir:1.8.1-node-browsers
+ - image: circleci/elixir:1.9.0-node-browsers
environment:
MIX_ENV: test
# match POSTGRES_PASSWORD for postgres image below
@@ -552,7 +552,7 @@ jobs:
coveralls_merge:
docker:
# Ensure .tool-versions matches
- - image: circleci/elixir:1.8.1
+ - image: circleci/elixir:1.9.0
environment:
MIX_ENV: test
diff --git a/.dialyzer-ignore b/.dialyzer-ignore
index 53e3554c6e..de105bbd51 100644
--- a/.dialyzer-ignore
+++ b/.dialyzer-ignore
@@ -2,6 +2,12 @@
:0: Unknown function 'Elixir.ExUnit.CaseTemplate':'__proxy__'/2
:0: Unknown type 'Elixir.Map':t/0
apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex:400: Function timestamp_to_datetime/1 has no local return
+lib/block_scout_web/views/layout_view.ex:174: The call 'Elixir.Poison.Parser':'parse!'(any(),#{'keys':='atoms!'}) will never return since the success typing is (binary() | maybe_improper_list(binary() | maybe_improper_list(any(),binary() | []) | byte(),binary() | []),[{atom(),_}]) -> 'false' | 'nil' | 'true' | binary() | ['false' | 'nil' | 'true' | binary() | [any()] | number() | map()] | number() | map() and the contract is (iodata(),'Elixir.Keyword':t()) -> t()
+lib/explorer/repo/prometheus_logger.ex:8
+lib/block_scout_web/views/layout_view.ex:175
+lib/explorer/smart_contract/publisher_worker.ex:6
apps/explorer/lib/explorer/repo/prometheus_logger.ex:8: Function microseconds_time/1 has no local return
apps/explorer/lib/explorer/repo/prometheus_logger.ex:8: The call 'Elixir.System':convert_time_unit(__@1::any(),'native','microseconds') breaks the contract (integer(),time_unit() | 'native',time_unit() | 'native') -> integer()
-apps/block_scout_web/lib/block_scout_web/views/layout_view.ex:174: The call 'Elixir.Poison.Parser':'parse!'(any(),#{'keys':='atoms!'}) will never return since the success typing is (binary() | maybe_improper_list(binary() | maybe_improper_list(any(),binary() | []) | byte(),binary() | []),[{atom(),_}]) -> 'false' | 'nil' | 'true' | binary() | ['false' | 'nil' | 'true' | binary() | [any()] | number() | map()] | number() | map() and the contract is (iodata(),'Elixir.Keyword':t()) -> t()
\ No newline at end of file
+apps/block_scout_web/lib/block_scout_web/views/layout_view.ex:175: The call 'Elixir.Poison.Parser':'parse!'(any(),#{'keys':='atoms!'}) will never return since the success typing is (binary() | maybe_improper_list(binary() | maybe_improper_list(any(),binary() | []) | byte(),binary() | []),[{atom(),_}]) -> 'false' | 'nil' | 'true' | binary() | ['false' | 'nil' | 'true' | binary() | [any()] | number() | map()] | number() | map() and the contract is (iodata(),'Elixir.Keyword':t()) -> t()
+apps/explorer/lib/explorer/smart_contract/publisher_worker.ex:6: The pattern 'false' can never match the type 'true'
+apps/explorer/lib/explorer/smart_contract/publisher_worker.ex:6: The test 5 == 'infinity' can never evaluate to 'true'
diff --git a/.tool-versions b/.tool-versions
index dc46bc2cfb..71379b2fa7 100644
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,3 +1,3 @@
-elixir 1.8.1
+elixir 1.9
erlang 21.0.4
nodejs 10.11.0
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3d4e6f755f..99588261ea 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,17 +2,66 @@
### Features
+### Fixes
+
+### Chore
+
+
+## 2.0.2-beta
+
+### Features
+- [#2412](https://github.com/poanetwork/blockscout/pull/2412) - dark theme
+- [#2399](https://github.com/poanetwork/blockscout/pull/2399) - decode verified smart contract's logs
+- [#2391](https://github.com/poanetwork/blockscout/pull/2391) - Controllers Improvements
+- [#2379](https://github.com/poanetwork/blockscout/pull/2379) - Disable network selector when is empty
+- [#2374](https://github.com/poanetwork/blockscout/pull/2374) - decode constructor arguments for verified smart contracts
+- [#2366](https://github.com/poanetwork/blockscout/pull/2366) - paginate eth logs
+- [#2360](https://github.com/poanetwork/blockscout/pull/2360) - add default evm version to smart contract verification
+- [#2352](https://github.com/poanetwork/blockscout/pull/2352) - Fetch rewards in parallel with transactions
- [#2294](https://github.com/poanetwork/blockscout/pull/2294) - add healthy block period checking endpoint
+- [#2324](https://github.com/poanetwork/blockscout/pull/2324) - set timeout for loading message on the main page
### Fixes
+- [#2421](https://github.com/poanetwork/blockscout/pull/2421) - Fix hiding of loader for txs on the main page
+- [#2420](https://github.com/poanetwork/blockscout/pull/2420) - fetch data from cache in healthy endpoint
+- [#2416](https://github.com/poanetwork/blockscout/pull/2416) - Fix "page not found" handling in the router
+- [#2413](https://github.com/poanetwork/blockscout/pull/2413) - remove outer tables for decoded data
+- [#2410](https://github.com/poanetwork/blockscout/pull/2410) - preload smart contract for logs decoding
+- [#2405](https://github.com/poanetwork/blockscout/pull/2405) - added templates for table loader and tile loader
+- [#2398](https://github.com/poanetwork/blockscout/pull/2398) - show only one decoded candidate
+- [#2389](https://github.com/poanetwork/blockscout/pull/2389) - Reduce Lodash lib size (86% of lib methods are not used)
+- [#2388](https://github.com/poanetwork/blockscout/pull/2388) - add create2 support to geth's js tracer
+- [#2387](https://github.com/poanetwork/blockscout/pull/2387) - fix not existing keys in transaction json rpc
+- [#2378](https://github.com/poanetwork/blockscout/pull/2378) - Page performance: exclude moment.js localization files except EN, remove unused css
+- [#2368](https://github.com/poanetwork/blockscout/pull/2368) - add two columns of smart contract info
+- [#2375](https://github.com/poanetwork/blockscout/pull/2375) - Update created_contract_code_indexed_at on transaction import conflict
+- [#2346](https://github.com/poanetwork/blockscout/pull/2346) - Avoid fetching internal transactions of blocks that still need refetching
+- [#2350](https://github.com/poanetwork/blockscout/pull/2350) - fix invalid User agent headers
+- [#2345](https://github.com/poanetwork/blockscout/pull/2345) - do not override existing market records
+- [#2337](https://github.com/poanetwork/blockscout/pull/2337) - set url params for prod explicitly
+- [#2341](https://github.com/poanetwork/blockscout/pull/2341) - fix transaction input json encoding
+- [#2311](https://github.com/poanetwork/blockscout/pull/2311) - fix market history overriding with zeroes
+- [#2310](https://github.com/poanetwork/blockscout/pull/2310) - parse url for api docs
- [#2299](https://github.com/poanetwork/blockscout/pull/2299) - fix interpolation in error message
- [#2303](https://github.com/poanetwork/blockscout/pull/2303) - fix transaction csv download link
- [#2304](https://github.com/poanetwork/blockscout/pull/2304) - footer grid fix for md resolution
- [#2291](https://github.com/poanetwork/blockscout/pull/2291) - dashboard fix for md resolution, transactions load fix, block info row fix, addresses page issue, check mark issue
+- [#2326](https://github.com/poanetwork/blockscout/pull/2326) - fix nested constructor arguments
### Chore
+- [#2422](https://github.com/poanetwork/blockscout/pull/2422) - check if address_id is binary in token_transfers_csv endpoint
+- [#2418](https://github.com/poanetwork/blockscout/pull/2418) - Remove parentheses in market cap percentage
+- [#2401](https://github.com/poanetwork/blockscout/pull/2401) - add ENV vars to manage updating period of average block time and market history cache
+- [#2363](https://github.com/poanetwork/blockscout/pull/2363) - add parameters example for eth rpc
+- [#2342](https://github.com/poanetwork/blockscout/pull/2342) - Upgrade Postgres image version in Docker setup
+- [#2325](https://github.com/poanetwork/blockscout/pull/2325) - Reduce function input to address' hash only where possible
+- [#2323](https://github.com/poanetwork/blockscout/pull/2323) - Group Explorer caches
+- [#2305](https://github.com/poanetwork/blockscout/pull/2305) - Improve Address controllers
- [#2302](https://github.com/poanetwork/blockscout/pull/2302) - fix names for xDai source
- [#2289](https://github.com/poanetwork/blockscout/pull/2289) - Optional websockets for dev environment
+- [#2307](https://github.com/poanetwork/blockscout/pull/2307) - add GoJoy to README
+- [#2293](https://github.com/poanetwork/blockscout/pull/2293) - remove request idle timeout configuration
+- [#2255](https://github.com/poanetwork/blockscout/pull/2255) - bump elixir version to 1.9.0
## 2.0.1-beta
@@ -99,6 +148,7 @@
### 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
+- [#2255](https://github.com/poanetwork/blockscout/pull/2255) - upgrade elixir version to 1.9.0
- [#2256](https://github.com/poanetwork/blockscout/pull/2256) - use the latest version of chromedriver
## 2.0.0-beta
diff --git a/README.md b/README.md
index 5dc91fb8ef..3c95a5d125 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,7 @@ Currently available full-featured block explorers (Etherscan, Etherchain, Blockc
| | | [Kotti Testnet](https://kottiexplorer.ethernode.io/) |
| | | [Loom](http://plasma-blockexplorer.dappchains.com/) |
| | | [Tenda](https://tenda.network) |
+| | | [GoJoy Chain](https://gojoychain.com/) |
Current BlockScout versions for hosted projects are available [on the forum](https://forum.poa.network/t/deployed-instances-on-blockscout-com/1938).
diff --git a/apps/block_scout_web/assets/__tests__/pages/pending_transactions.js b/apps/block_scout_web/assets/__tests__/pages/pending_transactions.js
index e9f7fc941d..ddbf84ce7d 100644
--- a/apps/block_scout_web/assets/__tests__/pages/pending_transactions.js
+++ b/apps/block_scout_web/assets/__tests__/pages/pending_transactions.js
@@ -1,4 +1,3 @@
-import _ from 'lodash'
import { reducer, initialState } from '../../js/pages/pending_transactions'
test('CHANNEL_DISCONNECTED', () => {
diff --git a/apps/block_scout_web/assets/css/app.scss b/apps/block_scout_web/assets/css/app.scss
index 394a6a5b1f..3c225b7e6e 100644
--- a/apps/block_scout_web/assets/css/app.scss
+++ b/apps/block_scout_web/assets/css/app.scss
@@ -128,6 +128,7 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
@import "components/new_smart_contract";
@import "components/radio_big";
@import "components/btn_no_border";
+@import "theme/dark-theme";
:export {
dashboardBannerChartAxisFontColor: $dashboard-banner-chart-axis-font-color;
@@ -136,4 +137,6 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts";
dashboardLineColorTransactions: $dashboard-line-color-transactions;
primary: $primary;
secondary: $secondary;
+ darkprimary: $dark-primary;
+ darksecondary: $dark-secondary;
}
diff --git a/apps/block_scout_web/assets/css/components/_footer.scss b/apps/block_scout_web/assets/css/components/_footer.scss
index be1c10d44a..f2eae03c3f 100644
--- a/apps/block_scout_web/assets/css/components/_footer.scss
+++ b/apps/block_scout_web/assets/css/components/_footer.scss
@@ -61,10 +61,6 @@ $footer-logo-width: auto !default;
}
}
-.footer-info {
- padding-top: 1em;
-}
-
.footer-link {
color: $footer-link-color;
diff --git a/apps/block_scout_web/assets/css/components/_navbar.scss b/apps/block_scout_web/assets/css/components/_navbar.scss
index 48c6c50f02..0d892b25ca 100644
--- a/apps/block_scout_web/assets/css/components/_navbar.scss
+++ b/apps/block_scout_web/assets/css/components/_navbar.scss
@@ -252,4 +252,10 @@ $navbar-logo-width: auto !default;
@include media-breakpoint-up(xl) {
padding-right: 0;
}
+}
+
+.nav-item-networks {
+ .topnav-nav-link {
+ transition: none !important;
+ }
}
\ No newline at end of file
diff --git a/apps/block_scout_web/assets/css/components/_network-selector.scss b/apps/block_scout_web/assets/css/components/_network-selector.scss
index 8c433f28c3..e8b5f72e78 100644
--- a/apps/block_scout_web/assets/css/components/_network-selector.scss
+++ b/apps/block_scout_web/assets/css/components/_network-selector.scss
@@ -287,16 +287,6 @@ $network-selector-item-icon-dimensions: 30px !default;
}
}
-.network-selector-load-more-container {
- flex-shrink: 1;
- padding: 0 $network-selector-horizontal-padding;
-
- .btn-network-selector-load-more {
- @include btn-line($btn-network-selector-load-more-background, $btn-network-selector-load-more-color);
- width: 100%;
- }
-}
-
.network-selector-item-favorite {
align-items: center;
cursor: pointer;
diff --git a/apps/block_scout_web/assets/css/components/_stakes_table.scss b/apps/block_scout_web/assets/css/components/_stakes_table.scss
index 79cdc20b38..1831dc4d5b 100644
--- a/apps/block_scout_web/assets/css/components/_stakes_table.scss
+++ b/apps/block_scout_web/assets/css/components/_stakes_table.scss
@@ -26,6 +26,43 @@ $stakes-table-cell-separation: 25px !default;
}
}
+// Loader
+.table-content-loader {
+ display: inline-block;
+ height: 24px;
+ width: 100%;
+ border-radius: 4px;
+ background-color: #f5f6fa;
+ overflow: hidden;
+ position: relative;
+ &:before {
+ width: inherit;
+ height: inherit;
+ content: '';
+ position: absolute;
+ background: linear-gradient(to right, #f5f6fa 2%, #eee 18%, #f5f6fa 33%);
+ animation-duration: 1.7s;
+ animation-fill-mode: forwards;
+ animation-iteration-count: infinite;
+ animation-timing-function: linear;
+ animation-name: placeholderAnimate;
+ background-size: 1300px;
+ }
+}
+
+@keyframes placeholderAnimate {
+ 0%{ background-position: -650px 0; }
+ 100% { background-position: 650px 0; }
+}
+
+.table-content-pseudo {
+ td {
+ &:last-child {
+ padding-right: 24px !important;
+ }
+ }
+}
+
.stakes-table {
min-width: fit-content;
width: 100%;
diff --git a/apps/block_scout_web/assets/css/components/_tile.scss b/apps/block_scout_web/assets/css/components/_tile.scss
index 8965715727..e621e4fd6d 100644
--- a/apps/block_scout_web/assets/css/components/_tile.scss
+++ b/apps/block_scout_web/assets/css/components/_tile.scss
@@ -339,9 +339,9 @@ $tile-body-a-color: #5959d8 !default;
padding-left: 6px;
padding-right: 6px;
}
- .tile-type-block {
- overflow: hidden;
- }
+ }
+ .tile-type-block {
+ overflow: hidden;
}
.row {
@include media-breakpoint-down(lg) {
@@ -351,3 +351,214 @@ $tile-body-a-color: #5959d8 !default;
}
}
}
+
+// Loader
+.tile-type-loading {
+ background-color: #fff;
+ padding-top: 30px;
+ padding-bottom: 28px;
+}
+
+.tile-loader {
+ display: inline-block;
+ height: 20px;
+ width: 100%;
+ border-radius: 4px;
+ background-color: #f5f6fa;
+ overflow: hidden;
+ position: relative;
+ &:before {
+ width: inherit;
+ height: inherit;
+ content: '';
+ position: absolute;
+ background: linear-gradient(to right, #f5f6fa 2%, #eee 18%, #f5f6fa 33%);
+ animation-duration: 1.7s;
+ animation-fill-mode: forwards;
+ animation-iteration-count: infinite;
+ animation-timing-function: linear;
+ animation-name: tilePlaceholderAnimate;
+ background-size: 1300px;
+ }
+}
+
+.tile-label-loader {
+ height: 14px;
+ width: 80px;
+}
+
+.tile-address-loader {
+ & + .tile-address-loader {
+ margin-top: 6px;
+ }
+}
+
+@keyframes tilePlaceholderAnimate {
+ 0%{ background-position: -650px 0; }
+ 100% { background-position: 650px 0; }
+}
+
+// Loading Animation
+
+@keyframes playBlockLoadingAnimation {
+ 0%, 90% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0;
+ }
+}
+
+[data-selector="chain-block-list"] {
+ .col-lg-3:first-child {
+ .tile-type-block-animation {
+ animation: playBlockLoadingAnimation 2.1s linear forwards;
+ }
+ }
+}
+
+.fade-up-blocks-chain {
+ .tile-type-block {
+ position: relative;
+ }
+ .tile-type-block-animation {
+ opacity: 0;
+ position: absolute;
+ top: -1px;
+ left: -4px;
+ width: calc(100% + 5px);
+ height: calc(100% + 2px);
+ background-color: #F6F7F9;
+ border-radius: 4px;
+ overflow: hidden;
+ transition: .24s ease-out;
+ border-top: 1px solid #dee2e6;
+ border-right: 1px solid #dee2e6;
+ border-bottom: 1px solid #dee2e6;
+ pointer-events: none;
+ .tile-type-line-up {
+ position: absolute;
+ bottom: -1px;
+ left: 0;
+ height: calc(100% + 2px);
+ width: 4px;
+ background-color: $tile-type-block-color;
+ transform: scaleY(0);
+ transform-origin: center bottom;
+ animation: blockLoaderLine 2s linear forwards;
+ z-index: 2;
+ }
+ &:after {
+ content: "";
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ width: 1px;
+ background-color: #dee2e6;
+ }
+ }
+}
+
+.cube-animation-title {
+ font-size: 12px;
+ color: #a3a9b5;
+ position: absolute;
+ bottom: 10px;
+ left: 50%;
+ transform: translateX(-50%);
+}
+
+.fade-up-blocks-chain:first-child {
+ .tile-type-block-animation {
+ opacity: 1;
+ }
+}
+
+@keyframes blockLoaderLine {
+ 0% {
+ transform: scaleY(0);
+ }
+ 100% {
+ transform: scaleY(1);
+ }
+}
+
+$cube-bezier: cubic-bezier(.25,.8,.25,1);
+$cube-quantity: 5;
+
+.cube-animation-wrapper {
+ width: 560px;
+ height: 290px;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%) scale(0.26);
+ svg {
+ width: 50px;
+ margin-top: -29px;
+
+ .side {
+ fill: $tile-type-block-color;
+ opacity: 1;
+
+ &:nth-of-type(2) {
+ fill: lighten($tile-type-block-color, 30);
+ opacity: 0.5;
+ }
+
+ &:nth-of-type(3) {
+ fill: lighten($tile-type-block-color, 80);
+ opacity: 0.5;
+ }
+ }
+ }
+
+ @while $cube-quantity > 0 {
+ .cube-animation-row:nth-of-type(#{$cube-quantity}) {
+ left: 25px * $cube-quantity;
+ top: 15px * $cube-quantity;
+ }
+ .cube-animation-column:nth-of-type(#{$cube-quantity}) {
+ position: relative;
+ top: 14px * $cube-quantity;
+ left: 25px * $cube-quantity;
+ }
+ .cube-animation-column:nth-of-type(#{$cube-quantity}) svg {
+ transform: translate3d(0,0,0);
+ animation: shrink-expand 2.8s $cube-bezier forwards;
+ animation-delay: -0.16s * $cube-quantity;
+ }
+
+ $cube-quantity: $cube-quantity - 1;
+ }
+}
+
+.cube-animation-center {
+ position: absolute;
+ top: 6%;
+ left: 20%;
+}
+
+.cube-animation-row {
+ display: flex;
+ flex-direction: row-reverse;
+ position: absolute;
+}
+
+.cube-animation-column {
+ display: flex;
+ flex-direction: column-reverse;
+}
+
+@keyframes shrink-expand {
+ 0% {
+ transform: scale(0);
+ }
+ 50% {
+ transform: scale(1);
+ }
+ 100% {
+ transform: scale(0);
+ }
+}
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 9f1b82c887..2238327bd4 100644
--- a/apps/block_scout_web/assets/css/theme/_base_variables.scss
+++ b/apps/block_scout_web/assets/css/theme/_base_variables.scss
@@ -70,7 +70,10 @@ $colors: map-merge(
);
$primary: $indigo !default;
+$dark-primary: #9b62ff !default;
+$dark-primary-alternate: #9b62ff !default;
$secondary: #7dd79f !default;
+$dark-secondary: #87e1a9 !default;
$tertiary: $purple !default;
$success: $green !default;
$info: $cyan !default;
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 50cbcfa290..0c09660240 100644
--- a/apps/block_scout_web/assets/css/theme/_dai_variables.scss
+++ b/apps/block_scout_web/assets/css/theme/_dai_variables.scss
@@ -62,4 +62,9 @@ $card-tab-active: $secondary;
// Badges
$badge-neutral-color: #20446e;
$badge-neutral-background-color: rgba(#20446e, .1);
-$api-text-monospace-color: #20446e;
\ No newline at end of file
+$api-text-monospace-color: #20446e;
+
+// Dark theme
+$dark-primary: #15bba6;
+$dark-secondary: #93d7ff;
+$dark-primary-alternate: #15bba6;
\ No newline at end of file
diff --git a/apps/block_scout_web/assets/css/theme/_dark-theme.scss b/apps/block_scout_web/assets/css/theme/_dark-theme.scss
new file mode 100644
index 0000000000..e648e01982
--- /dev/null
+++ b/apps/block_scout_web/assets/css/theme/_dark-theme.scss
@@ -0,0 +1,470 @@
+$body-dark: #1c1d31; // body background
+$dark-bg: #22223a; // hero shade
+$dark-light-bg: #282945; // pills bg shade
+$dark-light: #313355; // tile light top share
+$labels-dark: #8a8dba; // header nav, labels
+
+// Switcher
+.dark-mode-changer {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 36px;
+ height: 36px;
+ background: transparent;
+ border: none;
+ cursor: pointer;
+ margin-right: 5px;
+ outline: none !important;
+ box-shadow: none !important;
+ transition: .2s ease-in;
+ &:hover {
+ opacity: .8;
+ }
+ svg path {
+ fill: #828ba0;
+ }
+ &--dark {
+ svg path {
+ fill: $dark-primary;
+ }
+ }
+}
+
+.dark-theme-applied {
+ color: #fff;
+
+ // navbar
+ .navbar.navbar-primary {
+ background-color: $dark-light-bg;
+ }
+ .navbar-brand .navbar-logo {
+ filter: brightness(0) invert(1);
+ }
+ .navbar.navbar-primary .navbar-nav .nav-link {
+ color: $labels-dark;
+ .nav-link-icon {
+ svg path {
+ fill: $labels-dark;
+ }
+ }
+ &.active, &:hover {
+ .nav-link-icon {
+ svg path {
+ fill: $dark-primary;
+ }
+ }
+ &:before {
+ background-color: $dark-primary;
+ }
+ }
+ }
+ .navbar.navbar-primary .form-control {
+ background-color: $dark-bg;
+ border-color: $dark-bg;
+ color: #fff;
+ &::placeholder {
+ color: $labels-dark;
+ }
+ }
+ .navbar.navbar-primary .navbar-toggler .navbar-toggler-icon {
+ filter: invert(1);
+ }
+
+ // footer
+ .footer {
+ background: $dark-light-bg;
+ color: $labels-dark;
+ }
+ .footer-social-icon,
+ .footer-link {
+ color: $labels-dark;
+ }
+ .footer-social-icon:hover,
+ .footer-link:hover {
+ color: #fff;
+ }
+ .footer-list ul li::before {
+ background-color: $dark-secondary;
+ }
+
+ // hero stats
+
+ .layout-container .dashboard-banner-container {
+ background-image: none;
+ background-color: $dark-bg;
+ }
+ .dashboard-banner-network-plain-container,
+ .dashboard-banner-network-plain-container::after {
+ background-color: $dark-light-bg;
+ }
+ .dashboard-banner-network-stats-label,
+ .dashboard-banner-chart-legend .dashboard-banner-chart-legend-label {
+ color: $labels-dark;
+ }
+ .dashboard-banner-chart-legend .dashboard-banner-chart-legend-item:nth-child(1)::before {
+ background-color: $dark-primary;
+ }
+ .dashboard-banner-network-stats-item::before {
+ background-color: $dark-secondary;
+ }
+ .dashboard-banner-chart-legend .dashboard-banner-chart-legend-item:nth-child(2)::before {
+ background-color: $dark-secondary;
+ }
+
+ // main container, layout, cards
+ .layout-container main {
+ background-color: $body-dark;
+ }
+
+ .card {
+ background-color: $dark-light-bg;
+ box-shadow: 0 0 30px 0 rgba(23, 24, 41, 0.5);
+ }
+
+ .card-header {
+ border-bottom-color: darken($labels-dark, 30);
+ }
+
+ .address-detail-hash-title {
+ color: #fff;
+ }
+
+ .card-tabs {
+ border-bottom-color: darken($labels-dark, 30);
+ }
+
+ .card-tab {
+ background-color: transparent;
+ &:hover:not(.active) {
+ background-color: rgba($dark-primary, .15);
+ color: $dark-primary;
+ }
+ &.active {
+ background-color: $dark-primary-alternate;
+ color: #fff;
+ }
+ }
+
+ .card-background-1 {
+ background-color: $dark-primary-alternate;
+ }
+
+ // Components
+ a {
+ color: $dark-primary;
+ }
+
+ .tile {
+ border-top-color: $dark-light;
+ border-bottom-color: $dark-light;
+ border-right-color: $dark-light;
+ background-color: $dark-light;
+ color: $labels-dark;
+ &:not([class^="tile tile-type"]) {
+ border-left-color: $dark-light;
+ }
+ &.tile-type-coin-balance {
+ border-left-color: $dark-light;
+ }
+ .tile-title {
+ color: #fff;
+ }
+ .tile-transaction-type-block {
+ background-color: transparent;
+ }
+ }
+ .tile-bottom-contents {
+ background-color: $dark-bg;
+ }
+ a.tile-title {
+ color: #fff !important;
+ }
+ .tile.tile-type-block .tile-transaction-type-block a {
+ color: #fff;
+ }
+ .fade-up-blocks-chain .tile-type-block-animation {
+ background-color: $dark-light;
+ border-top-color: $dark-light;
+ border-right-color: $dark-light;
+ border-bottom-color: $dark-light;
+ }
+ .fade-up-blocks-chain .tile-type-block-animation:after {
+ background-color: $dark-light;
+ }
+ .cube-animation-title {
+ color: $labels-dark;
+ }
+ .tile .tile-body a,
+ .tile span[data-address-hash] { color: $dark-primary; }
+ .fade-up-blocks-chain .tile-type-block-animation .tile-type-line-up {
+ background-color: $dark-primary;
+ }
+ .tile.tile-type-block {
+ border-left-color: $dark-primary
+ }
+ .tile.tile-type-block .tile-status-label {
+ color: $dark-primary;
+ }
+ .tile.tile-type-block .tile-transaction-type-block {
+ border-right-color: $dark-primary;
+ border-top-color: $dark-primary;
+ border-bottom-color: $dark-primary;
+ }
+ .tile .progress {
+ background-color: rgba(#fff, .2);
+ }
+ .tile .progress .progress-bar {
+ background-color: $dark-primary;
+ }
+ .tile .tile-title-lg:not([data-balance-change-sign]) {
+ color: $dark-primary;
+ }
+
+ // btns
+
+ .btn-line {
+ background-color: transparent;
+ border-color: $dark-primary;
+ color: $dark-primary;
+ &:hover {
+ border-color: $dark-primary;
+ background-color: $dark-primary;
+ color: #fff;
+ }
+ }
+
+ .btn-copy-icon, .btn-qr-icon {
+ border-color: $dark-primary;
+ path {
+ fill: $dark-primary;
+ }
+ &:hover {
+ background-color: $dark-primary;
+ path {
+ fill: #fff;
+ }
+ }
+ }
+
+ // pagination
+ .pagination-container .pagination .page-link {
+ color: $labels-dark;
+ border-color: $dark-light;
+ background-color: $dark-light;
+ &:not(.no-hover):hover {
+ color: #fff;
+ background-color: $dark-primary;
+ path {
+ fill: #fff;
+ }
+ }
+ &[disabled] {
+ color: $labels-dark;
+ border-color: $dark-light;
+ background-color: $dark-light;
+ }
+ }
+
+ // dropdown
+ .dropdown-menu {
+ background-color: $dark-light;
+ border-left-color: $dark-light;
+ border-right-color: $dark-light;
+ border-bottom-color: $dark-light;
+ }
+
+ .dropdown-item {
+ color: #fff;
+ &:hover {
+ background-color: rgba(#fff, .1);
+ }
+ }
+ .dropdown-item.active {
+ background-color: $dark-primary;
+ }
+ .btn-dropdown-line {
+ background-color: $dark-light;
+ border-color: $dark-light;
+ color: $labels-dark;
+ }
+
+ // table
+ .stakes-table-th {
+ background-color: $dark-light;
+ color: $labels-dark;
+ }
+ .stakes-td {
+ border-bottom-color: darken($labels-dark, 30);
+ }
+ .table th, .table td {
+ border-top-color: darken($labels-dark, 30);
+ }
+ hr {
+ border-top-color: darken($labels-dark, 30);
+ }
+
+ // api's
+ .api-anchors-list {
+ background-color: $dark-light;
+ }
+ .api-doc-list-item {
+ border-bottom-color: darken($labels-dark, 30);
+ }
+ .card-subtitle,
+ .api-anchors-list-item-title,
+ .api-doc-list-item-title {
+ color: #fff;
+ }
+ .api-text-monospace {
+ color: $dark-primary;
+ }
+ .api-text-monospace-background {
+ background-color: rgba($dark-primary, .15);
+ }
+ .badge.badge-neutral {
+ background-color: rgba($dark-primary, .15);
+ color: $dark-primary;
+ }
+
+ // download csv button
+ .download-all-transactions .download-all-transactions-link svg path {
+ fill: $dark-primary;
+ }
+
+ //tooltips
+ .tooltip .arrow:before {
+ border-top-color: $dark-primary;
+ border-bottom-color: $dark-primary;
+ }
+ .tooltip > .tooltip-inner {background-color: $dark-primary;}
+
+ //network select
+ .network-selector-overlay {
+ background-color: rgba($dark-bg, .9);
+ }
+ .network-selector {
+ background-color: $dark-light-bg;
+ }
+ .network-selector-title {
+ color: #fff;
+ }
+ .network-selector-text {
+ color: $labels-dark;
+ }
+ .network-selector-close path {
+ fill: #fff;
+ }
+ .network-selector-search-container {
+ background-color: $dark-light;
+ }
+ .network-selector-search-container path {
+ fill: $labels-dark;
+ }
+ .network-selector-search-input {
+ color: #fff !important;
+ &::placeholder {
+ color: $labels-dark;
+ }
+ }
+ .network-selector-tab {
+ color: $labels-dark;
+ &:hover, &.active {
+ color: #fff;
+ }
+ &.active {
+ &:after {
+ background-color: $dark-primary;
+ }
+ }
+ }
+ .network-selector-item,
+ .network-selector-tabs-container {
+ border-bottom-color: darken($labels-dark, 30);
+ }
+ .network-selector-item-title {
+ color: #fff;
+ }
+ .network-selector-item-type {
+ color: $labels-dark;
+ }
+ .radio .radio-icon {
+ border-color: $labels-dark
+ }
+ .network-selector-item-url:hover .network-selector-item-type {
+ color: #fff;
+ }
+
+ //coin dropdown
+ .token-balance-dropdown.dropdown-menu {
+ border-color: $dark-light !important;
+ box-shadow: 0 0 30px 0 rgba(23, 24, 41, 0.5) !important;
+ }
+ .token-balance-dropdown .dropdown-search-icon path {
+ fill: $labels-dark;
+ }
+ .token-balance-dropdown .dropdown-search-field {
+ background-color: $dark-light;
+ border-color: $dark-light;
+ color: #fff;
+ &::placeholder {
+ color: $labels-dark;
+ }
+ }
+ .token-balance-dropdown[aria-labelledby="dropdown-tokens"] .dropdown-items .dropdown-item:hover {
+ color: #fff !important;
+ }
+ .dropdown-header {
+ color: $labels-dark;
+ }
+ .border-bottom {
+ border-bottom-color: darken($labels-dark, 30) !important;
+ }
+
+ // coin balance history chart
+ .chartjs-render-monitor[data-chart="coinBalanceHistoryChart"] {
+ filter: brightness(0) invert(1) !important;
+ }
+
+ // logs search
+ .logs-search-input, .logs-search-btn, .logs-search-btn-cancel {
+ background-color: $dark-light;
+ border-color: $dark-light;
+ color: #fff;
+ }
+
+ .logs-search-btn {
+ color: $labels-dark;
+ }
+
+ .logs-search-btn {
+ &:hover {
+ background-color: $dark-primary;
+ color: #fff;
+ }
+ }
+
+ .logs-search-input {
+ &::placeholder {
+ color: $labels-dark;
+ }
+ }
+
+ // code
+ pre {
+ color: #fff;
+ }
+
+ // info allert
+ .alert-info {
+ color: $labels-dark;
+ background-color: $dark-light;
+ border-color: $dark-light;
+ }
+
+ // dark text
+ .text-dark {
+ color: #fff;
+ }
+
+}
\ 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 68feab4dfc..dff677d7cc 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
@@ -74,4 +74,9 @@ $card-tab-active: $tertiary;
// Badges
$badge-neutral-color: $tertiary;
$badge-neutral-background-color: rgba($tertiary, .1);
-$api-text-monospace-color: $tertiary;
\ No newline at end of file
+$api-text-monospace-color: $tertiary;
+
+// Dark theme
+$dark-primary: #8588ff;
+$dark-secondary: #4ad7a7;
+$dark-primary-alternate: #5b5ed8;
\ No newline at end of file
diff --git a/apps/block_scout_web/assets/css/theme/_ethereum_variables.scss b/apps/block_scout_web/assets/css/theme/_ethereum_variables.scss
index 0755d3e4b4..0b5cac0c43 100644
--- a/apps/block_scout_web/assets/css/theme/_ethereum_variables.scss
+++ b/apps/block_scout_web/assets/css/theme/_ethereum_variables.scss
@@ -56,4 +56,18 @@ $card-tab-active: $secondary;
$dashboard-banner-gradient-end
);
}
+}
+
+// Dark theme
+$dark-primary: #49a2ee;
+$dark-secondary: #4ad7a7;
+$dark-primary-alternate: #49a2ee;
+
+.dark-theme-applied {
+ .dashboard-banner-chart-legend .dashboard-banner-chart-legend-item:nth-child(1)::before {
+ background-color: $dark-primary !important;
+ }
+ .dashboard-banner-chart-legend .dashboard-banner-chart-legend-item:nth-child(2)::before {
+ background-color: $dark-secondary !important;
+ }
}
\ 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 7900dd4c3b..04f953b477 100644
--- a/apps/block_scout_web/assets/css/theme/_goerli_variables.scss
+++ b/apps/block_scout_web/assets/css/theme/_goerli_variables.scss
@@ -78,3 +78,8 @@ $card-tab-active: $sub-accent-color;
$badge-neutral-color: $sub-accent-color;
$badge-neutral-background-color: rgba($sub-accent-color, .1);
$api-text-monospace-color: $sub-accent-color;
+
+// Dark theme
+$dark-primary: #e1995a;
+$dark-secondary: #aeaeae;
+$dark-primary-alternate: #e1995a;
\ No newline at end of file
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 351f046726..a4edc629d0 100644
--- a/apps/block_scout_web/assets/css/theme/_kovan_variables.scss
+++ b/apps/block_scout_web/assets/css/theme/_kovan_variables.scss
@@ -74,4 +74,9 @@ $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
+$api-text-monospace-color: $tertiary;
+
+// Dark theme
+$dark-primary: #42e2d7;
+$dark-secondary: #1f857f;
+$dark-primary-alternate: #1f857f;
\ 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 65f9a73b5f..a7b608bfa8 100644
--- a/apps/block_scout_web/assets/css/theme/_lukso_variables.scss
+++ b/apps/block_scout_web/assets/css/theme/_lukso_variables.scss
@@ -151,3 +151,20 @@ $dashboard-banner-network-plain-container-height: 150px;
$badge-neutral-color: $tertiary;
$badge-neutral-background-color: rgba($tertiary, .1);
$api-text-monospace-color: $tertiary;
+
+// Dark theme
+$dark-primary: #fdcec4;
+$dark-secondary: #a96c55;
+$dark-primary-alternate: #a96c55;
+
+.dark-theme-applied {
+ .dashboard-banner-network-stats-value {
+ color: $dark-primary !important;
+ }
+ .layout-container .dashboard-banner-container {
+ background-color: #282945 !important;
+ }
+ .dashboard-banner-network-plain-container::after {
+ box-shadow: none !important;
+ }
+}
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 4033934dd6..120a11c222 100644
--- a/apps/block_scout_web/assets/css/theme/_neutral_variables.scss
+++ b/apps/block_scout_web/assets/css/theme/_neutral_variables.scss
@@ -71,4 +71,9 @@ $api-text-monospace-color: $primary;
.dropdown-items .dropdown-item:hover {
color: $primary !important;
}
-}
\ No newline at end of file
+}
+
+// Dark theme
+$dark-primary: #9b62ff;
+$dark-secondary: #87e1a9;
+$dark-primary-alternate: #7e50d0;
\ 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 4033934dd6..120a11c222 100644
--- a/apps/block_scout_web/assets/css/theme/_poa_variables.scss
+++ b/apps/block_scout_web/assets/css/theme/_poa_variables.scss
@@ -71,4 +71,9 @@ $api-text-monospace-color: $primary;
.dropdown-items .dropdown-item:hover {
color: $primary !important;
}
-}
\ No newline at end of file
+}
+
+// Dark theme
+$dark-primary: #9b62ff;
+$dark-secondary: #87e1a9;
+$dark-primary-alternate: #7e50d0;
\ No newline at end of file
diff --git a/apps/block_scout_web/assets/css/theme/_rinkeby_variables.scss b/apps/block_scout_web/assets/css/theme/_rinkeby_variables.scss
index 1e62ea6d69..4fa700c50e 100644
--- a/apps/block_scout_web/assets/css/theme/_rinkeby_variables.scss
+++ b/apps/block_scout_web/assets/css/theme/_rinkeby_variables.scss
@@ -57,3 +57,8 @@ $card-tab-active: $secondary;
);
}
}
+
+// Dark theme
+$dark-primary: #38a9f5;
+$dark-secondary: #76f1ff;
+$dark-primary-alternate: #38a9f5;
\ No newline at end of file
diff --git a/apps/block_scout_web/assets/css/theme/_ropsten_variables.scss b/apps/block_scout_web/assets/css/theme/_ropsten_variables.scss
index 1e62ea6d69..4fa700c50e 100644
--- a/apps/block_scout_web/assets/css/theme/_ropsten_variables.scss
+++ b/apps/block_scout_web/assets/css/theme/_ropsten_variables.scss
@@ -57,3 +57,8 @@ $card-tab-active: $secondary;
);
}
}
+
+// Dark theme
+$dark-primary: #38a9f5;
+$dark-secondary: #76f1ff;
+$dark-primary-alternate: #38a9f5;
\ 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 76029e1a34..d33996a89e 100644
--- a/apps/block_scout_web/assets/css/theme/_rsk_variables.scss
+++ b/apps/block_scout_web/assets/css/theme/_rsk_variables.scss
@@ -65,3 +65,8 @@ $card-tab-active: $secondary;
// Badges
$badge-neutral-color: #1a323b;
$badge-neutral-background-color: rgba(#1a323b, .1);
+
+// Dark theme
+$dark-primary: #38c5a4;
+$dark-secondary: #e39a54;
+$dark-primary-alternate: #30ab8d;
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 444cdff178..8d479f397d 100644
--- a/apps/block_scout_web/assets/css/theme/_sokol_variables.scss
+++ b/apps/block_scout_web/assets/css/theme/_sokol_variables.scss
@@ -72,4 +72,9 @@ $card-tab-active: $sub-accent-color;
// Badges
$badge-neutral-color: $tertiary;
-$badge-neutral-background-color: rgba($tertiary, .1);
\ No newline at end of file
+$badge-neutral-background-color: rgba($tertiary, .1);
+
+// Dark theme
+$dark-primary: #40bfb2;
+$dark-secondary: #25c9ff;
+$dark-primary-alternate: #1c9f90;
\ No newline at end of file
diff --git a/apps/block_scout_web/assets/js/app.js b/apps/block_scout_web/assets/js/app.js
index aa01e24b10..812825cf59 100644
--- a/apps/block_scout_web/assets/js/app.js
+++ b/apps/block_scout_web/assets/js/app.js
@@ -34,6 +34,8 @@ import './pages/transactions'
import './pages/favorites'
import './pages/network-search'
import './pages/layout'
+import './pages/verification_form'
+import './pages/dark-mode-switcher'
import './pages/admin/tasks.js'
@@ -46,7 +48,6 @@ import './lib/history_chart'
import './lib/pending_transactions_toggle'
import './lib/pretty_json'
import './lib/reload_button'
-import './lib/smart_contract/new_smart_contract_form'
import './lib/smart_contract/read_only_functions'
import './lib/smart_contract/wei_ether_converter'
import './lib/stop_propagation'
diff --git a/apps/block_scout_web/assets/js/lib/async_listing_load.js b/apps/block_scout_web/assets/js/lib/async_listing_load.js
index 8610d901f6..8477a7d728 100644
--- a/apps/block_scout_web/assets/js/lib/async_listing_load.js
+++ b/apps/block_scout_web/assets/js/lib/async_listing_load.js
@@ -1,5 +1,6 @@
import $ from 'jquery'
-import _ from 'lodash'
+import map from 'lodash/map'
+import merge from 'lodash/merge'
import URI from 'urijs'
import humps from 'humps'
import listMorph from '../lib/list_morph'
@@ -164,7 +165,7 @@ export const elements = {
if (state.itemKey) {
const container = $el[0]
- const newElements = _.map(state.items, (item) => $(item)[0])
+ const newElements = map(state.items, (item) => $(item)[0])
listMorph(container, newElements, { key: state.itemKey })
return
}
@@ -244,7 +245,7 @@ export const elements = {
* adding or removing with the correct animation. Check list_morph.js for more informantion.
*/
export function createAsyncLoadStore (reducer, initialState, itemKey) {
- const state = _.merge(asyncInitialState, initialState)
+ const state = merge(asyncInitialState, initialState)
const store = createStore(reduceReducers(asyncReducer, reducer, state))
if (typeof itemKey !== 'undefined') {
diff --git a/apps/block_scout_web/assets/js/lib/history_chart.js b/apps/block_scout_web/assets/js/lib/history_chart.js
index 59397843eb..fcaa3aa421 100644
--- a/apps/block_scout_web/assets/js/lib/history_chart.js
+++ b/apps/block_scout_web/assets/js/lib/history_chart.js
@@ -4,6 +4,7 @@ import humps from 'humps'
import numeral from 'numeral'
import { formatUsdValue } from '../lib/currency'
import sassVariables from '../../css/app.scss'
+import { showLoader } from '../lib/utils'
const config = {
type: 'line',
@@ -103,6 +104,17 @@ function getMarketCapData (marketHistoryData, availableSupply) {
}
}
+// colors for light and dark theme
+var priceLineColor
+var mcapLineColor
+if (localStorage.getItem('current-color-mode') === 'dark') {
+ priceLineColor = sassVariables.darkprimary
+ mcapLineColor = sassVariables.darksecondary
+} else {
+ priceLineColor = sassVariables.dashboardLineColorPrice
+ mcapLineColor = sassVariables.dashboardLineColorMarket
+}
+
class MarketHistoryChart {
constructor (el, availableSupply, marketHistoryData, dataConfig) {
@@ -118,8 +130,8 @@ class MarketHistoryChart {
data: [],
fill: false,
pointRadius: 0,
- backgroundColor: sassVariables.dashboardLineColorPrice,
- borderColor: sassVariables.dashboardLineColorPrice,
+ backgroundColor: priceLineColor,
+ borderColor: priceLineColor,
lineTension: 0
}
if (dataConfig.market == undefined || dataConfig.market.indexOf("price") == -1){
@@ -133,8 +145,8 @@ class MarketHistoryChart {
data: [],
fill: false,
pointRadius: 0,
- backgroundColor: sassVariables.dashboardLineColorMarket,
- borderColor: sassVariables.dashboardLineColorMarket,
+ backgroundColor: mcapLineColor,
+ borderColor: mcapLineColor,
lineTension: 0
}
if (dataConfig.market == undefined || dataConfig.market.indexOf("market_cap") == -1){
@@ -186,6 +198,10 @@ export function createMarketHistoryChart (el) {
const dataConfig = $(el).data('history_chart_config')
const $chartLoading = $('[data-chart-loading-message]')
+
+ const isTimeout = true
+ const timeoutID = showLoader(isTimeout, $chartLoading)
+
const $chartError = $('[data-chart-error-message]')
const chart = new MarketHistoryChart(el, 0, [], dataConfig)
Object.keys(dataPaths).forEach(function(history_source){
diff --git a/apps/block_scout_web/assets/js/lib/infinite_scroll_helpers.js b/apps/block_scout_web/assets/js/lib/infinite_scroll_helpers.js
index 45b278ae4d..b7fbb940a2 100644
--- a/apps/block_scout_web/assets/js/lib/infinite_scroll_helpers.js
+++ b/apps/block_scout_web/assets/js/lib/infinite_scroll_helpers.js
@@ -1,5 +1,5 @@
import $ from 'jquery'
-import _ from 'lodash'
+import omit from 'lodash/omit'
import humps from 'humps'
import { connectElements } from './redux_helpers.js'
@@ -12,7 +12,7 @@ const initialState = {
function infiniteScrollReducer (state = initialState, action) {
switch (action.type) {
case 'INFINITE_SCROLL_ELEMENTS_LOAD': {
- return Object.assign({}, state, _.omit(action, 'type'))
+ return Object.assign({}, state, omit(action, 'type'))
}
case 'LOADING_NEXT_PAGE': {
return Object.assign({}, state, {
diff --git a/apps/block_scout_web/assets/js/lib/list_morph.js b/apps/block_scout_web/assets/js/lib/list_morph.js
index 175acb7c3f..6cdfea3e3f 100644
--- a/apps/block_scout_web/assets/js/lib/list_morph.js
+++ b/apps/block_scout_web/assets/js/lib/list_morph.js
@@ -1,5 +1,10 @@
import $ from 'jquery'
-import _ from 'lodash'
+import map from 'lodash/map'
+import get from 'lodash/get'
+import noop from 'lodash/noop'
+import find from 'lodash/find'
+import intersectionBy from 'lodash/intersectionBy'
+import differenceBy from 'lodash/differenceBy'
import morph from 'nanomorph'
import { updateAllAges } from './from_now'
@@ -25,12 +30,12 @@ import { updateAllAges } from './from_now'
export default function (container, newElements, { key, horizontal } = {}) {
if (!container) return
const oldElements = $(container).children().get()
- let currentList = _.map(oldElements, (el) => ({ id: _.get(el, key), el }))
- const newList = _.map(newElements, (el) => ({ id: _.get(el, key), el }))
- const overlap = _.intersectionBy(newList, currentList, 'id').map(({ id, el }) => ({ id, el: updateAllAges($(el))[0] }))
+ let currentList = map(oldElements, (el) => ({ id: get(el, key), el }))
+ const newList = map(newElements, (el) => ({ id: get(el, key), el }))
+ const overlap = intersectionBy(newList, currentList, 'id').map(({ id, el }) => ({ id, el: updateAllAges($(el))[0] }))
// remove old items
- const removals = _.differenceBy(currentList, newList, 'id')
+ const removals = differenceBy(currentList, newList, 'id')
let canAnimate = !horizontal && removals.length <= 1
removals.forEach(({ el }) => {
if (!canAnimate) return el.remove()
@@ -38,7 +43,7 @@ export default function (container, newElements, { key, horizontal } = {}) {
$el.addClass('shrink-out')
setTimeout(() => { slideUpRemove($el) }, 400)
})
- currentList = _.differenceBy(currentList, removals, 'id')
+ currentList = differenceBy(currentList, removals, 'id')
// update kept items
currentList = currentList.map(({ el }, i) => ({
@@ -47,14 +52,14 @@ export default function (container, newElements, { key, horizontal } = {}) {
}))
// add new items
- const finalList = newList.map(({ id, el }) => _.get(_.find(currentList, { id }), 'el', el)).reverse()
+ const finalList = newList.map(({ id, el }) => get(find(currentList, { id }), 'el', el)).reverse()
canAnimate = !horizontal
finalList.forEach((el, i) => {
if (el.parentElement) return
- if (!canAnimate) return container.insertBefore(el, _.get(finalList, `[${i - 1}]`))
+ if (!canAnimate) return container.insertBefore(el, get(finalList, `[${i - 1}]`))
canAnimate = false
- if (!_.get(finalList, `[${i - 1}]`)) return slideDownAppend($(container), el)
- slideDownBefore($(_.get(finalList, `[${i - 1}]`)), el)
+ if (!get(finalList, `[${i - 1}]`)) return slideDownAppend($(container), el)
+ slideDownBefore($(get(finalList, `[${i - 1}]`)), el)
})
}
@@ -80,7 +85,7 @@ function slideUpRemove ($el) {
})
}
-function smarterSlideDown ($el, { insert = _.noop } = {}) {
+function smarterSlideDown ($el, { insert = noop } = {}) {
if (!$el.length) return
const originalScrollHeight = document.body.scrollHeight
const scrollPosition = window.scrollY
@@ -100,7 +105,7 @@ function smarterSlideDown ($el, { insert = _.noop } = {}) {
}
}
-function smarterSlideUp ($el, { complete = _.noop } = {}) {
+function smarterSlideUp ($el, { complete = noop } = {}) {
if (!$el.length) return
const originalScrollHeight = document.body.scrollHeight
const scrollPosition = window.scrollY
diff --git a/apps/block_scout_web/assets/js/lib/redux_helpers.js b/apps/block_scout_web/assets/js/lib/redux_helpers.js
index fdf7c659b3..35dcc65b2c 100644
--- a/apps/block_scout_web/assets/js/lib/redux_helpers.js
+++ b/apps/block_scout_web/assets/js/lib/redux_helpers.js
@@ -1,5 +1,7 @@
import $ from 'jquery'
-import _ from 'lodash'
+import reduce from 'lodash/reduce'
+import isObject from 'lodash/isObject'
+import forIn from 'lodash/forIn'
import { createStore as reduxCreateStore } from 'redux'
/**
@@ -97,17 +99,17 @@ export function createStore (reducer) {
*/
export function connectElements ({ elements, store, action = 'ELEMENTS_LOAD' }) {
function loadElements () {
- return _.reduce(elements, (pageLoadParams, { load }, selector) => {
+ return reduce(elements, (pageLoadParams, { load }, selector) => {
if (!load) return pageLoadParams
const $el = $(selector)
if (!$el.length) return pageLoadParams
const morePageLoadParams = load($el, store)
- return _.isObject(morePageLoadParams) ? Object.assign(pageLoadParams, morePageLoadParams) : pageLoadParams
+ return isObject(morePageLoadParams) ? Object.assign(pageLoadParams, morePageLoadParams) : pageLoadParams
}, {})
}
function renderElements (state, oldState) {
- _.forIn(elements, ({ render }, selector) => {
+ forIn(elements, ({ render }, selector) => {
if (!render) return
const $el = $(selector)
if (!$el.length) return
diff --git a/apps/block_scout_web/assets/js/lib/smart_contract/new_smart_contract_form.js b/apps/block_scout_web/assets/js/lib/smart_contract/new_smart_contract_form.js
deleted file mode 100644
index 43419aeb9b..0000000000
--- a/apps/block_scout_web/assets/js/lib/smart_contract/new_smart_contract_form.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import $ from 'jquery'
-
-$(function () {
- $('.js-btn-add-contract-libraries').on('click', function () {
- $('.js-smart-contract-libraries-wrapper').show()
- $(this).hide()
- })
-
- $('.js-smart-contract-form-reset').on('click', function () {
- $('.js-contract-library-form-group').removeClass('active')
- $('.js-contract-library-form-group').first().addClass('active')
- $('.js-smart-contract-libraries-wrapper').hide()
- $('.js-btn-add-contract-libraries').show()
- $('.js-add-contract-library-wrapper').show()
- })
-
- $('.js-btn-add-contract-library').on('click', function () {
- let nextContractLibrary = $('.js-contract-library-form-group.active').next('.js-contract-library-form-group')
-
- if (nextContractLibrary) {
- nextContractLibrary.addClass('active')
- }
-
- if ($('.js-contract-library-form-group.active').length === $('.js-contract-library-form-group').length) {
- $('.js-add-contract-library-wrapper').hide()
- }
- })
-})
diff --git a/apps/block_scout_web/assets/js/lib/utils.js b/apps/block_scout_web/assets/js/lib/utils.js
index d3bd6e84d1..e00d06ec1b 100644
--- a/apps/block_scout_web/assets/js/lib/utils.js
+++ b/apps/block_scout_web/assets/js/lib/utils.js
@@ -1,8 +1,8 @@
-import _ from 'lodash'
+import debounce from 'lodash/debounce'
export function batchChannel (func) {
let msgs = []
- const debouncedFunc = _.debounce(() => {
+ const debouncedFunc = debounce(() => {
func.apply(this, [msgs])
msgs = []
}, 1000, { maxWait: 5000 })
@@ -11,3 +11,16 @@ export function batchChannel (func) {
debouncedFunc()
}
}
+
+export function showLoader (isTimeout, loader) {
+ if (isTimeout) {
+ const timeout = setTimeout(function () {
+ loader.removeAttr('hidden')
+ loader.show()
+ }, 1000)
+ return timeout
+ } else {
+ loader.hide()
+ return null
+ }
+}
diff --git a/apps/block_scout_web/assets/js/pages/address.js b/apps/block_scout_web/assets/js/pages/address.js
index 5b35a0b9d7..ddafbcb1e2 100644
--- a/apps/block_scout_web/assets/js/pages/address.js
+++ b/apps/block_scout_web/assets/js/pages/address.js
@@ -1,5 +1,5 @@
import $ from 'jquery'
-import _ from 'lodash'
+import omit from 'lodash/omit'
import URI from 'urijs'
import humps from 'humps'
import numeral from 'numeral'
@@ -25,7 +25,7 @@ export function reducer (state = initialState, action) {
switch (action.type) {
case 'PAGE_LOAD':
case 'ELEMENTS_LOAD': {
- return Object.assign({}, state, _.omit(action, 'type'))
+ return Object.assign({}, state, omit(action, 'type'))
}
case 'CHANNEL_DISCONNECTED': {
if (state.beyondPageOne) return state
diff --git a/apps/block_scout_web/assets/js/pages/address/coin_balances.js b/apps/block_scout_web/assets/js/pages/address/coin_balances.js
index 8c0100d3d2..b4523a6c64 100644
--- a/apps/block_scout_web/assets/js/pages/address/coin_balances.js
+++ b/apps/block_scout_web/assets/js/pages/address/coin_balances.js
@@ -1,5 +1,5 @@
import $ from 'jquery'
-import _ from 'lodash'
+import omit from 'lodash/omit'
import humps from 'humps'
import socket from '../../socket'
import { connectElements } from '../../lib/redux_helpers.js'
@@ -14,7 +14,7 @@ export function reducer (state, action) {
switch (action.type) {
case 'PAGE_LOAD':
case 'ELEMENTS_LOAD': {
- return Object.assign({}, state, _.omit(action, 'type'))
+ return Object.assign({}, state, omit(action, 'type'))
}
case 'CHANNEL_DISCONNECTED': {
if (state.beyondPageOne) return state
diff --git a/apps/block_scout_web/assets/js/pages/address/internal_transactions.js b/apps/block_scout_web/assets/js/pages/address/internal_transactions.js
index aa95278c71..6c3d503256 100644
--- a/apps/block_scout_web/assets/js/pages/address/internal_transactions.js
+++ b/apps/block_scout_web/assets/js/pages/address/internal_transactions.js
@@ -1,5 +1,5 @@
import $ from 'jquery'
-import _ from 'lodash'
+import omit from 'lodash/omit'
import humps from 'humps'
import numeral from 'numeral'
import socket from '../../socket'
@@ -20,7 +20,7 @@ export function reducer (state, action) {
switch (action.type) {
case 'PAGE_LOAD':
case 'ELEMENTS_LOAD': {
- return Object.assign({}, state, _.omit(action, 'type'))
+ return Object.assign({}, state, omit(action, 'type'))
}
case 'CHANNEL_DISCONNECTED': {
if (state.beyondPageOne) return state
diff --git a/apps/block_scout_web/assets/js/pages/address/logs.js b/apps/block_scout_web/assets/js/pages/address/logs.js
index 5b4cf8aecd..5c9bed9da2 100644
--- a/apps/block_scout_web/assets/js/pages/address/logs.js
+++ b/apps/block_scout_web/assets/js/pages/address/logs.js
@@ -1,5 +1,5 @@
import $ from 'jquery'
-import _ from 'lodash'
+import omit from 'lodash/omit'
import humps from 'humps'
import { connectElements } from '../../lib/redux_helpers.js'
import { createAsyncLoadStore } from '../../lib/async_listing_load'
@@ -13,7 +13,7 @@ export function reducer (state, action) {
switch (action.type) {
case 'PAGE_LOAD':
case 'ELEMENTS_LOAD': {
- return Object.assign({}, state, _.omit(action, 'type'))
+ return Object.assign({}, state, omit(action, 'type'))
}
case 'START_SEARCH': {
return Object.assign({}, state, {pagesStack: [], isSearch: true})
diff --git a/apps/block_scout_web/assets/js/pages/address/transactions.js b/apps/block_scout_web/assets/js/pages/address/transactions.js
index be70dcc3ba..ecd604e2d5 100644
--- a/apps/block_scout_web/assets/js/pages/address/transactions.js
+++ b/apps/block_scout_web/assets/js/pages/address/transactions.js
@@ -1,5 +1,5 @@
import $ from 'jquery'
-import _ from 'lodash'
+import omit from 'lodash/omit'
import URI from 'urijs'
import humps from 'humps'
import { subscribeChannel } from '../../socket'
@@ -16,7 +16,7 @@ export function reducer (state, action) {
switch (action.type) {
case 'PAGE_LOAD':
case 'ELEMENTS_LOAD': {
- return Object.assign({}, state, _.omit(action, 'type'))
+ return Object.assign({}, state, omit(action, 'type'))
}
case 'CHANNEL_DISCONNECTED': {
if (state.beyondPageOne) return state
diff --git a/apps/block_scout_web/assets/js/pages/address/validations.js b/apps/block_scout_web/assets/js/pages/address/validations.js
index 6aca07a98b..d2456a183e 100644
--- a/apps/block_scout_web/assets/js/pages/address/validations.js
+++ b/apps/block_scout_web/assets/js/pages/address/validations.js
@@ -1,5 +1,5 @@
import $ from 'jquery'
-import _ from 'lodash'
+import omit from 'lodash/omit'
import humps from 'humps'
import socket from '../../socket'
import { connectElements } from '../../lib/redux_helpers.js'
@@ -14,7 +14,7 @@ export function reducer (state = initialState, action) {
switch (action.type) {
case 'PAGE_LOAD':
case 'ELEMENTS_LOAD': {
- return Object.assign({}, state, _.omit(action, 'type'))
+ return Object.assign({}, state, omit(action, 'type'))
}
case 'CHANNEL_DISCONNECTED': {
return Object.assign({}, state, { channelDisconnected: true })
diff --git a/apps/block_scout_web/assets/js/pages/blocks.js b/apps/block_scout_web/assets/js/pages/blocks.js
index 2103c18a89..4d9bdc05d9 100644
--- a/apps/block_scout_web/assets/js/pages/blocks.js
+++ b/apps/block_scout_web/assets/js/pages/blocks.js
@@ -1,5 +1,10 @@
import $ from 'jquery'
-import _ from 'lodash'
+import omit from 'lodash/omit'
+import last from 'lodash/last'
+import min from 'lodash/min'
+import max from 'lodash/max'
+import keys from 'lodash/keys'
+import rangeRight from 'lodash/rangeRight'
import humps from 'humps'
import socket from '../socket'
import { connectElements } from '../lib/redux_helpers.js'
@@ -14,7 +19,7 @@ export const blockReducer = withMissingBlocks(baseReducer)
function baseReducer (state = initialState, action) {
switch (action.type) {
case 'ELEMENTS_LOAD': {
- return Object.assign({}, state, _.omit(action, 'type'))
+ return Object.assign({}, state, omit(action, 'type'))
}
case 'CHANNEL_DISCONNECTED': {
return Object.assign({}, state, {
@@ -25,7 +30,7 @@ function baseReducer (state = initialState, action) {
if (state.channelDisconnected || state.beyondPageOne || state.blockType !== 'block') return state
const blockNumber = getBlockNumber(action.msg.blockHtml)
- const minBlock = getBlockNumber(_.last(state.items))
+ const minBlock = getBlockNumber(last(state.items))
if (state.items.length && blockNumber < minBlock) return state
@@ -62,12 +67,12 @@ function withMissingBlocks (reducer) {
return acc
}, {})
- const blockNumbers = _(blockNumbersToItems).keys().map(x => parseInt(x, 10)).value()
- const minBlock = _.min(blockNumbers)
- const maxBlock = _.max(blockNumbers)
+ const blockNumbers = keys(blockNumbersToItems).map(x => parseInt(x, 10))
+ const minBlock = min(blockNumbers)
+ const maxBlock = max(blockNumbers)
return Object.assign({}, result, {
- items: _.rangeRight(minBlock, maxBlock + 1)
+ items: rangeRight(minBlock, maxBlock + 1)
.map((blockNumber) => blockNumbersToItems[blockNumber] || placeHolderBlock(blockNumber))
})
}
diff --git a/apps/block_scout_web/assets/js/pages/chain.js b/apps/block_scout_web/assets/js/pages/chain.js
index 5358ced62d..bb9625b282 100644
--- a/apps/block_scout_web/assets/js/pages/chain.js
+++ b/apps/block_scout_web/assets/js/pages/chain.js
@@ -1,11 +1,15 @@
import $ from 'jquery'
-import _ from 'lodash'
+import omit from 'lodash/omit'
+import first from 'lodash/first'
+import rangeRight from 'lodash/rangeRight'
+import find from 'lodash/find'
+import map from 'lodash/map'
import humps from 'humps'
import numeral from 'numeral'
import socket from '../socket'
import { exchangeRateChannel, formatUsdValue } from '../lib/currency'
import { createStore, connectElements } from '../lib/redux_helpers.js'
-import { batchChannel } from '../lib/utils'
+import { batchChannel, showLoader } from '../lib/utils'
import listMorph from '../lib/list_morph'
import { createMarketHistoryChart } from '../lib/history_chart'
@@ -33,7 +37,7 @@ export const reducer = withMissingBlocks(baseReducer)
function baseReducer (state = initialState, action) {
switch (action.type) {
case 'ELEMENTS_LOAD': {
- return Object.assign({}, state, _.omit(action, 'type'))
+ return Object.assign({}, state, omit(action, 'type'))
}
case 'RECEIVED_NEW_ADDRESS_COUNT': {
return Object.assign({}, state, {
@@ -127,12 +131,12 @@ function withMissingBlocks (reducer) {
if (!result.blocks || result.blocks.length < 2) return result
- const maxBlock = _.first(result.blocks).blockNumber
+ const maxBlock = first(result.blocks).blockNumber
const minBlock = maxBlock - (result.blocks.length - 1)
return Object.assign({}, result, {
- blocks: _.rangeRight(minBlock, maxBlock + 1)
- .map((blockNumber) => _.find(result.blocks, ['blockNumber', blockNumber]) || {
+ blocks: rangeRight(minBlock, maxBlock + 1)
+ .map((blockNumber) => find(result.blocks, ['blockNumber', blockNumber]) || {
blockNumber,
chainBlockHtml: placeHolderBlock(blockNumber)
})
@@ -203,7 +207,7 @@ const elements = {
const container = $el[0]
if (state.blocksLoading === false) {
- const blocks = _.map(state.blocks, ({ chainBlockHtml }) => $(chainBlockHtml)[0])
+ const blocks = map(state.blocks, ({ chainBlockHtml }) => $(chainBlockHtml)[0])
listMorph(container, blocks, { key: 'dataset.blockNumber', horizontal: true })
}
}
@@ -219,11 +223,7 @@ const elements = {
},
'[data-selector="chain-block-list"] [data-selector="loading-message"]': {
render ($el, state, oldState) {
- if (state.blocksLoading) {
- $el.show()
- } else {
- $el.hide()
- }
+ showLoader(state.blocksLoading, $el)
}
},
'[data-selector="transactions-list"] [data-selector="error-message"]': {
@@ -233,7 +233,7 @@ const elements = {
},
'[data-selector="transactions-list"] [data-selector="loading-message"]': {
render ($el, state, oldState) {
- $el.toggle(state.transactionsLoading)
+ showLoader(state.transactionsLoading, $el)
}
},
'[data-selector="transactions-list"]': {
@@ -243,7 +243,7 @@ const elements = {
render ($el, state, oldState) {
if (oldState.transactions === state.transactions) return
const container = $el[0]
- const newElements = _.map(state.transactions, ({ transactionHtml }) => $(transactionHtml)[0])
+ const newElements = map(state.transactions, ({ transactionHtml }) => $(transactionHtml)[0])
listMorph(container, newElements, { key: 'dataset.identifierHash' })
}
},
diff --git a/apps/block_scout_web/assets/js/pages/dark-mode-switcher.js b/apps/block_scout_web/assets/js/pages/dark-mode-switcher.js
new file mode 100644
index 0000000000..e9e0bdc524
--- /dev/null
+++ b/apps/block_scout_web/assets/js/pages/dark-mode-switcher.js
@@ -0,0 +1,11 @@
+import $ from 'jquery'
+
+$('.dark-mode-changer').click(function () {
+ if (localStorage.getItem('current-color-mode') === 'dark') {
+ localStorage.setItem('current-color-mode', 'light')
+ } else {
+ localStorage.setItem('current-color-mode', 'dark')
+ }
+ // reload each theme switch
+ document.location.reload(true)
+})
diff --git a/apps/block_scout_web/assets/js/pages/pending_transactions.js b/apps/block_scout_web/assets/js/pages/pending_transactions.js
index 1cbb5d47d0..711f7c4343 100644
--- a/apps/block_scout_web/assets/js/pages/pending_transactions.js
+++ b/apps/block_scout_web/assets/js/pages/pending_transactions.js
@@ -1,5 +1,5 @@
import $ from 'jquery'
-import _ from 'lodash'
+import omit from 'lodash/omit'
import humps from 'humps'
import numeral from 'numeral'
import socket from '../socket'
@@ -20,7 +20,7 @@ export const initialState = {
export function reducer (state = initialState, action) {
switch (action.type) {
case 'ELEMENTS_LOAD': {
- return Object.assign({}, state, _.omit(action, 'type'))
+ return Object.assign({}, state, omit(action, 'type'))
}
case 'CHANNEL_DISCONNECTED': {
return Object.assign({}, state, {
diff --git a/apps/block_scout_web/assets/js/pages/transaction.js b/apps/block_scout_web/assets/js/pages/transaction.js
index cd247d530a..139d1f527c 100644
--- a/apps/block_scout_web/assets/js/pages/transaction.js
+++ b/apps/block_scout_web/assets/js/pages/transaction.js
@@ -1,5 +1,5 @@
import $ from 'jquery'
-import _ from 'lodash'
+import omit from 'lodash/omit'
import humps from 'humps'
import numeral from 'numeral'
import socket from '../socket'
@@ -13,7 +13,7 @@ export const initialState = {
export function reducer (state = initialState, action) {
switch (action.type) {
case 'ELEMENTS_LOAD': {
- return Object.assign({}, state, _.omit(action, 'type'))
+ return Object.assign({}, state, omit(action, 'type'))
}
case 'RECEIVED_NEW_BLOCK': {
if ((action.msg.blockNumber - state.blockNumber) > state.confirmations) {
diff --git a/apps/block_scout_web/assets/js/pages/transactions.js b/apps/block_scout_web/assets/js/pages/transactions.js
index a6c038166b..f528f760c4 100644
--- a/apps/block_scout_web/assets/js/pages/transactions.js
+++ b/apps/block_scout_web/assets/js/pages/transactions.js
@@ -1,5 +1,5 @@
import $ from 'jquery'
-import _ from 'lodash'
+import omit from 'lodash/omit'
import humps from 'humps'
import numeral from 'numeral'
import socket from '../socket'
@@ -18,7 +18,7 @@ export const initialState = {
export function reducer (state = initialState, action) {
switch (action.type) {
case 'ELEMENTS_LOAD': {
- return Object.assign({}, state, _.omit(action, 'type'))
+ return Object.assign({}, state, omit(action, 'type'))
}
case 'CHANNEL_DISCONNECTED': {
return Object.assign({}, state, {
diff --git a/apps/block_scout_web/assets/js/pages/verification_form.js b/apps/block_scout_web/assets/js/pages/verification_form.js
new file mode 100644
index 0000000000..10504c144c
--- /dev/null
+++ b/apps/block_scout_web/assets/js/pages/verification_form.js
@@ -0,0 +1,144 @@
+import $ from 'jquery'
+import omit from 'lodash/omit'
+import URI from 'urijs'
+import humps from 'humps'
+import { subscribeChannel } from '../socket'
+import { createStore, connectElements } from '../lib/redux_helpers.js'
+
+export const initialState = {
+ channelDisconnected: false,
+ addressHash: null,
+ newForm: null
+}
+
+export function reducer (state = initialState, action) {
+ switch (action.type) {
+ case 'PAGE_LOAD':
+ case 'ELEMENTS_LOAD': {
+ return Object.assign({}, state, omit(action, 'type'))
+ }
+ case 'CHANNEL_DISCONNECTED': {
+ if (state.beyondPageOne) return state
+
+ return Object.assign({}, state, {
+ channelDisconnected: true
+ })
+ }
+ case 'RECEIVED_VERIFICATION_RESULT': {
+ if (action.msg.verificationResult === 'ok') {
+ return window.location.replace(window.location.href.split('/contract_verifications')[0] + '/contracts')
+ } else {
+ return Object.assign({}, state, {
+ newForm: action.msg.verificationResult
+ })
+ }
+ }
+ default:
+ return state
+ }
+}
+
+const elements = {
+ '[data-selector="channel-disconnected-message"]': {
+ render ($el, state) {
+ if (state.channelDisconnected) $el.show()
+ }
+ },
+ '[data-page="contract-verification"]': {
+ render ($el, state) {
+ if (state.newForm) {
+ $el.replaceWith(state.newForm)
+ $('button[data-button-loading="animation"]').click(event => {
+ $('#loading').removeClass('d-none')
+ })
+
+ $(function () {
+ $('.js-btn-add-contract-libraries').on('click', function () {
+ $('.js-smart-contract-libraries-wrapper').show()
+ $(this).hide()
+ })
+
+ $('.js-smart-contract-form-reset').on('click', function () {
+ $('.js-contract-library-form-group').removeClass('active')
+ $('.js-contract-library-form-group').first().addClass('active')
+ $('.js-smart-contract-libraries-wrapper').hide()
+ $('.js-btn-add-contract-libraries').show()
+ $('.js-add-contract-library-wrapper').show()
+ })
+
+ $('.js-btn-add-contract-library').on('click', function () {
+ let nextContractLibrary = $('.js-contract-library-form-group.active').next('.js-contract-library-form-group')
+
+ if (nextContractLibrary) {
+ nextContractLibrary.addClass('active')
+ }
+
+ if ($('.js-contract-library-form-group.active').length === $('.js-contract-library-form-group').length) {
+ $('.js-add-contract-library-wrapper').hide()
+ }
+ })
+ })
+
+ return $el
+ }
+ return $el
+ }
+ }
+}
+
+const $contractVerificationPage = $('[data-page="contract-verification"]')
+
+if ($contractVerificationPage.length) {
+ const store = createStore(reducer)
+ const addressHash = $('#smart_contract_address_hash').val()
+ const { filter, blockNumber } = humps.camelizeKeys(URI(window.location).query(true))
+
+ store.dispatch({
+ type: 'PAGE_LOAD',
+ addressHash,
+ filter,
+ beyondPageOne: !!blockNumber
+ })
+ connectElements({ store, elements })
+
+ const addressChannel = subscribeChannel(`addresses:${addressHash}`)
+
+ addressChannel.onError(() => store.dispatch({
+ type: 'CHANNEL_DISCONNECTED'
+ }))
+ addressChannel.on('verification', (msg) => store.dispatch({
+ type: 'RECEIVED_VERIFICATION_RESULT',
+ msg: humps.camelizeKeys(msg)
+ }))
+
+ $('button[data-button-loading="animation"]').click(event => {
+ $('#loading').removeClass('d-none')
+ })
+
+ $(function () {
+ $('.js-btn-add-contract-libraries').on('click', function () {
+ $('.js-smart-contract-libraries-wrapper').show()
+ $(this).hide()
+ })
+
+ $('.js-smart-contract-form-reset').on('click', function () {
+ $('.js-contract-library-form-group').removeClass('active')
+ $('.js-contract-library-form-group').first().addClass('active')
+ $('.js-smart-contract-libraries-wrapper').hide()
+ $('.js-btn-add-contract-libraries').show()
+ $('.js-add-contract-library-wrapper').show()
+ })
+
+ $('.js-btn-add-contract-library').on('click', function () {
+ let nextContractLibrary = $('.js-contract-library-form-group.active').next('.js-contract-library-form-group')
+
+ if (nextContractLibrary) {
+ nextContractLibrary.addClass('active')
+ }
+
+ if ($('.js-contract-library-form-group.active').length === $('.js-contract-library-form-group').length) {
+ $('.js-add-contract-library-wrapper').hide()
+ }
+ })
+ })
+}
diff --git a/apps/block_scout_web/assets/webpack.config.js b/apps/block_scout_web/assets/webpack.config.js
index 63c796d3f7..d7d504678d 100644
--- a/apps/block_scout_web/assets/webpack.config.js
+++ b/apps/block_scout_web/assets/webpack.config.js
@@ -1,6 +1,7 @@
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
+const { ContextReplacementPlugin } = require('webpack');
const glob = require("glob");
function transpileViewScript(file) {
@@ -74,7 +75,8 @@ const appJs =
},
plugins: [
new ExtractTextPlugin('../css/app.css'),
- new CopyWebpackPlugin([{ from: 'static/', to: '../' }])
+ new CopyWebpackPlugin([{ from: 'static/', to: '../' }]),
+ new ContextReplacementPlugin(/moment[\/\\]locale$/, /en/)
]
}
diff --git a/apps/block_scout_web/config/config.exs b/apps/block_scout_web/config/config.exs
index 88f08c73dc..d67d20eea6 100644
--- a/apps/block_scout_web/config/config.exs
+++ b/apps/block_scout_web/config/config.exs
@@ -35,11 +35,6 @@ config :block_scout_web, BlockScoutWeb.Counters.BlocksIndexedCounter, enabled: t
# Configures the endpoint
config :block_scout_web, BlockScoutWeb.Endpoint,
instrumenters: [BlockScoutWeb.Prometheus.Instrumenter, SpandexPhoenix.Instrumenter],
- http: [
- protocol_options: [
- idle_timeout: 90_000
- ]
- ],
url: [
host: System.get_env("BLOCKSCOUT_HOST") || "localhost",
path: System.get_env("NETWORK_PATH") || "/"
diff --git a/apps/block_scout_web/config/dev.exs b/apps/block_scout_web/config/dev.exs
index ce955a764c..de386f1a5d 100644
--- a/apps/block_scout_web/config/dev.exs
+++ b/apps/block_scout_web/config/dev.exs
@@ -16,15 +16,14 @@ port =
config :block_scout_web, BlockScoutWeb.Endpoint,
http: [
- protocol_options: [
- idle_timeout: 90_000
- ],
port: port || 4000
],
+ url: [
+ scheme: "http",
+ host: System.get_env("BLOCKSCOUT_HOST") || "localhost",
+ path: System.get_env("NETWORK_PATH") || "/"
+ ],
https: [
- protocol_options: [
- idle_timeout: 90_000
- ],
port: (port && port + 1) || 4001,
cipher_suite: :strong,
certfile: System.get_env("CERTFILE") || "priv/cert/selfsigned.pem",
diff --git a/apps/block_scout_web/config/prod.exs b/apps/block_scout_web/config/prod.exs
index 1fb27bdbed..c388caab64 100644
--- a/apps/block_scout_web/config/prod.exs
+++ b/apps/block_scout_web/config/prod.exs
@@ -20,8 +20,10 @@ config :block_scout_web, BlockScoutWeb.Endpoint,
check_origin: System.get_env("CHECK_ORIGIN") || false,
http: [port: System.get_env("PORT")],
url: [
- scheme: "http",
- port: System.get_env("PORT")
+ scheme: "https",
+ port: System.get_env("PORT"),
+ host: System.get_env("BLOCKSCOUT_HOST") || "localhost",
+ path: System.get_env("NETWORK_PATH") || "/"
]
config :block_scout_web, BlockScoutWeb.Tracer, env: "production", disabled?: true
diff --git a/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex b/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex
index cbbaa7eba7..4009d4f461 100644
--- a/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex
+++ b/apps/block_scout_web/lib/block_scout_web/channels/address_channel.ex
@@ -11,7 +11,7 @@ defmodule BlockScoutWeb.AddressChannel do
alias Explorer.ExchangeRates.Token
alias Phoenix.View
- intercept(["balance_update", "coin_balance", "count", "internal_transaction", "transaction"])
+ intercept(["balance_update", "coin_balance", "count", "internal_transaction", "transaction", "verification_result"])
def join("addresses:" <> address_hash, _params, socket) do
{:ok, %{}, assign(socket, :address_hash, address_hash)}
@@ -58,6 +58,18 @@ defmodule BlockScoutWeb.AddressChannel do
end
end
+ def handle_out("verification_result", result, socket) do
+ case result[:result] do
+ {:ok, _contract} ->
+ push(socket, "verification", %{verification_result: :ok})
+ {:noreply, socket}
+
+ {:error, result} ->
+ push(socket, "verification", %{verification_result: result})
+ {:noreply, socket}
+ end
+ end
+
def handle_out("count", %{count: count}, socket) do
Gettext.put_locale(BlockScoutWeb.Gettext, socket.assigns.locale)
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex
index 312edffcca..9267c0dcab 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_coin_balance_controller.ex
@@ -16,7 +16,7 @@ defmodule BlockScoutWeb.AddressCoinBalanceController 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 <- Chain.check_address_exists(address_hash) do
full_options = paging_options(params)
coin_balances_plus_one = Chain.address_to_coin_balances(address_hash, full_options)
@@ -32,7 +32,7 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
address_coin_balance_path(
conn,
:index,
- address,
+ address_hash,
Map.delete(next_page_params, "type")
)
end
@@ -52,7 +52,7 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
:error ->
unprocessable_entity(conn)
- {:error, :not_found} ->
+ :not_found ->
not_found(conn)
end
end
@@ -64,8 +64,8 @@ defmodule BlockScoutWeb.AddressCoinBalanceController do
address: address,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
- transaction_count: transaction_count(address),
- validation_count: validation_count(address),
+ transaction_count: transaction_count(address_hash),
+ validation_count: validation_count(address_hash),
current_path: current_path(conn)
)
end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex
index 34238a018d..5db900b12b 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_controller.ex
@@ -8,16 +8,26 @@ defmodule BlockScoutWeb.AddressContractController do
alias Indexer.Fetcher.CoinBalanceOnDemand
def index(conn, %{"address_id" => address_hash_string}) do
+ address_options = [
+ necessity_by_association: %{
+ :contracts_creation_internal_transaction => :optional,
+ :names => :optional,
+ :smart_contract => :optional,
+ :token => :optional,
+ :contracts_creation_transaction => :optional
+ }
+ ]
+
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
- {:ok, address} <- Chain.find_contract_address(address_hash) do
+ {:ok, address} <- Chain.find_contract_address(address_hash, address_options, true) do
render(
conn,
"index.html",
address: address,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
- transaction_count: transaction_count(address),
- validation_count: validation_count(address)
+ transaction_count: transaction_count(address_hash),
+ validation_count: validation_count(address_hash)
)
else
:error ->
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex
index dad0ebaeaf..cfffb85f3b 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_contract_verification_controller.ex
@@ -2,7 +2,7 @@ defmodule BlockScoutWeb.AddressContractVerificationController do
use BlockScoutWeb, :controller
alias Explorer.Chain.SmartContract
- alias Explorer.SmartContract.{Publisher, Solidity.CodeCompiler, Solidity.CompilerVersion}
+ alias Explorer.SmartContract.{PublisherWorker, Solidity.CodeCompiler, Solidity.CompilerVersion}
def new(conn, %{"address_id" => address_hash_string}) do
changeset =
@@ -16,31 +16,21 @@ defmodule BlockScoutWeb.AddressContractVerificationController do
render(conn, "new.html",
changeset: changeset,
compiler_versions: compiler_versions,
- evm_versions: CodeCompiler.allowed_evm_versions()
+ evm_versions: CodeCompiler.allowed_evm_versions(),
+ address_hash: address_hash_string
)
end
def create(
conn,
%{
- "address_id" => address_hash_string,
"smart_contract" => smart_contract,
"external_libraries" => external_libraries
}
) do
- case Publisher.publish(address_hash_string, smart_contract, external_libraries) do
- {:ok, _smart_contract} ->
- redirect(conn, to: address_contract_path(conn, :index, address_hash_string))
+ Que.add(PublisherWorker, {smart_contract["address_hash"], smart_contract, external_libraries, conn})
- {:error, changeset} ->
- {:ok, compiler_versions} = CompilerVersion.fetch_versions()
-
- render(conn, "new.html",
- changeset: changeset,
- compiler_versions: compiler_versions,
- evm_versions: CodeCompiler.allowed_evm_versions()
- )
- end
+ send_resp(conn, 204, "")
end
def parse_optimization_runs(%{"runs" => runs}) do
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex
index 6ce33a2773..9f955fa672 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex
@@ -5,7 +5,7 @@ defmodule BlockScoutWeb.AddressController do
alias BlockScoutWeb.AddressView
alias Explorer.{Chain, Market}
- alias Explorer.Chain.Address
+ alias Explorer.Chain.Hash
alias Explorer.ExchangeRates.Token
alias Phoenix.View
@@ -45,7 +45,7 @@ defmodule BlockScoutWeb.AddressController do
exchange_rate: exchange_rate,
total_supply: total_supply,
tx_count: tx_count,
- validation_count: validation_count(address)
+ validation_count: validation_count(address.hash)
)
end)
@@ -69,11 +69,11 @@ defmodule BlockScoutWeb.AddressController do
redirect(conn, to: address_transaction_path(conn, :index, id))
end
- def transaction_count(%Address{} = address) do
- Chain.total_transactions_sent_by_address(address)
+ def transaction_count(%Hash{byte_count: unquote(Hash.Address.byte_count())} = address_hash) do
+ Chain.total_transactions_sent_by_address(address_hash)
end
- def validation_count(%Address{} = address) do
- Chain.address_to_validation_count(address)
+ def validation_count(%Hash{byte_count: unquote(Hash.Address.byte_count())} = address_hash) do
+ Chain.address_to_validation_count(address_hash)
end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_decompiled_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_decompiled_contract_controller.ex
index ddee462ba2..be591ce384 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_decompiled_contract_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_decompiled_contract_controller.ex
@@ -16,8 +16,8 @@ defmodule BlockScoutWeb.AddressDecompiledContractController do
address: address,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
- transaction_count: transaction_count(address),
- validation_count: validation_count(address)
+ transaction_count: transaction_count(address_hash),
+ validation_count: validation_count(address_hash)
)
else
:error ->
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex
index c4f9b11c3b..15aa87e69a 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_internal_transaction_controller.ex
@@ -16,7 +16,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionController 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, [], false) do
full_options =
[
necessity_by_association: %{
@@ -28,7 +28,7 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do
|> Keyword.merge(paging_options(params))
|> Keyword.merge(current_filter(params))
- internal_transactions_plus_one = Chain.address_to_internal_transactions(address, full_options)
+ internal_transactions_plus_one = Chain.address_to_internal_transactions(address_hash, full_options)
{internal_transactions, next_page} = split_list_by_page(internal_transactions_plus_one)
next_page_path =
@@ -71,8 +71,8 @@ defmodule BlockScoutWeb.AddressInternalTransactionController do
current_path: current_path(conn),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
filter: params["filter"],
- transaction_count: transaction_count(address),
- validation_count: validation_count(address)
+ transaction_count: transaction_count(address_hash),
+ validation_count: validation_count(address_hash)
)
else
:error ->
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex
index db76b9d447..d3ea4e5303 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_logs_controller.ex
@@ -16,8 +16,8 @@ defmodule BlockScoutWeb.AddressLogsController 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
- logs_plus_one = Chain.address_to_logs(address, paging_options(params))
+ :ok <- Chain.check_address_exists(address_hash) do
+ logs_plus_one = Chain.address_to_logs(address_hash, paging_options(params))
{results, next_page} = split_list_by_page(logs_plus_one)
next_page_url =
@@ -26,7 +26,7 @@ defmodule BlockScoutWeb.AddressLogsController do
nil
next_page_params ->
- address_logs_path(conn, :index, address, Map.delete(next_page_params, "type"))
+ address_logs_path(conn, :index, address_hash, Map.delete(next_page_params, "type"))
end
items =
@@ -63,8 +63,8 @@ defmodule BlockScoutWeb.AddressLogsController do
current_path: current_path(conn),
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
- transaction_count: transaction_count(address),
- validation_count: validation_count(address)
+ transaction_count: transaction_count(address_hash),
+ validation_count: validation_count(address_hash)
)
else
_ ->
@@ -74,12 +74,12 @@ defmodule BlockScoutWeb.AddressLogsController do
def search_logs(conn, %{"topic" => topic, "address_id" => address_hash_string} = params) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
- {:ok, address} <- Chain.hash_to_address(address_hash) do
+ :ok <- Chain.check_address_exists(address_hash) do
topic = String.trim(topic)
formatted_topic = if String.starts_with?(topic, "0x"), do: topic, else: "0x" <> topic
- logs_plus_one = Chain.address_to_logs(address, topic: formatted_topic)
+ logs_plus_one = Chain.address_to_logs(address_hash, topic: formatted_topic)
{results, next_page} = split_list_by_page(logs_plus_one)
@@ -89,7 +89,7 @@ defmodule BlockScoutWeb.AddressLogsController do
nil
next_page_params ->
- address_logs_path(conn, :index, address, Map.delete(next_page_params, "type"))
+ address_logs_path(conn, :index, address_hash, Map.delete(next_page_params, "type"))
end
items =
@@ -115,4 +115,6 @@ defmodule BlockScoutWeb.AddressLogsController do
not_found(conn)
end
end
+
+ def search_logs(conn, _), do: not_found(conn)
end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex
index a9a30cc0d0..0849689dce 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_read_contract_controller.ex
@@ -15,16 +15,26 @@ defmodule BlockScoutWeb.AddressReadContractController do
import BlockScoutWeb.AddressController, only: [transaction_count: 1, validation_count: 1]
def index(conn, %{"address_id" => address_hash_string}) do
+ address_options = [
+ necessity_by_association: %{
+ :contracts_creation_internal_transaction => :optional,
+ :names => :optional,
+ :smart_contract => :optional,
+ :token => :optional,
+ :contracts_creation_transaction => :optional
+ }
+ ]
+
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
- {:ok, address} <- Chain.find_contract_address(address_hash) do
+ {:ok, address} <- Chain.find_contract_address(address_hash, address_options, true) do
render(
conn,
"index.html",
address: address,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
- transaction_count: transaction_count(address),
- validation_count: validation_count(address)
+ transaction_count: transaction_count(address_hash),
+ validation_count: validation_count(address_hash)
)
else
:error ->
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex
index 312b90176b..d77baa605a 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex
@@ -12,7 +12,7 @@ defmodule BlockScoutWeb.AddressTokenController 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, [], false) do
tokens_plus_one = Chain.address_tokens_with_balance(address_hash, paging_options(params))
{tokens, next_page} = split_list_by_page(tokens_plus_one)
@@ -64,8 +64,8 @@ defmodule BlockScoutWeb.AddressTokenController do
current_path: current_path(conn),
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
- transaction_count: transaction_count(address),
- validation_count: validation_count(address)
+ transaction_count: transaction_count(address_hash),
+ validation_count: validation_count(address_hash)
)
else
:error ->
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex
index 2a1e47b295..4e0023b067 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex
@@ -85,8 +85,8 @@ defmodule BlockScoutWeb.AddressTokenTransferController do
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
current_path: current_path(conn),
token: token,
- transaction_count: transaction_count(address),
- validation_count: validation_count(address)
+ transaction_count: transaction_count(address_hash),
+ validation_count: validation_count(address_hash)
)
else
:error ->
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 51503fd7c1..8f5e5d96d5 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
@@ -23,20 +23,22 @@ defmodule BlockScoutWeb.AddressTransactionController do
[token_transfers: :token] => :optional,
[token_transfers: :to_address] => :optional,
[token_transfers: :from_address] => :optional,
- [token_transfers: :token_contract_address] => :optional
+ [token_transfers: :token_contract_address] => :optional,
+ :block => :required
}
]
def index(conn, %{"address_id" => address_hash_string, "type" => "JSON"} = params) do
+ address_options = [necessity_by_association: %{:names => :optional}]
+
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
- {:ok, address} <- Chain.hash_to_address(address_hash, [:names], false) do
+ {:ok, address} <- Chain.hash_to_address(address_hash, address_options, false) do
options =
@transaction_necessity_by_association
- |> put_in([:necessity_by_association, :block], :required)
|> Keyword.merge(paging_options(params))
|> Keyword.merge(current_filter(params))
- results_plus_one = Chain.address_to_transactions_with_rewards(address, options)
+ results_plus_one = Chain.address_to_transactions_with_rewards(address_hash, options)
{results, next_page} = split_list_by_page(results_plus_one)
next_page_url =
@@ -95,8 +97,8 @@ defmodule BlockScoutWeb.AddressTransactionController do
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null(),
filter: params["filter"],
- transaction_count: transaction_count(address),
- validation_count: validation_count(address),
+ transaction_count: transaction_count(address_hash),
+ validation_count: validation_count(address_hash),
current_path: current_path(conn)
)
else
@@ -108,7 +110,7 @@ defmodule BlockScoutWeb.AddressTransactionController do
end
end
- def token_transfers_csv(conn, %{"address_id" => address_hash_string}) do
+ def token_transfers_csv(conn, %{"address_id" => address_hash_string}) when is_binary(address_hash_string) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do
address
@@ -128,6 +130,8 @@ defmodule BlockScoutWeb.AddressTransactionController do
end
end
+ def token_transfers_csv(conn, _), do: not_found(conn)
+
def transactions_csv(conn, %{"address_id" => address_hash_string}) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do
@@ -147,4 +151,6 @@ defmodule BlockScoutWeb.AddressTransactionController do
not_found(conn)
end
end
+
+ def transactions_csv(conn, _), do: not_found(conn)
end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_validation_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_validation_controller.ex
index 42170cbfc9..7226dffcf8 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_validation_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_validation_controller.ex
@@ -17,7 +17,7 @@ defmodule BlockScoutWeb.AddressValidationController 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.find_or_insert_address_from_hash(address_hash) do
+ {:ok, _} <- Chain.find_or_insert_address_from_hash(address_hash, [], false) do
full_options =
Keyword.merge(
[
@@ -31,7 +31,7 @@ defmodule BlockScoutWeb.AddressValidationController do
paging_options(params)
)
- blocks_plus_one = Chain.get_blocks_validated_by_address(full_options, address)
+ blocks_plus_one = Chain.get_blocks_validated_by_address(full_options, address_hash)
{blocks, next_page} = split_list_by_page(blocks_plus_one)
next_page_path =
@@ -63,9 +63,6 @@ defmodule BlockScoutWeb.AddressValidationController do
else
:error ->
unprocessable_entity(conn)
-
- {:error, :not_found} ->
- not_found(conn)
end
end
@@ -78,8 +75,8 @@ defmodule BlockScoutWeb.AddressValidationController do
address: address,
coin_balance_status: CoinBalanceOnDemand.trigger_fetch(address),
current_path: current_path(conn),
- transaction_count: transaction_count(address),
- validation_count: validation_count(address),
+ transaction_count: transaction_count(address.hash),
+ validation_count: validation_count(address.hash),
exchange_rate: Market.get_exchange_rate(Explorer.coin()) || Token.null()
)
else
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex
index 8beb8ea8ea..835e19bf3e 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/block_controller.ex
@@ -3,14 +3,13 @@ defmodule BlockScoutWeb.API.RPC.BlockController do
alias BlockScoutWeb.Chain, as: ChainWeb
alias Explorer.Chain
- alias Explorer.Chain.BlockNumberCache
+ alias Explorer.Chain.Cache.BlockNumber
def getblockreward(conn, params) do
with {:block_param, {:ok, unsafe_block_number}} <- {:block_param, Map.fetch(params, "blockno")},
{:ok, block_number} <- ChainWeb.param_to_block_number(unsafe_block_number),
- block_options = [necessity_by_association: %{transactions: :optional}],
- {:ok, block} <- Chain.number_to_block(block_number, block_options) do
- reward = Chain.block_reward(block)
+ {:ok, block} <- Chain.number_to_block(block_number) do
+ reward = Chain.block_reward(block_number)
render(conn, :block_reward, block: block, reward: reward)
else
@@ -27,7 +26,7 @@ defmodule BlockScoutWeb.API.RPC.BlockController do
def eth_block_number(conn, params) do
id = Map.get(params, "id", 1)
- max_block_number = BlockNumberCache.max_number()
+ max_block_number = BlockNumber.max_number()
render(conn, :eth_block_number, number: max_block_number, id: id)
end
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
index bb386940b1..26ffd5e47f 100644
--- 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
@@ -1,38 +1,10 @@
defmodule BlockScoutWeb.API.RPC.EthController do
use BlockScoutWeb, :controller
- alias Ecto.Type, as: EctoType
- alias Explorer.{Chain, Repo}
- alias Explorer.Chain.{Block, Data, Hash, Hash.Address, Wei}
- alias Explorer.Etherscan.Logs
-
- @methods %{
- "eth_getBalance" => %{
- action: :eth_get_balance,
- notes: """
- the `earliest` parameter will not work as expected currently, because genesis block balances
- are not currently imported
- """
- },
- "eth_getLogs" => %{
- action: :eth_get_logs,
- notes: """
- Will never return more than 1000 log entries.
- """
- }
- }
-
- @index_to_word %{
- 0 => "first",
- 1 => "second",
- 2 => "third",
- 3 => "fourth"
- }
-
- def methods, do: @methods
+ alias Explorer.EthRPC
def eth_request(%{body_params: %{"_json" => requests}} = conn, _) when is_list(requests) do
- responses = responses(requests)
+ responses = EthRPC.responses(requests)
conn
|> put_status(200)
@@ -40,7 +12,7 @@ defmodule BlockScoutWeb.API.RPC.EthController do
end
def eth_request(%{body_params: %{"_json" => request}} = conn, _) do
- [response] = responses([request])
+ [response] = EthRPC.responses([request])
conn
|> put_status(200)
@@ -59,297 +31,10 @@ defmodule BlockScoutWeb.API.RPC.EthController do
_ -> request
end
- [response] = responses([decoded_request])
+ [response] = EthRPC.responses([decoded_request])
conn
|> put_status(200)
|> render("response.json", %{response: response})
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
-
- def eth_get_logs(filter_options) do
- with {:ok, address_or_topic_params} <- address_or_topic_params(filter_options),
- {:ok, from_block_param, to_block_param} <- logs_blocks_filter(filter_options),
- {:ok, from_block} <- cast_block(from_block_param),
- {:ok, to_block} <- cast_block(to_block_param) do
- filter =
- address_or_topic_params
- |> Map.put(:from_block, from_block)
- |> Map.put(:to_block, to_block)
- |> Map.put(:allow_non_consensus, true)
-
- {:ok, filter |> Logs.list_logs() |> Enum.map(&render_log/1)}
- else
- {:error, message} when is_bitstring(message) ->
- {:error, message}
-
- {:error, :empty} ->
- {:ok, []}
-
- _ ->
- {:error, "Something went wrong."}
- end
- end
-
- defp render_log(log) do
- topics =
- Enum.reject(
- [log.first_topic, log.second_topic, log.third_topic, log.fourth_topic],
- &is_nil/1
- )
-
- %{
- "address" => to_string(log.address_hash),
- "blockHash" => to_string(log.block_hash),
- "blockNumber" => Integer.to_string(log.block_number, 16),
- "data" => to_string(log.data),
- "logIndex" => Integer.to_string(log.index, 16),
- "removed" => log.block_consensus == false,
- "topics" => topics,
- "transactionHash" => to_string(log.transaction_hash),
- "transactionIndex" => log.transaction_index,
- "transactionLogIndex" => log.index,
- "type" => "mined"
- }
- end
-
- defp cast_block("0x" <> hexadecimal_digits = input) do
- case Integer.parse(hexadecimal_digits, 16) do
- {integer, ""} -> {:ok, integer}
- _ -> {:error, input <> " is not a valid block number"}
- end
- end
-
- defp cast_block(integer) when is_integer(integer), do: {:ok, integer}
- defp cast_block(_), do: {:error, "invalid block number"}
-
- defp address_or_topic_params(filter_options) do
- address_param = Map.get(filter_options, "address")
- topics_param = Map.get(filter_options, "topics")
-
- with {:ok, address} <- validate_address(address_param),
- {:ok, topics} <- validate_topics(topics_param) do
- address_and_topics(address, topics)
- end
- end
-
- defp address_and_topics(nil, nil), do: {:error, "Must supply one of address and topics"}
- defp address_and_topics(address, nil), do: {:ok, %{address_hash: address}}
- defp address_and_topics(nil, topics), do: {:ok, topics}
- defp address_and_topics(address, topics), do: {:ok, Map.put(topics, :address_hash, address)}
-
- defp validate_address(nil), do: {:ok, nil}
-
- defp validate_address(address) do
- case Address.cast(address) do
- {:ok, address} -> {:ok, address}
- :error -> {:error, "invalid address"}
- end
- end
-
- defp validate_topics(nil), do: {:ok, nil}
- defp validate_topics([]), do: []
-
- defp validate_topics(topics) when is_list(topics) do
- topics
- |> Stream.with_index()
- |> Enum.reduce({:ok, %{}}, fn {topic, index}, {:ok, acc} ->
- case cast_topics(topic) do
- {:ok, data} ->
- with_filter = Map.put(acc, String.to_existing_atom("#{@index_to_word[index]}_topic"), data)
-
- {:ok, add_operator(with_filter, index)}
-
- :error ->
- {:error, "invalid topics"}
- end
- end)
- end
-
- defp add_operator(filters, 0), do: filters
-
- defp add_operator(filters, index) do
- Map.put(filters, String.to_existing_atom("topic#{index - 1}_#{index}_opr"), "and")
- end
-
- defp cast_topics(topics) when is_list(topics) do
- case EctoType.cast({:array, Data}, topics) do
- {:ok, data} -> {:ok, Enum.map(data, &to_string/1)}
- :error -> :error
- end
- end
-
- defp cast_topics(topic) do
- case Data.cast(topic) do
- {:ok, data} -> {:ok, to_string(data)}
- :error -> :error
- end
- 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 logs_blocks_filter(filter_options) do
- with {:filter, %{"blockHash" => block_hash_param}} <- {:filter, filter_options},
- {:block_hash, {:ok, block_hash}} <- {:block_hash, Hash.Full.cast(block_hash_param)},
- {:block, %{number: number}} <- {:block, Repo.get(Block, block_hash)} do
- {:ok, number, number}
- else
- {:filter, filters} ->
- from_block = Map.get(filters, "fromBlock", "latest")
- to_block = Map.get(filters, "toBlock", "latest")
-
- max_block_number =
- if from_block == "latest" || to_block == "latest" do
- max_consensus_block_number()
- end
-
- pending_block_number =
- if from_block == "pending" || to_block == "pending" do
- max_non_consensus_block_number(max_block_number)
- end
-
- if is_nil(pending_block_number) && from_block == "pending" && to_block == "pending" do
- {:error, :empty}
- else
- to_block_numbers(from_block, to_block, max_block_number, pending_block_number)
- end
-
- {:block, _} ->
- {:error, "Invalid Block Hash"}
-
- {:block_hash, _} ->
- {:error, "Invalid Block Hash"}
- end
- end
-
- defp to_block_numbers(from_block, to_block, max_block_number, pending_block_number) do
- actual_pending_block_number = pending_block_number || max_block_number
-
- with {:ok, from} <-
- to_block_number(from_block, max_block_number, actual_pending_block_number),
- {:ok, to} <- to_block_number(to_block, max_block_number, actual_pending_block_number) do
- {:ok, from, to}
- end
- end
-
- defp to_block_number(integer, _, _) when is_integer(integer), do: {:ok, integer}
- defp to_block_number("latest", max_block_number, _), do: {:ok, max_block_number || 0}
- defp to_block_number("earliest", _, _), do: {:ok, 0}
- defp to_block_number("pending", max_block_number, nil), do: {:ok, max_block_number || 0}
- defp to_block_number("pending", _, pending), do: {:ok, pending}
-
- defp to_block_number("0x" <> number, _, _) do
- case Integer.parse(number, 16) do
- {integer, ""} -> {:ok, integer}
- _ -> {:error, "invalid block number"}
- end
- end
-
- defp to_block_number(number, _, _) when is_bitstring(number) do
- case Integer.parse(number, 16) do
- {integer, ""} -> {:ok, integer}
- _ -> {:error, "invalid block number"}
- end
- end
-
- defp to_block_number(_, _, _), do: {:error, "invalid block number"}
-
- defp max_non_consensus_block_number(max) do
- case Chain.max_non_consensus_block_number(max) do
- {:ok, number} -> number
- _ -> nil
- end
- end
-
- defp max_consensus_block_number do
- case Chain.max_consensus_block_number() do
- {:ok, number} -> number
- _ -> nil
- 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),
- {:correct_arity, true} <-
- {:correct_arity, :erlang.function_exported(__MODULE__, action, Enum.count(params))} do
- apply(__MODULE__, action, params)
- else
- {:correct_arity, _} ->
- {:error, "Incorrect number of params."}
-
- _ ->
- {: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
-
- defp get_action(action) do
- case Map.get(@methods, action) do
- %{action: action} ->
- {:ok, action}
-
- _ ->
- :error
- end
- end
-
- 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/api/rpc/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex
index 344dbe1037..565ea4b6a5 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex
@@ -10,7 +10,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionController do
{:format, {:ok, transaction_hash}} <- to_transaction_hash(txhash_param),
{:transaction, {:ok, transaction}} <- transaction_from_hash(transaction_hash),
paging_options <- paging_options(params) do
- logs = Chain.transaction_to_logs(transaction, paging_options)
+ logs = Chain.transaction_to_logs(transaction_hash, paging_options)
{logs, next_page} = split_list_by_page(logs)
render(conn, :gettxinfo, %{
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller.ex
index 1eec849380..7b783b9761 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/decompiled_smart_contract_controller.ex
@@ -7,8 +7,9 @@ defmodule BlockScoutWeb.API.V1.DecompiledSmartContractController do
def create(conn, params) do
if auth_token(conn) == actual_token() do
with {:ok, hash} <- validate_address_hash(params["address_hash"]),
- :ok <- smart_contract_exists?(hash),
- :ok <- decompiled_contract_exists?(params["address_hash"], params["decompiler_version"]) do
+ :ok <- Chain.check_address_exists(hash),
+ {:contract, :not_found} <-
+ {:contract, Chain.check_decompiled_contract_exists(params["address_hash"], params["decompiler_version"])} do
case Chain.create_decompiled_smart_contract(params) do
{:ok, decompiled_smart_contract} ->
send_resp(conn, :created, Jason.encode!(decompiled_smart_contract))
@@ -29,7 +30,7 @@ defmodule BlockScoutWeb.API.V1.DecompiledSmartContractController do
:not_found ->
send_resp(conn, :unprocessable_entity, encode(%{error: "address is not found"}))
- :contract_exists ->
+ {:contract, :ok} ->
send_resp(
conn,
:unprocessable_entity,
@@ -41,13 +42,6 @@ defmodule BlockScoutWeb.API.V1.DecompiledSmartContractController do
end
end
- defp smart_contract_exists?(address_hash) do
- case Chain.hash_to_address(address_hash) do
- {:ok, _address} -> :ok
- _ -> :not_found
- end
- end
-
defp validate_address_hash(address_hash) do
case Address.cast(address_hash) do
{:ok, hash} -> {:ok, hash}
@@ -55,13 +49,6 @@ defmodule BlockScoutWeb.API.V1.DecompiledSmartContractController do
end
end
- defp decompiled_contract_exists?(address_hash, decompiler_version) do
- case Chain.decompiled_code(address_hash, decompiler_version) do
- {:ok, _} -> :contract_exists
- _ -> :ok
- end
- end
-
defp auth_token(conn) do
case get_req_header(conn, "auth_token") do
[token] -> token
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex
index 957dc797be..1e65fa75ba 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/health_controller.ex
@@ -4,19 +4,22 @@ defmodule BlockScoutWeb.API.V1.HealthController do
alias Explorer.Chain
def health(conn, _) do
- with {:ok, number, timestamp} <- Chain.last_block_status() do
- send_resp(conn, :ok, result(number, timestamp))
+ with {:ok, number, timestamp} <- Chain.last_db_block_status(),
+ {:ok, cache_number, cache_timestamp} <- Chain.last_cache_block_status() do
+ send_resp(conn, :ok, result(number, timestamp, cache_number, cache_timestamp))
else
status -> send_resp(conn, :internal_server_error, error(status))
end
end
- def result(number, timestamp) do
+ def result(number, timestamp, cache_number, cache_timestamp) do
%{
"healthy" => true,
"data" => %{
"latest_block_number" => to_string(number),
- "latest_block_inserted_at" => to_string(timestamp)
+ "latest_block_inserted_at" => to_string(timestamp),
+ "cache_latest_block_number" => to_string(cache_number),
+ "cache_latest_block_inserted_at" => to_string(cache_timestamp)
}
}
|> Jason.encode!()
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex
index 50334a1a45..8b3d3d71eb 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/verified_smart_contract_controller.ex
@@ -7,8 +7,8 @@ defmodule BlockScoutWeb.API.V1.VerifiedSmartContractController do
def create(conn, params) do
with {:ok, hash} <- validate_address_hash(params["address_hash"]),
- :ok <- smart_contract_exists?(hash),
- :ok <- verified_smart_contract_exists?(hash) do
+ :ok <- Chain.check_address_exists(hash),
+ {:contract, :not_found} <- {:contract, Chain.check_verified_smart_contract_exists(hash)} do
external_libraries = fetch_external_libraries(params)
case Publisher.publish(hash, params, external_libraries) do
@@ -31,7 +31,7 @@ defmodule BlockScoutWeb.API.V1.VerifiedSmartContractController do
:not_found ->
send_resp(conn, :unprocessable_entity, encode(%{error: "address is not found"}))
- :contract_exists ->
+ {:contract, :ok} ->
send_resp(
conn,
:unprocessable_entity,
@@ -40,13 +40,6 @@ defmodule BlockScoutWeb.API.V1.VerifiedSmartContractController do
end
end
- defp smart_contract_exists?(address_hash) do
- case Chain.hash_to_address(address_hash) do
- {:ok, _address} -> :ok
- _ -> :not_found
- end
- end
-
defp validate_address_hash(address_hash) do
case Address.cast(address_hash) do
{:ok, hash} -> {:ok, hash}
@@ -54,14 +47,6 @@ defmodule BlockScoutWeb.API.V1.VerifiedSmartContractController do
end
end
- defp verified_smart_contract_exists?(address_hash) do
- if Chain.address_hash_to_smart_contract(address_hash) do
- :contract_exists
- else
- :ok
- end
- end
-
defp encode(data) do
Jason.encode!(data)
end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api_docs_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api_docs_controller.ex
index 9309884d5c..4a658c409e 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/api_docs_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/api_docs_controller.ex
@@ -1,8 +1,8 @@
defmodule BlockScoutWeb.APIDocsController do
use BlockScoutWeb, :controller
- alias BlockScoutWeb.API.RPC.EthController
alias BlockScoutWeb.Etherscan
+ alias Explorer.EthRPC
def index(conn, _params) do
conn
@@ -12,7 +12,7 @@ defmodule BlockScoutWeb.APIDocsController do
def eth_rpc(conn, _params) do
conn
- |> assign(:documentation, EthController.methods())
+ |> assign(:documentation, EthRPC.methods())
|> render("eth_rpc.html")
end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/block_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/block_transaction_controller.ex
index 15b06f1edd..a1e4cbeb09 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/block_transaction_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/block_transaction_controller.ex
@@ -26,7 +26,7 @@ defmodule BlockScoutWeb.BlockTransactionController do
paging_options(params)
)
- transactions_plus_one = Chain.block_to_transactions(block, full_options)
+ transactions_plus_one = Chain.block_to_transactions(block.hash, full_options)
{transactions, next_page} = split_list_by_page(transactions_plus_one)
@@ -89,7 +89,7 @@ defmodule BlockScoutWeb.BlockTransactionController do
:rewards => :optional
}
) do
- block_transaction_count = Chain.block_to_transaction_count(block)
+ block_transaction_count = Chain.block_to_transaction_count(block.hash)
render(
conn,
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex
index 303c7cb6fe..d5e11ad302 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/chain_controller.ex
@@ -2,7 +2,7 @@ defmodule BlockScoutWeb.ChainController do
use BlockScoutWeb, :controller
alias BlockScoutWeb.ChainView
- alias Explorer.{Chain, PagingOptions, Repo}
+ alias Explorer.{Chain, PagingOptions}
alias Explorer.Chain.{Address, Block, Transaction}
alias Explorer.Chain.Transaction.History.TransactionStats
alias Explorer.Chain.Supply.RSK
@@ -75,6 +75,8 @@ defmodule BlockScoutWeb.ChainController do
end
end
+ def search(conn, _), do: not_found(conn)
+
def token_autocomplete(conn, %{"q" => term}) when is_binary(term) do
if term == "" do
json(conn, "{}")
@@ -95,9 +97,15 @@ defmodule BlockScoutWeb.ChainController do
def chain_blocks(conn, _params) do
if ajax?(conn) do
blocks =
- [paging_options: %PagingOptions{page_size: 4}]
+ [
+ paging_options: %PagingOptions{page_size: 4},
+ necessity_by_association: %{
+ [miner: :names] => :optional,
+ :transactions => :optional,
+ :rewards => :optional
+ }
+ ]
|> Chain.list_blocks()
- |> Repo.preload([[miner: :names], :transactions, :rewards])
|> Enum.map(fn block ->
%{
chain_block_html:
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex
index 70ec6219f3..ff0a4e67aa 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/smart_contract_controller.ex
@@ -30,10 +30,12 @@ defmodule BlockScoutWeb.SmartContractController do
end
end
+ def index(conn, _), do: not_found(conn)
+
def show(conn, params) do
with true <- ajax?(conn),
{:ok, address_hash} <- Chain.string_to_address_hash(params["id"]),
- {:ok, _address} <- Chain.find_contract_address(address_hash),
+ :ok <- Chain.check_contract_address_exists(address_hash),
outputs =
Reader.query_function(
address_hash,
@@ -51,7 +53,7 @@ defmodule BlockScoutWeb.SmartContractController do
:error ->
unprocessable_entity(conn)
- {:error, :not_found} ->
+ :not_found ->
not_found(conn)
_ ->
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/holder_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/holder_controller.ex
index 3b23d4099a..4de35fc012 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/holder_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/holder_controller.ex
@@ -43,8 +43,10 @@ defmodule BlockScoutWeb.Tokens.HolderController do
end
def index(conn, %{"token_id" => address_hash_string}) do
+ options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}]
+
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
- {:ok, token} <- Chain.token_from_address_hash(address_hash, [{:contract_address, :smart_contract}]) do
+ {:ok, token} <- Chain.token_from_address_hash(address_hash, options) do
render(
conn,
"index.html",
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex
index bc9111a34d..b9783b2c63 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex
@@ -60,8 +60,10 @@ defmodule BlockScoutWeb.Tokens.InventoryController do
end
def index(conn, %{"token_id" => address_hash_string}) do
+ options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}]
+
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
- {:ok, token} <- Chain.token_from_address_hash(address_hash, [{:contract_address, :smart_contract}]) do
+ {:ok, token} <- Chain.token_from_address_hash(address_hash, options) do
render(
conn,
"index.html",
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/read_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/read_contract_controller.ex
index 050802da54..f6ae63f537 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/read_contract_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/read_contract_controller.ex
@@ -4,8 +4,10 @@ defmodule BlockScoutWeb.Tokens.ReadContractController do
alias Explorer.{Chain, Market}
def index(conn, %{"token_id" => address_hash_string}) do
+ options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}]
+
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
- {:ok, token} <- Chain.token_from_address_hash(address_hash, [{:contract_address, :smart_contract}]) do
+ {:ok, token} <- Chain.token_from_address_hash(address_hash, options) do
render(
conn,
"index.html",
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/transfer_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/transfer_controller.ex
index f92bcf2ff1..9b75087c45 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/transfer_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/transfer_controller.ex
@@ -44,8 +44,10 @@ defmodule BlockScoutWeb.Tokens.TransferController do
end
def index(conn, %{"token_id" => address_hash_string}) do
+ options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}]
+
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
- {:ok, token} <- Chain.token_from_address_hash(address_hash, [{:contract_address, :smart_contract}]) do
+ {:ok, token} <- Chain.token_from_address_hash(address_hash, options) do
render(
conn,
"index.html",
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex
index 1bedf97c1d..3f849c0e7c 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex
@@ -62,15 +62,15 @@ defmodule BlockScoutWeb.TransactionController do
def show(conn, %{"id" => id}) do
with {:ok, transaction_hash} <- Chain.string_to_transaction_hash(id),
- {:ok, %Chain.Transaction{} = transaction} <- Chain.hash_to_transaction(transaction_hash) do
- if Chain.transaction_has_token_transfers?(transaction.hash) do
+ :ok <- Chain.check_transaction_exists(transaction_hash) do
+ if Chain.transaction_has_token_transfers?(transaction_hash) do
redirect(conn, to: transaction_token_transfer_path(conn, :index, id))
else
redirect(conn, to: transaction_internal_transaction_path(conn, :index, id))
end
else
:error -> conn |> put_status(422) |> render("invalid.html", transaction_hash: id)
- {:error, :not_found} -> conn |> put_status(404) |> render("not_found.html", transaction_hash: id)
+ :not_found -> conn |> put_status(404) |> render("not_found.html", transaction_hash: id)
end
end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex
index 159d144cdd..ca154a0136 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex
@@ -10,7 +10,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do
def index(conn, %{"transaction_id" => hash_string, "type" => "JSON"} = params) do
with {:ok, hash} <- Chain.string_to_transaction_hash(hash_string),
- {:ok, transaction} <- Chain.hash_to_transaction(hash) do
+ :ok <- Chain.check_transaction_exists(hash) do
full_options =
Keyword.merge(
[
@@ -24,7 +24,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do
paging_options(params)
)
- internal_transactions_plus_one = Chain.transaction_to_internal_transactions(transaction, full_options)
+ internal_transactions_plus_one = Chain.transaction_to_internal_transactions(hash, full_options)
{internal_transactions, next_page} = split_list_by_page(internal_transactions_plus_one)
@@ -37,7 +37,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do
transaction_internal_transaction_path(
conn,
:index,
- transaction,
+ hash,
Map.delete(next_page_params, "type")
)
end
@@ -66,7 +66,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do
|> put_view(TransactionView)
|> render("invalid.html", transaction_hash: hash_string)
- {:error, :not_found} ->
+ :not_found ->
conn
|> put_status(404)
|> put_view(TransactionView)
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_log_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_log_controller.ex
index 0a87d12493..e076b9ee7b 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_log_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_log_controller.ex
@@ -11,7 +11,9 @@ defmodule BlockScoutWeb.TransactionLogController do
def index(conn, %{"transaction_id" => transaction_hash_string, "type" => "JSON"} = params) do
with {:ok, transaction_hash} <- Chain.string_to_transaction_hash(transaction_hash_string),
{:ok, transaction} <-
- Chain.hash_to_transaction(transaction_hash) do
+ Chain.hash_to_transaction(transaction_hash,
+ necessity_by_association: %{[to_address: :smart_contract] => :optional}
+ ) do
full_options =
Keyword.merge(
[
@@ -22,7 +24,7 @@ defmodule BlockScoutWeb.TransactionLogController do
paging_options(params)
)
- logs_plus_one = Chain.transaction_to_logs(transaction, full_options)
+ logs_plus_one = Chain.transaction_to_logs(transaction_hash, full_options)
{logs, next_page} = split_list_by_page(logs_plus_one)
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex
index 3d090e8a9b..250a6b4442 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_raw_trace_controller.ex
@@ -19,7 +19,7 @@ defmodule BlockScoutWeb.TransactionRawTraceController do
:token_transfers => :optional
}
) do
- internal_transactions = Chain.transaction_to_internal_transactions(transaction)
+ internal_transactions = Chain.transaction_to_internal_transactions(hash)
render(
conn,
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_token_transfer_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_token_transfer_controller.ex
index cfb215da3e..585a01301f 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_token_transfer_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_token_transfer_controller.ex
@@ -10,8 +10,7 @@ defmodule BlockScoutWeb.TransactionTokenTransferController do
def index(conn, %{"transaction_id" => hash_string, "type" => "JSON"} = params) do
with {:ok, hash} <- Chain.string_to_transaction_hash(hash_string),
- {:ok, transaction} <-
- Chain.hash_to_transaction(hash) do
+ :ok <- Chain.check_transaction_exists(hash) do
full_options =
Keyword.merge(
[
@@ -24,7 +23,7 @@ defmodule BlockScoutWeb.TransactionTokenTransferController do
paging_options(params)
)
- token_transfers_plus_one = Chain.transaction_to_token_transfers(transaction, full_options)
+ token_transfers_plus_one = Chain.transaction_to_token_transfers(hash, full_options)
{token_transfers, next_page} = split_list_by_page(token_transfers_plus_one)
@@ -34,7 +33,7 @@ defmodule BlockScoutWeb.TransactionTokenTransferController do
nil
next_page_params ->
- transaction_token_transfer_path(conn, :index, transaction, Map.delete(next_page_params, "type"))
+ transaction_token_transfer_path(conn, :index, hash, Map.delete(next_page_params, "type"))
end
items =
@@ -62,7 +61,7 @@ defmodule BlockScoutWeb.TransactionTokenTransferController do
|> put_view(TransactionView)
|> render("invalid.html", transaction_hash: hash_string)
- {:error, :not_found} ->
+ :not_found ->
conn
|> put_status(404)
|> put_view(TransactionView)
diff --git a/apps/block_scout_web/lib/block_scout_web/notifier.ex b/apps/block_scout_web/lib/block_scout_web/notifier.ex
index 0139a9020a..40ce428144 100644
--- a/apps/block_scout_web/lib/block_scout_web/notifier.ex
+++ b/apps/block_scout_web/lib/block_scout_web/notifier.ex
@@ -4,12 +4,14 @@ defmodule BlockScoutWeb.Notifier do
"""
alias Absinthe.Subscription
- alias BlockScoutWeb.Endpoint
+ alias BlockScoutWeb.{AddressContractVerificationView, Endpoint}
alias Explorer.{Chain, Market, Repo}
alias Explorer.Chain.{Address, InternalTransaction, Transaction}
alias Explorer.Chain.Transaction.History.TransactionStats
alias Explorer.Counters.AverageBlockTime
alias Explorer.ExchangeRates.Token
+ alias Explorer.SmartContract.{Solidity.CodeCompiler, Solidity.CompilerVersion}
+ alias Phoenix.View
def handle_event({:chain_event, :addresses, type, addresses}) when type in [:realtime, :on_demand] do
Endpoint.broadcast("addresses:new_address", "count", %{count: Chain.count_addresses_with_balance_from_cache()})
@@ -24,6 +26,38 @@ defmodule BlockScoutWeb.Notifier do
Enum.each(address_coin_balances, &broadcast_address_coin_balance/1)
end
+ def handle_event(
+ {:chain_event, :contract_verification_result, :on_demand, {address_hash, contract_verification_result, conn}}
+ ) do
+ contract_verification_result =
+ case contract_verification_result do
+ {:ok, _} = result ->
+ result
+
+ {:error, changeset} ->
+ {:ok, compiler_versions} = CompilerVersion.fetch_versions()
+
+ result =
+ View.render_to_string(AddressContractVerificationView, "new.html",
+ changeset: changeset,
+ compiler_versions: compiler_versions,
+ evm_versions: CodeCompiler.allowed_evm_versions(),
+ address_hash: address_hash,
+ conn: conn
+ )
+
+ {:error, result}
+ end
+
+ Endpoint.broadcast(
+ "addresses:#{address_hash}",
+ "verification_result",
+ %{
+ result: contract_verification_result
+ }
+ )
+ end
+
def handle_event({:chain_event, :block_rewards, :realtime, rewards}) do
if Application.get_env(:block_scout_web, BlockScoutWeb.Chain)[:has_emission_funds] do
broadcast_rewards(rewards)
diff --git a/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex b/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex
index 508ba310c1..a02e656560 100644
--- a/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex
+++ b/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex
@@ -23,6 +23,7 @@ defmodule BlockScoutWeb.RealtimeEventHandler do
Subscriber.to(:transactions, :realtime)
Subscriber.to(:addresses, :on_demand)
Subscriber.to(:address_coin_balances, :on_demand)
+ Subscriber.to(:contract_verification_result, :on_demand)
# Does not come from the indexer
Subscriber.to(:exchange_rate)
Subscriber.to(:transaction_stats)
diff --git a/apps/block_scout_web/lib/block_scout_web/resolvers/transaction.ex b/apps/block_scout_web/lib/block_scout_web/resolvers/transaction.ex
index aa54a8e6da..fe96463794 100644
--- a/apps/block_scout_web/lib/block_scout_web/resolvers/transaction.ex
+++ b/apps/block_scout_web/lib/block_scout_web/resolvers/transaction.ex
@@ -12,8 +12,8 @@ defmodule BlockScoutWeb.Resolvers.Transaction do
end
end
- def get_by(%Address{} = address, args, _) do
- address
+ def get_by(%Address{hash: address_hash}, args, _) do
+ address_hash
|> GraphQL.address_to_transactions_query()
|> Connection.from_query(&Repo.all/1, args, options(args))
end
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 2ec112cda7..2ea7624298 100644
--- a/apps/block_scout_web/lib/block_scout_web/router.ex
+++ b/apps/block_scout_web/lib/block_scout_web/router.ex
@@ -29,6 +29,12 @@ defmodule BlockScoutWeb.Router do
resources("/verified_smart_contracts", VerifiedSmartContractController, only: [:create])
end
+ scope "/verify_smart_contract" do
+ pipe_through(:api)
+
+ post("/contract_verifications", BlockScoutWeb.AddressContractVerificationController, :create)
+ end
+
scope "/api", BlockScoutWeb.API.RPC do
pipe_through(:api)
@@ -166,7 +172,7 @@ defmodule BlockScoutWeb.Router do
resources(
"/contract_verifications",
AddressContractVerificationController,
- only: [:new, :create],
+ only: [:new],
as: :verify_contract
)
@@ -260,6 +266,6 @@ defmodule BlockScoutWeb.Router do
get("/api_docs", APIDocsController, :index)
get("/eth_rpc_api_docs", APIDocsController, :eth_rpc)
- get("/:page", PageNotFoundController, :index)
+ get("/*path", 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 9680faa2a8..56f1a2bbb2 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
@@ -21,7 +21,7 @@
<%= if @total_supply do %>
- (<%= balance_percentage(@address, @total_supply) %>)
+ <%= balance_percentage(@address, @total_supply) %>
<% end %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/index.html.eex
index 3aaab46ad5..c594915d9f 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/address/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/address/index.html.eex
@@ -38,7 +38,7 @@
-
+ <%= render BlockScoutWeb.CommonComponentsView, "_table-loader.html" %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex
index e6178a4e72..3cb95a5331 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex
@@ -74,7 +74,7 @@
<%= if contract?(@address) do %>
<%= gettext(">=") %>
- <%= incoming_transaction_count(@address) %>
+ <%= incoming_transaction_count(@address.hash) %>
<%= gettext("Incoming Transactions") %>
<% else %>
@@ -146,7 +146,7 @@
-
+
-
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
+
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex
index 505413aa96..e8d6501e02 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract/index.html.eex
@@ -23,12 +23,22 @@
<%= if BlockScoutWeb.AddressView.smart_contract_verified?(@address) do %>
- <%= gettext "Contract name:" %>
- <%= @address.smart_contract.name %>
+ <%= gettext "Contract name:" %>
+ <%= @address.smart_contract.name %>
+
+
+ <%= gettext "Optimization enabled" %>
+ <%= format_optimization_text(@address.smart_contract.optimization) %>
- <%= gettext "Compiler version" %>
- <%= @address.smart_contract.compiler_version %>
+ <%= gettext "Compiler version" %>
+ <%= @address.smart_contract.compiler_version %>
+
+
+ <%= if @address.smart_contract.optimization && @address.smart_contract.optimization_runs do %>
+ <%= gettext "Optimization runs" %>
+ <%= @address.smart_contract.optimization_runs %>
+ <% end %>
<%= if @address.smart_contract.evm_version do %>
@@ -36,35 +46,18 @@
<%= @address.smart_contract.evm_version %>
<% end %>
-
- <%= gettext "Optimization enabled" %>
- <%= format_optimization_text(@address.smart_contract.optimization) %>
-
- <%= if @address.smart_contract.optimization && @address.smart_contract.optimization_runs do %>
-
- <%= gettext "Optimization runs" %>
- <%= @address.smart_contract.optimization_runs %>
-
- <% end %>
- <%= if @address.smart_contract.constructor_arguments do %>
-
- <%= gettext "Constructor arguments" %>
- <%= @address.smart_contract.constructor_arguments %>
-
- <% end %>
- <%= if @address.smart_contract.external_libraries do %>
-
-
-
<%= gettext "External libraries" %>
-
-
-
<%= format_smart_contract_abi(@address.smart_contract.abi) %>
-
-
-
- <% end %>
-
+ <%= if @address.smart_contract.constructor_arguments do %>
+
+
+
<%= gettext "Constructor Arguments" %>
+
+
+
<%= raw(format_constructor_arguments(@address.smart_contract)) %>
+
+
+
+ <% end %>
<%= gettext "Contract source code" %>
@@ -109,7 +102,7 @@
<% {:ok, contract_code} -> %>
-
<%= gettext "Contract Byte Code" %>
+
<%= gettext "Contract Byte Code" %>
<%= gettext "Copy Contract Byte Code" %>
@@ -120,6 +113,19 @@
<% end %>
+ <%= if BlockScoutWeb.AddressView.smart_contract_verified?(@address) do %>
+ <%= if @address.smart_contract.external_libraries && @address.smart_contract.external_libraries != [] do %>
+
+
+
<%= gettext "External libraries" %>
+
+
+
<%= raw(format_external_libraries(@address.smart_contract.external_libraries)) %>
+
+
+
+ <% end %>
+ <% end %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex
index 7214af2eee..e73e1434f0 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/address_contract_verification/new.html.eex
@@ -1,9 +1,15 @@
-
+
+
+
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex
index 3bc2e7fe9b..f18638db0d 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/address_internal_transaction/index.html.eex
@@ -66,7 +66,9 @@
-
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
+
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex
index 1b5cb5c4fd..3fb638b1d9 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex
@@ -11,6 +11,76 @@
) %>
+ <%= gettext "Decoded" %>
+
+ <%= case decode(@log, @log.transaction) do %>
+ <% {:error, :contract_not_verified} -> %>
+
+ <%= gettext "To see decoded input data, the contract must be verified." %>
+ <%= case @log.transaction do %>
+ <% %{to_address: %{hash: hash}} -> %>
+ <%= gettext "Verify the contract " %>
<%= gettext "here" %>
+ <% _ -> %>
+ <%= nil %>
+ <% end %>
+
+ <% {:error, :could_not_decode} -> %>
+
+ <%= gettext "Failed to decode log data." %>
+
+ <% {:ok, method_id, text, mapping} -> %>
+
+
+
" class="table thead-light table-bordered">
+
+
+ <%= gettext "Name" %>
+ <%= gettext "Type" %>
+ <%= gettext "Indexed?" %>
+ <%= gettext "Data" %>
+
+ <%= for {name, type, indexed?, value} <- mapping do %>
+
+
+ <%= case BlockScoutWeb.ABIEncodedValueView.copy_text(type, value) do %>
+ <% :error -> %>
+ <%= nil %>
+ <% copy_text -> %>
+
+
+
+
+
+ <% end %>
+
+ <%= name %>
+ <%= type %>
+ <%= indexed? %>
+
+ <%= BlockScoutWeb.ABIEncodedValueView.value_html(type, value) %>
+
+
+ <% end %>
+
+
+ <% _ -> %>
+ <%= nil %>
+ <% end %>
<%= gettext "Topics" %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex
index 3a44e0cb2d..fabbc7f7d8 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/address_logs/index.html.eex
@@ -27,7 +27,9 @@
-
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
+
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex
index 840d9cc1da..8510bdca64 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex
@@ -19,7 +19,9 @@
-
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
+
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex
index c2ab502bf0..738d70883d 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token_transfer/index.html.eex
@@ -21,7 +21,9 @@
-
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
+
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex
index c3a4c94129..83c5454627 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex
@@ -65,7 +65,9 @@
-
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
+
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_validation/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_validation/index.html.eex
index 3f85f03aba..03b37369bc 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/address_validation/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/address_validation/index.html.eex
@@ -22,7 +22,9 @@
<%= gettext "Something went wrong, click to reload." %>
-
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
+
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/api_docs/eth_rpc.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/eth_rpc.html.eex
index ba6be00103..134b3e13e1 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/api_docs/eth_rpc.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/api_docs/eth_rpc.html.eex
@@ -22,11 +22,13 @@
Supported Method
Notes
+ Parameters example
<%= for {method, info} <- Map.to_list(@documentation) do %>
<%= method %>
<%= Map.get(info, :notes, "N/A") %>
+ <%= Map.get(info, :example, "N/A") %>
<% end %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/block/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/block/index.html.eex
index 92b91ce287..dd8a3ed093 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/block/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/block/index.html.eex
@@ -11,7 +11,9 @@
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
-
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
+
<%= gettext "There are no blocks." %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/block_transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/block_transaction/index.html.eex
index a497ab1b7e..e0847cac86 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/block_transaction/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/block_transaction/index.html.eex
@@ -29,7 +29,9 @@
-
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
+
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex
index e53d4616da..48dda23aaa 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/chain/show.html.eex
@@ -5,7 +5,7 @@
-
+
@@ -135,8 +135,8 @@
<%= gettext "Something went wrong, click to reload." %>
-
-
+
+
@@ -161,12 +161,8 @@
<%= gettext "Something went wrong, click to retry." %>
-
-
-
-
-
- <%= gettext("Loading...") %>
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_table-loader.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_table-loader.html.eex
new file mode 100644
index 0000000000..ac7cc71f56
--- /dev/null
+++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_table-loader.html.eex
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/common_components/_tile-loader.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_tile-loader.html.eex
new file mode 100644
index 0000000000..134613a5d1
--- /dev/null
+++ b/apps/block_scout_web/lib/block_scout_web/templates/common_components/_tile-loader.html.eex
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
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 f152ec6bf9..2c221e4471 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
@@ -1,8 +1,18 @@
-
+
+
<%= link to: chain_path(@conn, :show), class: "navbar-brand", "data-test": "header_logo" do %>
-
+
<% end %>
+
">
@@ -82,7 +92,7 @@
- " href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+ " href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<%= render BlockScoutWeb.IconsView, "_active_icon.html" %>
@@ -90,8 +100,14 @@
+
+
+
+
+
+
-
-<%= render BlockScoutWeb.LayoutView, "_network_selector.html" %>
\ No newline at end of file
+<%= render BlockScoutWeb.LayoutView, "_network_selector.html" %>
+
\ No newline at end of file
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex
index c4cdfcb788..6edf5750fb 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex
@@ -24,6 +24,11 @@
+
<%= if not Explorer.Chain.finished_indexing?() do %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/pending_transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/pending_transaction/index.html.eex
index 4c97be61e1..e819285629 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/pending_transaction/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/pending_transaction/index.html.eex
@@ -23,13 +23,8 @@
<%= gettext "There are no pending transactions." %>
-
-
-
-
-
-
- <%= gettext("Loading") %>...
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/index.html.eex
index 3b24912dd5..8e83021c80 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/holder/index.html.eex
@@ -28,7 +28,9 @@
-
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
+
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/index.html.eex
index da834dbc88..1e78b11353 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/inventory/index.html.eex
@@ -27,7 +27,9 @@
-
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
+
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_details.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_details.html.eex
index 376f70de73..50eccb7185 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_details.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/overview/_details.html.eex
@@ -109,7 +109,7 @@
-
+
-
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
+
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex
index f009eeb2b3..9e68d7e1bd 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex
@@ -1,4 +1,5 @@
-
" class="table thead-light table-bordered table-responsive transaction-info-table">
+
+
" class="table thead-light table-bordered">
<%= gettext "Method Id" %>
0x<%= @method_id %>
@@ -7,49 +8,52 @@
Call
<%= @text %>
-
+
+
<%= unless Enum.empty?(@mapping) do %>
- " class="table thead-light table-bordered table-responsive">
-
-
- <%= gettext "Name" %>
- <%= gettext "Type" %>
- <%= gettext "Data" %>
-
- <%= for {name, type, value} <- @mapping do %>
+
+
" class="table thead-light table-bordered">
+
+
+ <%= gettext "Name" %>
+ <%= gettext "Type" %>
+ <%= gettext "Data" %>
-
- <%= case BlockScoutWeb.ABIEncodedValueView.copy_text(type, value) do %>
- <% :error -> %>
- <%= nil %>
- <% copy_text -> %>
-
-
-
-
-
- <% end %>
-
- <%= name %>
- <%= type %>
-
- <%= case BlockScoutWeb.ABIEncodedValueView.value_html(type, value) do %>
- <% :error -> %>
-
- <%= gettext "Error rendering value" %>
-
- <% value -> %>
- <%= value %>
+ <%= for {name, type, value} <- @mapping do %>
+
+
+ <%= case BlockScoutWeb.ABIEncodedValueView.copy_text(type, value) do %>
+ <% :error -> %>
+ <%= nil %>
+ <% copy_text -> %>
+
+
+
+
+
<% end %>
-
-
- <% end %>
-
+
+
<%= name %>
+
<%= type %>
+
+ <%= case BlockScoutWeb.ABIEncodedValueView.value_html(type, value) do %>
+ <% :error -> %>
+
+ <%= gettext "Error rendering value" %>
+
+ <% value -> %>
+ <%= value %>
+ <% end %>
+
+
+ <% end %>
+
+
<% end %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/index.html.eex
index 18928915f2..bce59eb00b 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/index.html.eex
@@ -28,7 +28,9 @@
-
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
+
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex
index e6623b4061..70162a3be8 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex
@@ -16,7 +16,9 @@
-
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
+
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/_logs.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/_logs.html.eex
index 5a52821601..ff3e46d42a 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/_logs.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/_logs.html.eex
@@ -39,43 +39,45 @@
<%= text %>
- " class="table thead-light table-bordered table-responsive">
-
-
- <%= gettext "Name" %>
- <%= gettext "Type" %>
- <%= gettext "Indexed?" %>
- <%= gettext "Data" %>
-
- <%= for {name, type, indexed?, value} <- mapping do %>
+
+
" class="table thead-light table-bordered">
-
- <%= case BlockScoutWeb.ABIEncodedValueView.copy_text(type, value) do %>
- <% :error -> %>
- <%= nil %>
- <% copy_text -> %>
-
-
-
-
-
- <% end %>
-
- <%= name %>
- <%= type %>
- <%= indexed? %>
-
- <%= BlockScoutWeb.ABIEncodedValueView.value_html(type, value) %>
-
-
- <% end %>
-
+
+
<%= gettext "Name" %>
+
<%= gettext "Type" %>
+
<%= gettext "Indexed?" %>
+
<%= gettext "Data" %>
+
+ <%= for {name, type, indexed?, value} <- mapping do %>
+
+
+ <%= case BlockScoutWeb.ABIEncodedValueView.copy_text(type, value) do %>
+ <% :error -> %>
+ <%= nil %>
+ <% copy_text -> %>
+
+
+
+
+
+ <% end %>
+
+ <%= name %>
+ <%= type %>
+ <%= indexed? %>
+
+ <%= BlockScoutWeb.ABIEncodedValueView.value_html(type, value) %>
+
+
+ <% end %>
+
+
<% _ -> %>
<%= nil %>
<% end %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/index.html.eex
index 3c84e7c4f9..17f480b439 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_log/index.html.eex
@@ -19,7 +19,9 @@
-
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
+
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction_token_transfer/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction_token_transfer/index.html.eex
index d40dedd414..8d356a450a 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/transaction_token_transfer/index.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction_token_transfer/index.html.eex
@@ -18,7 +18,9 @@
-
+
+ <%= render BlockScoutWeb.CommonComponentsView, "_tile-loader.html" %>
+
<%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex
index 4dc99a2a68..57c6e11da7 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex
@@ -1,6 +1,7 @@
defmodule BlockScoutWeb.AddressContractView do
use BlockScoutWeb, :view
+ alias ABI.{FunctionSelector, TypeDecoder}
alias Explorer.Chain.{Address, Data, InternalTransaction}
def render("scripts.html", %{conn: conn}) do
@@ -21,6 +22,47 @@ defmodule BlockScoutWeb.AddressContractView do
def format_optimization_text(true), do: gettext("true")
def format_optimization_text(false), do: gettext("false")
+ def format_constructor_arguments(contract) do
+ constructor_abi = Enum.find(contract.abi, fn el -> el["type"] == "constructor" && el["inputs"] != [] end)
+
+ input_types = Enum.map(constructor_abi["inputs"], &FunctionSelector.parse_specification_type/1)
+
+ {_, result} =
+ contract.constructor_arguments
+ |> decode_data(input_types)
+ |> Enum.zip(constructor_abi["inputs"])
+ |> Enum.reduce({0, "#{contract.constructor_arguments}\n\n"}, fn {val, %{"type" => type}}, {count, acc} ->
+ formatted_val =
+ if is_binary(val) do
+ Base.encode16(val, case: :lower)
+ else
+ val
+ end
+
+ {count + 1, "#{acc}Arg [#{count}] (#{type} ) : #{formatted_val}\n"}
+ end)
+
+ result
+ rescue
+ _ -> contract.constructor_arguments
+ end
+
+ defp decode_data("0x" <> encoded_data, types) do
+ decode_data(encoded_data, types)
+ end
+
+ defp decode_data(encoded_data, types) do
+ encoded_data
+ |> Base.decode16!(case: :mixed)
+ |> TypeDecoder.decode_raw(types)
+ end
+
+ def format_external_libraries(libraries) do
+ Enum.reduce(libraries, "", fn %{name: name, address_hash: address_hash}, acc ->
+ "#{acc}#{name} : #{address_hash} \n"
+ end)
+ end
+
def contract_lines_with_index(contract_source_code) do
contract_lines = String.split(contract_source_code, "\n")
diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_logs_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_logs_view.ex
index 7155e65206..3cdfaf1f9b 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/address_logs_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/address_logs_view.ex
@@ -1,3 +1,9 @@
defmodule BlockScoutWeb.AddressLogsView do
use BlockScoutWeb, :view
+
+ alias Explorer.Chain.Log
+
+ def decode(log, transaction) do
+ Log.decode(log, transaction)
+ end
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex
index a3abe03cf4..f93b8bcb0b 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex
@@ -189,8 +189,8 @@ defmodule BlockScoutWeb.AddressView do
|> Timex.format!("{M}-{D}-{YYYY}")
end
- def qr_code(%Address{hash: hash}) do
- hash
+ def qr_code(address_hash) do
+ address_hash
|> to_string()
|> QRCode.to_png()
|> Base.encode64()
@@ -219,10 +219,10 @@ defmodule BlockScoutWeb.AddressView do
def token_title(%Token{name: name, symbol: symbol}), do: "#{name} (#{symbol})"
- def incoming_transaction_count(%Address{} = address) do
- count = Chain.address_to_incoming_transaction_count(address)
-
- Cldr.Number.to_string!(count, format: "#,###")
+ def incoming_transaction_count(address_hash) do
+ address_hash
+ |> Chain.address_to_incoming_transaction_count()
+ |> Cldr.Number.to_string!(format: "#,###")
end
def trimmed_hash(%Hash{} = hash) do
diff --git a/apps/block_scout_web/lib/block_scout_web/views/api_docs_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api_docs_view.ex
index f9c280925a..f71ebc240b 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/api_docs_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/api_docs_view.ex
@@ -35,8 +35,13 @@ defmodule BlockScoutWeb.APIDocsView do
end
def blockscout_url do
- if System.get_env("BLOCKSCOUT_HOST") do
- "http://" <> System.get_env("BLOCKSCOUT_HOST")
+ url_params = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)[:url]
+ host = url_params[:host]
+ path = url_params[:path]
+ scheme = url_params[:scheme]
+
+ if host != "localhost" do
+ scheme <> "://" <> host <> path
else
Endpoint.url()
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/internal_transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/internal_transaction_view.ex
index fe12f04302..46252f72ce 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/internal_transaction_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/internal_transaction_view.ex
@@ -23,6 +23,7 @@ defmodule BlockScoutWeb.InternalTransactionView do
def type(%InternalTransaction{type: :call, call_type: :delegatecall}), do: gettext("Delegate Call")
def type(%InternalTransaction{type: :call, call_type: :staticcall}), do: gettext("Static Call")
def type(%InternalTransaction{type: :create}), do: gettext("Create")
+ def type(%InternalTransaction{type: :create2}), do: gettext("Create2")
def type(%InternalTransaction{type: :selfdestruct}), do: gettext("Self-Destruct")
def type(%InternalTransaction{type: :reward}), do: gettext("Reward")
end
diff --git a/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex b/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex
index b33b1dd4c2..fdcd199ede 100644
--- a/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex
+++ b/apps/block_scout_web/lib/block_scout_web/views/layout_view.ex
@@ -110,7 +110,8 @@ defmodule BlockScoutWeb.LayoutView do
user_agent =
case Conn.get_req_header(conn, "user-agent") do
[] -> "unknown"
- user_agent -> user_agent
+ [user_agent] -> if String.valid?(user_agent), do: user_agent, else: "unknown"
+ _other -> "unknown"
end
"""
diff --git a/apps/block_scout_web/mix.exs b/apps/block_scout_web/mix.exs
index 02e9d203fb..e431d4ecf5 100644
--- a/apps/block_scout_web/mix.exs
+++ b/apps/block_scout_web/mix.exs
@@ -15,7 +15,7 @@ defmodule BlockScoutWeb.Mixfile do
plt_add_deps: :transitive,
ignore_warnings: "../../.dialyzer-ignore"
],
- elixir: "~> 1.8",
+ elixir: "~> 1.9",
elixirc_paths: elixirc_paths(Mix.env()),
lockfile: "../../mix.lock",
package: package(),
diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot
index 96ed671590..3d44f6fcf2 100644
--- a/apps/block_scout_web/priv/gettext/default.pot
+++ b/apps/block_scout_web/priv/gettext/default.pot
@@ -62,7 +62,7 @@ msgid "(query)"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/app.html.eex:35
+#: lib/block_scout_web/templates/layout/app.html.eex:40
msgid "- We're indexing this chain right now. Some of the counts may be inaccurate."
msgstr ""
@@ -87,7 +87,7 @@ msgid "API for the %{subnetwork} - BlockScout"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:56
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:66
msgid "Accounts"
msgstr ""
@@ -157,7 +157,7 @@ msgid "Block Height: %{height}"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/app.html.eex:50
+#: lib/block_scout_web/templates/layout/app.html.eex:55
msgid "Block Mined, awaiting import..."
msgstr ""
@@ -178,13 +178,13 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:87
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:16
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:20
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:26
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:30
msgid "Blocks"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/app.html.eex:49
+#: lib/block_scout_web/templates/layout/app.html.eex:54
msgid "Blocks Indexed"
msgstr ""
@@ -197,7 +197,7 @@ msgid "Blocks Validated"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:247
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:253
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:47
msgid "Cancel"
msgstr ""
@@ -225,12 +225,12 @@ msgid "Code"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:34
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:40
msgid "Compiler"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:30
+#: lib/block_scout_web/templates/address_contract/index.html.eex:34
msgid "Compiler version"
msgstr ""
@@ -258,12 +258,12 @@ msgid "Connection Lost, click to load newer validations"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:82
+#: lib/block_scout_web/templates/address_contract/index.html.eex:75
msgid "Contract ABI"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:12
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:18
#: lib/block_scout_web/views/address_view.ex:97
msgid "Contract Address"
msgstr ""
@@ -286,7 +286,7 @@ msgid "Contract Creation"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:23
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:29
msgid "Contract Name"
msgstr ""
@@ -296,7 +296,7 @@ msgid "Contract name:"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:70
+#: lib/block_scout_web/templates/address_contract/index.html.eex:63
msgid "Contract source code"
msgstr ""
@@ -328,14 +328,6 @@ msgstr ""
msgid "Curl"
msgstr ""
-#, elixir-format
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:44
-#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:18
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:48
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:113
-msgid "Data"
-msgstr ""
-
#, elixir-format
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:53
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:188
@@ -376,7 +368,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_balance_card.html.eex:15
#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:21
-#: lib/block_scout_web/templates/layout/app.html.eex:55
+#: lib/block_scout_web/templates/layout/app.html.eex:60
#: 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:196
@@ -400,7 +392,7 @@ msgid "Fetching tokens..."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:26
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:36
msgid "Forked Blocks (Reorgs)"
msgstr ""
@@ -454,7 +446,7 @@ msgid "IN"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/app.html.eex:51
+#: lib/block_scout_web/templates/layout/app.html.eex:56
msgid "Indexing Tokens"
msgstr ""
@@ -486,7 +478,7 @@ msgid "Inventory"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/app.html.eex:52
+#: lib/block_scout_web/templates/layout/app.html.eex:57
msgid "Less than"
msgstr ""
@@ -507,13 +499,15 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:31
-#: lib/block_scout_web/templates/layout/app.html.eex:53
+#: lib/block_scout_web/templates/layout/app.html.eex:58
+#: lib/block_scout_web/views/address_view.ex:121
#: lib/block_scout_web/views/address_view.ex:121
msgid "Market Cap"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:129
+#: lib/block_scout_web/views/transaction_view.ex:129
msgid "Max of"
msgstr ""
@@ -553,14 +547,15 @@ msgid "Must be set to:"
msgstr ""
#, elixir-format
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:46
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:52
-#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:16
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:45
+#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:19
+#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:46
msgid "Name"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:61
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:67
msgid "No"
msgstr ""
@@ -577,7 +572,7 @@ msgid "OUT"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:40
+#: lib/block_scout_web/templates/address_contract/index.html.eex:30
msgid "Optimization enabled"
msgstr ""
@@ -602,7 +597,7 @@ msgid "Parent Hash"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:44
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:54
#: lib/block_scout_web/views/transaction_view.ex:143
#: lib/block_scout_web/views/transaction_view.ex:177
msgid "Pending"
@@ -620,7 +615,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:24
-#: lib/block_scout_web/templates/layout/app.html.eex:54
+#: lib/block_scout_web/templates/layout/app.html.eex:59
msgid "Price"
msgstr ""
@@ -651,7 +646,7 @@ msgid "Request URL"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:244
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:250
msgid "Reset"
msgstr ""
@@ -667,8 +662,8 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:14
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:102
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:119
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:118
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:135
msgid "Search"
msgstr ""
@@ -781,7 +776,7 @@ msgid "To"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:6
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:16
msgid "Toggle navigation"
msgstr ""
@@ -832,12 +827,6 @@ msgstr ""
msgid "Top Accounts - %{subnetwork} Explorer"
msgstr ""
-#, elixir-format
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:14
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:83
-msgid "Topics"
-msgstr ""
-
#, elixir-format
#: lib/block_scout_web/templates/block/overview.html.eex:67
msgid "Total Difficulty"
@@ -880,7 +869,7 @@ msgstr ""
#: lib/block_scout_web/templates/block_transaction/index.html.eex:10
#: lib/block_scout_web/templates/block_transaction/index.html.eex:18
#: lib/block_scout_web/templates/chain/show.html.eex:108
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:35
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:45
#: lib/block_scout_web/views/address_view.ex:305
msgid "Transactions"
msgstr ""
@@ -907,7 +896,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/block/overview.html.eex:80
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:23
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:33
msgid "Uncles"
msgstr ""
@@ -922,7 +911,7 @@ msgid "Used"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:39
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:49
msgid "Validated"
msgstr ""
@@ -948,7 +937,7 @@ msgid "Verify & Publish"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:243
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:249
msgid "Verify & publish"
msgstr ""
@@ -998,7 +987,7 @@ msgid "Wei"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:66
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:72
msgid "Yes"
msgstr ""
@@ -1008,7 +997,7 @@ msgid "at"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/address_contract_view.ex:22
+#: lib/block_scout_web/views/address_contract_view.ex:23
msgid "false"
msgstr ""
@@ -1026,7 +1015,7 @@ msgid "string"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/address_contract_view.ex:21
+#: lib/block_scout_web/views/address_contract_view.ex:22
msgid "true"
msgstr ""
@@ -1046,14 +1035,7 @@ msgid "Delegate Call"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/block/_tile.html.eex:47
-#: lib/block_scout_web/templates/chain/_block.html.eex:23
#: lib/block_scout_web/views/internal_transaction_view.ex:27
-msgid "Reward"
-msgstr ""
-
-#, elixir-format
-#: lib/block_scout_web/views/internal_transaction_view.ex:26
msgid "Self-Destruct"
msgstr ""
@@ -1065,33 +1047,27 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_read_contract/index.html.eex:14
#: lib/block_scout_web/templates/chain/show.html.eex:99
-#: lib/block_scout_web/templates/chain/show.html.eex:125
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:21
msgid "Loading..."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:241
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:247
msgid "Loading...."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:64
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:74
msgid "APIs"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:68
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:78
msgid "GraphQL"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/pending_transaction/index.html.eex:32
-msgid "Loading"
-msgstr ""
-
-#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:73
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:83
msgid "RPC"
msgstr ""
@@ -1141,48 +1117,37 @@ msgid "Static Call"
msgstr ""
#, elixir-format
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:14
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:14
msgid "Decoded"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:47
-msgid "Indexed?"
-msgstr ""
-
-#, elixir-format
-#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:17
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:46
-msgid "Type"
-msgstr ""
-
-#, elixir-format
-#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:3
+#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:4
msgid "Method Id"
msgstr ""
#, elixir-format
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:19
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:19
msgid "To see decoded input data, the contract must be verified."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:1
-msgid "Transaction Info"
-msgstr ""
-
-#, elixir-format
-#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:13
+#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:2
+#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:16
msgid "Transaction Inputs"
msgstr ""
#, elixir-format
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:22
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:11
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:22
msgid "Verify the contract "
msgstr ""
#, elixir-format
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:22
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:11
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:22
msgid "here"
@@ -1194,26 +1159,10 @@ msgid "Failed to decode input data."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:46
+#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:49
msgid "Error rendering value"
msgstr ""
-#, elixir-format
-#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:28
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:58
-msgid "Copy Value"
-msgstr ""
-
-#, elixir-format
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:29
-msgid "Failed to decode log data."
-msgstr ""
-
-#, elixir-format
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:42
-msgid "Log Data"
-msgstr ""
-
#, elixir-format
#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:34
#: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:61
@@ -1284,7 +1233,7 @@ msgid "There are no pending transactions."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/block/index.html.eex:16
+#: lib/block_scout_web/templates/block/index.html.eex:18
msgid "There are no blocks."
msgstr ""
@@ -1414,17 +1363,17 @@ msgid "Support"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:84
+#: lib/block_scout_web/templates/address_contract/index.html.eex:77
msgid "Copy ABI"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:100
+#: lib/block_scout_web/templates/address_contract/index.html.eex:93
msgid "Copy Contract Creation Code"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:72
+#: lib/block_scout_web/templates/address_contract/index.html.eex:65
msgid "Copy Source Code"
msgstr ""
@@ -1434,7 +1383,7 @@ msgid "Genesis Block"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:112
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:118
msgid "Contract Libraries"
msgstr ""
@@ -1490,14 +1439,14 @@ msgid "Error: Could not determine contract creator."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:96
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:100
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:112
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:116
msgid "Search by address, token symbol name, transaction hash, or block number"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:35
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:45
+#: lib/block_scout_web/templates/address_contract/index.html.eex:45
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:51
msgid "EVM Version"
msgstr ""
@@ -1527,8 +1476,8 @@ msgid "Decompiler version"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:45
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:77
+#: lib/block_scout_web/templates/address_contract/index.html.eex:39
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:83
msgid "Optimization runs"
msgstr ""
@@ -1594,27 +1543,27 @@ msgid "Block Details"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:112
+#: lib/block_scout_web/templates/address_contract/index.html.eex:105
msgid "Contract Byte Code"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:98
+#: lib/block_scout_web/templates/address_contract/index.html.eex:91
msgid "Contract Creation Code"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:104
+#: lib/block_scout_web/templates/address_contract/index.html.eex:97
msgid "Contracts that self destruct in their constructors have no contract code published and cannot be verified."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:114
+#: lib/block_scout_web/templates/address_contract/index.html.eex:107
msgid "Copy Contract Byte Code"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:105
+#: lib/block_scout_web/templates/address_contract/index.html.eex:98
msgid "Displaying the init data provided of the creating transaction."
msgstr ""
@@ -1664,35 +1613,35 @@ msgid "Topic"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:98
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:104
msgid "ABI-encoded Constructor Arguments (if required by the contract)"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:87
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:93
msgid "Enter the Solidity Contract Code"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:127
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:149
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:171
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:193
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:215
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:133
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:155
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:177
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:199
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:221
msgid "Library Address"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:117
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:139
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:161
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:183
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:205
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:123
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:145
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:167
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:189
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:211
msgid "Library Name"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:3
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:9
msgid "New Smart Contract Verification"
msgstr ""
@@ -1717,7 +1666,7 @@ msgid "ETH RPC API Documentation"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:78
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:88
msgid "Eth RPC"
msgstr ""
@@ -1752,8 +1701,8 @@ msgid "here."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_token/index.html.eex:26
-#: lib/block_scout_web/templates/address_transaction/index.html.eex:72
+#: lib/block_scout_web/templates/address_token/index.html.eex:28
+#: lib/block_scout_web/templates/address_transaction/index.html.eex:74
msgid "CSV"
msgstr ""
@@ -1762,11 +1711,6 @@ msgstr ""
msgid "Change Network"
msgstr ""
-#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:51
-msgid "Constructor arguments"
-msgstr ""
-
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:44
msgid "ERC-20 "
@@ -1778,7 +1722,7 @@ msgid "ERC-721 "
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:58
+#: lib/block_scout_web/templates/address_contract/index.html.eex:120
msgid "External libraries"
msgstr ""
@@ -1806,3 +1750,72 @@ msgstr ""
#: lib/block_scout_web/templates/layout/_network_selector.html.eex:12
msgid "Use the search box to find a hosted network, or select from the list of available networks below."
msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:4
+msgid "Connection Lost"
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/address_contract/index.html.eex:53
+msgid "Constructor Arguments"
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:59
+#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:31
+#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:59
+msgid "Copy Value"
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/views/internal_transaction_view.ex:26
+msgid "Create2"
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:49
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:114
+#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:21
+#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:49
+#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:115
+msgid "Data"
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:29
+#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:29
+msgid "Failed to decode log data."
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:48
+#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:48
+msgid "Indexed?"
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:43
+#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:43
+msgid "Log Data"
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/block/_tile.html.eex:47
+#: lib/block_scout_web/templates/chain/_block.html.eex:23
+#: lib/block_scout_web/views/internal_transaction_view.ex:28
+msgid "Reward"
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:84
+#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:85
+msgid "Topics"
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:47
+#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:20
+#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:47
+msgid "Type"
+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 2a85e8470d..76e3303bf9 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
@@ -62,7 +62,7 @@ msgid "(query)"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/app.html.eex:35
+#: lib/block_scout_web/templates/layout/app.html.eex:40
msgid "- We're indexing this chain right now. Some of the counts may be inaccurate."
msgstr ""
@@ -87,7 +87,7 @@ msgid "API for the %{subnetwork} - BlockScout"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:56
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:66
msgid "Accounts"
msgstr ""
@@ -157,7 +157,7 @@ msgid "Block Height: %{height}"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/app.html.eex:50
+#: lib/block_scout_web/templates/layout/app.html.eex:55
msgid "Block Mined, awaiting import..."
msgstr ""
@@ -178,13 +178,13 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:87
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:16
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:20
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:26
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:30
msgid "Blocks"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/app.html.eex:49
+#: lib/block_scout_web/templates/layout/app.html.eex:54
msgid "Blocks Indexed"
msgstr ""
@@ -197,7 +197,7 @@ msgid "Blocks Validated"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:247
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:253
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:47
msgid "Cancel"
msgstr ""
@@ -225,12 +225,12 @@ msgid "Code"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:34
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:40
msgid "Compiler"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:30
+#: lib/block_scout_web/templates/address_contract/index.html.eex:34
msgid "Compiler version"
msgstr ""
@@ -258,12 +258,12 @@ msgid "Connection Lost, click to load newer validations"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:82
+#: lib/block_scout_web/templates/address_contract/index.html.eex:75
msgid "Contract ABI"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:12
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:18
#: lib/block_scout_web/views/address_view.ex:97
msgid "Contract Address"
msgstr ""
@@ -286,7 +286,7 @@ msgid "Contract Creation"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:23
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:29
msgid "Contract Name"
msgstr ""
@@ -296,7 +296,7 @@ msgid "Contract name:"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:70
+#: lib/block_scout_web/templates/address_contract/index.html.eex:63
msgid "Contract source code"
msgstr ""
@@ -328,14 +328,6 @@ msgstr ""
msgid "Curl"
msgstr ""
-#, elixir-format
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:44
-#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:18
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:48
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:113
-msgid "Data"
-msgstr ""
-
#, elixir-format
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:53
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:188
@@ -376,7 +368,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_balance_card.html.eex:15
#: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:21
-#: lib/block_scout_web/templates/layout/app.html.eex:55
+#: lib/block_scout_web/templates/layout/app.html.eex:60
#: 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:196
@@ -400,7 +392,7 @@ msgid "Fetching tokens..."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:26
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:36
msgid "Forked Blocks (Reorgs)"
msgstr ""
@@ -454,7 +446,7 @@ msgid "IN"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/app.html.eex:51
+#: lib/block_scout_web/templates/layout/app.html.eex:56
msgid "Indexing Tokens"
msgstr ""
@@ -486,7 +478,7 @@ msgid "Inventory"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/app.html.eex:52
+#: lib/block_scout_web/templates/layout/app.html.eex:57
msgid "Less than"
msgstr ""
@@ -507,13 +499,15 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:31
-#: lib/block_scout_web/templates/layout/app.html.eex:53
+#: lib/block_scout_web/templates/layout/app.html.eex:58
+#: lib/block_scout_web/views/address_view.ex:121
#: lib/block_scout_web/views/address_view.ex:121
msgid "Market Cap"
msgstr ""
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:129
+#: lib/block_scout_web/views/transaction_view.ex:129
msgid "Max of"
msgstr ""
@@ -553,14 +547,15 @@ msgid "Must be set to:"
msgstr ""
#, elixir-format
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:46
#: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:52
-#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:16
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:45
+#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:19
+#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:46
msgid "Name"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:61
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:67
msgid "No"
msgstr ""
@@ -577,7 +572,7 @@ msgid "OUT"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:40
+#: lib/block_scout_web/templates/address_contract/index.html.eex:30
msgid "Optimization enabled"
msgstr ""
@@ -602,7 +597,7 @@ msgid "Parent Hash"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:44
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:54
#: lib/block_scout_web/views/transaction_view.ex:143
#: lib/block_scout_web/views/transaction_view.ex:177
msgid "Pending"
@@ -620,7 +615,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/chain/show.html.eex:24
-#: lib/block_scout_web/templates/layout/app.html.eex:54
+#: lib/block_scout_web/templates/layout/app.html.eex:59
msgid "Price"
msgstr ""
@@ -651,7 +646,7 @@ msgid "Request URL"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:244
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:250
msgid "Reset"
msgstr ""
@@ -667,14 +662,14 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_logs/index.html.eex:14
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:102
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:119
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:118
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:135
msgid "Search"
msgstr ""
#, elixir-format
#:
-#: lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex:28
+#: lib/block_scout_web/templates/address_token_balance/_token_balances.html.eex:30
msgid "Search tokens"
msgstr ""
@@ -772,7 +767,7 @@ msgid "To"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:6
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:16
msgid "Toggle navigation"
msgstr ""
@@ -823,12 +818,6 @@ msgstr ""
msgid "Top Accounts - %{subnetwork} Explorer"
msgstr ""
-#, elixir-format
-#: lib/block_scout_web/templates/address_logs/_logs.html.eex:14
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:83
-msgid "Topics"
-msgstr ""
-
#, elixir-format
#: lib/block_scout_web/templates/block/overview.html.eex:67
msgid "Total Difficulty"
@@ -871,13 +860,13 @@ msgstr ""
#: lib/block_scout_web/templates/block_transaction/index.html.eex:10
#: lib/block_scout_web/templates/block_transaction/index.html.eex:18
#: lib/block_scout_web/templates/chain/show.html.eex:108
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:35
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:45
#: lib/block_scout_web/views/address_view.ex:305
msgid "Transactions"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address/_tile.html.eex:19
+#: lib/block_scout_web/templates/address/_tile.html.eex:31
msgid "Transactions sent"
msgstr ""
@@ -898,7 +887,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/block/overview.html.eex:80
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:23
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:33
msgid "Uncles"
msgstr ""
@@ -913,7 +902,7 @@ msgid "Used"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:39
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:49
msgid "Validated"
msgstr ""
@@ -923,7 +912,7 @@ msgid "Validated Transactions"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address/_tile.html.eex:23
+#: lib/block_scout_web/templates/address/_tile.html.eex:35
msgid "Validations"
msgstr ""
@@ -939,7 +928,7 @@ msgid "Verify & Publish"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:243
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:249
msgid "Verify & publish"
msgstr ""
@@ -989,7 +978,7 @@ msgid "Wei"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:66
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:72
msgid "Yes"
msgstr ""
@@ -999,7 +988,7 @@ msgid "at"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/address_contract_view.ex:22
+#: lib/block_scout_web/views/address_contract_view.ex:23
msgid "false"
msgstr ""
@@ -1017,7 +1006,7 @@ msgid "string"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/views/address_contract_view.ex:21
+#: lib/block_scout_web/views/address_contract_view.ex:22
msgid "true"
msgstr ""
@@ -1037,14 +1026,7 @@ msgid "Delegate Call"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/block/_tile.html.eex:47
-#: lib/block_scout_web/templates/chain/_block.html.eex:23
#: lib/block_scout_web/views/internal_transaction_view.ex:27
-msgid "Reward"
-msgstr ""
-
-#, elixir-format
-#: lib/block_scout_web/views/internal_transaction_view.ex:26
msgid "Self-Destruct"
msgstr ""
@@ -1056,33 +1038,27 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address_read_contract/index.html.eex:14
#: lib/block_scout_web/templates/chain/show.html.eex:99
-#: lib/block_scout_web/templates/chain/show.html.eex:125
#: lib/block_scout_web/templates/tokens/read_contract/index.html.eex:21
msgid "Loading..."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:241
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:247
msgid "Loading...."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:64
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:74
msgid "APIs"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:68
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:78
msgid "GraphQL"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/pending_transaction/index.html.eex:32
-msgid "Loading"
-msgstr ""
-
-#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:73
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:83
msgid "RPC"
msgstr ""
@@ -1132,48 +1108,37 @@ msgid "Static Call"
msgstr ""
#, elixir-format
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:14
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:14
msgid "Decoded"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:47
-msgid "Indexed?"
-msgstr ""
-
-#, elixir-format
-#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:17
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:46
-msgid "Type"
-msgstr ""
-
-#, elixir-format
-#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:3
+#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:4
msgid "Method Id"
msgstr ""
#, elixir-format
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:19
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:19
msgid "To see decoded input data, the contract must be verified."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:1
-msgid "Transaction Info"
-msgstr ""
-
-#, elixir-format
-#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:13
+#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:2
+#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:16
msgid "Transaction Inputs"
msgstr ""
#, elixir-format
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:22
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:11
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:22
msgid "Verify the contract "
msgstr ""
#, elixir-format
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:22
#: lib/block_scout_web/templates/transaction/_decoded_input.html.eex:11
#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:22
msgid "here"
@@ -1185,25 +1150,10 @@ msgid "Failed to decode input data."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:46
+#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:49
msgid "Error rendering value"
msgstr ""
-
-#, elixir-format
-#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:28
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:58
-msgid "Copy Value"
-msgstr ""
-
-#, elixir-format
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:29
-msgid "Failed to decode log data."
-msgstr ""
-
-#, elixir-format
-#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:42
-msgid "Log Data"
-msgstr ""
+""
#, elixir-format
#: lib/block_scout_web/templates/address_coin_balance/index.html.eex:34
@@ -1275,7 +1225,7 @@ msgid "There are no pending transactions."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/block/index.html.eex:16
+#: lib/block_scout_web/templates/block/index.html.eex:18
msgid "There are no blocks."
msgstr ""
@@ -1405,17 +1355,17 @@ msgid "Support"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:84
+#: lib/block_scout_web/templates/address_contract/index.html.eex:77
msgid "Copy ABI"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:100
+#: lib/block_scout_web/templates/address_contract/index.html.eex:93
msgid "Copy Contract Creation Code"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:72
+#: lib/block_scout_web/templates/address_contract/index.html.eex:65
msgid "Copy Source Code"
msgstr ""
@@ -1425,7 +1375,7 @@ msgid "Genesis Block"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:112
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:118
msgid "Contract Libraries"
msgstr ""
@@ -1481,14 +1431,14 @@ msgid "Error: Could not determine contract creator."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:96
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:100
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:112
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:116
msgid "Search by address, token symbol name, transaction hash, or block number"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:35
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:45
+#: lib/block_scout_web/templates/address_contract/index.html.eex:45
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:51
msgid "EVM Version"
msgstr ""
@@ -1518,8 +1468,8 @@ msgid "Decompiler version"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:45
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:77
+#: lib/block_scout_web/templates/address_contract/index.html.eex:39
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:83
msgid "Optimization runs"
msgstr ""
@@ -1585,27 +1535,27 @@ msgid "Block Details"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:112
+#: lib/block_scout_web/templates/address_contract/index.html.eex:105
msgid "Contract Byte Code"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:98
+#: lib/block_scout_web/templates/address_contract/index.html.eex:91
msgid "Contract Creation Code"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:104
+#: lib/block_scout_web/templates/address_contract/index.html.eex:97
msgid "Contracts that self destruct in their constructors have no contract code published and cannot be verified."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:114
+#: lib/block_scout_web/templates/address_contract/index.html.eex:107
msgid "Copy Contract Byte Code"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:105
+#: lib/block_scout_web/templates/address_contract/index.html.eex:98
msgid "Displaying the init data provided of the creating transaction."
msgstr ""
@@ -1655,35 +1605,35 @@ msgid "Topic"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:98
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:104
msgid "ABI-encoded Constructor Arguments (if required by the contract)"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:87
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:93
msgid "Enter the Solidity Contract Code"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:127
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:149
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:171
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:193
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:215
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:133
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:155
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:177
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:199
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:221
msgid "Library Address"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:117
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:139
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:161
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:183
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:205
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:123
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:145
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:167
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:189
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:211
msgid "Library Name"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:3
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:9
msgid "New Smart Contract Verification"
msgstr ""
@@ -1708,7 +1658,7 @@ msgid "ETH RPC API Documentation"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/layout/_topnav.html.eex:78
+#: lib/block_scout_web/templates/layout/_topnav.html.eex:88
msgid "Eth RPC"
msgstr ""
@@ -1743,8 +1693,8 @@ msgid "here."
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_token/index.html.eex:26
-#: lib/block_scout_web/templates/address_transaction/index.html.eex:72
+#: lib/block_scout_web/templates/address_token/index.html.eex:28
+#: lib/block_scout_web/templates/address_transaction/index.html.eex:74
msgid "CSV"
msgstr ""
@@ -1753,11 +1703,6 @@ msgstr ""
msgid "Change Network"
msgstr ""
-#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:51
-msgid "Constructor arguments"
-msgstr ""
-
#, elixir-format
#: lib/block_scout_web/views/transaction_view.ex:44
msgid "ERC-20 "
@@ -1769,7 +1714,7 @@ msgid "ERC-721 "
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address_contract/index.html.eex:58
+#: lib/block_scout_web/templates/address_contract/index.html.eex:120
msgid "External libraries"
msgstr ""
@@ -1797,3 +1742,72 @@ msgstr ""
#: lib/block_scout_web/templates/layout/_network_selector.html.eex:12
msgid "Use the search box to find a hosted network, or select from the list of available networks below."
msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/address_contract_verification/new.html.eex:4
+msgid "Connection Lost"
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/address_contract/index.html.eex:53
+msgid "Constructor Arguments"
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:59
+#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:31
+#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:59
+msgid "Copy Value"
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/views/internal_transaction_view.ex:26
+msgid "Create2"
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:49
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:114
+#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:21
+#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:49
+#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:115
+msgid "Data"
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:29
+#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:29
+msgid "Failed to decode log data."
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:48
+#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:48
+msgid "Indexed?"
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:43
+#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:43
+msgid "Log Data"
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/block/_tile.html.eex:47
+#: lib/block_scout_web/templates/chain/_block.html.eex:23
+#: lib/block_scout_web/views/internal_transaction_view.ex:28
+msgid "Reward"
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:84
+#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:85
+msgid "Topics"
+msgstr ""
+
+#, elixir-format
+#: lib/block_scout_web/templates/address_logs/_logs.html.eex:47
+#: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:20
+#: lib/block_scout_web/templates/transaction_log/_logs.html.eex:47
+msgid "Type"
+msgstr ""
diff --git a/apps/block_scout_web/priv/gettext/errors.pot b/apps/block_scout_web/priv/gettext/errors.pot
index ca584666e5..cdaaac6299 100644
--- a/apps/block_scout_web/priv/gettext/errors.pot
+++ b/apps/block_scout_web/priv/gettext/errors.pot
@@ -1,52 +1,52 @@
-## This file is a PO Template file.
-##
-## `msgid`s here are often extracted from source code.
-## Add new translations manually only if they're dynamic
-## translations that can't be statically extracted.
-##
-## Run `mix gettext.extract` to bring this file up to
-## date. Leave `msgstr`s empty as changing them here as no
-## effect: edit them in PO (`.po`) files instead.
-## From Ecto.Changeset.cast/4
+## This file is a PO Template file.
+##
+## `msgid`s here are often extracted from source code.
+## Add new translations manually only if they're dynamic
+## translations that can't be statically extracted.
+##
+## Run `mix gettext.extract` to bring this file up to
+## date. Leave `msgstr`s empty as changing them here as no
+## effect: edit them in PO (`.po`) files instead.
+## From Ecto.Changeset.cast/4
msgid "can't be blank"
msgstr ""
-## From Ecto.Changeset.unique_constraint/3
+## From Ecto.Changeset.unique_constraint/3
msgid "has already been taken"
msgstr ""
-## From Ecto.Changeset.put_change/3
+## From Ecto.Changeset.put_change/3
msgid "is invalid"
msgstr ""
-## From Ecto.Changeset.validate_acceptance/3
+## From Ecto.Changeset.validate_acceptance/3
msgid "must be accepted"
msgstr ""
-## From Ecto.Changeset.validate_format/3
+## From Ecto.Changeset.validate_format/3
msgid "has invalid format"
msgstr ""
-## From Ecto.Changeset.validate_subset/3
+## From Ecto.Changeset.validate_subset/3
msgid "has an invalid entry"
msgstr ""
-## From Ecto.Changeset.validate_exclusion/3
+## From Ecto.Changeset.validate_exclusion/3
msgid "is reserved"
msgstr ""
-## From Ecto.Changeset.validate_confirmation/3
+## From Ecto.Changeset.validate_confirmation/3
msgid "does not match confirmation"
msgstr ""
-## From Ecto.Changeset.no_assoc_constraint/3
+## From Ecto.Changeset.no_assoc_constraint/3
msgid "is still associated with this entry"
msgstr ""
msgid "are still associated with this entry"
msgstr ""
-## From Ecto.Changeset.validate_length/3
+## From Ecto.Changeset.validate_length/3
msgid "should be %{count} character(s)"
msgid_plural "should be %{count} character(s)"
msgstr[0] ""
@@ -77,7 +77,7 @@ msgid_plural "should have at most %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
-## From Ecto.Changeset.validate_number/3
+## From Ecto.Changeset.validate_number/3
msgid "must be less than %{number}"
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
index 1b9273c66c..4b43909ac4 100644
--- 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
@@ -125,6 +125,57 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do
assert [%{"data" => "0x010101"}, %{"data" => "0x020202"}] = Enum.sort_by(response["result"], &Map.get(&1, "data"))
end
+ test "paginates logs", %{conn: conn, api_params: api_params} do
+ contract_address = insert(:contract_address)
+
+ transaction =
+ :transaction
+ |> insert(to_address: contract_address)
+ |> with_block()
+
+ inserted_records =
+ insert_list(2000, :log, address: contract_address, transaction: transaction, first_topic: "0x01")
+
+ params = params(api_params, [%{"address" => to_string(contract_address), "topics" => [["0x01"]]}])
+
+ assert response =
+ conn
+ |> post("/api/eth_rpc", params)
+ |> json_response(200)
+
+ assert Enum.count(response["result"]) == 1000
+
+ {last_log_index, ""} = Integer.parse(List.last(response["result"])["logIndex"], 16)
+
+ next_page_params = %{
+ "blockNumber" => Integer.to_string(transaction.block_number, 16),
+ "transactionIndex" => transaction.index,
+ "logIndex" => Integer.to_string(last_log_index, 16)
+ }
+
+ new_params =
+ params(api_params, [
+ %{"paging_options" => next_page_params, "address" => to_string(contract_address), "topics" => [["0x01"]]}
+ ])
+
+ assert new_response =
+ conn
+ |> post("/api/eth_rpc", new_params)
+ |> json_response(200)
+
+ assert Enum.count(response["result"]) == 1000
+
+ all_found_logs = response["result"] ++ new_response["result"]
+
+ assert Enum.all?(inserted_records, fn record ->
+ Enum.any?(all_found_logs, fn found_log ->
+ {index, ""} = Integer.parse(found_log["logIndex"], 16)
+
+ record.index == index
+ end)
+ end)
+ end
+
test "with a matching address and multiple topic matches in different positions", %{
conn: conn,
api_params: api_params
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v1/health_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v1/health_controller_test.exs
index 41735f685a..19631ff746 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/api/v1/health_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v1/health_controller_test.exs
@@ -1,6 +1,15 @@
defmodule BlockScoutWeb.API.V1.HealthControllerTest do
use BlockScoutWeb.ConnCase
+ alias Explorer.{Chain, PagingOptions}
+
+ setup do
+ Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
+ Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
+
+ :ok
+ end
+
describe "GET last_block_status/0" do
test "returns error when there are no blocks in db", %{conn: conn} do
request = get(conn, api_v1_health_path(conn, :health))
@@ -32,19 +41,50 @@ defmodule BlockScoutWeb.API.V1.HealthControllerTest do
end
test "returns ok when last block is not stale", %{conn: conn} do
- insert(:block, consensus: true, timestamp: DateTime.utc_now())
+ block1 = insert(:block, consensus: true, timestamp: DateTime.utc_now(), number: 2)
+ insert(:block, consensus: true, timestamp: DateTime.utc_now(), number: 1)
request = get(conn, api_v1_health_path(conn, :health))
assert request.status == 200
+ result = Poison.decode!(request.resp_body)
+
+ assert result["healthy"] == true
+
assert %{
- "healthy" => true,
- "data" => %{
- "latest_block_number" => _,
- "latest_block_inserted_at" => _
- }
- } = Poison.decode!(request.resp_body)
+ "latest_block_number" => to_string(block1.number),
+ "latest_block_inserted_at" => to_string(block1.timestamp),
+ "cache_latest_block_number" => to_string(block1.number),
+ "cache_latest_block_inserted_at" => to_string(block1.timestamp)
+ } == result["data"]
end
end
+
+ test "return error when cache is stale", %{conn: conn} do
+ stale_block = insert(:block, consensus: true, timestamp: Timex.shift(DateTime.utc_now(), hours: -50), number: 3)
+ state_block_hash = stale_block.hash
+
+ assert [%{hash: ^state_block_hash}] = Chain.list_blocks(paging_options: %PagingOptions{page_size: 1})
+
+ insert(:block, consensus: true, timestamp: DateTime.utc_now(), number: 1)
+
+ assert [%{hash: ^state_block_hash}] = Chain.list_blocks(paging_options: %PagingOptions{page_size: 1})
+
+ request = get(conn, api_v1_health_path(conn, :health))
+
+ assert request.status == 500
+
+ assert %{
+ "healthy" => false,
+ "error_code" => 5001,
+ "error_title" => "blocks fetching is stuck",
+ "error_description" =>
+ "There are no new blocks in the DB for the last 5 mins. Check the healthiness of Ethereum archive node or the Blockscout DB instance",
+ "data" => %{
+ "latest_block_number" => _,
+ "latest_block_inserted_at" => _
+ }
+ } = Poison.decode!(request.resp_body)
+ end
end
diff --git a/apps/block_scout_web/test/block_scout_web/views/address_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/address_view_test.exs
index 9be8a1d45a..6522f20459 100644
--- a/apps/block_scout_web/test/block_scout_web/views/address_view_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/views/address_view_test.exs
@@ -199,7 +199,7 @@ defmodule BlockScoutWeb.AddressViewTest do
describe "qr_code/1" do
test "it returns an encoded value" do
address = build(:address)
- assert {:ok, _} = Base.decode64(AddressView.qr_code(address))
+ assert {:ok, _} = Base.decode64(AddressView.qr_code(address.hash))
end
end
diff --git a/apps/block_scout_web/test/block_scout_web/views/api_docs_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/api_docs_view_test.exs
new file mode 100644
index 0000000000..da43a33ad0
--- /dev/null
+++ b/apps/block_scout_web/test/block_scout_web/views/api_docs_view_test.exs
@@ -0,0 +1,31 @@
+defmodule BlockScoutWeb.ApiDocsViewTest do
+ use BlockScoutWeb.ConnCase, async: false
+
+ alias BlockScoutWeb.APIDocsView
+
+ describe "blockscout_url/0" do
+ setup do
+ original = Application.get_env(:block_scout_web, BlockScoutWeb.Endpoint)
+
+ on_exit(fn -> Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint, original) end)
+
+ :ok
+ end
+
+ test "returns url with scheme and host without port" do
+ Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint,
+ url: [scheme: "https", host: "blockscout.com", port: 9999, path: "/"]
+ )
+
+ assert APIDocsView.blockscout_url() == "https://blockscout.com/"
+ end
+
+ test "returns url with scheme and host with path" do
+ Application.put_env(:block_scout_web, BlockScoutWeb.Endpoint,
+ url: [scheme: "https", host: "blockscout.com", port: 9999, path: "/chain/dog"]
+ )
+
+ assert APIDocsView.blockscout_url() == "https://blockscout.com/chain/dog"
+ end
+ end
+end
diff --git a/apps/block_scout_web/test/support/conn_case.ex b/apps/block_scout_web/test/support/conn_case.ex
index d0b9b066a1..ca486b33c2 100644
--- a/apps/block_scout_web/test/support/conn_case.ex
+++ b/apps/block_scout_web/test/support/conn_case.ex
@@ -38,8 +38,8 @@ defmodule BlockScoutWeb.ConnCase do
Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()})
end
- Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.TransactionsCache.cache_name()})
- Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.TransactionsCache.cache_name()})
+ Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Transactions.cache_name()})
+ Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Transactions.cache_name()})
{:ok, conn: Phoenix.ConnTest.build_conn()}
end
diff --git a/apps/block_scout_web/test/support/feature_case.ex b/apps/block_scout_web/test/support/feature_case.ex
index b9e6f000d7..a9476a7b85 100644
--- a/apps/block_scout_web/test/support/feature_case.ex
+++ b/apps/block_scout_web/test/support/feature_case.ex
@@ -27,8 +27,8 @@ defmodule BlockScoutWeb.FeatureCase do
Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()})
end
- Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.TransactionsCache.cache_name()})
- Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.TransactionsCache.cache_name()})
+ Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Transactions.cache_name()})
+ Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Transactions.cache_name()})
metadata = Phoenix.Ecto.SQL.Sandbox.metadata_for(Explorer.Repo, self())
{:ok, session} = Wallaby.start_session(metadata: metadata)
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/call.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/call.ex
index 1555ea6c8f..c83b157324 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/call.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/call.ex
@@ -426,13 +426,14 @@ defmodule EthereumJSONRPC.Geth.Call do
"transactionHash" => transaction_hash,
"index" => index,
"traceAddress" => trace_address,
- "type" => "create" = type,
+ "type" => type,
"from" => from_address_hash,
"error" => error,
"gas" => gas,
"init" => init,
"value" => value
- }) do
+ })
+ when type in ~w(create create2) do
%{
block_number: block_number,
transaction_index: transaction_index,
@@ -454,7 +455,7 @@ defmodule EthereumJSONRPC.Geth.Call do
"transactionHash" => transaction_hash,
"index" => index,
"traceAddress" => trace_address,
- "type" => "create",
+ "type" => type,
"from" => from_address_hash,
"createdContractAddressHash" => created_contract_address_hash,
"gas" => gas,
@@ -462,14 +463,15 @@ defmodule EthereumJSONRPC.Geth.Call do
"init" => init,
"createdContractCode" => created_contract_code,
"value" => value
- }) do
+ })
+ when type in ~w(create create2) do
%{
block_number: block_number,
transaction_index: transaction_index,
transaction_hash: transaction_hash,
index: index,
trace_address: trace_address,
- type: "create",
+ type: type,
from_address_hash: from_address_hash,
gas: gas,
gas_used: gas_used,
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex
index 6545250b4a..cbc43fa71d 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth/tracer.ex
@@ -113,6 +113,7 @@ defmodule EthereumJSONRPC.Geth.Tracer do
end
defp op(%{"op" => "CREATE"} = log, ctx), do: create_op(log, ctx)
+ defp op(%{"op" => "CREATE2"} = log, ctx), do: create_op(log, ctx, "create2")
defp op(%{"op" => "SELFDESTRUCT"} = log, ctx), do: self_destruct_op(log, ctx)
defp op(%{"op" => "CALL"} = log, ctx), do: call_op(log, "call", ctx)
defp op(%{"op" => "CALLCODE"} = log, ctx), do: call_op(log, "callcode", ctx)
@@ -155,7 +156,8 @@ defmodule EthereumJSONRPC.Geth.Tracer do
defp create_op(
%{"stack" => log_stack, "memory" => log_memory},
- %{depth: stack_depth, stack: stack, trace_address: trace_address, calls: calls} = ctx
+ %{depth: stack_depth, stack: stack, trace_address: trace_address, calls: calls} = ctx,
+ type \\ "create"
) do
[value, input_offset, input_length | _] = Enum.reverse(log_stack)
@@ -165,7 +167,7 @@ defmodule EthereumJSONRPC.Geth.Tracer do
|> String.slice(quantity_to_integer("0x" <> input_offset) * 2, quantity_to_integer("0x" <> input_length) * 2)
call = %{
- "type" => "create",
+ "type" => type,
"from" => nil,
"traceAddress" => Enum.reverse(trace_address),
"init" => "0x" <> init,
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/rolling_window.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/rolling_window.ex
index 82e4a54072..c1de043499 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/rolling_window.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/rolling_window.ex
@@ -209,14 +209,20 @@ defmodule EthereumJSONRPC.RollingWindow do
@doc """
Display the raw contents of all windows for a given key.
"""
- @spec inspect(table :: atom, key :: term()) :: nonempty_list(non_neg_integer)
+ @spec inspect(table :: atom, key :: term()) :: nonempty_list(non_neg_integer) | []
def inspect(table, key) do
- case :ets.lookup(table, key) do
- [{_, current_window, windows}] ->
- [current_window | windows]
-
- _ ->
+ case :ets.whereis(table) do
+ :undefined ->
[]
+
+ tid ->
+ case :ets.lookup(tid, key) do
+ [{_, current_window, windows}] ->
+ [current_window | windows]
+
+ _ ->
+ []
+ end
end
end
end
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex
index f021bfa153..7992c0e63c 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex
@@ -353,4 +353,8 @@ defmodule EthereumJSONRPC.Transaction do
_ -> {key, quantity_to_integer(chain_id)}
end
end
+
+ defp entry_to_elixir(_) do
+ {nil, nil}
+ end
end
diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transport.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transport.ex
index 1301fa5675..dc7f1839a6 100644
--- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transport.ex
+++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transport.ex
@@ -88,10 +88,6 @@ defmodule EthereumJSONRPC.Transport do
`%{"id" => ..., "error" => reason}`. The transport can also give any `term()` for `reason` if a more specific
reason is possible.
- """
- @callback json_rpc(request, options) :: {:ok, result} | {:error, reason :: term()}
-
- @doc """
Runs a batch of Remote Procedure Call (RPC) `request`s with `options`.
## Returns
@@ -99,8 +95,9 @@ defmodule EthereumJSONRPC.Transport do
* `{:ok, [response]}` unlike `json_rpc(request, options)`, the individual `t:response.t/0` are not unwrapped and it
is the callers responsibility to extract the `t:result/0` or error `reason`.
* `{:error, reason}` an error that affects *all* `t:request/0`s, such as the batch as a whole being rejected.
-
"""
+ @callback json_rpc(request, options) :: {:ok, result} | {:error, reason :: term()}
+
@callback json_rpc(batch_request, options) :: {:ok, batch_response} | {:error, reason :: term()}
@doc """
diff --git a/apps/ethereum_jsonrpc/mix.exs b/apps/ethereum_jsonrpc/mix.exs
index a805dbd649..7c2299c4a8 100644
--- a/apps/ethereum_jsonrpc/mix.exs
+++ b/apps/ethereum_jsonrpc/mix.exs
@@ -15,7 +15,7 @@ defmodule EthereumJsonrpc.MixProject do
plt_add_apps: [:mix],
ignore_warnings: "../../.dialyzer-ignore"
],
- elixir: "~> 1.8",
+ elixir: "~> 1.9",
elixirc_paths: elixirc_paths(Mix.env()),
lockfile: "../../mix.lock",
preferred_cli_env: [
diff --git a/apps/ethereum_jsonrpc/priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js b/apps/ethereum_jsonrpc/priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js
index 15db7d8ff9..5c3c98f871 100644
--- a/apps/ethereum_jsonrpc/priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js
+++ b/apps/ethereum_jsonrpc/priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js
@@ -91,6 +91,9 @@
case 'CREATE':
this.createOp(log);
break;
+ case 'CREATE2':
+ this.create2Op(log);
+ break;
case 'SELFDESTRUCT':
this.selfDestructOp(log, db);
break;
@@ -127,7 +130,7 @@
const ret = log.stack.peek(0);
if (!ret.equals(0)) {
- if (call.type === 'create') {
+ if (call.type === 'create' || call.type === 'create2') {
call.createdContractAddressHash = toHex(toAddress(ret.toString(16)));
call.createdContractCode = toHex(db.getCode(toAddress(ret.toString(16))));
} else {
@@ -162,6 +165,21 @@
this.callStack.push(call);
},
+ create2Op(log) {
+ const inputOffset = log.stack.peek(1).valueOf();
+ const inputLength = log.stack.peek(2).valueOf();
+ const inputEnd = inputOffset + inputLength;
+ const stackValue = log.stack.peek(0);
+
+ const call = {
+ type: 'create2',
+ from: toHex(log.contract.getAddress()),
+ init: toHex(log.memory.slice(inputOffset, inputEnd)),
+ valueBigInt: bigInt(stackValue.toString(10))
+ };
+ this.callStack.push(call);
+ },
+
selfDestructOp(log, db) {
const contractAddress = log.contract.getAddress();
@@ -243,6 +261,9 @@
case 'CREATE':
result = this.ctxToCreate(ctx, db);
break;
+ case 'CREATE2':
+ result = this.ctxToCreate2(ctx, db);
+ break;
}
return result;
@@ -292,6 +313,22 @@
return result;
},
+ ctxToCreate2(ctx, db) {
+ const result = {
+ type: 'create2',
+ from: toHex(ctx.from),
+ init: toHex(ctx.input),
+ valueBigInt: bigInt(ctx.value.toString(10)),
+ gasBigInt: bigInt(ctx.gas),
+ gasUsedBigInt: bigInt(ctx.gasUsed)
+ };
+
+ this.putBottomChildCalls(result);
+ this.putErrorOrCreatedContract(result, ctx, db);
+
+ return result;
+ },
+
putBottomChildCalls(result) {
const bottomCall = this.bottomCall();
const bottomChildCalls = bottomCall.calls;
@@ -422,4 +459,3 @@
call.gasUsed = '0x' + gasUsedBigInt.toString(16);
}
}
-
diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/rolling_window_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/rolling_window_test.exs
index 25b7e41382..43f400434a 100644
--- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/rolling_window_test.exs
+++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/rolling_window_test.exs
@@ -10,7 +10,10 @@ defmodule EthereumJSONRPC.RollingWindowTest do
setup do
# We set `window_length` to a large time frame so that we can sweep manually to simulate
# time passing
- RollingWindow.start_link([table: @table, duration: :timer.minutes(120), window_count: 3], name: RollingWindow)
+ {:ok, pid} =
+ RollingWindow.start_link([table: @table, duration: :timer.minutes(120), window_count: 3], name: RollingWindow)
+
+ on_exit(fn -> Process.exit(pid, :normal) end)
:ok
end
diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/transaction_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/transaction_test.exs
index ad7780181d..a8a8fcdfd2 100644
--- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/transaction_test.exs
+++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/transaction_test.exs
@@ -2,4 +2,14 @@ defmodule EthereumJSONRPC.TransactionTest do
use ExUnit.Case, async: true
doctest EthereumJSONRPC.Transaction
+
+ alias EthereumJSONRPC.Transaction
+
+ describe "to_elixir/1" do
+ test "skips unsupported keys" do
+ map = %{"key" => "value", "key1" => "value1"}
+
+ assert %{nil: nil} = Transaction.to_elixir(map)
+ end
+ end
end
diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs
index b677306abd..d048afb1a3 100644
--- a/apps/explorer/config/config.exs
+++ b/apps/explorer/config/config.exs
@@ -12,14 +12,22 @@ config :explorer,
token_functions_reader_max_retries: 3,
allowed_evm_versions:
System.get_env("ALLOWED_EVM_VERSIONS") ||
- "homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg",
+ "homestead,tangerineWhistle,spuriousDragon,byzantium,constantinople,petersburg,default",
include_uncles_in_average_block_time:
if(System.get_env("UNCLES_IN_AVERAGE_BLOCK_TIME") == "false", do: false, else: true),
healthy_blocks_period: System.get_env("HEALTHY_BLOCKS_PERIOD") || :timer.minutes(5)
-config :explorer, Explorer.Counters.AverageBlockTime, enabled: true
+average_block_period =
+ case Integer.parse(System.get_env("AVERAGE_BLOCK_CACHE_PERIOD", "")) do
+ {secs, ""} -> :timer.seconds(secs)
+ _ -> :timer.minutes(30)
+ end
+
+config :explorer, Explorer.Counters.AverageBlockTime,
+ enabled: true,
+ period: average_block_period
-config :explorer, Explorer.Chain.BlockNumberCache, enabled: true
+config :explorer, Explorer.Chain.Cache.BlockNumber, enabled: true
config :explorer, Explorer.ExchangeRates.Source.CoinMarketCap,
pages: String.to_integer(System.get_env("COINMARKETCAP_PAGES") || "10")
@@ -108,6 +116,14 @@ config :spandex_ecto, SpandexEcto.EctoLogger,
tracer: Explorer.Tracer,
otp_app: :explorer
+market_history_cache_period =
+ case Integer.parse(System.get_env("MARKET_HISTORY_CACHE_PERIOD", "")) do
+ {secs, ""} -> :timer.seconds(secs)
+ _ -> :timer.hours(6)
+ end
+
+config :explorer, Explorer.Market.MarketHistoryCache, period: market_history_cache_period
+
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs"
diff --git a/apps/explorer/config/test.exs b/apps/explorer/config/test.exs
index 510c2f5adc..f0a33f3e08 100644
--- a/apps/explorer/config/test.exs
+++ b/apps/explorer/config/test.exs
@@ -13,7 +13,7 @@ config :explorer, Explorer.Repo,
config :explorer, Explorer.ExchangeRates, enabled: false, store: :ets
-config :explorer, Explorer.Chain.BlockNumberCache, enabled: false
+config :explorer, Explorer.Chain.Cache.BlockNumber, enabled: false
config :explorer, Explorer.KnownTokens, enabled: false, store: :ets
diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex
index 7097d7f2cd..e5d757830b 100644
--- a/apps/explorer/lib/explorer/application.ex
+++ b/apps/explorer/lib/explorer/application.ex
@@ -7,13 +7,13 @@ defmodule Explorer.Application do
alias Explorer.Admin
- alias Explorer.Chain.{
- BlockCountCache,
- BlockNumberCache,
- BlocksCache,
- NetVersionCache,
- TransactionCountCache,
- TransactionsCache
+ alias Explorer.Chain.Cache.{
+ BlockCount,
+ BlockNumber,
+ Blocks,
+ NetVersion,
+ TransactionCount,
+ Transactions
}
alias Explorer.Chain.Supply.RSK
@@ -42,13 +42,13 @@ defmodule Explorer.Application do
Explorer.SmartContract.SolcDownloader,
{Registry, keys: :duplicate, name: Registry.ChainEvents, id: Registry.ChainEvents},
{Admin.Recovery, [[], [name: Admin.Recovery]]},
- {TransactionCountCache, [[], []]},
- {BlockCountCache, []},
- con_cache_child_spec(BlocksCache.cache_name()),
- con_cache_child_spec(NetVersionCache.cache_name()),
+ {TransactionCount, [[], []]},
+ {BlockCount, []},
+ con_cache_child_spec(Blocks.cache_name()),
+ con_cache_child_spec(NetVersion.cache_name()),
con_cache_child_spec(MarketHistoryCache.cache_name()),
con_cache_child_spec(RSK.cache_name(), ttl_check_interval: :timer.minutes(1), global_ttl: :timer.minutes(30)),
- con_cache_child_spec(TransactionsCache.cache_name())
+ con_cache_child_spec(Transactions.cache_name())
]
children = base_children ++ configurable_children()
@@ -57,7 +57,7 @@ defmodule Explorer.Application do
res = Supervisor.start_link(children, opts)
- BlockNumberCache.setup()
+ BlockNumber.setup()
res
end
diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex
index fcad694a58..c2e480957d 100644
--- a/apps/explorer/lib/explorer/chain.ex
+++ b/apps/explorer/lib/explorer/chain.ex
@@ -31,9 +31,6 @@ defmodule Explorer.Chain do
Address.CurrentTokenBalance,
Address.TokenBalance,
Block,
- BlockCountCache,
- BlockNumberCache,
- BlocksCache,
Data,
DecompiledSmartContract,
Hash,
@@ -45,12 +42,19 @@ defmodule Explorer.Chain do
Token,
TokenTransfer,
Transaction,
- TransactionCountCache,
- TransactionsCache,
Wei
}
alias Explorer.Chain.Block.{EmissionReward, Reward}
+
+ alias Explorer.Chain.Cache.{
+ BlockCount,
+ BlockNumber,
+ Blocks,
+ TransactionCount,
+ Transactions
+ }
+
alias Explorer.Chain.Import.Runner
alias Explorer.Counters.AddressesWithBalanceCounter
alias Explorer.Market.MarketHistoryCache
@@ -126,7 +130,7 @@ defmodule Explorer.Chain do
end
@doc """
- `t:Explorer.Chain.InternalTransaction/0`s from `address`.
+ `t:Explorer.Chain.InternalTransaction/0`s from the address with the given `hash`.
This function excludes any internal transactions in the results where the
internal transaction has no siblings within the parent transaction.
@@ -145,10 +149,10 @@ defmodule Explorer.Chain do
transactions older than the `block_number`, `transaction index`, and `index` that are passed.
"""
- @spec address_to_internal_transactions(Address.t(), [paging_options | necessity_by_association_option]) :: [
+ @spec address_to_internal_transactions(Hash.Address.t(), [paging_options | necessity_by_association_option]) :: [
InternalTransaction.t()
]
- def address_to_internal_transactions(%Address{hash: hash}, options \\ []) do
+ def address_to_internal_transactions(hash, options \\ []) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
direction = Keyword.get(options, :direction)
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
@@ -171,13 +175,13 @@ defmodule Explorer.Chain do
end
@doc """
- Get the total number of transactions sent by the given address according to the last block indexed.
+ Get the total number of transactions sent by the address with the given hash according to the last block indexed.
We have to increment +1 in the last nonce result because it works like an array position, the first
nonce has the value 0. When last nonce is nil, it considers that the given address has 0 transactions.
"""
- @spec total_transactions_sent_by_address(Address.t()) :: non_neg_integer()
- def total_transactions_sent_by_address(%Address{hash: address_hash}) do
+ @spec total_transactions_sent_by_address(Hash.Address.t()) :: non_neg_integer()
+ def total_transactions_sent_by_address(address_hash) do
last_nonce =
address_hash
|> Transaction.last_nonce_by_address_query()
@@ -190,9 +194,9 @@ defmodule Explorer.Chain do
end
@doc """
- Fetches the transactions related to the given address, including transactions
- that only have the address in the `token_transfers` related table and rewards
- for block validation.
+ Fetches the transactions related to the address with the given hash, including
+ transactions that only have the address in the `token_transfers` related table
+ and rewards for block validation.
This query is divided into multiple subqueries intentionally in order to
improve the listing performance.
@@ -214,33 +218,21 @@ defmodule Explorer.Chain do
the `block_number` and `index` that are passed.
"""
- @spec address_to_transactions_with_rewards(Address.t(), [paging_options | necessity_by_association_option]) :: [
+ @spec address_to_transactions_with_rewards(Hash.Address.t(), [paging_options | necessity_by_association_option]) :: [
Transaction.t()
]
- def address_to_transactions_with_rewards(
- %Address{hash: %Hash{byte_count: unquote(Hash.Address.byte_count())} = address_hash},
- options \\ []
- )
- when is_list(options) do
- direction = Keyword.get(options, :direction)
- necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
+ def address_to_transactions_with_rewards(address_hash, options \\ []) when is_list(options) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
- transaction_hashes_from_token_transfers =
- TokenTransfer.where_any_address_fields_match(direction, address_hash, paging_options)
-
- 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()
-
if Application.get_env(:block_scout_web, BlockScoutWeb.Chain)[:has_emission_funds] do
+ rewards_task =
+ Task.async(fn ->
+ Reward.fetch_emission_rewards_tuples(address_hash, paging_options)
+ end)
+
address_hash
- |> Reward.fetch_emission_rewards_tuples(paging_options)
- |> Enum.concat(transactions_list)
+ |> address_to_transactions_without_rewards(paging_options, options)
+ |> Enum.concat(Task.await(rewards_task))
|> Enum.sort_by(fn item ->
case item do
{%Reward{} = emission_reward, _} ->
@@ -252,33 +244,42 @@ defmodule Explorer.Chain do
end)
|> Enum.take(paging_options.page_size)
else
- transactions_list
+ address_to_transactions_without_rewards(address_hash, paging_options, options)
end
end
- @spec address_to_logs(Address.t(), Keyword.t()) :: [
- Log.t()
- ]
- def address_to_logs(
- %Address{hash: %Hash{byte_count: unquote(Hash.Address.byte_count())} = address_hash},
- options \\ []
- )
- when is_list(options) do
+ defp address_to_transactions_without_rewards(address_hash, paging_options, options) do
+ direction = Keyword.get(options, :direction)
+ necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
+
+ transaction_hashes_from_token_transfers =
+ TokenTransfer.where_any_address_fields_match(direction, address_hash, paging_options)
+
+ 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()
+ end
+
+ @spec address_to_logs(Hash.Address.t(), Keyword.t()) :: [Log.t()]
+ def address_to_logs(address_hash, options \\ []) when is_list(options) do
paging_options = Keyword.get(options, :paging_options) || %PagingOptions{page_size: 50}
- {block_number, transaction_index, log_index} = paging_options.key || {BlockNumberCache.max_number(), 0, 0}
+ {block_number, transaction_index, log_index} = paging_options.key || {BlockNumber.max_number(), 0, 0}
base_query =
from(log in Log,
inner_join: transaction in assoc(log, :transaction),
order_by: [desc: transaction.block_number, desc: transaction.index],
- preload: [:transaction],
- where:
- log.address_hash == ^address_hash and
- (transaction.block_number < ^block_number or
- (transaction.block_number == ^block_number and transaction.index > ^transaction_index) or
- (transaction.block_number == ^block_number and transaction.index == ^transaction_index and
- log.index > ^log_index)),
+ preload: [:transaction, transaction: [to_address: :smart_contract]],
+ where: transaction.block_number < ^block_number,
+ or_where: transaction.block_number == ^block_number and transaction.index > ^transaction_index,
+ or_where:
+ transaction.block_number == ^block_number and transaction.index == ^transaction_index and
+ log.index > ^log_index,
+ where: log.address_hash == ^address_hash,
limit: ^paging_options.page_size,
select: log
)
@@ -363,8 +364,8 @@ defmodule Explorer.Chain do
Uncles are not currently accounted for.
"""
- @spec block_reward(Block.t()) :: Wei.t()
- def block_reward(%Block{number: block_number}) do
+ @spec block_reward(Block.block_number()) :: Wei.t()
+ def block_reward(block_number) do
query =
from(
block in Block,
@@ -414,8 +415,8 @@ defmodule Explorer.Chain do
`:key` (a tuple of the lowest/oldest `{index}`) and. Results will be the transactions older than
the `index` that are passed.
"""
- @spec block_to_transactions(Block.t(), [paging_options | necessity_by_association_option]) :: [Transaction.t()]
- def block_to_transactions(%Block{hash: block_hash}, options \\ []) when is_list(options) do
+ @spec block_to_transactions(Hash.Full.t(), [paging_options | necessity_by_association_option]) :: [Transaction.t()]
+ def block_to_transactions(block_hash, options \\ []) when is_list(options) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
options
@@ -431,8 +432,8 @@ defmodule Explorer.Chain do
@doc """
Counts the number of `t:Explorer.Chain.Transaction.t/0` in the `block`.
"""
- @spec block_to_transaction_count(Block.t()) :: non_neg_integer()
- def block_to_transaction_count(%Block{hash: block_hash}) do
+ @spec block_to_transaction_count(Hash.Full.t()) :: non_neg_integer()
+ def block_to_transaction_count(block_hash) do
query =
from(
transaction in Transaction,
@@ -442,8 +443,8 @@ defmodule Explorer.Chain do
Repo.aggregate(query, :count, :hash)
end
- @spec address_to_incoming_transaction_count(Address.t()) :: non_neg_integer()
- def address_to_incoming_transaction_count(%Address{hash: address_hash}) do
+ @spec address_to_incoming_transaction_count(Hash.Address.t()) :: non_neg_integer()
+ def address_to_incoming_transaction_count(address_hash) do
paging_options = %PagingOptions{page_size: @max_incoming_transactions_count}
base_query =
@@ -676,31 +677,40 @@ 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
+ ## Options
+
+ * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is
+ `:required`, and the `t:Explorer.Chain.Address.t/0` has no associated record for that association,
+ then the `t:Explorer.Chain.Address.t/0` will not be included in the list.
+
+ Optionally it also accepts a boolean to fetch the `has_decompiled_code?` virtual field or not
"""
- @spec hash_to_address(Hash.Address.t(), [Macro.t()], boolean()) :: {:ok, Address.t()} | {:error, :not_found}
+ @spec hash_to_address(Hash.Address.t(), [necessity_by_association_option], 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
+ options \\ [
+ necessity_by_association: %{
+ :contracts_creation_internal_transaction => :optional,
+ :names => :optional,
+ :smart_contract => :optional,
+ :token => :optional,
+ :contracts_creation_transaction => :optional
+ }
],
query_decompiled_code_flag \\ true
) do
+ necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
+
query =
from(
address in Address,
- preload: ^preloads,
where: address.hash == ^hash
)
query
+ |> join_associations(necessity_by_association)
|> with_decompiled_code_flag(hash, query_decompiled_code_flag)
|> Repo.one()
|> case do
@@ -772,16 +782,39 @@ defmodule Explorer.Chain do
iex> {:ok, %Explorer.Chain.Address{hash: found_hash}} = Explorer.Chain.hash_to_address(hash)
iex> found_hash == hash
true
+
+
+ ## Options
+
+ * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is
+ `:required`, and the `t:Explorer.Chain.Address.t/0` has no associated record for that association,
+ then the `t:Explorer.Chain.Address.t/0` will not be included in the list.
+
+ Optionally it also accepts a boolean to fetch the `has_decompiled_code?` virtual field or not
+
"""
- @spec find_or_insert_address_from_hash(Hash.Address.t()) :: {:ok, Address.t()}
- def find_or_insert_address_from_hash(%Hash{byte_count: unquote(Hash.Address.byte_count())} = hash) do
- case hash_to_address(hash) do
+ @spec find_or_insert_address_from_hash(Hash.Address.t(), [necessity_by_association_option], boolean()) ::
+ {:ok, Address.t()}
+ def find_or_insert_address_from_hash(
+ %Hash{byte_count: unquote(Hash.Address.byte_count())} = hash,
+ options \\ [
+ necessity_by_association: %{
+ :contracts_creation_internal_transaction => :optional,
+ :names => :optional,
+ :smart_contract => :optional,
+ :token => :optional,
+ :contracts_creation_transaction => :optional
+ }
+ ],
+ query_decompiled_code_flag \\ true
+ ) do
+ case hash_to_address(hash, options, query_decompiled_code_flag) do
{:ok, address} ->
{:ok, address}
{:error, :not_found} ->
create_address(%{hash: to_string(hash)})
- hash_to_address(hash)
+ hash_to_address(hash, options, query_decompiled_code_flag)
end
end
@@ -810,7 +843,7 @@ defmodule Explorer.Chain do
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) ::
+ @spec get_balance_as_of_block(Hash.Address.t(), Block.block_number() | :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 =
@@ -897,33 +930,44 @@ defmodule Explorer.Chain do
Repo.all(query)
end
- @spec find_contract_address(Hash.t()) :: {:ok, Address.t()} | {:error, :not_found}
- def find_contract_address(%Hash{byte_count: unquote(Hash.Address.byte_count())} = hash) do
+ @doc """
+ Finds an `t:Explorer.Chain.Address.t/0` that has the provided `t:Explorer.Chain.Address.t/0` `hash` and a contract.
+
+ ## Options
+
+ * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is
+ `:required`, and the `t:Explorer.Chain.Address.t/0` has no associated record for that association,
+ then the `t:Explorer.Chain.Address.t/0` will not be included in the list.
+
+ Optionally it also accepts a boolean to fetch the `has_decompiled_code?` virtual field or not
+
+ """
+ @spec find_contract_address(Hash.Address.t(), [necessity_by_association_option], boolean()) ::
+ {:ok, Address.t()} | {:error, :not_found}
+ def find_contract_address(
+ %Hash{byte_count: unquote(Hash.Address.byte_count())} = hash,
+ options \\ [],
+ query_decompiled_code_flag \\ false
+ ) do
+ necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
+
query =
from(
address in Address,
- preload: [
- :contracts_creation_internal_transaction,
- :names,
- :smart_contract,
- :token,
- :contracts_creation_transaction
- ],
where: address.hash == ^hash and not is_nil(address.contract_code)
)
- query_with_decompiled_flag = with_decompiled_code_flag(query, hash)
-
- address = Repo.one(query_with_decompiled_flag)
-
- if address do
- {:ok, address}
- else
- {:error, :not_found}
+ query
+ |> join_associations(necessity_by_association)
+ |> with_decompiled_code_flag(hash, query_decompiled_code_flag)
+ |> Repo.one()
+ |> case do
+ nil -> {:error, :not_found}
+ address -> {:ok, address}
end
end
- @spec find_decompiled_contract_address(Hash.t()) :: {:ok, Address.t()} | {:error, :not_found}
+ @spec find_decompiled_contract_address(Hash.Address.t()) :: {:ok, Address.t()} | {:error, :not_found}
def find_decompiled_contract_address(%Hash{byte_count: unquote(Hash.Address.byte_count())} = hash) do
query =
from(
@@ -1132,7 +1176,7 @@ defmodule Explorer.Chain do
"""
@spec indexed_ratio() :: Decimal.t()
def indexed_ratio do
- {min, max} = BlockNumberCache.min_and_max_numbers()
+ {min, max} = BlockNumber.min_and_max_numbers()
case {min, max} do
{0, 0} ->
@@ -1214,12 +1258,12 @@ defmodule Explorer.Chain do
block_type = Keyword.get(options, :block_type, "Block")
if block_type == "Block" && !paging_options.key do
- if BlocksCache.enough_elements?(paging_options.page_size) do
- BlocksCache.blocks(paging_options.page_size)
+ if Blocks.enough_elements?(paging_options.page_size) do
+ Blocks.blocks(paging_options.page_size)
else
elements = fetch_blocks(block_type, paging_options, necessity_by_association)
- BlocksCache.rewrite_cache(elements)
+ Blocks.rewrite_cache(elements)
elements
end
@@ -1301,7 +1345,7 @@ defmodule Explorer.Chain do
end
@doc """
- Finds all Blocks validated by the address given.
+ Finds all Blocks validated by the address with the given hash.
## Options
* `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is
@@ -1315,15 +1359,15 @@ defmodule Explorer.Chain do
"""
@spec get_blocks_validated_by_address(
[paging_options | necessity_by_association_option],
- Address.t()
+ Hash.Address.t()
) :: [Block.t()]
- def get_blocks_validated_by_address(options \\ [], %Address{hash: hash}) when is_list(options) do
+ def get_blocks_validated_by_address(options \\ [], address_hash) when is_list(options) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
Block
|> join_associations(necessity_by_association)
- |> where(miner_hash: ^hash)
+ |> where(miner_hash: ^address_hash)
|> page_blocks(paging_options)
|> limit(^paging_options.page_size)
|> order_by(desc: :number)
@@ -1347,10 +1391,10 @@ defmodule Explorer.Chain do
end
@doc """
- Counts the number of `t:Explorer.Chain.Block.t/0` validated by the `address`.
+ Counts the number of `t:Explorer.Chain.Block.t/0` validated by the address with the given `hash`.
"""
- @spec address_to_validation_count(Address.t()) :: non_neg_integer()
- def address_to_validation_count(%Address{hash: hash}) do
+ @spec address_to_validation_count(Hash.Address.t()) :: non_neg_integer()
+ def address_to_validation_count(hash) do
query = from(block in Block, where: block.miner_hash == ^hash, select: fragment("COUNT(*)"))
Repo.one(query)
@@ -1418,6 +1462,7 @@ defmodule Explorer.Chain do
iex> non_consensus = insert(:block, consensus: false)
iex> unfetched = insert(:block)
iex> fetched = insert(:block, internal_transactions_indexed_at: DateTime.utc_now())
+ iex> to_be_refetched = insert(:block, refetch_needed: true)
iex> {:ok, number_set} = Explorer.Chain.stream_blocks_with_unfetched_internal_transactions(
...> [:number],
...> MapSet.new(),
@@ -1431,6 +1476,8 @@ defmodule Explorer.Chain do
true
iex> fetched.hash in number_set
false
+ iex> to_be_refetched.number in number_set
+ false
"""
@spec stream_blocks_with_unfetched_internal_transactions(
@@ -1459,7 +1506,9 @@ defmodule Explorer.Chain do
query =
from(
b in Block,
- where: b.consensus and is_nil(b.internal_transactions_indexed_at),
+ where: b.consensus,
+ where: is_nil(b.internal_transactions_indexed_at),
+ where: not b.refetch_needed,
select: ^fields
)
@@ -1769,7 +1818,7 @@ defmodule Explorer.Chain do
Repo.one!(query)
end
- def last_block_status do
+ def last_db_block_status do
query =
from(block in Block,
select: {block.number, block.timestamp},
@@ -1778,22 +1827,39 @@ defmodule Explorer.Chain do
limit: 1
)
- case Repo.one(query) do
- nil ->
- {:error, :no_blocks}
+ query
+ |> Repo.one()
+ |> block_status()
+ end
- {number, timestamp} ->
- now = DateTime.utc_now()
- last_block_period = DateTime.diff(now, timestamp, :millisecond)
+ def last_cache_block_status do
+ [
+ paging_options: %PagingOptions{page_size: 1}
+ ]
+ |> list_blocks()
+ |> List.last()
+ |> case do
+ %{timestamp: timestamp, number: number} ->
+ block_status({number, timestamp})
- if last_block_period > Application.get_env(:explorer, :healthy_blocks_period) do
- {:error, number, timestamp}
- else
- {:ok, number, timestamp}
- end
+ _ ->
+ block_status(nil)
+ end
+ end
+
+ defp block_status({number, timestamp}) do
+ now = DateTime.utc_now()
+ last_block_period = DateTime.diff(now, timestamp, :millisecond)
+
+ if last_block_period > Application.get_env(:explorer, :healthy_blocks_period) do
+ {:error, number, timestamp}
+ else
+ {:ok, number, timestamp}
end
end
+ defp block_status(nil), do: {:error, :no_blocks}
+
@doc """
Calculates the ranges of missing consensus blocks in `range`.
@@ -1978,11 +2044,11 @@ defmodule Explorer.Chain do
if is_nil(paging_options.key) do
paging_options.page_size
- |> TransactionsCache.take_enough()
+ |> Transactions.take_enough()
|> case do
nil ->
transactions = fetch_recent_collated_transactions(paging_options, necessity_by_association)
- TransactionsCache.update(transactions)
+ Transactions.update(transactions)
transactions
transactions ->
@@ -2127,7 +2193,7 @@ defmodule Explorer.Chain do
"""
@spec transaction_estimated_count() :: non_neg_integer()
def transaction_estimated_count do
- cached_value = TransactionCountCache.value()
+ cached_value = TransactionCount.value()
if is_nil(cached_value) do
%Postgrex.Result{rows: [[rows]]} =
@@ -2146,7 +2212,7 @@ defmodule Explorer.Chain do
"""
@spec block_estimated_count() :: non_neg_integer()
def block_estimated_count do
- cached_value = BlockCountCache.count()
+ cached_value = BlockCount.count()
if is_nil(cached_value) do
%Postgrex.Result{rows: [[count]]} = Repo.query!("SELECT reltuples FROM pg_class WHERE relname = 'blocks';")
@@ -2171,14 +2237,10 @@ defmodule Explorer.Chain do
"""
- @spec transaction_to_internal_transactions(Transaction.t(), [paging_options | necessity_by_association_option]) :: [
+ @spec transaction_to_internal_transactions(Hash.Full.t(), [paging_options | necessity_by_association_option]) :: [
InternalTransaction.t()
]
- def transaction_to_internal_transactions(
- %Transaction{hash: %Hash{byte_count: unquote(Hash.Full.byte_count())} = hash},
- options \\ []
- )
- when is_list(options) do
+ def transaction_to_internal_transactions(hash, options \\ []) when is_list(options) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
@@ -2206,12 +2268,8 @@ defmodule Explorer.Chain do
the `index` that are passed.
"""
- @spec transaction_to_logs(Transaction.t(), [paging_options | necessity_by_association_option]) :: [Log.t()]
- def transaction_to_logs(
- %Transaction{hash: %Hash{byte_count: unquote(Hash.Full.byte_count())} = transaction_hash},
- options \\ []
- )
- when is_list(options) do
+ @spec transaction_to_logs(Hash.Full.t(), [paging_options | necessity_by_association_option]) :: [Log.t()]
+ def transaction_to_logs(transaction_hash, options \\ []) when is_list(options) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
@@ -2238,14 +2296,10 @@ defmodule Explorer.Chain do
the `index` that are passed.
"""
- @spec transaction_to_token_transfers(Transaction.t(), [paging_options | necessity_by_association_option]) :: [
+ @spec transaction_to_token_transfers(Hash.Full.t(), [paging_options | necessity_by_association_option]) :: [
TokenTransfer.t()
]
- def transaction_to_token_transfers(
- %Transaction{hash: %Hash{byte_count: unquote(Hash.Full.byte_count())} = transaction_hash},
- options \\ []
- )
- when is_list(options) do
+ def transaction_to_token_transfers(transaction_hash, options \\ []) when is_list(options) do
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
@@ -2394,8 +2448,13 @@ defmodule Explorer.Chain do
naming the address for reference.
"""
@spec create_smart_contract(map()) :: {:ok, SmartContract.t()} | {:error, Ecto.Changeset.t()}
- def create_smart_contract(attrs \\ %{}) do
- smart_contract_changeset = SmartContract.changeset(%SmartContract{}, attrs)
+ def create_smart_contract(attrs \\ %{}, external_libraries \\ []) do
+ new_contract = %SmartContract{}
+
+ smart_contract_changeset =
+ new_contract
+ |> SmartContract.changeset(attrs)
+ |> Changeset.put_change(:external_libraries, external_libraries)
insert_result =
Multi.new()
@@ -2467,16 +2526,16 @@ defmodule Explorer.Chain do
|> repo.insert(on_conflict: :nothing, conflict_target: [:address_hash, :name])
end
- @spec address_hash_to_address_with_source_code(%Explorer.Chain.Hash{}) :: %Explorer.Chain.Address{} | nil
- def address_hash_to_address_with_source_code(%Explorer.Chain.Hash{} = address_hash) do
+ @spec address_hash_to_address_with_source_code(Hash.Address.t()) :: Address.t() | nil
+ def address_hash_to_address_with_source_code(address_hash) do
case Repo.get(Address, address_hash) do
nil -> nil
address -> Repo.preload(address, [:smart_contract, :decompiled_smart_contracts])
end
end
- @spec address_hash_to_smart_contract(%Explorer.Chain.Hash{}) :: %Explorer.Chain.SmartContract{} | nil
- def address_hash_to_smart_contract(%Explorer.Chain.Hash{} = address_hash) do
+ @spec address_hash_to_smart_contract(Hash.Address.t()) :: SmartContract.t() | nil
+ def address_hash_to_smart_contract(address_hash) do
query =
from(
smart_contract in SmartContract,
@@ -2715,21 +2774,30 @@ defmodule Explorer.Chain do
@doc """
Fetches a `t:Token.t/0` by an address hash.
- Optionally accepts a list of bindings to preload, just like `Ecto.Query.preload/3`
+ ## Options
+
+ * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is
+ `:required`, and the `t:Token.t/0` has no associated record for that association,
+ then the `t:Token.t/0` will not be included in the list.
"""
- @spec token_from_address_hash(Hash.Address.t(), [Macro.t()]) :: {:ok, Token.t()} | {:error, :not_found}
+ @spec token_from_address_hash(Hash.Address.t(), [necessity_by_association_option]) ::
+ {:ok, Token.t()} | {:error, :not_found}
def token_from_address_hash(
%Hash{byte_count: unquote(Hash.Address.byte_count())} = hash,
- preloads \\ []
+ options \\ []
) do
+ necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
+
query =
from(
token in Token,
- where: token.contract_address_hash == ^hash,
- preload: ^preloads
+ where: token.contract_address_hash == ^hash
)
- case Repo.one(query) do
+ query
+ |> join_associations(necessity_by_association)
+ |> Repo.one()
+ |> case do
nil ->
{:error, :not_found}
@@ -3224,8 +3292,6 @@ defmodule Explorer.Chain do
defp staking_pool_filter(query, _), do: query
- 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
@@ -3248,4 +3314,203 @@ defmodule Explorer.Chain do
|> Base.decode16!(case: :mixed)
|> TypeDecoder.decode_raw(types)
end
+
+ @doc """
+ Checks if an `t:Explorer.Chain.Address.t/0` with the given `hash` exists.
+
+ Returns `:ok` if found
+
+ iex> {:ok, %Explorer.Chain.Address{hash: hash}} = Explorer.Chain.create_address(
+ ...> %{hash: "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"}
+ ...> )
+ iex> Explorer.Chain.check_address_exists(hash)
+ :ok
+
+ Returns `:not_found` if not found
+
+ iex> {:ok, hash} = Explorer.Chain.string_to_address_hash("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed")
+ iex> Explorer.Chain.check_address_exists(hash)
+ :not_found
+
+ """
+ @spec check_address_exists(Hash.Address.t()) :: :ok | :not_found
+ def check_address_exists(address_hash) do
+ address_hash
+ |> address_exists?()
+ |> boolean_to_check_result()
+ end
+
+ @doc """
+ Checks if an `t:Explorer.Chain.Address.t/0` with the given `hash` exists.
+
+ Returns `true` if found
+
+ iex> {:ok, %Explorer.Chain.Address{hash: hash}} = Explorer.Chain.create_address(
+ ...> %{hash: "0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed"}
+ ...> )
+ iex> Explorer.Chain.address_exists?(hash)
+ true
+
+ Returns `false` if not found
+
+ iex> {:ok, hash} = Explorer.Chain.string_to_address_hash("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed")
+ iex> Explorer.Chain.address_exists?(hash)
+ false
+
+ """
+ @spec address_exists?(Hash.Address.t()) :: boolean()
+ def address_exists?(address_hash) do
+ query =
+ from(
+ address in Address,
+ where: address.hash == ^address_hash
+ )
+
+ Repo.exists?(query)
+ end
+
+ @doc """
+ Checks if it exists an `t:Explorer.Chain.Address.t/0` that has the provided
+ `t:Explorer.Chain.Address.t/0` `hash` and a contract.
+
+ Returns `:ok` if found and `:not_found` otherwise.
+ """
+ @spec check_contract_address_exists(Hash.Address.t()) :: :ok | :not_found
+ def check_contract_address_exists(address_hash) do
+ address_hash
+ |> contract_address_exists?()
+ |> boolean_to_check_result()
+ end
+
+ @doc """
+ Checks if it exists an `t:Explorer.Chain.Address.t/0` that has the provided
+ `t:Explorer.Chain.Address.t/0` `hash` and a contract.
+
+ Returns `true` if found and `false` otherwise.
+ """
+ @spec contract_address_exists?(Hash.Address.t()) :: boolean()
+ def contract_address_exists?(address_hash) do
+ query =
+ from(
+ address in Address,
+ where: address.hash == ^address_hash and not is_nil(address.contract_code)
+ )
+
+ Repo.exists?(query)
+ end
+
+ @doc """
+ Checks if it exists a `t:Explorer.Chain.DecompiledSmartContract.t/0` for the
+ `t:Explorer.Chain.Address.t/0` with the provided `hash` and with the provided version.
+
+ Returns `:ok` if found and `:not_found` otherwise.
+ """
+ @spec check_decompiled_contract_exists(Hash.Address.t(), String.t()) :: :ok | :not_found
+ def check_decompiled_contract_exists(address_hash, version) do
+ address_hash
+ |> decompiled_contract_exists?(version)
+ |> boolean_to_check_result()
+ end
+
+ @doc """
+ Checks if it exists a `t:Explorer.Chain.DecompiledSmartContract.t/0` for the
+ `t:Explorer.Chain.Address.t/0` with the provided `hash` and with the provided version.
+
+ Returns `true` if found and `false` otherwise.
+ """
+ @spec decompiled_contract_exists?(Hash.Address.t(), String.t()) :: boolean()
+ def decompiled_contract_exists?(address_hash, version) do
+ query =
+ from(contract in DecompiledSmartContract,
+ where: contract.address_hash == ^address_hash and contract.decompiler_version == ^version
+ )
+
+ Repo.exists?(query)
+ end
+
+ @doc """
+ Checks if it exists a verified `t:Explorer.Chain.SmartContract.t/0` for the
+ `t:Explorer.Chain.Address.t/0` with the provided `hash`.
+
+ Returns `:ok` if found and `:not_found` otherwise.
+ """
+ @spec check_verified_smart_contract_exists(Hash.Address.t()) :: :ok | :not_found
+ def check_verified_smart_contract_exists(address_hash) do
+ address_hash
+ |> verified_smart_contract_exists?()
+ |> boolean_to_check_result()
+ end
+
+ @doc """
+ Checks if it exists a verified `t:Explorer.Chain.SmartContract.t/0` for the
+ `t:Explorer.Chain.Address.t/0` with the provided `hash`.
+
+ Returns `true` if found and `false` otherwise.
+ """
+ @spec verified_smart_contract_exists?(Hash.Address.t()) :: boolean()
+ def verified_smart_contract_exists?(address_hash) do
+ query =
+ from(
+ smart_contract in SmartContract,
+ where: smart_contract.address_hash == ^address_hash
+ )
+
+ Repo.exists?(query)
+ end
+
+ @doc """
+ Checks if a `t:Explorer.Chain.Transaction.t/0` with the given `hash` exists.
+
+ Returns `:ok` if found
+
+ iex> %Transaction{hash: hash} = insert(:transaction)
+ iex> Explorer.Chain.check_transaction_exists(hash)
+ :ok
+
+ Returns `:not_found` if not found
+
+ iex> {:ok, hash} = Explorer.Chain.string_to_transaction_hash(
+ ...> "0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b"
+ ...> )
+ iex> Explorer.Chain.check_transaction_exists(hash)
+ :not_found
+ """
+ @spec check_transaction_exists(Hash.Full.t()) :: :ok | :not_found
+ def check_transaction_exists(hash) do
+ hash
+ |> transaction_exists?()
+ |> boolean_to_check_result()
+ end
+
+ @doc """
+ Checks if a `t:Explorer.Chain.Transaction.t/0` with the given `hash` exists.
+
+ Returns `true` if found
+
+ iex> %Transaction{hash: hash} = insert(:transaction)
+ iex> Explorer.Chain.transaction_exists?(hash)
+ true
+
+ Returns `false` if not found
+
+ iex> {:ok, hash} = Explorer.Chain.string_to_transaction_hash(
+ ...> "0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b"
+ ...> )
+ iex> Explorer.Chain.transaction_exists?(hash)
+ false
+ """
+ @spec transaction_exists?(Hash.Full.t()) :: boolean()
+ def transaction_exists?(hash) do
+ query =
+ from(
+ transaction in Transaction,
+ where: transaction.hash == ^hash
+ )
+
+ Repo.exists?(query)
+ end
+
+ defp boolean_to_check_result(true), do: :ok
+
+ defp boolean_to_check_result(false), do: :not_found
end
diff --git a/apps/explorer/lib/explorer/chain/address.ex b/apps/explorer/lib/explorer/chain/address.ex
index 28b67f1d66..478c35deb1 100644
--- a/apps/explorer/lib/explorer/chain/address.ex
+++ b/apps/explorer/lib/explorer/chain/address.ex
@@ -16,13 +16,14 @@ defmodule Explorer.Chain.Address do
DecompiledSmartContract,
Hash,
InternalTransaction,
- NetVersionCache,
SmartContract,
Token,
Transaction,
Wei
}
+ alias Explorer.Chain.Cache.NetVersion
+
@optional_attrs ~w(contract_code fetched_coin_balance fetched_coin_balance_block_number nonce decompiled verified)a
@required_attrs ~w(hash)a
@allowed_attrs @optional_attrs ++ @required_attrs
@@ -169,7 +170,7 @@ defmodule Explorer.Chain.Address do
end
def rsk_checksum(hash) do
- chain_id = NetVersionCache.version()
+ chain_id = NetVersion.version()
string_hash =
hash
diff --git a/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex b/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex
index 96bd56afef..10d14fcc44 100644
--- a/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex
+++ b/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex
@@ -4,7 +4,7 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporter do
"""
alias Explorer.{Chain, PagingOptions}
- alias Explorer.Chain.{Address, TokenTransfer, Transaction}
+ alias Explorer.Chain.{TokenTransfer, Transaction}
alias NimbleCSV.RFC4180
@necessity_by_association [
@@ -24,18 +24,18 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporter do
@paging_options %PagingOptions{page_size: @page_size + 1}
def export(address) do
- address
+ address.hash
|> fetch_all_transactions(@paging_options)
|> to_token_transfers()
|> to_csv_format(address)
|> dump_to_stream()
end
- defp fetch_all_transactions(address, paging_options, acc \\ []) do
+ defp fetch_all_transactions(address_hash, paging_options, acc \\ []) do
options = Keyword.merge(@necessity_by_association, paging_options: paging_options)
transactions =
- address
+ address_hash
|> Chain.address_to_transactions_with_rewards(options)
|> Enum.filter(fn transaction -> Enum.count(transaction.token_transfers) > 0 end)
@@ -44,7 +44,7 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporter do
case Enum.split(transactions, @page_size) do
{_transactions, [%Transaction{block_number: block_number, index: index}]} ->
new_paging_options = %{@paging_options | key: {block_number, index}}
- fetch_all_transactions(address, new_paging_options, new_acc)
+ fetch_all_transactions(address_hash, new_paging_options, new_acc)
{_, []} ->
new_acc
@@ -90,7 +90,7 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporter do
token_transfer.from_address |> to_string() |> String.downcase(),
token_transfer.to_address |> to_string() |> String.downcase(),
token_transfer.token_contract_address |> to_string() |> String.downcase(),
- type(token_transfer, address),
+ type(token_transfer, address.hash),
token_transfer.token.symbol,
token_transfer.amount,
fee(token_transfer.transaction),
@@ -102,9 +102,9 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporter do
Stream.concat([row_names], token_transfer_lists)
end
- defp type(%TokenTransfer{from_address_hash: from_address}, %Address{hash: from_address}), do: "OUT"
+ defp type(%TokenTransfer{from_address_hash: address_hash}, address_hash), do: "OUT"
- defp type(%TokenTransfer{to_address_hash: to_address}, %Address{hash: to_address}), do: "IN"
+ defp type(%TokenTransfer{to_address_hash: address_hash}, address_hash), do: "IN"
defp type(_, _), do: ""
diff --git a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex
index 32028e02d3..64df1c7e59 100644
--- a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex
+++ b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex
@@ -35,23 +35,23 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do
def export(address) do
exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null()
- address
+ address.hash
|> fetch_all_transactions(@paging_options)
|> to_csv_format(address, exchange_rate)
|> dump_to_stream()
end
- defp fetch_all_transactions(address, paging_options, acc \\ []) do
+ defp fetch_all_transactions(address_hash, paging_options, acc \\ []) do
options = Keyword.merge(@necessity_by_association, paging_options: paging_options)
- transactions = Chain.address_to_transactions_with_rewards(address, options)
+ transactions = Chain.address_to_transactions_with_rewards(address_hash, options)
new_acc = transactions ++ acc
case Enum.split(transactions, @page_size) do
{_transactions, [%Transaction{block_number: block_number, index: index}]} ->
new_paging_options = %{@paging_options | key: {block_number, index}}
- fetch_all_transactions(address, new_paging_options, new_acc)
+ fetch_all_transactions(address_hash, new_paging_options, new_acc)
{_, []} ->
new_acc
@@ -93,7 +93,7 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do
to_string(transaction.from_address),
to_string(transaction.to_address),
to_string(transaction.created_contract_address),
- type(transaction, address),
+ type(transaction, address.hash),
Wei.to(transaction.value, :wei),
fee(transaction),
transaction.status,
@@ -107,9 +107,9 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do
Stream.concat([row_names], transaction_lists)
end
- defp type(%Transaction{from_address_hash: from_address}, %Address{hash: from_address}), do: "OUT"
+ defp type(%Transaction{from_address_hash: address_hash}, address_hash), do: "OUT"
- defp type(%Transaction{to_address_hash: to_address}, %Address{hash: to_address}), do: "IN"
+ defp type(%Transaction{to_address_hash: address_hash}, address_hash), do: "IN"
defp type(_, _), do: ""
diff --git a/apps/explorer/lib/explorer/chain/block_count_cache.ex b/apps/explorer/lib/explorer/chain/cache/block_count.ex
similarity index 98%
rename from apps/explorer/lib/explorer/chain/block_count_cache.ex
rename to apps/explorer/lib/explorer/chain/cache/block_count.ex
index 8eaa1ccf58..8ba09cc86f 100644
--- a/apps/explorer/lib/explorer/chain/block_count_cache.ex
+++ b/apps/explorer/lib/explorer/chain/cache/block_count.ex
@@ -1,4 +1,4 @@
-defmodule Explorer.Chain.BlockCountCache do
+defmodule Explorer.Chain.Cache.BlockCount do
@moduledoc """
Cache for block count.
"""
diff --git a/apps/explorer/lib/explorer/chain/block_number_cache.ex b/apps/explorer/lib/explorer/chain/cache/block_number.ex
similarity index 97%
rename from apps/explorer/lib/explorer/chain/block_number_cache.ex
rename to apps/explorer/lib/explorer/chain/cache/block_number.ex
index 2f335c7a2c..5212117793 100644
--- a/apps/explorer/lib/explorer/chain/block_number_cache.ex
+++ b/apps/explorer/lib/explorer/chain/cache/block_number.ex
@@ -1,4 +1,4 @@
-defmodule Explorer.Chain.BlockNumberCache do
+defmodule Explorer.Chain.Cache.BlockNumber do
@moduledoc """
Cache for max and min block numbers.
"""
diff --git a/apps/explorer/lib/explorer/chain/blocks_cache.ex b/apps/explorer/lib/explorer/chain/cache/blocks.ex
similarity index 98%
rename from apps/explorer/lib/explorer/chain/blocks_cache.ex
rename to apps/explorer/lib/explorer/chain/cache/blocks.ex
index 778d2bc5d6..76fc2473a6 100644
--- a/apps/explorer/lib/explorer/chain/blocks_cache.ex
+++ b/apps/explorer/lib/explorer/chain/cache/blocks.ex
@@ -1,4 +1,4 @@
-defmodule Explorer.Chain.BlocksCache do
+defmodule Explorer.Chain.Cache.Blocks do
@moduledoc """
Caches the last imported blocks
"""
diff --git a/apps/explorer/lib/explorer/chain/net_version_cache.ex b/apps/explorer/lib/explorer/chain/cache/net_version.ex
similarity index 94%
rename from apps/explorer/lib/explorer/chain/net_version_cache.ex
rename to apps/explorer/lib/explorer/chain/cache/net_version.ex
index c3df467e0c..ac33ca8f45 100644
--- a/apps/explorer/lib/explorer/chain/net_version_cache.ex
+++ b/apps/explorer/lib/explorer/chain/cache/net_version.ex
@@ -1,4 +1,4 @@
-defmodule Explorer.Chain.NetVersionCache do
+defmodule Explorer.Chain.Cache.NetVersion do
@moduledoc """
Caches chain version.
"""
diff --git a/apps/explorer/lib/explorer/chain/transaction_count_cache.ex b/apps/explorer/lib/explorer/chain/cache/transaction_count.ex
similarity index 98%
rename from apps/explorer/lib/explorer/chain/transaction_count_cache.ex
rename to apps/explorer/lib/explorer/chain/cache/transaction_count.ex
index 95685c0fd4..e1fe2dad82 100644
--- a/apps/explorer/lib/explorer/chain/transaction_count_cache.ex
+++ b/apps/explorer/lib/explorer/chain/cache/transaction_count.ex
@@ -1,4 +1,4 @@
-defmodule Explorer.Chain.TransactionCountCache do
+defmodule Explorer.Chain.Cache.TransactionCount do
@moduledoc """
Cache for estimated transaction count.
"""
diff --git a/apps/explorer/lib/explorer/chain/transactions_cache.ex b/apps/explorer/lib/explorer/chain/cache/transactions.ex
similarity index 98%
rename from apps/explorer/lib/explorer/chain/transactions_cache.ex
rename to apps/explorer/lib/explorer/chain/cache/transactions.ex
index 3859561295..54748258cf 100644
--- a/apps/explorer/lib/explorer/chain/transactions_cache.ex
+++ b/apps/explorer/lib/explorer/chain/cache/transactions.ex
@@ -1,4 +1,4 @@
-defmodule Explorer.Chain.TransactionsCache do
+defmodule Explorer.Chain.Cache.Transactions do
@moduledoc """
Caches the latest imported transactions
"""
diff --git a/apps/explorer/lib/explorer/chain/data.ex b/apps/explorer/lib/explorer/chain/data.ex
index ee03bdeb24..a3bec019af 100644
--- a/apps/explorer/lib/explorer/chain/data.ex
+++ b/apps/explorer/lib/explorer/chain/data.ex
@@ -7,6 +7,7 @@ defmodule Explorer.Chain.Data do
"""
alias Explorer.Chain.Data
+ alias Poison.Encoder.BitString
@behaviour Ecto.Type
@@ -380,4 +381,22 @@ defmodule Explorer.Chain.Data do
@for.to_string(data)
end
end
+
+ defimpl Poison.Encoder do
+ def encode(data, options) do
+ data
+ |> to_string()
+ |> BitString.encode(options)
+ end
+ end
+
+ defimpl Jason.Encoder do
+ alias Jason.Encode
+
+ def encode(data, opts) do
+ data
+ |> to_string()
+ |> Encode.string(opts)
+ end
+ end
end
diff --git a/apps/explorer/lib/explorer/chain/events/publisher.ex b/apps/explorer/lib/explorer/chain/events/publisher.ex
index 7077fdfeab..c7e01eef3a 100644
--- a/apps/explorer/lib/explorer/chain/events/publisher.ex
+++ b/apps/explorer/lib/explorer/chain/events/publisher.ex
@@ -3,7 +3,7 @@ defmodule Explorer.Chain.Events.Publisher do
Publishes events related to the Chain context.
"""
- @allowed_events ~w(addresses address_coin_balances blocks block_rewards internal_transactions token_transfers transactions)a
+ @allowed_events ~w(addresses address_coin_balances blocks block_rewards internal_transactions token_transfers transactions contract_verification_result)a
def broadcast(_data, false), do: :ok
diff --git a/apps/explorer/lib/explorer/chain/events/subscriber.ex b/apps/explorer/lib/explorer/chain/events/subscriber.ex
index 32e8a9e605..02c7019f9c 100644
--- a/apps/explorer/lib/explorer/chain/events/subscriber.ex
+++ b/apps/explorer/lib/explorer/chain/events/subscriber.ex
@@ -3,9 +3,9 @@ defmodule Explorer.Chain.Events.Subscriber do
Subscribes to events related to the Chain context.
"""
- @allowed_broadcast_events ~w(addresses address_coin_balances blocks block_rewards internal_transactions token_transfers transactions)a
+ @allowed_broadcast_events ~w(addresses address_coin_balances blocks block_rewards internal_transactions token_transfers transactions contract_verification_result)a
- @allowed_broadcast_types ~w(catchup realtime on_demand)a
+ @allowed_broadcast_types ~w(catchup realtime on_demand contract_verification_result)a
@allowed_events ~w(exchange_rate transaction_stats)a
diff --git a/apps/explorer/lib/explorer/chain/import/runner/transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/transactions.ex
index 2c36d7c7e5..570d934218 100644
--- a/apps/explorer/lib/explorer/chain/import/runner/transactions.ex
+++ b/apps/explorer/lib/explorer/chain/import/runner/transactions.ex
@@ -105,6 +105,7 @@ defmodule Explorer.Chain.Import.Runner.Transactions do
old_block_hash: transaction.block_hash,
block_number: fragment("EXCLUDED.block_number"),
created_contract_address_hash: fragment("EXCLUDED.created_contract_address_hash"),
+ created_contract_code_indexed_at: fragment("EXCLUDED.created_contract_code_indexed_at"),
cumulative_gas_used: fragment("EXCLUDED.cumulative_gas_used"),
error: fragment("EXCLUDED.error"),
from_address_hash: fragment("EXCLUDED.from_address_hash"),
@@ -128,10 +129,11 @@ defmodule Explorer.Chain.Import.Runner.Transactions do
],
where:
fragment(
- "(EXCLUDED.block_hash, EXCLUDED.block_number, EXCLUDED.created_contract_address_hash, EXCLUDED.cumulative_gas_used, EXCLUDED.cumulative_gas_used, EXCLUDED.from_address_hash, EXCLUDED.gas, EXCLUDED.gas_price, EXCLUDED.gas_used, EXCLUDED.index, EXCLUDED.internal_transactions_indexed_at, EXCLUDED.input, EXCLUDED.nonce, EXCLUDED.r, EXCLUDED.s, EXCLUDED.status, EXCLUDED.to_address_hash, EXCLUDED.v, EXCLUDED.value) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+ "(EXCLUDED.block_hash, EXCLUDED.block_number, EXCLUDED.created_contract_address_hash, EXCLUDED.created_contract_code_indexed_at, EXCLUDED.cumulative_gas_used, EXCLUDED.cumulative_gas_used, EXCLUDED.from_address_hash, EXCLUDED.gas, EXCLUDED.gas_price, EXCLUDED.gas_used, EXCLUDED.index, EXCLUDED.internal_transactions_indexed_at, EXCLUDED.input, EXCLUDED.nonce, EXCLUDED.r, EXCLUDED.s, EXCLUDED.status, EXCLUDED.to_address_hash, EXCLUDED.v, EXCLUDED.value) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
transaction.block_hash,
transaction.block_number,
transaction.created_contract_address_hash,
+ transaction.created_contract_code_indexed_at,
transaction.cumulative_gas_used,
transaction.cumulative_gas_used,
transaction.from_address_hash,
diff --git a/apps/explorer/lib/explorer/chain/internal_transaction.ex b/apps/explorer/lib/explorer/chain/internal_transaction.ex
index 1231677a57..77f7d15da2 100644
--- a/apps/explorer/lib/explorer/chain/internal_transaction.ex
+++ b/apps/explorer/lib/explorer/chain/internal_transaction.ex
@@ -392,7 +392,7 @@ defmodule Explorer.Chain.InternalTransaction do
@create_required_fields ~w(from_address_hash gas index init trace_address transaction_hash value)a
@create_allowed_fields @create_optional_fields ++ @create_required_fields
- defp type_changeset(changeset, attrs, :create) do
+ defp type_changeset(changeset, attrs, type) when type in [:create, :create2] do
changeset
|> cast(attrs, @create_allowed_fields)
|> validate_required(@create_required_fields)
@@ -537,7 +537,7 @@ defmodule Explorer.Chain.InternalTransaction do
|> put_raw_call_error_or_result(transaction)
end
- defp internal_transaction_to_raw(%{type: :create} = transaction) do
+ defp internal_transaction_to_raw(%{type: type} = transaction) when type in [:create, :create2] do
%{
from_address_hash: from_address_hash,
gas: gas,
@@ -549,7 +549,7 @@ defmodule Explorer.Chain.InternalTransaction do
action = %{"from" => from_address_hash, "gas" => gas, "init" => init, "value" => value}
%{
- "type" => "create",
+ "type" => Atom.to_string(type),
"action" => Action.to_raw(action),
"traceAddress" => trace_address
}
diff --git a/apps/explorer/lib/explorer/chain/internal_transaction/type.ex b/apps/explorer/lib/explorer/chain/internal_transaction/type.ex
index 4133dcf45f..0a13587afe 100644
--- a/apps/explorer/lib/explorer/chain/internal_transaction/type.ex
+++ b/apps/explorer/lib/explorer/chain/internal_transaction/type.ex
@@ -11,7 +11,7 @@ defmodule Explorer.Chain.InternalTransaction.Type do
* `:reward`
* `:selfdestruct`
"""
- @type t :: :call | :create | :reward | :selfdestruct
+ @type t :: :call | :create | :create2 | :reward | :selfdestruct
@doc """
Casts `term` to `t:t/0`
@@ -22,6 +22,8 @@ defmodule Explorer.Chain.InternalTransaction.Type do
{:ok, :call}
iex> Explorer.Chain.InternalTransaction.Type.cast(:create)
{:ok, :create}
+ iex> Explorer.Chain.InternalTransaction.Type.cast(:create2)
+ {:ok, :create2}
iex> Explorer.Chain.InternalTransaction.Type.cast(:reward)
{:ok, :reward}
iex> Explorer.Chain.InternalTransaction.Type.cast(:selfdestruct)
@@ -33,6 +35,8 @@ defmodule Explorer.Chain.InternalTransaction.Type do
{:ok, :call}
iex> Explorer.Chain.InternalTransaction.Type.cast("create")
{:ok, :create}
+ iex> Explorer.Chain.InternalTransaction.Type.cast("create2")
+ {:ok, :create2}
iex> Explorer.Chain.InternalTransaction.Type.cast("reward")
{:ok, :reward}
iex> Explorer.Chain.InternalTransaction.Type.cast("selfdestruct")
@@ -53,9 +57,10 @@ defmodule Explorer.Chain.InternalTransaction.Type do
"""
@impl Ecto.Type
@spec cast(term()) :: {:ok, t()} | :error
- def cast(t) when t in ~w(call create selfdestruct reward)a, do: {:ok, t}
+ def cast(t) when t in ~w(call create create2 selfdestruct reward)a, do: {:ok, t}
def cast("call"), do: {:ok, :call}
def cast("create"), do: {:ok, :create}
+ def cast("create2"), do: {:ok, :create2}
def cast("reward"), do: {:ok, :reward}
def cast("selfdestruct"), do: {:ok, :selfdestruct}
def cast(_), do: :error
@@ -67,6 +72,8 @@ defmodule Explorer.Chain.InternalTransaction.Type do
{:ok, "call"}
iex> Explorer.Chain.InternalTransaction.Type.dump(:create)
{:ok, "create"}
+ iex> Explorer.Chain.InternalTransaction.Type.dump(:create2)
+ {:ok, "create2"}
iex> Explorer.Chain.InternalTransaction.Type.dump(:reward)
{:ok, "reward"}
iex> Explorer.Chain.InternalTransaction.Type.dump(:selfdestruct)
@@ -87,6 +94,7 @@ defmodule Explorer.Chain.InternalTransaction.Type do
@spec dump(term()) :: {:ok, String.t()} | :error
def dump(:call), do: {:ok, "call"}
def dump(:create), do: {:ok, "create"}
+ def dump(:create2), do: {:ok, "create2"}
def dump(:reward), do: {:ok, "reward"}
def dump(:selfdestruct), do: {:ok, "selfdestruct"}
def dump(_), do: :error
@@ -98,6 +106,8 @@ defmodule Explorer.Chain.InternalTransaction.Type do
{:ok, :call}
iex> Explorer.Chain.InternalTransaction.Type.load("create")
{:ok, :create}
+ iex> Explorer.Chain.InternalTransaction.Type.load("create2")
+ {:ok, :create2}
iex> Explorer.Chain.InternalTransaction.Type.load("reward")
{:ok, :reward}
iex> Explorer.Chain.InternalTransaction.Type.load("selfdestruct")
@@ -118,6 +128,7 @@ defmodule Explorer.Chain.InternalTransaction.Type do
@spec load(term()) :: {:ok, t()} | :error
def load("call"), do: {:ok, :call}
def load("create"), do: {:ok, :create}
+ def load("create2"), do: {:ok, :create2}
def load("reward"), do: {:ok, :reward}
def load("selfdestruct"), do: {:ok, :selfdestruct}
# deprecated
diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex
index 9dee6b147e..ed579e8e00 100644
--- a/apps/explorer/lib/explorer/chain/smart_contract.ex
+++ b/apps/explorer/lib/explorer/chain/smart_contract.ex
@@ -13,6 +13,7 @@ defmodule Explorer.Chain.SmartContract do
use Explorer.Schema
alias Explorer.Chain.{Address, ContractMethod, DecompiledSmartContract, Hash}
+ alias Explorer.Chain.SmartContract.ExternalLibrary
alias Explorer.Repo
@typedoc """
@@ -212,7 +213,7 @@ defmodule Explorer.Chain.SmartContract do
field(:constructor_arguments, :string)
field(:evm_version, :string)
field(:optimization_runs, :integer)
- field(:external_libraries, :map)
+ embeds_many(:external_libraries, ExternalLibrary)
field(:abi, {:array, :map})
has_many(
@@ -247,8 +248,7 @@ defmodule Explorer.Chain.SmartContract do
:abi,
:constructor_arguments,
:evm_version,
- :optimization_runs,
- :external_libraries
+ :optimization_runs
])
|> validate_required([:name, :compiler_version, :optimization, :contract_source_code, :abi, :address_hash])
|> unique_constraint(:address_hash)
diff --git a/apps/explorer/lib/explorer/chain/smart_contract/external_library.ex b/apps/explorer/lib/explorer/chain/smart_contract/external_library.ex
new file mode 100644
index 0000000000..ac52388de5
--- /dev/null
+++ b/apps/explorer/lib/explorer/chain/smart_contract/external_library.ex
@@ -0,0 +1,12 @@
+defmodule Explorer.Chain.SmartContract.ExternalLibrary do
+ @moduledoc """
+ The representation of an external library that was used for a smart contract.
+ """
+
+ use Ecto.Schema
+
+ embedded_schema do
+ field(:name)
+ field(:address_hash)
+ end
+end
diff --git a/apps/explorer/lib/explorer/chain/supply/rsk.ex b/apps/explorer/lib/explorer/chain/supply/rsk.ex
index bc4171e45f..cdb043418b 100644
--- a/apps/explorer/lib/explorer/chain/supply/rsk.ex
+++ b/apps/explorer/lib/explorer/chain/supply/rsk.ex
@@ -10,7 +10,8 @@ defmodule Explorer.Chain.Supply.RSK do
alias EthereumJSONRPC.FetchedBalances
alias Explorer.Chain.Address.CoinBalance
- alias Explorer.Chain.{Block, BlockNumberCache, Wei}
+ alias Explorer.Chain.{Block, Wei}
+ alias Explorer.Chain.Cache.BlockNumber
alias Explorer.Repo
@cache_name :rsk_balance
@@ -99,7 +100,7 @@ defmodule Explorer.Chain.Supply.RSK do
def cache_name, do: @cache_name
defp fetch_circulating_value do
- max_number = BlockNumberCache.max_number()
+ max_number = BlockNumber.max_number()
params = [
%{block_quantity: integer_to_quantity(max_number), hash_data: "0x0000000000000000000000000000000001000006"}
diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex
index 3b109504eb..78d162fa4e 100644
--- a/apps/explorer/lib/explorer/chain/transaction.ex
+++ b/apps/explorer/lib/explorer/chain/transaction.ex
@@ -416,7 +416,8 @@ defmodule Explorer.Chain.Transaction do
candidates_query =
from(
contract_method in ContractMethod,
- where: contract_method.identifier == ^method_id
+ where: contract_method.identifier == ^method_id,
+ limit: 1
)
candidates =
diff --git a/apps/explorer/lib/explorer/counters/average_block_time.ex b/apps/explorer/lib/explorer/counters/average_block_time.ex
index 233c15f8b3..1065ede2bd 100644
--- a/apps/explorer/lib/explorer/counters/average_block_time.ex
+++ b/apps/explorer/lib/explorer/counters/average_block_time.ex
@@ -11,7 +11,7 @@ defmodule Explorer.Counters.AverageBlockTime do
alias Explorer.Repo
alias Timex.Duration
- @refresh_period 30 * 60 * 1_000
+ @refresh_period Application.get_env(:explorer, __MODULE__)[:period]
@doc """
Starts a process to periodically update the counter of the token holders.
diff --git a/apps/explorer/lib/explorer/eth_rpc.ex b/apps/explorer/lib/explorer/eth_rpc.ex
new file mode 100644
index 0000000000..e0864ac279
--- /dev/null
+++ b/apps/explorer/lib/explorer/eth_rpc.ex
@@ -0,0 +1,371 @@
+defmodule Explorer.EthRPC do
+ @moduledoc """
+ Ethreum JSON RPC methods logic implementation.
+ """
+
+ alias Ecto.Type, as: EctoType
+ alias Explorer.{Chain, Repo}
+ alias Explorer.Chain.{Block, Data, Hash, Hash.Address, Wei}
+ alias Explorer.Etherscan.Logs
+
+ @methods %{
+ "eth_getBalance" => %{
+ action: :eth_get_balance,
+ notes: """
+ the `earliest` parameter will not work as expected currently, because genesis block balances
+ are not currently imported
+ """,
+ example: """
+ {"id": 0, "jsonrpc": "2.0", "method": "eth_getBalance", "params": ["0x0000000000000000000000000000000000000007", "2"]}
+ """
+ },
+ "eth_getLogs" => %{
+ action: :eth_get_logs,
+ notes: """
+ Will never return more than 1000 log entries.\n
+ For this reason, you can use pagination options to request the next page. Pagination options params: {"logIndex": "3D", "blockNumber": "6423AC", "transactionIndex": 53} which include parameters from the last log received from the previous request. These three parameters are required for pagination.
+ """,
+ example: """
+ {"id": 0, "jsonrpc": "2.0", "method": "eth_getLogs",
+ "params": [
+ {"address": "0xc78Be425090Dbd437532594D12267C5934Cc6c6f",
+ "paging_options": {"logIndex": "3D", "blockNumber": "6423AC", "transactionIndex": 53},
+ "fromBlock": "earliest",
+ "toBlock": "latest",
+ "topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]}]}
+ """
+ }
+ }
+
+ @index_to_word %{
+ 0 => "first",
+ 1 => "second",
+ 2 => "third",
+ 3 => "fourth"
+ }
+
+ def 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
+
+ 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
+
+ def eth_get_logs(filter_options) do
+ with {:ok, address_or_topic_params} <- address_or_topic_params(filter_options),
+ {:ok, from_block_param, to_block_param} <- logs_blocks_filter(filter_options),
+ {:ok, from_block} <- cast_block(from_block_param),
+ {:ok, to_block} <- cast_block(to_block_param),
+ {:ok, paging_options} <- paging_options(filter_options) do
+ filter =
+ address_or_topic_params
+ |> Map.put(:from_block, from_block)
+ |> Map.put(:to_block, to_block)
+ |> Map.put(:allow_non_consensus, true)
+
+ logs =
+ filter
+ |> Logs.list_logs(paging_options)
+ |> Enum.map(&render_log/1)
+
+ {:ok, logs}
+ else
+ {:error, message} when is_bitstring(message) ->
+ {:error, message}
+
+ {:error, :empty} ->
+ {:ok, []}
+
+ _ ->
+ {:error, "Something went wrong."}
+ end
+ end
+
+ defp render_log(log) do
+ topics =
+ Enum.reject(
+ [log.first_topic, log.second_topic, log.third_topic, log.fourth_topic],
+ &is_nil/1
+ )
+
+ %{
+ "address" => to_string(log.address_hash),
+ "blockHash" => to_string(log.block_hash),
+ "blockNumber" => Integer.to_string(log.block_number, 16),
+ "data" => to_string(log.data),
+ "logIndex" => Integer.to_string(log.index, 16),
+ "removed" => log.block_consensus == false,
+ "topics" => topics,
+ "transactionHash" => to_string(log.transaction_hash),
+ "transactionIndex" => log.transaction_index,
+ "transactionLogIndex" => log.index,
+ "type" => "mined"
+ }
+ end
+
+ defp cast_block("0x" <> hexadecimal_digits = input) do
+ case Integer.parse(hexadecimal_digits, 16) do
+ {integer, ""} -> {:ok, integer}
+ _ -> {:error, input <> " is not a valid block number"}
+ end
+ end
+
+ defp cast_block(integer) when is_integer(integer), do: {:ok, integer}
+ defp cast_block(_), do: {:error, "invalid block number"}
+
+ defp address_or_topic_params(filter_options) do
+ address_param = Map.get(filter_options, "address")
+ topics_param = Map.get(filter_options, "topics")
+
+ with {:ok, address} <- validate_address(address_param),
+ {:ok, topics} <- validate_topics(topics_param) do
+ address_and_topics(address, topics)
+ end
+ end
+
+ defp address_and_topics(nil, nil), do: {:error, "Must supply one of address and topics"}
+ defp address_and_topics(address, nil), do: {:ok, %{address_hash: address}}
+ defp address_and_topics(nil, topics), do: {:ok, topics}
+ defp address_and_topics(address, topics), do: {:ok, Map.put(topics, :address_hash, address)}
+
+ defp validate_address(nil), do: {:ok, nil}
+
+ defp validate_address(address) do
+ case Address.cast(address) do
+ {:ok, address} -> {:ok, address}
+ :error -> {:error, "invalid address"}
+ end
+ end
+
+ defp validate_topics(nil), do: {:ok, nil}
+ defp validate_topics([]), do: []
+
+ defp validate_topics(topics) when is_list(topics) do
+ topics
+ |> Stream.with_index()
+ |> Enum.reduce({:ok, %{}}, fn {topic, index}, {:ok, acc} ->
+ case cast_topics(topic) do
+ {:ok, data} ->
+ with_filter = Map.put(acc, String.to_existing_atom("#{@index_to_word[index]}_topic"), data)
+
+ {:ok, add_operator(with_filter, index)}
+
+ :error ->
+ {:error, "invalid topics"}
+ end
+ end)
+ end
+
+ defp add_operator(filters, 0), do: filters
+
+ defp add_operator(filters, index) do
+ Map.put(filters, String.to_existing_atom("topic#{index - 1}_#{index}_opr"), "and")
+ end
+
+ defp cast_topics(topics) when is_list(topics) do
+ case EctoType.cast({:array, Data}, topics) do
+ {:ok, data} -> {:ok, Enum.map(data, &to_string/1)}
+ :error -> :error
+ end
+ end
+
+ defp cast_topics(topic) do
+ case Data.cast(topic) do
+ {:ok, data} -> {:ok, to_string(data)}
+ :error -> :error
+ end
+ end
+
+ defp logs_blocks_filter(filter_options) do
+ with {:filter, %{"blockHash" => block_hash_param}} <- {:filter, filter_options},
+ {:block_hash, {:ok, block_hash}} <- {:block_hash, Hash.Full.cast(block_hash_param)},
+ {:block, %{number: number}} <- {:block, Repo.get(Block, block_hash)} do
+ {:ok, number, number}
+ else
+ {:filter, filters} ->
+ from_block = Map.get(filters, "fromBlock", "latest")
+ to_block = Map.get(filters, "toBlock", "latest")
+
+ max_block_number =
+ if from_block == "latest" || to_block == "latest" do
+ max_consensus_block_number()
+ end
+
+ pending_block_number =
+ if from_block == "pending" || to_block == "pending" do
+ max_non_consensus_block_number(max_block_number)
+ end
+
+ if is_nil(pending_block_number) && from_block == "pending" && to_block == "pending" do
+ {:error, :empty}
+ else
+ to_block_numbers(from_block, to_block, max_block_number, pending_block_number)
+ end
+
+ {:block, _} ->
+ {:error, "Invalid Block Hash"}
+
+ {:block_hash, _} ->
+ {:error, "Invalid Block Hash"}
+ end
+ end
+
+ defp paging_options(%{
+ "paging_options" => %{
+ "logIndex" => log_index,
+ "transactionIndex" => transaction_index,
+ "blockNumber" => block_number
+ }
+ })
+ when is_integer(transaction_index) do
+ with {:ok, parsed_block_number} <- to_number(block_number, "invalid block number"),
+ {:ok, parsed_log_index} <- to_number(log_index, "invalid log index") do
+ {:ok,
+ %{
+ log_index: parsed_log_index,
+ transaction_index: transaction_index,
+ block_number: parsed_block_number
+ }}
+ end
+ end
+
+ defp paging_options(_), do: {:ok, nil}
+
+ defp to_block_numbers(from_block, to_block, max_block_number, pending_block_number) do
+ actual_pending_block_number = pending_block_number || max_block_number
+
+ with {:ok, from} <-
+ to_block_number(from_block, max_block_number, actual_pending_block_number),
+ {:ok, to} <- to_block_number(to_block, max_block_number, actual_pending_block_number) do
+ {:ok, from, to}
+ end
+ end
+
+ defp to_block_number(integer, _, _) when is_integer(integer), do: {:ok, integer}
+ defp to_block_number("latest", max_block_number, _), do: {:ok, max_block_number || 0}
+ defp to_block_number("earliest", _, _), do: {:ok, 0}
+ defp to_block_number("pending", max_block_number, nil), do: {:ok, max_block_number || 0}
+ defp to_block_number("pending", _, pending), do: {:ok, pending}
+
+ defp to_block_number("0x" <> number, _, _) do
+ case Integer.parse(number, 16) do
+ {integer, ""} -> {:ok, integer}
+ _ -> {:error, "invalid block number"}
+ end
+ end
+
+ defp to_block_number(number, _, _) when is_bitstring(number) do
+ case Integer.parse(number, 16) do
+ {integer, ""} -> {:ok, integer}
+ _ -> {:error, "invalid block number"}
+ end
+ end
+
+ defp to_block_number(_, _, _), do: {:error, "invalid block number"}
+
+ defp to_number(number, error_message) when is_bitstring(number) do
+ case Integer.parse(number, 16) do
+ {integer, ""} -> {:ok, integer}
+ _ -> {:error, error_message}
+ end
+ end
+
+ defp to_number(_, error_message), do: {:error, error_message}
+
+ defp max_non_consensus_block_number(max) do
+ case Chain.max_non_consensus_block_number(max) do
+ {:ok, number} -> number
+ _ -> nil
+ end
+ end
+
+ defp max_consensus_block_number do
+ case Chain.max_consensus_block_number() do
+ {:ok, number} -> number
+ _ -> nil
+ 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),
+ {:correct_arity, true} <-
+ {:correct_arity, :erlang.function_exported(__MODULE__, action, Enum.count(params))} do
+ apply(__MODULE__, action, params)
+ else
+ {:correct_arity, _} ->
+ {:error, "Incorrect number of params."}
+
+ _ ->
+ {: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
+
+ defp get_action(action) do
+ case Map.get(@methods, action) do
+ %{action: action} ->
+ {:ok, action}
+
+ _ ->
+ :error
+ end
+ end
+
+ 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
+
+ def methods, do: @methods
+end
diff --git a/apps/explorer/lib/explorer/etherscan/logs.ex b/apps/explorer/lib/explorer/etherscan/logs.ex
index 9e9d70ae02..336eae106b 100644
--- a/apps/explorer/lib/explorer/etherscan/logs.ex
+++ b/apps/explorer/lib/explorer/etherscan/logs.ex
@@ -5,7 +5,7 @@ defmodule Explorer.Etherscan.Logs do
"""
- import Ecto.Query, only: [from: 2, where: 3, subquery: 1]
+ import Ecto.Query, only: [from: 2, where: 3, subquery: 1, order_by: 3]
alias Explorer.Chain.{Block, InternalTransaction, Log, Transaction}
alias Explorer.Repo
@@ -38,6 +38,8 @@ defmodule Explorer.Etherscan.Logs do
:type
]
+ @default_paging_options %{block_number: nil, transaction_index: nil, log_index: nil}
+
@doc """
Gets a list of logs that meet the criteria in a given filter map.
@@ -68,7 +70,10 @@ defmodule Explorer.Etherscan.Logs do
"""
@spec list_logs(map()) :: [map()]
- def list_logs(%{address_hash: address_hash} = filter) when not is_nil(address_hash) do
+ def list_logs(filter, paging_options \\ @default_paging_options)
+
+ def list_logs(%{address_hash: address_hash} = filter, paging_options) when not is_nil(address_hash) do
+ paging_options = if is_nil(paging_options), do: @default_paging_options, else: paging_options
prepared_filter = Map.merge(@base_filter, filter)
logs_query = where_topic_match(Log, prepared_filter)
@@ -134,14 +139,18 @@ defmodule Explorer.Etherscan.Logs do
)
end
- Repo.all(query_with_consensus)
+ query_with_consensus
+ |> order_by([log], asc: log.index)
+ |> page_logs(paging_options)
+ |> Repo.all()
end
# Since address_hash was not present, we know that a
# topic filter has been applied, so we use a different
# query that is optimized for a logs filter over an
# address_hash
- def list_logs(filter) do
+ def list_logs(filter, paging_options) do
+ paging_options = if is_nil(paging_options), do: @default_paging_options, else: paging_options
prepared_filter = Map.merge(@base_filter, filter)
logs_query = where_topic_match(Log, prepared_filter)
@@ -182,7 +191,10 @@ defmodule Explorer.Etherscan.Logs do
select_merge: map(log, ^@log_fields)
)
- Repo.all(query_with_block_transaction_data)
+ query_with_block_transaction_data
+ |> order_by([log], asc: log.index)
+ |> page_logs(paging_options)
+ |> Repo.all()
end
@topics [
@@ -231,4 +243,17 @@ defmodule Explorer.Etherscan.Logs do
end
defp where_multiple_topics_match(query, _, _, _), do: query
+
+ defp page_logs(query, %{block_number: nil, transaction_index: nil, log_index: nil}) do
+ query
+ end
+
+ defp page_logs(query, %{block_number: block_number, transaction_index: transaction_index, log_index: log_index}) do
+ from(
+ data in query,
+ where:
+ data.index > ^log_index and data.block_number >= ^block_number and
+ data.transaction_index >= ^transaction_index
+ )
+ end
end
diff --git a/apps/explorer/lib/explorer/graphql.ex b/apps/explorer/lib/explorer/graphql.ex
index 2ab931f743..5f1404a14f 100644
--- a/apps/explorer/lib/explorer/graphql.ex
+++ b/apps/explorer/lib/explorer/graphql.ex
@@ -12,7 +12,6 @@ defmodule Explorer.GraphQL do
]
alias Explorer.Chain.{
- Address,
Hash,
InternalTransaction,
TokenTransfer,
@@ -23,12 +22,12 @@ defmodule Explorer.GraphQL do
@doc """
Returns a query to fetch transactions with a matching `to_address_hash`,
- `from_address_hash`, or `created_contract_address_hash` field for a given address.
+ `from_address_hash`, or `created_contract_address_hash` field for a given address hash.
Orders transactions by descending block number and index.
"""
- @spec address_to_transactions_query(Address.t()) :: Ecto.Query.t()
- def address_to_transactions_query(%Address{hash: %Hash{byte_count: unquote(Hash.Address.byte_count())} = address_hash}) do
+ @spec address_to_transactions_query(Hash.Address.t()) :: Ecto.Query.t()
+ def address_to_transactions_query(address_hash) do
Transaction
|> order_by([transaction], desc: transaction.block_number, desc: transaction.index)
|> where([transaction], transaction.to_address_hash == ^address_hash)
diff --git a/apps/explorer/lib/explorer/market/history/source/crypto_compare.ex b/apps/explorer/lib/explorer/market/history/source/crypto_compare.ex
index 82bf3714d5..6888d83b2b 100644
--- a/apps/explorer/lib/explorer/market/history/source/crypto_compare.ex
+++ b/apps/explorer/lib/explorer/market/history/source/crypto_compare.ex
@@ -24,7 +24,12 @@ defmodule Explorer.Market.History.Source.CryptoCompare do
case HTTPoison.get(url, headers) do
{:ok, %Response{body: body, status_code: 200}} ->
- {:ok, format_data(body)}
+ result =
+ body
+ |> format_data()
+ |> reject_zeros()
+
+ {:ok, result}
_ ->
:error
@@ -67,4 +72,10 @@ defmodule Explorer.Market.History.Source.CryptoCompare do
"#{base_url()}/data/histoday?#{URI.encode_query(query_params)}"
end
+
+ defp reject_zeros(items) do
+ Enum.reject(items, fn item ->
+ Decimal.equal?(item.closing_price, 0) && Decimal.equal?(item.opening_price, 0)
+ end)
+ end
end
diff --git a/apps/explorer/lib/explorer/market/market.ex b/apps/explorer/lib/explorer/market/market.ex
index f3e35f7d64..3adb7316fd 100644
--- a/apps/explorer/lib/explorer/market/market.ex
+++ b/apps/explorer/lib/explorer/market/market.ex
@@ -40,7 +40,12 @@ defmodule Explorer.Market do
@doc false
def bulk_insert_history(records) do
- Repo.insert_all(MarketHistory, records, on_conflict: :replace_all, conflict_target: [:date])
+ records_without_zeroes =
+ Enum.reject(records, fn item ->
+ Decimal.equal?(item.closing_price, 0) && Decimal.equal?(item.opening_price, 0)
+ end)
+
+ Repo.insert_all(MarketHistory, records_without_zeroes, on_conflict: :nothing, conflict_target: [:date])
end
def add_price(%{symbol: symbol} = token) do
diff --git a/apps/explorer/lib/explorer/market/market_history_cache.ex b/apps/explorer/lib/explorer/market/market_history_cache.ex
index 04b6193716..4ed3d9b7d2 100644
--- a/apps/explorer/lib/explorer/market/market_history_cache.ex
+++ b/apps/explorer/lib/explorer/market/market_history_cache.ex
@@ -12,7 +12,7 @@ defmodule Explorer.Market.MarketHistoryCache do
@last_update_key :last_update
@history_key :history
# 6 hours
- @cache_period 1_000 * 60 * 60 * 6
+ @cache_period Application.get_env(:explorer, __MODULE__)[:period]
@recent_days 30
def fetch do
diff --git a/apps/explorer/lib/explorer/smart_contract/publisher.ex b/apps/explorer/lib/explorer/smart_contract/publisher.ex
index 702b45976d..ae5a24c088 100644
--- a/apps/explorer/lib/explorer/smart_contract/publisher.ex
+++ b/apps/explorer/lib/explorer/smart_contract/publisher.ex
@@ -28,17 +28,17 @@ defmodule Explorer.SmartContract.Publisher do
case Verifier.evaluate_authenticity(address_hash, params_with_external_libaries) do
{:ok, %{abi: abi}} ->
- publish_smart_contract(address_hash, params, abi)
+ publish_smart_contract(address_hash, params_with_external_libaries, abi)
{:error, error} ->
- {:error, unverified_smart_contract(address_hash, params, error)}
+ {:error, unverified_smart_contract(address_hash, params_with_external_libaries, error)}
end
end
defp publish_smart_contract(address_hash, params, abi) do
- address_hash
- |> attributes(params, abi)
- |> Chain.create_smart_contract()
+ attrs = address_hash |> attributes(params, abi)
+
+ Chain.create_smart_contract(attrs, attrs.external_libraries)
end
defp unverified_smart_contract(address_hash, params, error) do
@@ -64,6 +64,8 @@ defmodule Explorer.SmartContract.Publisher do
nil
end
+ prepared_external_libraries = prepare_external_libraies(params["external_libraries"])
+
%{
address_hash: address_hash,
name: params["name"],
@@ -73,11 +75,20 @@ defmodule Explorer.SmartContract.Publisher do
optimization: params["optimization"],
contract_source_code: params["contract_source_code"],
constructor_arguments: clean_constructor_arguments,
- external_libaries: params["external_libraries"],
+ external_libraries: prepared_external_libraries,
abi: abi
}
end
+ defp prepare_external_libraies(nil), do: []
+
+ defp prepare_external_libraies(map) do
+ map
+ |> Enum.map(fn {key, value} ->
+ %{name: key, address_hash: value}
+ end)
+ end
+
defp add_external_libraries(params, external_libraries) do
clean_external_libraries =
Enum.reduce(1..5, %{}, fn number, acc ->
diff --git a/apps/explorer/lib/explorer/smart_contract/publisher_worker.ex b/apps/explorer/lib/explorer/smart_contract/publisher_worker.ex
new file mode 100644
index 0000000000..f0c248c8fb
--- /dev/null
+++ b/apps/explorer/lib/explorer/smart_contract/publisher_worker.ex
@@ -0,0 +1,23 @@
+defmodule Explorer.SmartContract.PublisherWorker do
+ @moduledoc """
+ Background smart contract verification worker.
+ """
+
+ use Que.Worker, concurrency: 5
+
+ alias Explorer.Chain.Events.Publisher, as: EventsPublisher
+ alias Explorer.SmartContract.Publisher
+
+ def perform({address_hash, params, external_libraries, conn}) do
+ result =
+ case Publisher.publish(address_hash, params, external_libraries) do
+ {:ok, _contract} = result ->
+ result
+
+ {:error, changeset} ->
+ {:error, changeset}
+ end
+
+ EventsPublisher.broadcast([{:contract_verification_result, {address_hash, result, conn}}], :on_demand)
+ end
+end
diff --git a/apps/explorer/lib/explorer/smart_contract/reader.ex b/apps/explorer/lib/explorer/smart_contract/reader.ex
index 72b40f55a4..cc3aae5e83 100644
--- a/apps/explorer/lib/explorer/smart_contract/reader.ex
+++ b/apps/explorer/lib/explorer/smart_contract/reader.ex
@@ -8,7 +8,7 @@ defmodule Explorer.SmartContract.Reader do
alias EthereumJSONRPC.Contract
alias Explorer.Chain
- alias Explorer.Chain.Hash
+ alias Explorer.Chain.{Hash, SmartContract}
@typedoc """
Map of functions to call with the values for the function to be called with.
@@ -34,6 +34,8 @@ defmodule Explorer.SmartContract.Reader do
@doc """
Queries the contract functions on the blockchain and returns the call results.
+ Optionally accepts the abi if it has already been fetched.
+
## Examples
Note that for this example to work the database must be up to date with the
@@ -57,14 +59,20 @@ defmodule Explorer.SmartContract.Reader do
)
# => %{"sum" => {:error, "Data overflow encoding int, data `abc` cannot fit in 256 bits"}}
"""
- @spec query_verified_contract(Hash.Address.t(), functions()) :: functions_results()
- def query_verified_contract(address_hash, functions) do
+ @spec query_verified_contract(Hash.Address.t(), functions(), SmartContract.abi() | nil) :: functions_results()
+ def query_verified_contract(address_hash, functions, mabi \\ nil) do
contract_address = Hash.to_string(address_hash)
abi =
- address_hash
- |> Chain.address_hash_to_smart_contract()
- |> Map.get(:abi)
+ case mabi do
+ nil ->
+ address_hash
+ |> Chain.address_hash_to_smart_contract()
+ |> Map.get(:abi)
+
+ _ ->
+ mabi
+ end
query_contract(contract_address, abi, functions)
end
@@ -156,41 +164,41 @@ defmodule Explorer.SmartContract.Reader do
"""
@spec read_only_functions(Hash.t()) :: [%{}]
def read_only_functions(contract_address_hash) do
- contract_address_hash
- |> Chain.address_hash_to_smart_contract()
- |> Map.get(:abi, [])
- |> Enum.filter(& &1["constant"])
- |> fetch_current_value_from_blockchain(contract_address_hash, [])
- |> Enum.reverse()
- end
-
- def fetch_current_value_from_blockchain(
- [%{"inputs" => []} = function | tail],
- contract_address_hash,
- acc
- ) do
- values =
- fetch_from_blockchain(contract_address_hash, %{
- name: function["name"],
- args: function["inputs"],
- outputs: function["outputs"]
- })
+ abi =
+ contract_address_hash
+ |> Chain.address_hash_to_smart_contract()
+ |> Map.get(:abi)
- formatted = Map.replace!(function, "outputs", values)
+ case abi do
+ nil ->
+ []
- fetch_current_value_from_blockchain(tail, contract_address_hash, [formatted | acc])
+ _ ->
+ abi
+ |> Enum.filter(& &1["constant"])
+ |> Enum.map(&fetch_current_value_from_blockchain(&1, abi, contract_address_hash))
+ end
end
- def fetch_current_value_from_blockchain([function | tail], contract_address_hash, acc) do
- values = link_outputs_and_values(%{}, Map.get(function, "outputs", []), function["name"])
+ defp fetch_current_value_from_blockchain(function, abi, contract_address_hash) do
+ values =
+ case function do
+ %{"inputs" => []} ->
+ name = function["name"]
+ args = function["inputs"]
+ outputs = function["outputs"]
+
+ contract_address_hash
+ |> query_verified_contract(%{name => normalize_args(args)}, abi)
+ |> link_outputs_and_values(outputs, name)
- formatted = Map.replace!(function, "outputs", values)
+ _ ->
+ link_outputs_and_values(%{}, Map.get(function, "outputs", []), function["name"])
+ end
- fetch_current_value_from_blockchain(tail, contract_address_hash, [formatted | acc])
+ Map.replace!(function, "outputs", values)
end
- def fetch_current_value_from_blockchain([], _contract_address_hash, acc), do: acc
-
@doc """
Fetches the blockchain value of a function that requires arguments.
"""
@@ -201,23 +209,27 @@ defmodule Explorer.SmartContract.Reader do
@spec query_function(Hash.t(), %{name: String.t(), args: [term()]}) :: [%{}]
def query_function(contract_address_hash, %{name: name, args: args}) do
- function =
+ abi =
contract_address_hash
|> Chain.address_hash_to_smart_contract()
- |> Map.get(:abi, [])
- |> Enum.filter(fn function -> function["name"] == name end)
- |> List.first()
-
- fetch_from_blockchain(contract_address_hash, %{
- name: name,
- args: args,
- outputs: function["outputs"]
- })
- end
+ |> Map.get(:abi)
+
+ outputs =
+ case abi do
+ nil ->
+ nil
+
+ _ ->
+ function =
+ abi
+ |> Enum.filter(fn function -> function["name"] == name end)
+ |> List.first()
+
+ function["outputs"]
+ end
- defp fetch_from_blockchain(contract_address_hash, %{name: name, args: args, outputs: outputs}) do
contract_address_hash
- |> query_verified_contract(%{name => normalize_args(args)})
+ |> query_verified_contract(%{name => normalize_args(args)}, abi)
|> link_outputs_and_values(outputs, name)
end
diff --git a/apps/explorer/lib/explorer/smart_contract/verifier.ex b/apps/explorer/lib/explorer/smart_contract/verifier.ex
index f4e486fd50..7aa9957bab 100644
--- a/apps/explorer/lib/explorer/smart_contract/verifier.ex
+++ b/apps/explorer/lib/explorer/smart_contract/verifier.ex
@@ -72,7 +72,8 @@ defmodule Explorer.SmartContract.Verifier do
generated_bytecode != blockchain_bytecode_without_whisper ->
{:error, :generated_bytecode}
- has_constructor_with_params?(abi) && !ConstructorArguments.verify(address_hash, arguments_data) ->
+ has_constructor_with_params?(abi) &&
+ !ConstructorArguments.verify(address_hash, blockchain_bytecode_without_whisper, arguments_data) ->
{:error, :constructor_arguments}
true ->
diff --git a/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex b/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex
index d6d1572f10..61167a4559 100644
--- a/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex
+++ b/apps/explorer/lib/explorer/smart_contract/verifier/constructor_arguments.ex
@@ -5,31 +5,54 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArguments do
alias Explorer.Chain
- def verify(address_hash, arguments_data) do
- arguments_data = String.replace(arguments_data, "0x", "")
+ def verify(address_hash, contract_code, arguments_data) do
+ arguments_data = arguments_data |> String.trim_trailing() |> String.trim_leading("0x")
- address_hash
- |> Chain.contract_creation_input_data()
- |> String.replace("0x", "")
- |> extract_constructor_arguments()
+ creation_code =
+ address_hash
+ |> Chain.contract_creation_input_data()
+ |> String.replace("0x", "")
+
+ if verify_older_version(creation_code, contract_code, arguments_data) do
+ true
+ else
+ extract_constructor_arguments(creation_code, arguments_data)
+ end
+ end
+
+ # Earlier versions of Solidity didn't have whisper code.
+ # constructor argument were directly appended to source code
+ defp verify_older_version(creation_code, contract_code, arguments_data) do
+ creation_code
+ |> String.split(contract_code)
+ |> List.last()
|> Kernel.==(arguments_data)
end
- defp extract_constructor_arguments(<<>>), do: ""
+ defp extract_constructor_arguments(code, passed_constructor_arguments) do
+ case code do
+ # Solidity ~ 4.23 # https://solidity.readthedocs.io/en/v0.4.23/metadata.html
+ "a165627a7a72305820" <> <<_::binary-size(64)>> <> "0029" <> constructor_arguments ->
+ if passed_constructor_arguments == constructor_arguments do
+ true
+ else
+ extract_constructor_arguments(constructor_arguments, passed_constructor_arguments)
+ end
- defp extract_constructor_arguments("a165627a7a72305820" <> <<_::binary-size(64)>> <> "0029" <> constructor_arguments) do
- constructor_arguments
- end
+ # Solidity >= 0.5.10 https://solidity.readthedocs.io/en/v0.5.10/metadata.html
+ "a265627a7a72305820" <>
+ <<_::binary-size(64)>> <> "64736f6c6343" <> <<_::binary-size(6)>> <> "0032" <> constructor_arguments ->
+ if passed_constructor_arguments == constructor_arguments do
+ true
+ else
+ extract_constructor_arguments(constructor_arguments, passed_constructor_arguments)
+ end
- # Solidity >= 0.5.9; https://github.com/ethereum/solidity/blob/aa4ee3a1559ebc0354926af962efb3fcc7dc15bd/docs/metadata.rst
- defp extract_constructor_arguments(
- "a265627a7a72305820" <>
- <<_::binary-size(64)>> <> "64736f6c6343" <> <<_::binary-size(6)>> <> "0032" <> constructor_arguments
- ) do
- constructor_arguments
- end
+ <<>> ->
+ passed_constructor_arguments == ""
- defp extract_constructor_arguments(<<_::binary-size(2)>> <> rest) do
- extract_constructor_arguments(rest)
+ <<_::binary-size(2)>> <> rest ->
+ extract_constructor_arguments(rest, passed_constructor_arguments)
+ end
end
end
diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs
index 79574b9354..4956363cc4 100644
--- a/apps/explorer/mix.exs
+++ b/apps/explorer/mix.exs
@@ -15,7 +15,7 @@ defmodule Explorer.Mixfile do
plt_add_apps: ~w(ex_unit mix)a,
ignore_warnings: "../../.dialyzer-ignore"
],
- elixir: "~> 1.8",
+ elixir: "~> 1.9",
elixirc_paths: elixirc_paths(Mix.env()),
lockfile: "../../mix.lock",
package: package(),
@@ -106,6 +106,7 @@ defmodule Explorer.Mixfile do
},
# bypass optional dependency
{:plug_cowboy, "~> 2.0", only: [:dev, :test]},
+ {:que, "~> 0.10.1"},
{:sobelow, ">= 0.7.0", only: [:dev, :test], runtime: false},
# Tracing
{:spandex, github: "spandex-project/spandex", branch: "allow-setting-trace-key", override: true},
diff --git a/apps/explorer/priv/compile_solc.js b/apps/explorer/priv/compile_solc.js
index 99048d9ffd..e6ba69c300 100755
--- a/apps/explorer/priv/compile_solc.js
+++ b/apps/explorer/priv/compile_solc.js
@@ -16,15 +16,7 @@ var solc = solc.setupMethods(compilerSnapshot);
var fs = require('fs');
var sourceCode = fs.readFileSync(sourceCodePath, 'utf8');
-const input = {
- language: 'Solidity',
- sources: {
- [newContractName]: {
- content: sourceCode
- }
- },
- settings: {
- evmVersion: evmVersion,
+var settings = {
optimizer: {
enabled: optimize == '1',
runs: optimizationRuns
@@ -37,7 +29,20 @@ const input = {
'*': ['*']
}
}
- }
+}
+
+if (evmVersion !== 'default') {
+ settings = Object.assign(settings, {evmVersion: evmVersion})
+}
+
+const input = {
+ language: 'Solidity',
+ sources: {
+ [newContractName]: {
+ content: sourceCode
+ }
+ },
+ settings: settings
}
diff --git a/apps/explorer/priv/repo/migrations/20190709103104_add_external_libraries_to_smart_contracts.exs b/apps/explorer/priv/repo/migrations/20190709103104_add_external_libraries_to_smart_contracts.exs
new file mode 100644
index 0000000000..5200253f84
--- /dev/null
+++ b/apps/explorer/priv/repo/migrations/20190709103104_add_external_libraries_to_smart_contracts.exs
@@ -0,0 +1,13 @@
+defmodule Explorer.Repo.Migrations.AddExternalLibrariesToSmartContracts do
+ use Ecto.Migration
+
+ def change do
+ alter table(:smart_contracts) do
+ remove(:external_libraries)
+ end
+
+ alter table(:smart_contracts) do
+ add(:external_libraries, {:array, :map}, default: [])
+ end
+ end
+end
diff --git a/apps/explorer/test/explorer/chain/block_number_cache_test.exs b/apps/explorer/test/explorer/chain/block_number_cache_test.exs
deleted file mode 100644
index 7b501a718b..0000000000
--- a/apps/explorer/test/explorer/chain/block_number_cache_test.exs
+++ /dev/null
@@ -1,59 +0,0 @@
-defmodule Explorer.Chain.BlockNumberCacheTest do
- use Explorer.DataCase
-
- alias Explorer.Chain.BlockNumberCache
-
- setup do
- Application.put_env(:explorer, Explorer.Chain.BlockNumberCache, enabled: true)
-
- on_exit(fn ->
- Application.put_env(:explorer, Explorer.Chain.BlockNumberCache, enabled: false)
- end)
- end
-
- describe "max_number/1" do
- test "returns max number" do
- insert(:block, number: 5)
-
- BlockNumberCache.setup()
-
- assert BlockNumberCache.max_number() == 5
- end
- end
-
- describe "min_number/1" do
- test "returns max number" do
- insert(:block, number: 2)
-
- BlockNumberCache.setup()
-
- assert BlockNumberCache.max_number() == 2
- end
- end
-
- describe "update/1" do
- test "updates max number" do
- insert(:block, number: 2)
-
- BlockNumberCache.setup()
-
- assert BlockNumberCache.max_number() == 2
-
- assert BlockNumberCache.update(3)
-
- assert BlockNumberCache.max_number() == 3
- end
-
- test "updates min number" do
- insert(:block, number: 2)
-
- BlockNumberCache.setup()
-
- assert BlockNumberCache.min_number() == 2
-
- assert BlockNumberCache.update(1)
-
- assert BlockNumberCache.min_number() == 1
- end
- end
-end
diff --git a/apps/explorer/test/explorer/chain/block_count_cache_test.exs b/apps/explorer/test/explorer/chain/cache/block_count_test.exs
similarity index 52%
rename from apps/explorer/test/explorer/chain/block_count_cache_test.exs
rename to apps/explorer/test/explorer/chain/cache/block_count_test.exs
index d1bbd5eb53..646af8de43 100644
--- a/apps/explorer/test/explorer/chain/block_count_cache_test.exs
+++ b/apps/explorer/test/explorer/chain/cache/block_count_test.exs
@@ -1,55 +1,55 @@
-defmodule Explorer.Chain.BlockCountCacheTest do
+defmodule Explorer.Chain.Cache.BlockCountTest do
use Explorer.DataCase
- alias Explorer.Chain.BlockCountCache
+ alias Explorer.Chain.Cache.BlockCount
test "returns default transaction count" do
- BlockCountCache.start_link(name: BlockTestCache)
+ BlockCount.start_link(name: BlockTestCache)
- result = BlockCountCache.count(BlockTestCache)
+ result = BlockCount.count(BlockTestCache)
assert is_nil(result)
end
test "updates cache if initial value is zero" do
- BlockCountCache.start_link(name: BlockTestCache)
+ BlockCount.start_link(name: BlockTestCache)
insert(:block, consensus: true)
insert(:block, consensus: true)
insert(:block, consensus: false)
- _result = BlockCountCache.count(BlockTestCache)
+ _result = BlockCount.count(BlockTestCache)
Process.sleep(1000)
- updated_value = BlockCountCache.count(BlockTestCache)
+ updated_value = BlockCount.count(BlockTestCache)
assert updated_value == 2
end
test "does not update cache if cache period did not pass" do
- BlockCountCache.start_link(name: BlockTestCache)
+ BlockCount.start_link(name: BlockTestCache)
insert(:block, consensus: true)
insert(:block, consensus: true)
insert(:block, consensus: false)
- _result = BlockCountCache.count(BlockTestCache)
+ _result = BlockCount.count(BlockTestCache)
Process.sleep(1000)
- updated_value = BlockCountCache.count(BlockTestCache)
+ updated_value = BlockCount.count(BlockTestCache)
assert updated_value == 2
insert(:block, consensus: true)
insert(:block, consensus: true)
- _updated_value = BlockCountCache.count(BlockTestCache)
+ _updated_value = BlockCount.count(BlockTestCache)
Process.sleep(1000)
- updated_value = BlockCountCache.count(BlockTestCache)
+ updated_value = BlockCount.count(BlockTestCache)
assert updated_value == 2
end
diff --git a/apps/explorer/test/explorer/chain/cache/block_number_test.exs b/apps/explorer/test/explorer/chain/cache/block_number_test.exs
new file mode 100644
index 0000000000..381ded440b
--- /dev/null
+++ b/apps/explorer/test/explorer/chain/cache/block_number_test.exs
@@ -0,0 +1,59 @@
+defmodule Explorer.Chain.Cache.BlockNumberTest do
+ use Explorer.DataCase
+
+ alias Explorer.Chain.Cache.BlockNumber
+
+ setup do
+ Application.put_env(:explorer, Explorer.Chain.Cache.BlockNumber, enabled: true)
+
+ on_exit(fn ->
+ Application.put_env(:explorer, Explorer.Chain.Cache.BlockNumber, enabled: false)
+ end)
+ end
+
+ describe "max_number/1" do
+ test "returns max number" do
+ insert(:block, number: 5)
+
+ BlockNumber.setup()
+
+ assert BlockNumber.max_number() == 5
+ end
+ end
+
+ describe "min_number/1" do
+ test "returns max number" do
+ insert(:block, number: 2)
+
+ BlockNumber.setup()
+
+ assert BlockNumber.max_number() == 2
+ end
+ end
+
+ describe "update/1" do
+ test "updates max number" do
+ insert(:block, number: 2)
+
+ BlockNumber.setup()
+
+ assert BlockNumber.max_number() == 2
+
+ assert BlockNumber.update(3)
+
+ assert BlockNumber.max_number() == 3
+ end
+
+ test "updates min number" do
+ insert(:block, number: 2)
+
+ BlockNumber.setup()
+
+ assert BlockNumber.min_number() == 2
+
+ assert BlockNumber.update(1)
+
+ assert BlockNumber.min_number() == 1
+ end
+ end
+end
diff --git a/apps/explorer/test/explorer/chain/blocks_cache_test.exs b/apps/explorer/test/explorer/chain/cache/blocks_test.exs
similarity index 65%
rename from apps/explorer/test/explorer/chain/blocks_cache_test.exs
rename to apps/explorer/test/explorer/chain/cache/blocks_test.exs
index 687d2ff30a..52ac4eabb4 100644
--- a/apps/explorer/test/explorer/chain/blocks_cache_test.exs
+++ b/apps/explorer/test/explorer/chain/cache/blocks_test.exs
@@ -1,7 +1,7 @@
-defmodule Explorer.Chain.BlocksCacheTest do
+defmodule Explorer.Chain.Cache.BlocksTest do
use Explorer.DataCase
- alias Explorer.Chain.BlocksCache
+ alias Explorer.Chain.Cache.Blocks
alias Explorer.Repo
setup do
@@ -14,9 +14,9 @@ defmodule Explorer.Chain.BlocksCacheTest do
test "adds a new value to cache" do
block = insert(:block) |> Repo.preload([:transactions, [miner: :names], :rewards])
- BlocksCache.update(block)
+ Blocks.update(block)
- assert BlocksCache.blocks() == [block]
+ assert Blocks.blocks() == [block]
end
test "adds a new elements removing the oldest one" do
@@ -25,43 +25,43 @@ defmodule Explorer.Chain.BlocksCacheTest do
|> Enum.map(fn number ->
block = insert(:block, number: number)
- BlocksCache.update(block)
+ Blocks.update(block)
block.number
end)
new_block = insert(:block, number: 70)
- BlocksCache.update(new_block)
+ Blocks.update(new_block)
new_blocks = blocks |> List.replace_at(0, new_block.number) |> Enum.sort() |> Enum.reverse()
- assert Enum.map(BlocksCache.blocks(), & &1.number) == new_blocks
+ assert Enum.map(Blocks.blocks(), & &1.number) == new_blocks
end
test "does not add too old blocks" do
block = insert(:block, number: 100_000) |> Repo.preload([:transactions, [miner: :names], :rewards])
old_block = insert(:block, number: 1_000)
- BlocksCache.update(block)
- BlocksCache.update(old_block)
+ Blocks.update(block)
+ Blocks.update(old_block)
- assert BlocksCache.blocks() == [block]
+ assert Blocks.blocks() == [block]
end
test "adds missing element" do
block1 = insert(:block, number: 10)
block2 = insert(:block, number: 4)
- BlocksCache.update(block1)
- BlocksCache.update(block2)
+ Blocks.update(block1)
+ Blocks.update(block2)
- assert Enum.count(BlocksCache.blocks()) == 2
+ assert Enum.count(Blocks.blocks()) == 2
block3 = insert(:block, number: 6)
- BlocksCache.update(block3)
+ Blocks.update(block3)
- assert Enum.map(BlocksCache.blocks(), & &1.number) == [10, 6, 4]
+ assert Enum.map(Blocks.blocks(), & &1.number) == [10, 6, 4]
end
end
@@ -69,16 +69,16 @@ defmodule Explorer.Chain.BlocksCacheTest do
test "updates cache" do
block = insert(:block)
- BlocksCache.update(block)
+ Blocks.update(block)
block1 = insert(:block) |> Repo.preload([:transactions, [miner: :names], :rewards])
block2 = insert(:block) |> Repo.preload([:transactions, [miner: :names], :rewards])
new_blocks = [block1, block2]
- BlocksCache.rewrite_cache(new_blocks)
+ Blocks.rewrite_cache(new_blocks)
- assert BlocksCache.blocks() == [block2, block1]
+ assert Blocks.blocks() == [block2, block1]
end
end
end
diff --git a/apps/explorer/test/explorer/chain/cache/transaction_count_test.exs b/apps/explorer/test/explorer/chain/cache/transaction_count_test.exs
new file mode 100644
index 0000000000..5020486cb8
--- /dev/null
+++ b/apps/explorer/test/explorer/chain/cache/transaction_count_test.exs
@@ -0,0 +1,54 @@
+defmodule Explorer.Chain.Cache.TransactionCountTest do
+ use Explorer.DataCase
+
+ alias Explorer.Chain.Cache.TransactionCount
+
+ test "returns default transaction count" do
+ TransactionCount.start_link([[], [name: TestCache]])
+
+ result = TransactionCount.value(TestCache)
+
+ assert is_nil(result)
+ end
+
+ test "updates cache if initial value is zero" do
+ TransactionCount.start_link([[], [name: TestCache]])
+
+ insert(:transaction)
+ insert(:transaction)
+
+ _result = TransactionCount.value(TestCache)
+
+ Process.sleep(1000)
+
+ updated_value = TransactionCount.value(TestCache)
+
+ assert updated_value == 2
+ end
+
+ test "does not update cache if cache period did not pass" do
+ TransactionCount.start_link([[], [name: TestCache]])
+
+ insert(:transaction)
+ insert(:transaction)
+
+ _result = TransactionCount.value(TestCache)
+
+ Process.sleep(1000)
+
+ updated_value = TransactionCount.value(TestCache)
+
+ assert updated_value == 2
+
+ insert(:transaction)
+ insert(:transaction)
+
+ _updated_value = TransactionCount.value(TestCache)
+
+ Process.sleep(1000)
+
+ updated_value = TransactionCount.value(TestCache)
+
+ assert updated_value == 2
+ end
+end
diff --git a/apps/explorer/test/explorer/chain/transactions_cache_test.exs b/apps/explorer/test/explorer/chain/cache/transactions_test.exs
similarity index 70%
rename from apps/explorer/test/explorer/chain/transactions_cache_test.exs
rename to apps/explorer/test/explorer/chain/cache/transactions_test.exs
index a72055c276..8a768b7dae 100644
--- a/apps/explorer/test/explorer/chain/transactions_cache_test.exs
+++ b/apps/explorer/test/explorer/chain/cache/transactions_test.exs
@@ -1,7 +1,7 @@
-defmodule Explorer.Chain.TransactionsCacheTest do
+defmodule Explorer.Chain.Cache.TransactionsTest do
use Explorer.DataCase
- alias Explorer.Chain.TransactionsCache
+ alias Explorer.Chain.Cache.Transactions
alias Explorer.Repo
@size 51
@@ -10,9 +10,9 @@ defmodule Explorer.Chain.TransactionsCacheTest do
test "adds a new value to a new cache with preloads" do
transaction = insert(:transaction) |> preload_all()
- TransactionsCache.update(transaction)
+ Transactions.update(transaction)
- assert TransactionsCache.take(1) == [transaction]
+ assert Transactions.take(1) == [transaction]
end
test "adds several elements, removing the oldest when necessary" do
@@ -23,9 +23,9 @@ defmodule Explorer.Chain.TransactionsCacheTest do
insert(:transaction) |> with_block(block)
end)
- TransactionsCache.update(transactions)
+ Transactions.update(transactions)
- assert TransactionsCache.all() == Enum.reverse(preload_all(transactions))
+ assert Transactions.all() == Enum.reverse(preload_all(transactions))
more_transactions =
(@size + 1)..(@size + 10)
@@ -34,14 +34,14 @@ defmodule Explorer.Chain.TransactionsCacheTest do
insert(:transaction) |> with_block(block)
end)
- TransactionsCache.update(more_transactions)
+ Transactions.update(more_transactions)
kept_transactions =
Enum.reverse(transactions ++ more_transactions)
|> Enum.take(@size)
|> preload_all()
- assert TransactionsCache.take(@size) == kept_transactions
+ assert Transactions.take(@size) == kept_transactions
end
test "does not add a transaction too old when full" do
@@ -52,28 +52,28 @@ defmodule Explorer.Chain.TransactionsCacheTest do
insert(:transaction) |> with_block(block)
end)
- TransactionsCache.update(transactions)
+ Transactions.update(transactions)
loaded_transactions = Enum.reverse(preload_all(transactions))
- assert TransactionsCache.all() == loaded_transactions
+ assert Transactions.all() == loaded_transactions
block = insert(:block, number: 1)
- insert(:transaction) |> with_block(block) |> TransactionsCache.update()
+ insert(:transaction) |> with_block(block) |> Transactions.update()
- assert TransactionsCache.all() == loaded_transactions
+ assert Transactions.all() == loaded_transactions
end
test "adds intermediate transactions" do
blocks = 1..10 |> Map.new(fn n -> {n, insert(:block, number: n)} end)
- insert(:transaction) |> with_block(blocks[1]) |> TransactionsCache.update()
- insert(:transaction) |> with_block(blocks[10]) |> TransactionsCache.update()
+ insert(:transaction) |> with_block(blocks[1]) |> Transactions.update()
+ insert(:transaction) |> with_block(blocks[10]) |> Transactions.update()
- assert TransactionsCache.size() == 2
+ assert Transactions.size() == 2
- insert(:transaction) |> with_block(blocks[5]) |> TransactionsCache.update()
+ insert(:transaction) |> with_block(blocks[5]) |> Transactions.update()
- assert TransactionsCache.size() == 3
+ assert Transactions.size() == 3
end
end
diff --git a/apps/explorer/test/explorer/chain/import/runner/transactions_test.exs b/apps/explorer/test/explorer/chain/import/runner/transactions_test.exs
new file mode 100644
index 0000000000..2bfea8750b
--- /dev/null
+++ b/apps/explorer/test/explorer/chain/import/runner/transactions_test.exs
@@ -0,0 +1,50 @@
+defmodule Explorer.Chain.Import.Runner.TransactionsTest do
+ use Explorer.DataCase
+
+ alias Ecto.Multi
+ alias Explorer.Chain.{Address, Transaction}
+ alias Explorer.Chain.Import.Runner.Transactions
+
+ describe "run/1" do
+ test "transaction's created_contract_code_indexed_at is modified on update" do
+ %Address{hash: address_hash} = insert(:address)
+
+ transaction =
+ insert(:transaction,
+ created_contract_address_hash: address_hash,
+ created_contract_code_indexed_at: DateTime.utc_now()
+ )
+
+ assert not is_nil(transaction.created_contract_code_indexed_at)
+
+ non_indexed_transaction_params = %{
+ from_address_hash: transaction.from_address.hash,
+ gas: transaction.gas,
+ gas_price: transaction.gas_price,
+ hash: transaction.hash,
+ input: transaction.input,
+ nonce: transaction.nonce,
+ r: transaction.r,
+ s: transaction.s,
+ to_address_hash: transaction.to_address.hash,
+ v: transaction.v,
+ value: transaction.value,
+ created_contract_address_hash: address_hash,
+ created_contract_code_indexed_at: nil
+ }
+
+ assert {:ok, _} = run_transactions([non_indexed_transaction_params])
+
+ assert is_nil(Repo.get(Transaction, transaction.hash).created_contract_code_indexed_at)
+ end
+ end
+
+ defp run_transactions(changes_list) when is_list(changes_list) do
+ Multi.new()
+ |> Transactions.run(changes_list, %{
+ timeout: :infinity,
+ timestamps: %{inserted_at: DateTime.utc_now(), updated_at: DateTime.utc_now()}
+ })
+ |> Repo.transaction()
+ end
+end
diff --git a/apps/explorer/test/explorer/chain/transaction_count_cache_test.exs b/apps/explorer/test/explorer/chain/transaction_count_cache_test.exs
deleted file mode 100644
index a1b0d72485..0000000000
--- a/apps/explorer/test/explorer/chain/transaction_count_cache_test.exs
+++ /dev/null
@@ -1,54 +0,0 @@
-defmodule Explorer.Chain.TransactionCountCacheTest do
- use Explorer.DataCase
-
- alias Explorer.Chain.TransactionCountCache
-
- test "returns default transaction count" do
- TransactionCountCache.start_link([[], [name: TestCache]])
-
- result = TransactionCountCache.value(TestCache)
-
- assert is_nil(result)
- end
-
- test "updates cache if initial value is zero" do
- TransactionCountCache.start_link([[], [name: TestCache]])
-
- insert(:transaction)
- insert(:transaction)
-
- _result = TransactionCountCache.value(TestCache)
-
- Process.sleep(1000)
-
- updated_value = TransactionCountCache.value(TestCache)
-
- assert updated_value == 2
- end
-
- test "does not update cache if cache period did not pass" do
- TransactionCountCache.start_link([[], [name: TestCache]])
-
- insert(:transaction)
- insert(:transaction)
-
- _result = TransactionCountCache.value(TestCache)
-
- Process.sleep(1000)
-
- updated_value = TransactionCountCache.value(TestCache)
-
- assert updated_value == 2
-
- insert(:transaction)
- insert(:transaction)
-
- _updated_value = TransactionCountCache.value(TestCache)
-
- Process.sleep(1000)
-
- updated_value = TransactionCountCache.value(TestCache)
-
- assert updated_value == 2
- end
-end
diff --git a/apps/explorer/test/explorer/chain/transaction_test.exs b/apps/explorer/test/explorer/chain/transaction_test.exs
index 08f30a0bfa..5fe6b9958c 100644
--- a/apps/explorer/test/explorer/chain/transaction_test.exs
+++ b/apps/explorer/test/explorer/chain/transaction_test.exs
@@ -286,4 +286,18 @@ defmodule Explorer.Chain.TransactionTest do
assert Transaction.decoded_input_data(transaction) == {:ok, "60fe47b1", "set(uint256 x)", [{"x", "uint256", 10}]}
end
end
+
+ describe "Poison.encode!/1" do
+ test "encodes transaction input" do
+ assert %{
+ insert(:transaction)
+ | input: %Explorer.Chain.Data{
+ bytes:
+ <<169, 5, 156, 187, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 193, 108, 45, 196, 42, 228, 149, 239, 119,
+ 191, 128, 248>>
+ }
+ }
+ |> Poison.encode!()
+ end
+ end
end
diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs
index cfb8b5ab14..234252dbe4 100644
--- a/apps/explorer/test/explorer/chain_test.exs
+++ b/apps/explorer/test/explorer/chain_test.exs
@@ -50,27 +50,41 @@ defmodule Explorer.ChainTest do
end
end
- describe "last_block_status/0" do
+ describe "last_db_block_status/0" do
test "return no_blocks errors if db is empty" do
- assert {:error, :no_blocks} = Chain.last_block_status()
+ assert {:error, :no_blocks} = Chain.last_db_block_status()
end
test "returns {:ok, last_block_period} if block is in healthy period" do
insert(:block, consensus: true)
- assert {:ok, _, _} = Chain.last_block_status()
+ assert {:ok, _, _} = Chain.last_db_block_status()
end
test "return {:ok, last_block_period} if block is not in healthy period" do
insert(:block, consensus: true, timestamp: Timex.shift(DateTime.utc_now(), hours: -50))
- assert {:error, _, _} = Chain.last_block_status()
+ assert {:error, _, _} = Chain.last_db_block_status()
+ end
+ end
+
+ describe "last_cache_block_status/0" do
+ test "returns success if cache is not stale" do
+ insert(:block, consensus: true)
+
+ assert {:ok, _, _} = Chain.last_cache_block_status()
+ end
+
+ test "return error if cache is stale" do
+ insert(:block, consensus: true, timestamp: Timex.shift(DateTime.utc_now(), hours: -50))
+
+ assert {:error, _, _} = Chain.last_cache_block_status()
end
end
describe "address_to_logs/2" do
test "fetches logs" do
- address = insert(:address)
+ %Address{hash: address_hash} = address = insert(:address)
transaction1 =
:transaction
@@ -86,11 +100,11 @@ defmodule Explorer.ChainTest do
insert(:log, transaction: transaction2, index: 2, address: address)
- assert Enum.count(Chain.address_to_logs(address)) == 2
+ assert Enum.count(Chain.address_to_logs(address_hash)) == 2
end
test "paginates logs" do
- address = insert(:address)
+ %Address{hash: address_hash} = address = insert(:address)
transaction =
:transaction
@@ -105,15 +119,15 @@ defmodule Explorer.ChainTest do
paging_options1 = %PagingOptions{page_size: 1}
- [_log] = Chain.address_to_logs(address, paging_options: paging_options1)
+ [_log] = Chain.address_to_logs(address_hash, paging_options: paging_options1)
paging_options2 = %PagingOptions{page_size: 60, key: {transaction.block_number, transaction.index, log1.index}}
- assert Enum.count(Chain.address_to_logs(address, paging_options: paging_options2)) == 50
+ assert Enum.count(Chain.address_to_logs(address_hash, paging_options: paging_options2)) == 50
end
test "searches logs by topic when the first topic matches" do
- address = insert(:address)
+ %Address{hash: address_hash} = address = insert(:address)
transaction1 =
:transaction
@@ -129,13 +143,13 @@ defmodule Explorer.ChainTest do
insert(:log, transaction: transaction2, index: 2, address: address, first_topic: "test")
- [found_log] = Chain.address_to_logs(address, topic: "test")
+ [found_log] = Chain.address_to_logs(address_hash, topic: "test")
assert found_log.transaction.hash == transaction2.hash
end
test "searches logs by topic when the fourth topic matches" do
- address = insert(:address)
+ %Address{hash: address_hash} = address = insert(:address)
transaction1 =
:transaction
@@ -151,7 +165,7 @@ defmodule Explorer.ChainTest do
insert(:log, transaction: transaction2, index: 2, address: address)
- [found_log] = Chain.address_to_logs(address, topic: "test")
+ [found_log] = Chain.address_to_logs(address_hash, topic: "test")
assert found_log.transaction.hash == transaction1.hash
end
@@ -159,15 +173,15 @@ defmodule Explorer.ChainTest do
describe "address_to_transactions_with_rewards/2" do
test "without transactions" do
- address = insert(:address)
+ %Address{hash: address_hash} = insert(:address)
assert Repo.aggregate(Transaction, :count, :hash) == 0
- assert [] == Chain.address_to_transactions_with_rewards(address)
+ assert [] == Chain.address_to_transactions_with_rewards(address_hash)
end
test "with from transactions" do
- address = insert(:address)
+ %Address{hash: address_hash} = address = insert(:address)
transaction =
:transaction
@@ -176,12 +190,12 @@ defmodule Explorer.ChainTest do
|> Repo.preload(:token_transfers)
assert [transaction] ==
- Chain.address_to_transactions_with_rewards(address, direction: :from)
+ Chain.address_to_transactions_with_rewards(address_hash, direction: :from)
|> Repo.preload([:block, :to_address, :from_address])
end
test "with to transactions" do
- address = insert(:address)
+ %Address{hash: address_hash} = address = insert(:address)
transaction =
:transaction
@@ -190,12 +204,12 @@ defmodule Explorer.ChainTest do
|> Repo.preload(:token_transfers)
assert [transaction] ==
- Chain.address_to_transactions_with_rewards(address, direction: :to)
+ Chain.address_to_transactions_with_rewards(address_hash, direction: :to)
|> Repo.preload([:block, :to_address, :from_address])
end
test "with to and from transactions and direction: :from" do
- address = insert(:address)
+ %Address{hash: address_hash} = address = insert(:address)
transaction =
:transaction
@@ -205,12 +219,12 @@ defmodule Explorer.ChainTest do
# only contains "from" transaction
assert [transaction] ==
- Chain.address_to_transactions_with_rewards(address, direction: :from)
+ Chain.address_to_transactions_with_rewards(address_hash, direction: :from)
|> Repo.preload([:block, :to_address, :from_address])
end
test "with to and from transactions and direction: :to" do
- address = insert(:address)
+ %Address{hash: address_hash} = address = insert(:address)
transaction =
:transaction
@@ -219,12 +233,12 @@ defmodule Explorer.ChainTest do
|> Repo.preload(:token_transfers)
assert [transaction] ==
- Chain.address_to_transactions_with_rewards(address, direction: :to)
+ Chain.address_to_transactions_with_rewards(address_hash, direction: :to)
|> Repo.preload([:block, :to_address, :from_address])
end
test "with to and from transactions and no :direction option" do
- address = insert(:address)
+ %Address{hash: address_hash} = address = insert(:address)
block = insert(:block)
transaction1 =
@@ -240,7 +254,7 @@ defmodule Explorer.ChainTest do
|> Repo.preload(:token_transfers)
assert [transaction2, transaction1] ==
- Chain.address_to_transactions_with_rewards(address)
+ Chain.address_to_transactions_with_rewards(address_hash)
|> Repo.preload([:block, :to_address, :from_address])
end
@@ -259,11 +273,11 @@ defmodule Explorer.ChainTest do
transaction_index: transaction.index
)
- assert [] == Chain.address_to_transactions_with_rewards(address)
+ assert [] == Chain.address_to_transactions_with_rewards(address.hash)
end
test "returns transactions that have token transfers for the given to_address" do
- address = insert(:address)
+ %Address{hash: address_hash} = address = insert(:address)
transaction =
:transaction
@@ -277,12 +291,12 @@ defmodule Explorer.ChainTest do
)
assert [transaction.hash] ==
- Chain.address_to_transactions_with_rewards(address)
+ Chain.address_to_transactions_with_rewards(address_hash)
|> Enum.map(& &1.hash)
end
test "returns just the token transfers related to the given address" do
- address = insert(:address)
+ %Address{hash: address_hash} = address = insert(:address)
transaction =
:transaction
@@ -303,7 +317,7 @@ defmodule Explorer.ChainTest do
)
transaction =
- address
+ address_hash
|> Chain.address_to_transactions_with_rewards()
|> List.first()
@@ -344,7 +358,7 @@ defmodule Explorer.ChainTest do
)
transaction =
- contract_address
+ contract_address.hash
|> Chain.address_to_transactions_with_rewards()
|> List.first()
@@ -360,7 +374,8 @@ defmodule Explorer.ChainTest do
end
test "returns all token transfers when the given address is the token contract address" do
- contract_address = insert(:address, contract_code: Factory.data("contract_code"))
+ %Address{hash: contract_address_hash} =
+ contract_address = insert(:address, contract_code: Factory.data("contract_code"))
transaction =
:transaction
@@ -381,7 +396,7 @@ defmodule Explorer.ChainTest do
transaction: transaction
)
- transaction = Chain.address_to_transactions_with_rewards(contract_address) |> List.first()
+ transaction = Chain.address_to_transactions_with_rewards(contract_address_hash) |> List.first()
assert Enum.count(transaction.token_transfers) == 2
end
@@ -427,7 +442,7 @@ defmodule Explorer.ChainTest do
)
transactions_hashes =
- paul
+ paul.hash
|> Chain.address_to_transactions_with_rewards()
|> Enum.map(& &1.hash)
@@ -436,7 +451,7 @@ defmodule Explorer.ChainTest do
end
test "with transactions can be paginated" do
- address = insert(:address)
+ %Address{hash: address_hash} = address = insert(:address)
second_page_hashes =
2
@@ -450,7 +465,7 @@ defmodule Explorer.ChainTest do
|> with_block()
assert second_page_hashes ==
- address
+ address_hash
|> Chain.address_to_transactions_with_rewards(
paging_options: %PagingOptions{
key: {block_number, index},
@@ -462,7 +477,7 @@ defmodule Explorer.ChainTest do
end
test "returns results in reverse chronological order by block number and transaction index" do
- address = insert(:address)
+ %Address{hash: address_hash} = address = insert(:address)
a_block = insert(:block, number: 6000)
@@ -499,7 +514,7 @@ defmodule Explorer.ChainTest do
|> with_block(b_block)
result =
- address
+ address_hash
|> Chain.address_to_transactions_with_rewards()
|> Enum.map(& &1.hash)
@@ -525,7 +540,7 @@ defmodule Explorer.ChainTest do
address_type: :emission_funds
)
- assert [{_, _}] = Chain.address_to_transactions_with_rewards(block.miner)
+ assert [{_, _}] = Chain.address_to_transactions_with_rewards(block.miner.hash)
Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false)
end
@@ -554,7 +569,7 @@ defmodule Explorer.ChainTest do
|> with_block()
|> Repo.preload(:token_transfers)
- assert [_, {_, _}] = Chain.address_to_transactions_with_rewards(block.miner, direction: :from)
+ assert [_, {_, _}] = Chain.address_to_transactions_with_rewards(block.miner.hash, direction: :from)
Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false)
end
@@ -578,7 +593,7 @@ defmodule Explorer.ChainTest do
address_type: :emission_funds
)
- assert [] == Chain.address_to_transactions_with_rewards(block.miner)
+ assert [] == Chain.address_to_transactions_with_rewards(block.miner.hash)
end
end
@@ -590,7 +605,7 @@ defmodule Explorer.ChainTest do
|> insert(nonce: 100, from_address: address)
|> with_block(insert(:block, number: 1000))
- assert Chain.total_transactions_sent_by_address(address) == 101
+ assert Chain.total_transactions_sent_by_address(address.hash) == 101
end
test "returns 0 when the address did not send transactions" do
@@ -600,7 +615,7 @@ defmodule Explorer.ChainTest do
|> insert(nonce: 100, to_address: address)
|> with_block(insert(:block, number: 1000))
- assert Chain.total_transactions_sent_by_address(address) == 0
+ assert Chain.total_transactions_sent_by_address(address.hash) == 0
end
end
@@ -629,7 +644,7 @@ defmodule Explorer.ChainTest do
assert Repo.aggregate(Transaction, :count, :hash) == 0
- assert [] = Chain.block_to_transactions(block)
+ assert [] = Chain.block_to_transactions(block.hash)
end
test "with transactions" do
@@ -638,7 +653,7 @@ defmodule Explorer.ChainTest do
|> insert()
|> with_block()
- assert [%Transaction{hash: ^transaction_hash}] = Chain.block_to_transactions(block)
+ assert [%Transaction{hash: ^transaction_hash}] = Chain.block_to_transactions(block.hash)
end
test "with transactions can be paginated by {index}" do
@@ -656,7 +671,7 @@ defmodule Explorer.ChainTest do
|> with_block(block)
assert second_page_hashes ==
- block
+ block.hash
|> Chain.block_to_transactions(paging_options: %PagingOptions{key: {index}, page_size: 50})
|> Enum.map(& &1.hash)
|> Enum.reverse()
@@ -682,7 +697,7 @@ defmodule Explorer.ChainTest do
token: token
)
- fetched_transaction = List.first(Explorer.Chain.block_to_transactions(block))
+ fetched_transaction = List.first(Explorer.Chain.block_to_transactions(block.hash))
assert fetched_transaction.hash == transaction.hash
assert length(fetched_transaction.token_transfers) == 2
end
@@ -692,7 +707,7 @@ defmodule Explorer.ChainTest do
test "without transactions" do
block = insert(:block)
- assert Chain.block_to_transaction_count(block) == 0
+ assert Chain.block_to_transaction_count(block.hash) == 0
end
test "with transactions" do
@@ -701,21 +716,21 @@ defmodule Explorer.ChainTest do
|> insert()
|> with_block()
- assert Chain.block_to_transaction_count(block) == 1
+ assert Chain.block_to_transaction_count(block.hash) == 1
end
end
describe "address_to_incoming_transaction_count/1" do
test "without transactions" do
- address = insert(:address)
+ %Address{hash: address_hash} = insert(:address)
- assert Chain.address_to_incoming_transaction_count(address) == 0
+ assert Chain.address_to_incoming_transaction_count(address_hash) == 0
end
test "with transactions" do
%Transaction{to_address: to_address} = insert(:transaction)
- assert Chain.address_to_incoming_transaction_count(to_address) == 1
+ assert Chain.address_to_incoming_transaction_count(to_address.hash) == 1
end
end
@@ -1575,20 +1590,20 @@ defmodule Explorer.ChainTest do
describe "get_blocks_validated_by_address/2" do
test "returns nothing when there are no blocks" do
- address = insert(:address)
+ %Address{hash: address_hash} = insert(:address)
- assert [] = Chain.get_blocks_validated_by_address(address)
+ assert [] = Chain.get_blocks_validated_by_address(address_hash)
end
test "returns the blocks validated by a specified address" do
- address = insert(:address)
+ %Address{hash: address_hash} = address = insert(:address)
another_address = insert(:address)
block = insert(:block, miner: address, miner_hash: address.hash)
insert(:block, miner: another_address, miner_hash: another_address.hash)
results =
- address
+ address_hash
|> Chain.get_blocks_validated_by_address()
|> Enum.map(& &1.hash)
@@ -1596,20 +1611,20 @@ defmodule Explorer.ChainTest do
end
test "with blocks can be paginated" do
- address = insert(:address)
+ %Address{hash: address_hash} = address = insert(:address)
first_page_block = insert(:block, miner: address, miner_hash: address.hash, number: 0)
second_page_block = insert(:block, miner: address, miner_hash: address.hash, number: 2)
assert [first_page_block.number] ==
[paging_options: %PagingOptions{key: {1}, page_size: 1}]
- |> Chain.get_blocks_validated_by_address(address)
+ |> Chain.get_blocks_validated_by_address(address_hash)
|> Enum.map(& &1.number)
|> Enum.reverse()
assert [second_page_block.number] ==
[paging_options: %PagingOptions{key: {3}, page_size: 1}]
- |> Chain.get_blocks_validated_by_address(address)
+ |> Chain.get_blocks_validated_by_address(address_hash)
|> Enum.map(& &1.number)
|> Enum.reverse()
end
@@ -1674,7 +1689,7 @@ defmodule Explorer.ChainTest do
)
result =
- address
+ address.hash
|> Chain.address_to_internal_transactions()
|> Enum.map(&{&1.transaction_hash, &1.index})
@@ -1683,7 +1698,7 @@ defmodule Explorer.ChainTest do
end
test "loads associations in necessity_by_association" do
- address = insert(:address)
+ %Address{hash: address_hash} = address = insert(:address)
block = insert(:block, number: 2000)
transaction =
@@ -1714,7 +1729,7 @@ defmodule Explorer.ChainTest do
transaction: %Transaction{}
}
| _
- ] = Chain.address_to_internal_transactions(address)
+ ] = Chain.address_to_internal_transactions(address_hash)
assert [
%InternalTransaction{
@@ -1725,7 +1740,7 @@ defmodule Explorer.ChainTest do
| _
] =
Chain.address_to_internal_transactions(
- address,
+ address_hash,
necessity_by_association: %{
[from_address: :names] => :optional,
[to_address: :names] => :optional,
@@ -1844,7 +1859,7 @@ defmodule Explorer.ChainTest do
)
result =
- address
+ address.hash
|> Chain.address_to_internal_transactions()
|> Enum.map(&{&1.transaction_hash, &1.index})
@@ -1968,7 +1983,7 @@ defmodule Explorer.ChainTest do
{second_transaction_hash, second_index},
{first_transaction_hash, first_index}
] ==
- address
+ address.hash
|> Chain.address_to_internal_transactions(
paging_options: %PagingOptions{key: {6001, 3, 2}, page_size: 8}
)
@@ -1981,7 +1996,7 @@ defmodule Explorer.ChainTest do
{second_transaction_hash, second_index},
{first_transaction_hash, first_index}
] ==
- address
+ address.hash
|> Chain.address_to_internal_transactions(
paging_options: %PagingOptions{key: {6000, 0, 1}, page_size: 8}
)
@@ -1994,7 +2009,7 @@ defmodule Explorer.ChainTest do
{second_transaction_hash, second_index},
{first_transaction_hash, first_index}
] ==
- address
+ address.hash
|> Chain.address_to_internal_transactions(
paging_options: %PagingOptions{key: {6000, -1, -1}, page_size: 8}
)
@@ -2002,7 +2017,7 @@ defmodule Explorer.ChainTest do
# block number <
assert [] ==
- address
+ address.hash
|> Chain.address_to_internal_transactions(
paging_options: %PagingOptions{key: {2000, -1, -1}, page_size: 8}
)
@@ -2010,7 +2025,7 @@ defmodule Explorer.ChainTest do
end
test "excludes internal transactions of type `call` when they are alone in the parent transaction" do
- address = insert(:address)
+ %Address{hash: address_hash} = address = insert(:address)
transaction =
:transaction
@@ -2025,11 +2040,11 @@ defmodule Explorer.ChainTest do
transaction_index: transaction.index
)
- assert Enum.empty?(Chain.address_to_internal_transactions(address))
+ assert Enum.empty?(Chain.address_to_internal_transactions(address_hash))
end
test "includes internal transactions of type `create` even when they are alone in the parent transaction" do
- address = insert(:address)
+ %Address{hash: address_hash} = address = insert(:address)
transaction =
:transaction
@@ -2046,7 +2061,7 @@ defmodule Explorer.ChainTest do
transaction_index: transaction.index
)
- actual = Enum.at(Chain.address_to_internal_transactions(address), 0)
+ actual = Enum.at(Chain.address_to_internal_transactions(address_hash), 0)
assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index}
end
@@ -2089,7 +2104,7 @@ defmodule Explorer.ChainTest do
test "with transaction without internal transactions" do
transaction = insert(:transaction)
- assert [] = Chain.transaction_to_internal_transactions(transaction)
+ assert [] = Chain.transaction_to_internal_transactions(transaction.hash)
end
test "with transaction with internal transactions returns all internal transactions for a given transaction hash" do
@@ -2116,7 +2131,7 @@ defmodule Explorer.ChainTest do
transaction_index: transaction.index
)
- results = [internal_transaction | _] = Chain.transaction_to_internal_transactions(transaction)
+ results = [internal_transaction | _] = Chain.transaction_to_internal_transactions(transaction.hash)
assert 2 == length(results)
@@ -2150,7 +2165,7 @@ defmodule Explorer.ChainTest do
to_address: %Ecto.Association.NotLoaded{},
transaction: %Transaction{block: %Ecto.Association.NotLoaded{}}
}
- ] = Chain.transaction_to_internal_transactions(transaction)
+ ] = Chain.transaction_to_internal_transactions(transaction.hash)
assert [
%InternalTransaction{
@@ -2160,7 +2175,7 @@ defmodule Explorer.ChainTest do
}
] =
Chain.transaction_to_internal_transactions(
- transaction,
+ transaction.hash,
necessity_by_association: %{
:from_address => :optional,
:to_address => :optional,
@@ -2182,7 +2197,7 @@ defmodule Explorer.ChainTest do
transaction_index: transaction.index
)
- result = Chain.transaction_to_internal_transactions(transaction)
+ result = Chain.transaction_to_internal_transactions(transaction.hash)
assert Enum.empty?(result)
end
@@ -2201,7 +2216,7 @@ defmodule Explorer.ChainTest do
transaction_index: transaction.index
)
- actual = Enum.at(Chain.transaction_to_internal_transactions(transaction), 0)
+ actual = Enum.at(Chain.transaction_to_internal_transactions(transaction.hash), 0)
assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index}
end
@@ -2221,7 +2236,7 @@ defmodule Explorer.ChainTest do
transaction_index: transaction.index
)
- actual = Enum.at(Chain.transaction_to_internal_transactions(transaction), 0)
+ actual = Enum.at(Chain.transaction_to_internal_transactions(transaction.hash), 0)
assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index}
end
@@ -2242,7 +2257,7 @@ defmodule Explorer.ChainTest do
transaction_index: transaction.index
)
- actual = Enum.at(Chain.transaction_to_internal_transactions(transaction), 0)
+ actual = Enum.at(Chain.transaction_to_internal_transactions(transaction.hash), 0)
assert {actual.transaction_hash, actual.index} == {expected.transaction_hash, expected.index}
end
@@ -2270,7 +2285,7 @@ defmodule Explorer.ChainTest do
)
result =
- transaction
+ transaction.hash
|> Chain.transaction_to_internal_transactions()
|> Enum.map(&{&1.transaction_hash, &1.index})
@@ -2300,17 +2315,17 @@ defmodule Explorer.ChainTest do
)
assert [{first_transaction_hash, first_index}, {second_transaction_hash, second_index}] ==
- transaction
+ transaction.hash
|> Chain.transaction_to_internal_transactions(paging_options: %PagingOptions{key: {-1}, page_size: 2})
|> Enum.map(&{&1.transaction_hash, &1.index})
assert [{first_transaction_hash, first_index}] ==
- transaction
+ transaction.hash
|> Chain.transaction_to_internal_transactions(paging_options: %PagingOptions{key: {-1}, page_size: 1})
|> Enum.map(&{&1.transaction_hash, &1.index})
assert [{second_transaction_hash, second_index}] ==
- transaction
+ transaction.hash
|> Chain.transaction_to_internal_transactions(paging_options: %PagingOptions{key: {0}, page_size: 2})
|> Enum.map(&{&1.transaction_hash, &1.index})
end
@@ -2320,7 +2335,7 @@ defmodule Explorer.ChainTest do
test "without logs" do
transaction = insert(:transaction)
- assert [] = Chain.transaction_to_logs(transaction)
+ assert [] = Chain.transaction_to_logs(transaction.hash)
end
test "with logs" do
@@ -2331,7 +2346,7 @@ defmodule Explorer.ChainTest do
%Log{transaction_hash: transaction_hash, index: index} = insert(:log, transaction: transaction)
- assert [%Log{transaction_hash: ^transaction_hash, index: ^index}] = Chain.transaction_to_logs(transaction)
+ assert [%Log{transaction_hash: ^transaction_hash, index: ^index}] = Chain.transaction_to_logs(transaction.hash)
end
test "with logs can be paginated" do
@@ -2348,7 +2363,7 @@ defmodule Explorer.ChainTest do
|> Enum.map(& &1.index)
assert second_page_indexes ==
- transaction
+ transaction.hash
|> Chain.transaction_to_logs(paging_options: %PagingOptions{key: {log.index}, page_size: 50})
|> Enum.map(& &1.index)
end
@@ -2363,7 +2378,7 @@ defmodule Explorer.ChainTest do
assert [%Log{address: %Address{}, transaction: %Transaction{}}] =
Chain.transaction_to_logs(
- transaction,
+ transaction.hash,
necessity_by_association: %{
address: :optional,
transaction: :optional
@@ -2375,7 +2390,7 @@ defmodule Explorer.ChainTest do
address: %Ecto.Association.NotLoaded{},
transaction: %Ecto.Association.NotLoaded{}
}
- ] = Chain.transaction_to_logs(transaction)
+ ] = Chain.transaction_to_logs(transaction.hash)
end
end
@@ -2383,7 +2398,7 @@ defmodule Explorer.ChainTest do
test "without token transfers" do
transaction = insert(:transaction)
- assert [] = Chain.transaction_to_token_transfers(transaction)
+ assert [] = Chain.transaction_to_token_transfers(transaction.hash)
end
test "with token transfers" do
@@ -2396,7 +2411,7 @@ defmodule Explorer.ChainTest do
insert(:token_transfer, transaction: transaction)
assert [%TokenTransfer{transaction_hash: ^transaction_hash, log_index: ^log_index}] =
- Chain.transaction_to_token_transfers(transaction)
+ Chain.transaction_to_token_transfers(transaction.hash)
end
test "token transfers necessity_by_association loads associations" do
@@ -2409,7 +2424,7 @@ defmodule Explorer.ChainTest do
assert [%TokenTransfer{token: %Token{}, transaction: %Transaction{}}] =
Chain.transaction_to_token_transfers(
- transaction,
+ transaction.hash,
necessity_by_association: %{
token: :optional,
transaction: :optional
@@ -2421,7 +2436,7 @@ defmodule Explorer.ChainTest do
token: %Ecto.Association.NotLoaded{},
transaction: %Ecto.Association.NotLoaded{}
}
- ] = Chain.transaction_to_token_transfers(transaction)
+ ] = Chain.transaction_to_token_transfers(transaction.hash)
end
end
@@ -2479,7 +2494,17 @@ defmodule Explorer.ChainTest do
insert(:address, contract_code: Factory.data("contract_code"), smart_contract: nil, names: [])
|> Repo.preload([:contracts_creation_internal_transaction, :contracts_creation_transaction, :token])
- response = Chain.find_contract_address(address.hash)
+ options = [
+ necessity_by_association: %{
+ :contracts_creation_internal_transaction => :optional,
+ :names => :optional,
+ :smart_contract => :optional,
+ :token => :optional,
+ :contracts_creation_transaction => :optional
+ }
+ ]
+
+ response = Chain.find_contract_address(address.hash, options, true)
assert response == {:ok, address}
end
@@ -2522,11 +2547,11 @@ defmodule Explorer.ChainTest do
|> Decimal.add(Decimal.new(3))
|> Wei.from(:wei)
- assert expected == Chain.block_reward(block)
+ assert expected == Chain.block_reward(block.number)
end
test "with block without transactions", %{block: block, emission_reward: emission_reward} do
- assert emission_reward.reward == Chain.block_reward(block)
+ assert emission_reward.reward == Chain.block_reward(block.number)
end
end
@@ -3456,9 +3481,9 @@ defmodule Explorer.ChainTest do
smart_contract = build(:smart_contract)
address = insert(:address, smart_contract: smart_contract)
token = insert(:token, contract_address: address)
+ options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}]
- assert {:ok, result} =
- Chain.token_from_address_hash(token.contract_address_hash, [{:contract_address, :smart_contract}])
+ assert {:ok, result} = Chain.token_from_address_hash(token.contract_address_hash, options)
assert smart_contract = result.contract_address.smart_contract
end
diff --git a/apps/explorer/test/explorer/etherscan/logs_test.exs b/apps/explorer/test/explorer/etherscan/logs_test.exs
index 00050db6f4..490dce199d 100644
--- a/apps/explorer/test/explorer/etherscan/logs_test.exs
+++ b/apps/explorer/test/explorer/etherscan/logs_test.exs
@@ -158,6 +158,46 @@ defmodule Explorer.Etherscan.LogsTest do
assert found_log.transaction_hash == transaction_block1.hash
end
+ test "paginates logs" do
+ contract_address = insert(:contract_address)
+
+ transaction =
+ %Transaction{block: block} =
+ :transaction
+ |> insert(to_address: contract_address)
+ |> with_block()
+
+ inserted_records = insert_list(2000, :log, address: contract_address, transaction: transaction)
+
+ filter = %{
+ from_block: block.number,
+ to_block: block.number,
+ address_hash: contract_address.hash
+ }
+
+ first_found_logs = Logs.list_logs(filter)
+
+ assert Enum.count(first_found_logs) == 1_000
+
+ last_record = List.last(first_found_logs)
+
+ next_page_params = %{
+ log_index: last_record.index,
+ transaction_index: last_record.transaction_index,
+ block_number: transaction.block_number
+ }
+
+ second_found_logs = Logs.list_logs(filter, next_page_params)
+
+ assert Enum.count(second_found_logs) == 1_000
+
+ all_found_logs = first_found_logs ++ second_found_logs
+
+ assert Enum.all?(inserted_records, fn record ->
+ Enum.any?(all_found_logs, fn found_log -> found_log.index == record.index end)
+ end)
+ end
+
test "with a valid topic{x}" do
contract_address = insert(:contract_address)
diff --git a/apps/explorer/test/explorer/graphql_test.exs b/apps/explorer/test/explorer/graphql_test.exs
index ef0dce974d..41dd008dae 100644
--- a/apps/explorer/test/explorer/graphql_test.exs
+++ b/apps/explorer/test/explorer/graphql_test.exs
@@ -4,12 +4,14 @@ defmodule Explorer.GraphQLTest do
import Explorer.Factory
alias Explorer.{GraphQL, Repo}
+ alias Explorer.Chain.Address
describe "address_to_transactions_query/1" do
test "with address hash with zero transactions" do
result =
:address
|> insert()
+ |> Map.get(:hash)
|> GraphQL.address_to_transactions_query()
|> Repo.all()
@@ -17,12 +19,12 @@ defmodule Explorer.GraphQLTest do
end
test "with matching 'to_address_hash'" do
- address = insert(:address)
+ %Address{hash: address_hash} = address = insert(:address)
transaction = insert(:transaction, to_address: address)
insert(:transaction)
[found_transaction] =
- address
+ address_hash
|> GraphQL.address_to_transactions_query()
|> Repo.all()
@@ -30,12 +32,12 @@ defmodule Explorer.GraphQLTest do
end
test "with matching 'from_address_hash'" do
- address = insert(:address)
+ %Address{hash: address_hash} = address = insert(:address)
transaction = insert(:transaction, from_address: address)
insert(:transaction)
[found_transaction] =
- address
+ address_hash
|> GraphQL.address_to_transactions_query()
|> Repo.all()
@@ -43,12 +45,12 @@ defmodule Explorer.GraphQLTest do
end
test "with matching 'created_contract_address_hash'" do
- address = insert(:address)
+ %Address{hash: address_hash} = address = insert(:address)
transaction = insert(:transaction, created_contract_address: address)
insert(:transaction)
[found_transaction] =
- address
+ address_hash
|> GraphQL.address_to_transactions_query()
|> Repo.all()
@@ -60,7 +62,7 @@ defmodule Explorer.GraphQLTest do
second_block = insert(:block)
third_block = insert(:block)
- address = insert(:address)
+ %Address{hash: address_hash} = address = insert(:address)
3
|> insert_list(:transaction, from_address: address)
@@ -75,7 +77,7 @@ defmodule Explorer.GraphQLTest do
|> with_block(first_block)
found_transactions =
- address
+ address_hash
|> GraphQL.address_to_transactions_query()
|> Repo.all()
diff --git a/apps/explorer/test/explorer/market/history/source/crypto_compare_test.exs b/apps/explorer/test/explorer/market/history/source/crypto_compare_test.exs
index 9b662549c2..1fc4223f72 100644
--- a/apps/explorer/test/explorer/market/history/source/crypto_compare_test.exs
+++ b/apps/explorer/test/explorer/market/history/source/crypto_compare_test.exs
@@ -86,5 +86,59 @@ defmodule Explorer.Market.History.Source.CryptoCompareTest do
assert :error == CryptoCompare.fetch_history(3)
end
+
+ test "rejects empty prices", %{bypass: bypass} do
+ json = """
+ {
+ "Response": "Success",
+ "Type": 100,
+ "Aggregated": false,
+ "Data": [
+ {
+ "time": 1524528000,
+ "close": 0,
+ "high": 9741.91,
+ "low": 8957.68,
+ "open": 0,
+ "volumefrom": 136352.05,
+ "volumeto": 1276464750.74
+ },
+ {
+ "time": 1524614400,
+ "close": 0,
+ "high": 9765.23,
+ "low": 8757.06,
+ "open": 0,
+ "volumefrom": 192797.41,
+ "volumeto": 1779806222.98
+ },
+ {
+ "time": 1524700800,
+ "close": 8804.32,
+ "high": 8965.84,
+ "low": 8669.38,
+ "open": 8873.57,
+ "volumefrom": 74704.5,
+ "volumeto": 661168891
+ }
+ ],
+ "TimeTo": 1524700800,
+ "TimeFrom": 1523836800,
+ "FirstValueInArray": true,
+ "ConversionType": {
+ "type": "direct",
+ "conversionSymbol": ""
+ }
+ }
+ """
+
+ Bypass.expect(bypass, fn conn -> Conn.resp(conn, 200, json) end)
+
+ expected = [
+ %{closing_price: Decimal.from_float(8804.32), date: ~D[2018-04-26], opening_price: Decimal.from_float(8873.57)}
+ ]
+
+ assert {:ok, expected} == CryptoCompare.fetch_history(3)
+ end
end
end
diff --git a/apps/explorer/test/explorer/market/market_history_cache_test.exs b/apps/explorer/test/explorer/market/market_history_cache_test.exs
index 1421d75214..6dbd15fe7f 100644
--- a/apps/explorer/test/explorer/market/market_history_cache_test.exs
+++ b/apps/explorer/test/explorer/market/market_history_cache_test.exs
@@ -9,8 +9,8 @@ defmodule Explorer.Market.MarketHistoryCacheTest do
Supervisor.restart_child(Explorer.Supervisor, {ConCache, MarketHistoryCache.cache_name()})
on_exit(fn ->
- Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.BlocksCache.cache_name()})
- Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.BlocksCache.cache_name()})
+ Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
+ Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
end)
:ok
diff --git a/apps/explorer/test/explorer/market/market_test.exs b/apps/explorer/test/explorer/market/market_test.exs
index 7e399ea4cd..881e1dccc8 100644
--- a/apps/explorer/test/explorer/market/market_test.exs
+++ b/apps/explorer/test/explorer/market/market_test.exs
@@ -6,12 +6,12 @@ defmodule Explorer.MarketTest do
alias Explorer.Repo
setup do
- Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.BlocksCache.cache_name()})
- Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.BlocksCache.cache_name()})
+ Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
+ Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
on_exit(fn ->
- Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.BlocksCache.cache_name()})
- Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.BlocksCache.cache_name()})
+ Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
+ Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
end)
:ok
@@ -72,9 +72,30 @@ defmodule Explorer.MarketTest do
assert missing_records == %{}
end
- test "overrides existing records on date conflict" do
+ test "doesn't replace existing records with zeros" do
date = ~D[2018-04-01]
- Repo.insert(%MarketHistory{date: date})
+
+ {:ok, old_record} =
+ Repo.insert(%MarketHistory{date: date, closing_price: Decimal.new(1), opening_price: Decimal.new(1)})
+
+ new_record = %{
+ date: date,
+ closing_price: Decimal.new(0),
+ opening_price: Decimal.new(0)
+ }
+
+ Market.bulk_insert_history([new_record])
+
+ fetched_record = Repo.get_by(MarketHistory, date: date)
+ assert fetched_record.closing_price == old_record.closing_price
+ assert fetched_record.opening_price == old_record.opening_price
+ end
+
+ test "does not override existing records on date conflict" do
+ date = ~D[2018-04-01]
+
+ {:ok, old_record} =
+ Repo.insert(%MarketHistory{date: date, closing_price: Decimal.new(2), opening_price: Decimal.new(2)})
new_record = %{
date: date,
@@ -85,8 +106,8 @@ defmodule Explorer.MarketTest do
Market.bulk_insert_history([new_record])
fetched_record = Repo.get_by(MarketHistory, date: date)
- assert fetched_record.closing_price == new_record.closing_price
- assert fetched_record.opening_price == new_record.opening_price
+ assert fetched_record.closing_price == old_record.closing_price
+ assert fetched_record.opening_price == old_record.opening_price
end
end
end
diff --git a/apps/explorer/test/explorer/smart_contract/reader_test.exs b/apps/explorer/test/explorer/smart_contract/reader_test.exs
index ab63b74d1e..82f0a54385 100644
--- a/apps/explorer/test/explorer/smart_contract/reader_test.exs
+++ b/apps/explorer/test/explorer/smart_contract/reader_test.exs
@@ -102,7 +102,7 @@ defmodule Explorer.SmartContract.ReaderTest do
end
end
- describe "query_verified_contract/2" do
+ describe "query_verified_contract/3" do
test "correctly returns the results of the smart contract functions" do
hash =
:smart_contract
diff --git a/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs b/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs
index d1c1d04b45..29674274ca 100644
--- a/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs
+++ b/apps/explorer/test/explorer/smart_contract/solidity/code_compiler_test.exs
@@ -53,6 +53,26 @@ defmodule Explorer.SmartContract.Solidity.CodeCompilerTest do
}} = response
end
+ test "compiles smart contract with default evm version", %{contract_code_info: contract_code_info} do
+ optimize = true
+
+ response =
+ CodeCompiler.run(
+ name: contract_code_info.name,
+ compiler_version: contract_code_info.version,
+ code: contract_code_info.source_code,
+ optimize: optimize,
+ evm_version: "default"
+ )
+
+ assert {:ok,
+ %{
+ "abi" => _,
+ "bytecode" => _,
+ "name" => _
+ }} = response
+ end
+
test "compiles code with external libraries" do
Enum.each(@compiler_tests, fn compiler_test ->
compiler_version = compiler_test["compiler_version"]
diff --git a/apps/explorer/test/explorer/smart_contract/verifier/constructor_arguments_test.exs b/apps/explorer/test/explorer/smart_contract/verifier/constructor_arguments_test.exs
index a5e019c86f..9c4f6c8f68 100644
--- a/apps/explorer/test/explorer/smart_contract/verifier/constructor_arguments_test.exs
+++ b/apps/explorer/test/explorer/smart_contract/verifier/constructor_arguments_test.exs
@@ -22,6 +22,47 @@ defmodule Explorer.SmartContract.Verifier.ConstructorArgumentsTest do
|> insert(created_contract_address_hash: address.hash, input: input_data)
|> with_block()
- assert ConstructorArguments.verify(address.hash, constructor_arguments)
+ assert ConstructorArguments.verify(address.hash, "", constructor_arguments)
+ end
+
+ test "verifies with multiple nested constructor arguments" do
+ address = insert(:address)
+
+ constructor_arguments =
+ "000000000000000000000000314159265dd8dbb310642f98f50c066173c1259b93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae00000000000000000000000000000000000000000000000000000000590b09b0"
+
+ input =
+ "a165627a7a72305820fbfa6f8a2024760ef0e0eb29a332c9a820526e92f8b4fbcce6f00c7643234b1400297b6c4b278d165a6b33958f8ea5dfb00c8c9d4d0acf1985bef5d10786898bc3e7a165627a7a723058203c2db82e7c80cd1e371fe349b03d49b812c324ba4a3fcd063b7bc2662353c5de0029000000000000000000000000314159265dd8dbb310642f98f50c066173c1259b93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae00000000000000000000000000000000000000000000000000000000590b09b0"
+
+ input_data = %Data{
+ bytes: Base.decode16!(input, case: :lower)
+ }
+
+ :transaction
+ |> insert(created_contract_address_hash: address.hash, input: input_data)
+ |> with_block()
+
+ assert ConstructorArguments.verify(address.hash, "", constructor_arguments)
+ end
+
+ test "verifies older version of Solidity where constructor_arguments were directly appended to source code" do
+ address = insert(:address)
+
+ constructor_arguments =
+ "000000000000000000000000314159265dd8dbb310642f98f50c066173c1259b93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae00000000000000000000000000000000000000000000000000000000590b09b0"
+
+ source_code = "0001"
+
+ input = source_code <> constructor_arguments
+
+ input_data = %Data{
+ bytes: Base.decode16!(input, case: :lower)
+ }
+
+ :transaction
+ |> insert(created_contract_address_hash: address.hash, input: input_data)
+ |> with_block()
+
+ assert ConstructorArguments.verify(address.hash, source_code, constructor_arguments)
end
end
diff --git a/apps/explorer/test/support/data_case.ex b/apps/explorer/test/support/data_case.ex
index 2ec7cde365..e159891987 100644
--- a/apps/explorer/test/support/data_case.ex
+++ b/apps/explorer/test/support/data_case.ex
@@ -39,11 +39,11 @@ defmodule Explorer.DataCase do
Ecto.Adapters.SQL.Sandbox.mode(Explorer.Repo, {:shared, self()})
end
- Explorer.Chain.BlockNumberCache.setup()
- Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.BlocksCache.cache_name()})
- Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.BlocksCache.cache_name()})
- Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.TransactionsCache.cache_name()})
- Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.TransactionsCache.cache_name()})
+ Explorer.Chain.Cache.BlockNumber.setup()
+ Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
+ Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Blocks.cache_name()})
+ Supervisor.terminate_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Transactions.cache_name()})
+ Supervisor.restart_child(Explorer.Supervisor, {ConCache, Explorer.Chain.Cache.Transactions.cache_name()})
:ok
end
diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex
index 2189b8f601..a6e07683a5 100644
--- a/apps/explorer/test/support/factory.ex
+++ b/apps/explorer/test/support/factory.ex
@@ -147,7 +147,8 @@ defmodule Explorer.Factory do
size: Enum.random(1..100_000),
gas_limit: Enum.random(1..100_000),
gas_used: Enum.random(1..100_000),
- timestamp: DateTime.utc_now()
+ timestamp: DateTime.utc_now(),
+ refetch_needed: false
}
end
diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex
index b436d61e03..4155538e76 100644
--- a/apps/indexer/lib/indexer/block/fetcher.ex
+++ b/apps/indexer/lib/indexer/block/fetcher.ex
@@ -11,7 +11,9 @@ defmodule Indexer.Block.Fetcher do
alias EthereumJSONRPC.{Blocks, FetchedBeneficiaries}
alias Explorer.Chain
- alias Explorer.Chain.{Address, Block, BlockNumberCache, BlocksCache, Hash, Import, Transaction, TransactionsCache}
+ alias Explorer.Chain.{Address, Block, Hash, Import, Transaction}
+ alias Explorer.Chain.Cache.Blocks, as: BlocksCache
+ alias Explorer.Chain.Cache.{BlockNumber, Transactions}
alias Indexer.Block.Fetcher.Receipts
alias Indexer.Fetcher.{
@@ -185,13 +187,13 @@ defmodule Indexer.Block.Fetcher do
max_block = Enum.max_by(blocks, fn block -> block.number end)
min_block = Enum.min_by(blocks, fn block -> block.number end)
- BlockNumberCache.update(max_block.number)
- BlockNumberCache.update(min_block.number)
+ BlockNumber.update(max_block.number)
+ BlockNumber.update(min_block.number)
BlocksCache.update_blocks(blocks)
end
defp update_transactions_cache(transactions) do
- TransactionsCache.update(transactions)
+ Transactions.update(transactions)
end
def import(
diff --git a/apps/indexer/lib/indexer/fetcher/coin_balance_on_demand.ex b/apps/indexer/lib/indexer/fetcher/coin_balance_on_demand.ex
index 75af9c9bfc..4dfed6e73a 100644
--- a/apps/indexer/lib/indexer/fetcher/coin_balance_on_demand.ex
+++ b/apps/indexer/lib/indexer/fetcher/coin_balance_on_demand.ex
@@ -17,8 +17,9 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemand do
alias EthereumJSONRPC.FetchedBalances
alias Explorer.{Chain, Repo}
- alias Explorer.Chain.{Address, BlockNumberCache}
+ alias Explorer.Chain.Address
alias Explorer.Chain.Address.CoinBalance
+ alias Explorer.Chain.Cache.BlockNumber
alias Explorer.Counters.AverageBlockTime
alias Indexer.Fetcher.CoinBalance, as: CoinBalanceFetcher
alias Timex.Duration
@@ -161,7 +162,7 @@ defmodule Indexer.Fetcher.CoinBalanceOnDemand do
end
defp latest_block_number do
- BlockNumberCache.max_number()
+ BlockNumber.max_number()
end
defp stale_balance_window(block_number) do
diff --git a/apps/indexer/lib/indexer/fetcher/token.ex b/apps/indexer/lib/indexer/fetcher/token.ex
index e0992a450f..8c924e2ad2 100644
--- a/apps/indexer/lib/indexer/fetcher/token.ex
+++ b/apps/indexer/lib/indexer/fetcher/token.ex
@@ -52,7 +52,9 @@ defmodule Indexer.Fetcher.Token do
@impl BufferedTask
@decorate trace(name: "fetch", resource: "Indexer.Fetcher.Token.run/2", service: :indexer, tracer: Tracer)
def run([token_contract_address], _json_rpc_named_arguments) do
- case Chain.token_from_address_hash(token_contract_address, [{:contract_address, :smart_contract}]) do
+ options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}]
+
+ case Chain.token_from_address_hash(token_contract_address, options) do
{:ok, %Token{} = token} ->
catalog_token(token)
end
diff --git a/apps/indexer/lib/indexer/fetcher/token_updater.ex b/apps/indexer/lib/indexer/fetcher/token_updater.ex
index 304f2f068c..f596687ff8 100644
--- a/apps/indexer/lib/indexer/fetcher/token_updater.ex
+++ b/apps/indexer/lib/indexer/fetcher/token_updater.ex
@@ -36,8 +36,10 @@ defmodule Indexer.Fetcher.TokenUpdater do
@doc false
def update_metadata(token_addresses) when is_list(token_addresses) do
+ options = [necessity_by_association: %{[contract_address: :smart_contract] => :optional}]
+
Enum.each(token_addresses, fn address ->
- case Chain.token_from_address_hash(address, [{:contract_address, :smart_contract}]) do
+ case Chain.token_from_address_hash(address, options) do
{:ok, %Token{cataloged: true} = token} ->
update_metadata(token)
end
diff --git a/apps/indexer/mix.exs b/apps/indexer/mix.exs
index f70d758912..5b95f4ab5c 100644
--- a/apps/indexer/mix.exs
+++ b/apps/indexer/mix.exs
@@ -10,7 +10,7 @@ defmodule Indexer.MixProject do
deps: deps(),
deps_path: "../../deps",
description: "Fetches block chain data from on-chain node for later reading with Explorer.",
- elixir: "~> 1.8",
+ elixir: "~> 1.9",
elixirc_paths: elixirc_paths(Mix.env()),
lockfile: "../../mix.lock",
preferred_cli_env: [
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 73501a9b9d..f73c4b34f0 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -1,4 +1,4 @@
-FROM bitwalker/alpine-elixir-phoenix
+FROM bitwalker/alpine-elixir-phoenix:1.9.0
RUN apk --no-cache --update add alpine-sdk gmp-dev automake libtool inotify-tools autoconf python
diff --git a/docker/Makefile b/docker/Makefile
index ac1c2677d6..6005fbdaeb 100644
--- a/docker/Makefile
+++ b/docker/Makefile
@@ -3,7 +3,7 @@ HOST = host.docker.internal
DOCKER_IMAGE = blockscout_prod
BS_CONTAINER_NAME = blockscout
PG_CONTAINER_NAME = postgres
-PG_CONTAINER_IMAGE = postgres:10.4
+PG_CONTAINER_IMAGE = postgres:10.6
THIS_FILE = $(lastword $(MAKEFILE_LIST))
ifeq ($(SYSTEM), Linux)
diff --git a/docs/ansible-deployment.md b/docs/ansible-deployment.md
index a613091920..a923816f10 100644
--- a/docs/ansible-deployment.md
+++ b/docs/ansible-deployment.md
@@ -76,63 +76,100 @@ The single point of configuration in this script is a `group_vars/all.yml` file.
## Common variables
-- `aws_access_key` and `aws_secret_key` is a credentials pair that provides access to AWS for the deployer;
-- `backend` variable defines whether deployer should keep state files remote or locally. Set `backend` variable to `true` if you want to save state file to the remote S3 bucket;
-- `upload_config_to_s3` - set to `true` if you want to upload config `all.yml` file to the S3 bucket automatically after the deployment. Will not work if `backend` is set to false;
-- `upload_debug_info_to_s3` - set to `true` if you want to upload full log output to the S3 bucket automatically after the deployment. Will not work if `backend` is set to false. *IMPORTANT*: Locally logs are stored at `log.txt` which is not cleaned automatically. Please, do not forget to clean it manually or using the `clean.yml` playbook;
-- `bucket` represents a globally unique name of the bucket where your configs and state will be stored. It will be created automatically during the deployment;
-- `prefix` - is a unique tag to use for provisioned resources (5 alphanumeric chars or less);
-- `chains` - maps chains to the URLs of HTTP RPC endpoints, an ordinary blockchain node can be used;
-- The `region` should be left at `us-east-1` as some of the other regions fail for different reasons;
+- `aws_access_key` and `aws_secret_key` is a credentials pair that provides access to AWS for the deployer.
-*Note*: a chain name SHOULD NOT be more than 5 characters. Otherwise, it will throw an error because the aws load balancer name should not be greater than 32 characters.
+- `backend` variable defines whether deployer should keep state files remote or locally. Set `backend` variable to `true` if you want to save state file to the remote S3 bucket.
+
+- `upload_config_to_s3` - set to `true` if you want to upload config `all.yml` file to the S3 bucket automatically after the deployment. Will not work if `backend` is set to false.
+
+- `upload_debug_info_to_s3` - set to `true` if you want to upload full log output to the S3 bucket automatically after the deployment. Will not work if `backend` is set to false.
+ >[!DANGER]
+ >Locally logs are stored at `log.txt` which is not cleaned automatically. Please, do not forget to clean it manually or using the `clean.yml` playbook.
+
+- `bucket` represents a globally unique name of the bucket where your configs and state will be stored. It will be created automatically during the deployment.
+
+- `prefix` - is a unique tag to use for provisioned resources (5 alphanumeric chars or less).
+
+- `chains` - maps chains to the URLs of HTTP RPC endpoints, an ordinary blockchain node can be used.
+
+- The `region` should be left at `us-east-1` as some of the other regions fail for different reasons.
+ >[!WARNING]
+ >a chain name SHOULD NOT be more than 5 characters. Otherwise, it will throw an error because the aws load balancer name should not be greater than 32 characters.
## Infrastructure related variables
-- `dynamodb_table` represents the name of table that will be used for Terraform state lock management;
-- If `ec2_ssh_key_content` variable is not empty, Terraform will try to create EC2 SSH key with the `ec2_ssh_key_name` name. Otherwise, the existing key with `ec2_ssh_key_name` name will be used;
-- `instance_type` defines a size of the Blockscout instance that will be launched during the deployment process;
-- `vpc_cidr`, `public_subnet_cidr`, `db_subnet_cidr` represents the network configuration for the deployment. Usually you want to leave it as is. However, if you want to modify it, please, expect that `db_subnet_cidr` represents not a single network, but a group of networks started with defined CIDR block increased by 8 bits.
- Example:
- Number of networks: 2
- `db_subnet_cidr`: "10.0.1.0/16"
- Real networks: 10.0.1.0/24 and 10.0.2.0/24
-- An internal DNS zone with`dns_zone_name` name will be created to take care of BlockScout internal communications;
+- `dynamodb_table` represents the name of table that will be used for Terraform state lock management.
+
+- If `ec2_ssh_key_content` variable is not empty, Terraform will try to create EC2 SSH key with the `ec2_ssh_key_name` name. Otherwise, the existing key with `ec2_ssh_key_name` name will be used.
+
+- `instance_type` defines a size of the Blockscout instance that will be launched during the deployment process.
+
+- `vpc_cidr`, `public_subnet_cidr`, `db_subnet_cidr` represent the network configuration for the deployment. Usually you will leave as is. However, if you want to modify, understand that `db_subnet_cidr` represents not a single network, but a group of networks that start with a defined CIDR block increased by 8 bits.
+> [!TIP|label: Example]
+> Number of networks: 2
+> `db_subnet_cidr`: "10.0.1.0/16"
+> Real networks: 10.0.1.0/24 and 10.0.2.0/24
+
+- An internal DNS zone with`dns_zone_name` name will be created to take care of BlockScout internal communications.
+
- The name of a IAM key pair to use for EC2 instances, if you provide a name which
- already exists it will be used, otherwise it will be generated for you;
+ already exists it will be used, otherwise it will be generated for you.
+
+- If `use_ssl` is set to `false`, SSL will be forced on Blockscout. To configure SSL, use `alb_ssl_policy` and `alb_certificate_arn` variables.
-* If `use_ssl` is set to `false`, SSL will be forced on Blockscout. To configure SSL, use `alb_ssl_policy` and `alb_certificate_arn` variables;
+- The `root_block_size` is the amount of storage on your EC2 instance. This value can be adjusted by how frequently logs are rotated. Logs are located in `/opt/app/logs` of your EC2 instance.
-- The `root_block_size` is the amount of storage on your EC2 instance. This value can be adjusted by how frequently logs are rotated. Logs are located in `/opt/app/logs` of your EC2 instance;
- The `pool_size` defines the number of connections allowed by the RDS instance;
-- `secret_key_base` is a random password used for BlockScout internally. It is highly recommended to gernerate your own `secret_key_base` before the deployment. For instance, you can do it via `openssl rand -base64 64 | tr -d '\n'` command;
-- `new_relic_app_name` and `new_relic_license_key` should usually stay empty unless you want and know how to configure New Relic integration;
-- `elixir_version` - is an Elixir version used in BlockScout release;
-- `chain_trace_endpoint` - maps chains to the URLs of HTTP RPC endpoints, which represents a node where state pruning is disabled (archive node) and tracing is enabled. If you don't have a trace endpoint, you can simply copy values from `chains` variable;
-- `chain_ws_endpoint` - maps chains to the URLs of HTTP RPCs that supports websockets. This is required to get the real-time updates. Can be the same as `chains` if websocket is enabled there (but make sure to use`ws(s)` instead of `htpp(s)` protocol);
-- `chain_jsonrpc_variant` - a client used to connect to the network. Can be `parity`, `geth`, etc;
-- `chain_logo` - maps chains to the it logos. Place your own logo at `apps/block_scout_web/assets/static` and specify a relative path at `chain_logo` variable;
-- `chain_coin` - a name of the coin used in each particular chain;
-- `chain_network` - usually, a name of the organization keeping group of networks, but can represent a name of any logical network grouping you want;
-- `chain_subnetwork` - a name of the network to be shown at BlockScout;
+- `secret_key_base` is a random password used for BlockScout internally. It is highly recommended to gernerate your own `secret_key_base` before the deployment. For instance, you can do it via `openssl rand -base64 64 | tr -d '\n'` command.
+
+- `new_relic_app_name` and `new_relic_license_key` should usually stay empty unless you want and know how to configure New Relic integration.
+
+- `elixir_version` - is an Elixir version used in BlockScout release.
+
+- `chain_trace_endpoint` - maps chains to the URLs of HTTP RPC endpoints, which represents a node where state pruning is disabled (archive node) and tracing is enabled. If you don't have a trace endpoint, you can simply copy values from `chains` variable.
+
+- `chain_ws_endpoint` - maps chains to the URLs of HTTP RPCs that supports websockets. This is required to get the real-time updates. Can be the same as `chains` if websocket is enabled there (but make sure to use`ws(s)` instead of `htpp(s)` protocol).
+
+- `chain_jsonrpc_variant` - a client used to connect to the network. Can be `parity`, `geth`, etc.
+
+- `chain_logo` - maps chains to the it logos. Place your own logo at `apps/block_scout_web/assets/static` and specify a relative path at `chain_logo` variable.
+
+- `chain_coin` - a name of the coin used in each particular chain.
+
+- `chain_network` - usually, a name of the organization keeping group of networks, but can represent a name of any logical network grouping you want.
+
+- `chain_subnetwork` - a name of the network to be shown at BlockScout.
+
- `chain_network_path` - a relative URL path which will be used as an endpoint for defined chain. For example, if we will have our BlockScout at `blockscout.com` domain and place `core` network at `/poa/core`, then the resulting endpoint will be `blockscout.com/poa/core` for this network.
-- `chain_network_icon` - maps the chain name to the network navigation icon at apps/block_scout_web/lib/block_scout_web/templates/icons without .eex extension
+
+- `chain_network_icon` - maps the chain name to the network navigation icon at apps/block_scout_web/lib/block_scout_web/templates/icons without .eex extension.
+
- `chain_graphiql_transaction` - is a variable that maps chain to a random transaction hash on that chain. This hash will be used to provide a sample query in the GraphIQL Playground.
-- `chain_block_transformer` - will be `clique` for clique networks like Rinkeby and Goerli, and `base` for the rest;
-- `chain_heart_beat_timeout`, `chain_heart_command` - configs for the integrated heartbeat. First describes a timeout after the command described at the second variable will be executed;
+
+- `chain_block_transformer` - will be `clique` for clique networks like Rinkeby and Goerli, and `base` for the rest.
+
+- `chain_heart_beat_timeout`, `chain_heart_command` - configs for the integrated heartbeat. First describes a timeout after the command described at the second variable will be executed.
+
- Each of the `chain_db_*` variables configures the database for each chain. Each chain will have the separate RDS instance.
+
- `chain_blockscout_version` - is a text at the footer of BlockScout instance. Usually represents the current BlockScout version.
## Blockscout related variables
-- `blockscout_repo` - a direct link to the Blockscout repo;
-- `chain_branch` - maps branch at `blockscout_repo` to each chain;
-- Specify the `chain_merge_commit` variable if you want to merge any of the specified `chains` with the commit in the other branch. Usually may be used to update production branches with the releases from master branch;
-- `skip_fetch` - if this variable is set to `true` , BlockScout repo will not be cloned and the process will start from building the dependencies. Use this variable to prevent playbooks from overriding manual changes in cloned repo;
-- `ps_*` variables represents a connection details to the test Postgres database. This one will not be installed automatically, so make sure `ps_*` credentials are valid before starting the deployment;
+- `blockscout_repo` - a direct link to the Blockscout repo.
+
+- `chain_branch` - maps branch at `blockscout_repo` to each chain.
+
+- Specify the `chain_merge_commit` variable if you want to merge any of the specified `chains` with the commit in the other branch. Usually may be used to update production branches with the releases from master branch.
+
+- `skip_fetch` - if this variable is set to `true` , BlockScout repo will not be cloned and the process will start from building the dependencies. Use this variable to prevent playbooks from overriding manual changes in cloned repo.
+
+- `ps_*` variables represents a connection details to the test Postgres database. This one will not be installed automatically, so make sure `ps_*` credentials are valid before starting the deployment.
+
- `chain_custom_environment` - is a map of variables that should be overrided when deploying the new version of Blockscout. Can be omitted.
-*Note*: `chain_custom_environment` variables will not be propagated to the Parameter Store at production servers and need to be set there manually.
+ >[!NOTE]
+ > `chain_custom_environment` variables will not be propagated to the Parameter Store at production servers and need to be set there manually.
# Database Storage Required
@@ -158,103 +195,115 @@ The configuration variable `db_storage` can be used to define the amount of stor
cat group_vars/infrastructure.yml.example group_vars/all.yml.example > group_vars/all.yml
```
-4. Set the variables at `group_vars/all.yml` config template file as described at the [corresponding part of instruction](#Configuration);
+4. Set the variables at `group_vars/all.yml` config template file as described in the [configuration section](#Configuration);
5. Run `ansible-playbook deploy_infra.yml`;
- - During the deployment the ["diffs didn't match"](#error-applying-plan-diffs-didnt-match) error may occur, it will be ignored automatically. If Ansible play recap shows 0 failed plays, then the deployment was successful despite the error.
+ - During the deployment the ["diffs didn't match"](#error-applying-plan-diffs-didnt-match) error may occur. If it does, it will be ignored automatically. If the Ansible play recap shows 0 failed plays, then the deployment was successful despite the error.
- - Optionally, you may want to check the variables the were uploaded to the [Parameter Store](https://console.aws.amazon.com/systems-manager/parameters) at AWS Console.
+ - Optionally, you may want to check the variables uploaded to the [Parameter Store](https://console.aws.amazon.com/systems-manager/parameters) on your AWS Console.
# Deploying BlockScout
-1. Ensure all the [BlockScout prerequisites](#Prerequisites-for-deploying-blockscout) are installed and has the right version number;
-2. Merge `blockscout` and `all` config template files into single config file:
+1. Ensure all the [BlockScout prerequisites](#Prerequisites-for-deploying-blockscout) are installed and has the right version number.
+2. Merge `blockscout` and `all` config template files into a single config file:
```bash
cat group_vars/blockscout.yml.example group_vars/all.yml.example > group_vars/all.yml
```
-**Note!** All three configuration files are compatible to each other, so you can simply `cat group_vars/blockscout.yml.example >> group_vars/all.yml` if you already do have the `all.yml` file after the deploying of infrastructure.
+> [!NOTE]
+> All three configuration files are compatible with one another, so you can simply `cat group_vars/blockscout.yml.example >> group_vars/all.yml` if you already have the `all.yml` file after deploying the infrastructure.
-3. Set the variables at `group_vars/all.yml` config template file as described at the [corresponding part of instruction](#Configuration);
- **Note!** Use `chain_custom_environment` to update the variables in each deployment. Map each deployed chain with variables as they should appear at the Parameter Store. Check the example at `group_vars/blockscout.yml.example` config file. `chain_*` variables will be ignored during BlockScout software deployment.
+3. Set the variables at `group_vars/all.yml` config template file as described in the [configuration section](#Configuration).
+> [!NOTE]
+> Use `chain_custom_environment` to update the variables in each deployment. Map each deployed chain with variables as they should appear at the Parameter Store. Check the example at `group_vars/blockscout.yml.example` config file. `chain_*` variables will be ignored during BlockScout software deployment.
-4. This step is for mac OS users. Please skip it, if this is not your case.
+4. This step is for mac OS users only. Please skip if you are not using this OS.
-To avoid the error
-```
-TASK [main_software : Fetch environment variables] ************************************
-objc[12816]: +[__NSPlaceholderDate initialize] may have been in progress in another thread when fork() was called.
-objc[12816]: +[__NSPlaceholderDate initialize] may have been in progress in another thread when fork() was called. We cannot safely call it or ignore it in the fork() child process. Crashing instead. Set a breakpoint on objc_initializeAfterForkError to debug.
-```
-error and crashing of Python follow the next steps:
+ To avoid the the following Python crash error:
+ ```
+ TASK [main_software : Fetch environment variables] ************************************
+ objc[12816]: +[__NSPlaceholderDate initialize] may have been in progress in another thread when fork() was called.
+ objc[12816]: +[__NSPlaceholderDate initialize] may have been in progress in another thread when fork() was called. We cannot safely call it or ignore it in the fork() child process. Crashing instead. Set a breakpoint on objc_initializeAfterForkError to debug.
+ ```
-- Open terminal: `nano .bash_profile`;
-- Add the following line to the end of the file: `export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES`;
-- Save, exit, close terminal and re-open the terminal. Check to see that the environment variable is now set: `env`
+ - Open terminal: `nano .bash_profile`;
+ - Add the following line to the end of the file: `export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES`;
+ - Save, exit, close terminal and re-open the terminal. Check to see that the environment variable is now set: `env`
-(source: https://stackoverflow.com/questions/50168647/multiprocessing-causes-python-to-crash-and-gives-an-error-may-have-been-in-progr);
+ (source: https://stackoverflow.com/questions/50168647/multiprocessing-causes-python-to-crash-and-gives-an-error-may-have-been-in-progr);
+
+5. Run `ansible-playbook deploy_software.yml`.
-5. Run `ansible-playbook deploy_software.yml`;
6. When the prompt appears, check that server is running and there is no visual artifacts. The server will be launched at port 4000 at the same machine where you run the Ansible playbooks. If you face any errors you can either fix it or cancel the deployment by pressing **Ctrl+C** and then pressing **A** when additionally prompted.
+
7. When server is ready to be deployed simply press enter and deployer will upload Blockscout to the appropriate S3.
+
8. Two other prompts will appear to ensure your will on updating the Parameter Store variables and deploying the BlockScout through the CodeDeploy. Both **yes** and **true** will be interpreted as the confirmation.
+
9. Monitor and manage your deployment at [CodeDeploy](https://console.aws.amazon.com/codesuite/codedeploy/applications) service page at AWS Console.
# Destroying Provisioned Infrastructure
-First of all you have to remove autoscaling groups (ASG) deployed via CodeDeploy manually since Terraform doesn't track them and will miss them during the automatic destroy process. Once ASG is deleted you can use `ansible-playbook destroy.yml` playbook to remove the rest of generated infrastructure. Make sure to check the playbook output since in some cases it might not be able to delete everything. Check the error description for details.
+First, remove autoscaling groups (ASG) deployed via CodeDeploy manually since Terraform doesn't track them and will miss them during the automatic destroy process. Once ASG is deleted you can use the `ansible-playbook destroy.yml` playbook to remove the rest of generated infrastructure. Make sure to check the playbook output since in some cases it may not delete everything. Check the error description for details.
-**Note!** While Terraform is stateful, Ansible is stateless, so if you modify `bucket` or `dynamodb_table` variables and run `destroy.yml` or `deploy_infra.yml` playbooks, it will not alter the current S3/Dynamo resources names, but create a new resources. Moreover, altering `bucket` variable will make Terraform to forget about existing infrastructure and, as a consequence, redeploy it. If it absolutely necessary for you to alter the S3 or DynamoDB names you can do it manually and then change the appropriate variable accordingly.
+> [!WARNING]
+>While Terraform is stateful, Ansible is stateless, so if you modify `bucket` or `dynamodb_table` variables and run `destroy.yml` or `deploy_infra.yml` playbooks, it will not alter the current S3/Dynamo resources names, but create a new resources. Moreover, altering `bucket` variable will make Terraform to forget about existing infrastructure and, as a consequence, redeploy it. If it is absolutely necessary for you to alter the S3 or DynamoDB names, perform this operation manually and then change the appropriate variable accordingly.
-Also note, that changing `backend` variable will force Terraform to forget about created infrastructure also, since it will start searching the current state files locally instead of remote.
+> [!NOTE]
+> Changing the `backend` variable will force Terraform to forget about created infrastructure, since it will start searching the current state files locally instead of remote.
# Useful information
## Cleaning Deployment cache
-Despite the fact that Terraform cache is automatically cleared automatically before each deployment, you may also want to force the cleaning process manually. To do this simply run the `ansible-playbook clean.yml` command, and Terraform cache will be cleared.
+Despite the fact that the Terraform cache is automatically cleared before each deployment, you may also want to manually force the cleaning process. To clear the Terraform cache, Run the `ansible-playbook clean.yml` command.
## Migrating deployer to another machine
-You can easily manipulate your deployment from any machine with sufficient prerequisites. If `upload_debug_info_to_s3` variable is set to true, the deployer will automatically upload your `all.yml` file to the s3 bucket, so you can easily download it to any other machine. Simply download this file to your `group_vars` folder and your new deployer will pick up the current deployment instead of creating a new one.
+You can easily manipulate your deployment from any machine with sufficient prerequisites. If the `upload_debug_info_to_s3` variable is set to true, the deployer will automatically upload your `all.yml` file to the s3 bucket, so you can download it to any other machine. Simply download this file to your `group_vars` folder and your new deployer will pick up the current deployment instead of creating a new one.
## Attaching the existing RDS instance to the current deployment
-In some cases you may want not to create a new database, but to add the existing one to use within the deployment. In order to do that configure all the proper values at `group_vars/all.yml` including yours DB ID and name and execute the `ansible-playbook attach_existing_rds.yml` command. This will add the current DB instance into Terraform-managed resource group. After that run `ansible-playbook deploy_infra.yml` as usually.
-
-**Note 1**: while executing `ansible-playbook attach_existing_rds.yml` the S3 and DynamoDB will be automatically created (if `backend` variable is set to `true`) to store Terraform state files.
-
-**Note 2**: the actual name of your resource must include prefix that you will use in this deployment.
+Rather than create a new database, you may want to add an existing instance to use with the deployment. To do this, configure all proper values at `group_vars/all.yml`, including your DB ID and name, and execute the `ansible-playbook attach_existing_rds.yml` command. This will add the current DB instance into the Terraform-managed resource group. After that run `ansible-playbook deploy_infra.yml` as you normally would.
-Example:
+> [!NOTE|label: Note 1]
+> While executing `ansible-playbook attach_existing_rds.yml` the S3 and DynamoDB instances will be automatically created (if `backend` variable is set to `true`) to store Terraform state files.
- Real resource: tf-poa
+> [!NOTE|label: Note 2]
+> The actual name of your resource must include the prefix you are using with this deployment.
+>
+>Example:
+>
+>Real resource: tf-poa
+>
+> `prefix` variable: tf
+>
+> `chain_db_id` variable: poa
- `prefix` variable: tf
+> [!NOTE|label: Note 3]
+> mMke sure MultiAZ is disabled on your database.
- `chain_db_id` variable: poa
+> [!NOTE|label: Note 4]
+> Make sure that all the variables at `group_vars/all.yml` are exactly the same as your existing DB.
-**Note 3**: make sure MultiAZ is disabled on your database.
-
-**Note 4**: make sure that all the variables at `group_vars/all.yml` are exactly the same as at your existing DB.
-
-## Using AWS CodeDeploy to Mmnitor and manage a BlockScout deployment
+## Using AWS CodeDeploy to Monitor and manage a BlockScout deployment
BlockScout deployment can be managed through the AWS console. [A brief tutorial is available on our forum](https://forum.poa.network/t/monitor-and-manage-a-blockscout-deployment-using-codedeploy-in-your-aws-console/2499).
# Common Errors and Questions
-### S3: 403 error during provisioning
-Usually appears if S3 bucket already exists. Remember, S3 bucket has globally unique name, so if you don't have it, it doesn't mean, that it doesn't exists at all. Login to your AWS console and try to create S3 bucket with the same name you specified at `bucket` variable to ensure.
+## S3: 403 error during provisioning
-### Error Applying Plan (diffs didn't match)
+This usually appears if the S3 bucket already exists. Remember, the S3 bucket has a unique global name. Login to your AWS console and create an S3 bucket with the same name you specified in the `bucket` variable to ensure they match.
-If you see something like the following:
+## Error Applying Plan (diffs didn't match)
-```
+If you see something similar to the following:
+
+```bash
Error: Error applying plan:
1 error(s) occurred:
@@ -268,8 +317,8 @@ Please include the following information in your report:
Mismatch reason: attribute mismatch: availability_zones.1252502072
```
-This is due to a bug in Terraform, however the fix is to just rerun `ansible-playbook deploy_infra.yml` again, and Terraform will pick up where it left off. This does not always happen, but this is the current workaround if you see it.
+This is due to a bug in Terraform, the fix is to run `ansible-playbook deploy_infra.yml` again, and Terraform will pick up where it left off. This does not always happen, but this is the current workaround if needed.
-### Server doesn't start during deployment
+## Server doesn't start during deployment
-Even if server is configured correctly, sometimes it may not bind the appropriate 4000 port due to unknown reason. If so, simply go to the appropriate nested blockscout folder, kill and rerun server. For example, you can use the following command: `pkill beam.smp && pkill node && sleep 10 && mix phx.server`.
+Even if the server is configured correctly, sometimes it may not bind the appropriate 4000 port for unknown reasons. If so, simply go to the appropriate nested blockscout folder, kill and rerun the server. For example, you can use the following command: `pkill beam.smp && pkill node && sleep 10 && mix phx.server`.
diff --git a/docs/env-variables.md b/docs/env-variables.md
index 0c93460150..d6dd56d7af 100644
--- a/docs/env-variables.md
+++ b/docs/env-variables.md
@@ -2,6 +2,17 @@
Below is a table outlining the environment variables utilized by BlockScout.
+**Notes:**
+- This table is horizontally scrollable, version information is located in the last column.
+- Settings related to the `ETHEREUM_JSONRPC_VARIANT` variable and client related settings for running a full archive node with geth or parity are located in [this forum post](https://forum.poa.network/t/faq-what-settings-are-required-on-a-parity-or-geth-client/1805).
+- Additional information related to certain variables is available on the [ansible deployment](ansible-deployment.md) page.
+- To set variables using the CLI, use the export command. For example:
+```bash
+$ export ETHEREUM_JSONRPC_VARIANT=parity
+$ export COIN=POA
+$ export NETWORK=POA
+```
+
| Variable | Required | Description | Default | Version |
| --- | --- | --- | ---| --- |
@@ -32,7 +43,7 @@ Below is a table outlining the environment variables utilized by BlockScout.
| `HEART_BEAT_TIMEOUT` | | Production environment variable to restart the application in the event of a crash. | 30 | all |
| `HEART_COMMAND` | | Production environment variable to restart the application in the event of a crash. | systemctl restart explorer.service | all |
| `BLOCKSCOUT_VERSION` | | Added to the footer to signify the current BlockScout version. | (empty) | v1.3.4+ |
-| `RELEASE_LINK` | | The link to Blockscout release notes in the footer. | https://github.com/poanetwork/ blockscout/releases/ tag/${BLOCKSCOUT_VERSION} | v1.3.5+ |
+| `RELEASE_LINK` | | The link to Blockscout release notes in the footer. | https: //github.com/poanetwork/ blockscout/releases/ tag/${BLOCKSCOUT_VERSION} | v1.3.5+ |
| `ELIXIR_VERSION` | | Elixir version to install on the node before Blockscout deploy. | (empty) | all |
| `BLOCK_TRANSFORMER` | | Transformer for blocks: base or clique. | base | v1.3.4+ |
| `GRAPHIQL _TRANSACTION` | | Default transaction in query to GraphiQL. | (empty) | v1.3.4+ |
diff --git a/docs/index.html b/docs/index.html
index 73107d93fa..37702cd231 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -1,20 +1,19 @@
-
+
BlockScout Docs
-
+
-
-
-
+
+
+
-
-
+
+
+
\ No newline at end of file
diff --git a/docs/manual-deployment.md b/docs/manual-deployment.md
index f2291b846c..fcbaf6e050 100644
--- a/docs/manual-deployment.md
+++ b/docs/manual-deployment.md
@@ -1,8 +1,12 @@
-## Manual Deployment
+# Manual Deployment
-Below is the procedure for manual deployment of BlockScout. For automated deployment, see [ansible deployment](ansible-deployment.md).
+Below is the procedure for manual deployment of BlockScout. For automated deployment, see [ansible deployment](ansible-deployment.md).
+
+BlockScout currently requires a full archive node in order to import every state change for every address on the target network. For client specific settings related to a node running parity or geth, please see [this forum post](https://forum.poa.network/t/faq-what-settings-are-required-on-a-parity-or-geth-client/1805).
+
+## Deployment Steps
1. `git clone https://github.com/poanetwork/blockscout`
@@ -27,9 +31,13 @@ Below is the procedure for manual deployment of BlockScout. For automated deploy
7. If not already running, start postgres: `pg_ctl -D /usr/local/var/postgres start`
+ > [!TIP]
+ > To check [postgres status](https://www.postgresql.org/docs/9.6/app-pg-isready.html): `pg_isready`
+
8. Create and migrate database `mix do ecto.create, ecto.migrate`
- *Note:* If you have run previously, drop the previous database
+ > [!NOTE]
+ > If you have run previously, drop the previous database
`mix do ecto.drop, ecto.create, ecto.migrate`
9. Install Node.js dependencies
@@ -41,10 +49,10 @@ Below is the procedure for manual deployment of BlockScout. For automated deploy
10. Enable HTTPS in development. The Phoenix server only runs with HTTPS.
* `cd apps/block_scout_web`
+ * `mix phx.gen.cert blockscout blockscout.local; cd -`
+ * Add blockscout and blockscout.local to your `/etc/hosts`
- * `mix phx.gen.cert blockscout blockscout.local; cd -`
- * Add blockscout and blockscout.local to your `/etc/hosts`
-```
+```bash
127.0.0.1 localhost blockscout blockscout.local
@@ -54,15 +62,21 @@ Below is the procedure for manual deployment of BlockScout. For automated deploy
```
-* If using Chrome, Enable `chrome://flags/#allow-insecure-localhost` .
+> [!NOTE]
+> If using Chrome, Enable `chrome://flags/#allow-insecure-localhost`
+
+11. Set your [environment variables](env-variables.md) as needed.
-11. Set your [environment variables](env-variables.md) as needed. For example:
+CLI Example:
```bash
export COIN=DAI
export NETWORK_ICON=_network_icon.html
export ...
```
+> [!NOTE]
+>The `ETHEREUM_JSONRPC_VARIANT` will vary depending on your client (parity, geth etc). [See this forum post](https://forum.poa.network/t/faq-what-settings-are-required-on-a-parity-or-geth-client/1805) for more information on client settings.
+
12. Return to the root directory and start the Phoenix Server. `mix phx.server`
## Check your instance:
diff --git a/docs/requirements.md b/docs/requirements.md
index 2df13c6936..c64558afbc 100644
--- a/docs/requirements.md
+++ b/docs/requirements.md
@@ -5,7 +5,7 @@
| Dependency | Mac | Linux |
|-------------|-----|-------|
| [Erlang/OTP 21.0.4](https://github.com/erlang/otp) | `brew install erlang` | [Erlang Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L134) |
-| [Elixir 1.8.1](https://elixir-lang.org/) | :point_up: | [Elixir Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L138) |
+| [Elixir 1.9.0](https://elixir-lang.org/) | :point_up: | [Elixir Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L138) |
| [Postgres 10.3](https://www.postgresql.org/) | `brew install postgresql` | [Postgres Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L187) |
| [Node.js 10.x.x](https://nodejs.org/en/) | `brew install node` | [Node.js Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L66) |
| [Automake](https://www.gnu.org/software/automake/) | `brew install automake` | [Automake Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L72) |
diff --git a/mix.exs b/mix.exs
index 47ee0f7a75..c6c74689f9 100644
--- a/mix.exs
+++ b/mix.exs
@@ -6,6 +6,7 @@ defmodule BlockScout.Mixfile do
def project do
[
aliases: aliases(Mix.env()),
+ version: "2.0",
apps_path: "apps",
deps: deps(),
dialyzer: [
@@ -13,7 +14,7 @@ defmodule BlockScout.Mixfile do
plt_add_apps: ~w(ex_unit mix)a,
ignore_warnings: ".dialyzer-ignore"
],
- elixir: "~> 1.8",
+ elixir: "~> 1.9",
preferred_cli_env: [
coveralls: :test,
"coveralls.detail": :test,
@@ -23,7 +24,17 @@ defmodule BlockScout.Mixfile do
dialyzer: :test
],
start_permanent: Mix.env() == :prod,
- test_coverage: [tool: ExCoveralls]
+ test_coverage: [tool: ExCoveralls],
+ releases: [
+ blockscout: [
+ applications: [
+ block_scout_web: :permanent,
+ ethereum_jsonrpc: :permanent,
+ explorer: :permanent,
+ indexer: :permanent
+ ]
+ ]
+ ]
]
end
@@ -61,8 +72,6 @@ defmodule BlockScout.Mixfile do
# and cannot be accessed from applications inside the apps folder
defp deps do
[
- # Release
- {:distillery, "~> 2.0", runtime: false},
# Documentation
{:ex_doc, "~> 0.19.0", only: [:dev]},
# Code coverage
diff --git a/mix.lock b/mix.lock
index 95bc035f89..5ff4c0f8d0 100644
--- a/mix.lock
+++ b/mix.lock
@@ -42,6 +42,7 @@
"ex_doc": {:hex, :ex_doc, "0.19.2", "6f4081ccd9ed081b6dc0bd5af97a41e87f5554de469e7d76025fba535180565f", [:mix], [{:earmark, "~> 1.2", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
"ex_machina": {:hex, :ex_machina, "2.2.2", "d84217a6fb7840ff771d2561b8aa6d74a0d8968e4b10ecc0d7e9890dc8fb1c6a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm"},
"ex_rlp": {:hex, :ex_rlp, "0.5.2", "7f4ce7bd55e543c054ce6d49629b01e9833c3462e3d547952be89865f39f2c58", [:mix], [], "hexpm"},
+ "ex_utils": {:hex, :ex_utils, "0.1.7", "2c133e0bcdc49a858cf8dacf893308ebc05bc5fba501dc3d2935e65365ec0bf3", [:mix], [], "hexpm"},
"exactor": {:hex, :exactor, "2.2.4", "5efb4ddeb2c48d9a1d7c9b465a6fffdd82300eb9618ece5d34c3334d5d7245b1", [:mix], []},
"excoveralls": {:git, "https://github.com/KronicDeth/excoveralls.git", "0a859b68851eeba9b43eba59fbc8f9098299cfe1", [branch: "circle-workflows"]},
"exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, optional: false]}]},
@@ -66,6 +67,7 @@
"makeup_elixir": {:hex, :makeup_elixir, "0.13.0", "be7a477997dcac2e48a9d695ec730b2d22418292675c75aa2d34ba0909dcdeda", [:mix], [{:makeup, "~> 0.8", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
"math": {:hex, :math, "0.3.0", "e14e7291115201cb155a3567e66d196bf5088a6f55b030d598107d7ae934a11c", [:mix], []},
"meck": {:hex, :meck, "0.8.12", "1f7b1a9f5d12c511848fec26bbefd09a21e1432eadb8982d9a8aceb9891a3cf2", [:rebar3], [], "hexpm"},
+ "memento": {:hex, :memento, "0.3.1", "b2909390820550d8b90b68ec96f9e15ff8a45a28b6f97fa4a62ef50e87c2f9d9", [:mix], [], "hexpm"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []},
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"},
@@ -98,6 +100,7 @@
"prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm"},
"prometheus_process_collector": {:hex, :prometheus_process_collector, "1.4.0", "6dbd39e3165b9ef1c94a7a820e9ffe08479f949dcdd431ed4aaea7b250eebfde", [:rebar3], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
"qrcode": {:hex, :qrcode, "0.1.4", "544dc67ba42eed5ebce3d2a691d053387937740561d251f83f0a067917fae2dc", [:mix], [], "hexpm"},
+ "que": {:hex, :que, "0.10.1", "788ed0ec92ed69bdf9cfb29bf41a94ca6355b8d44959bd0669cf706e557ac891", [:mix], [{:ex_utils, "~> 0.1.6", [hex: :ex_utils, repo: "hexpm", optional: false]}, {:memento, "~> 0.3.0", [hex: :memento, repo: "hexpm", optional: false]}], "hexpm"},
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
"set_locale": {:git, "https://github.com/minifast/set_locale.git", "da9ae029642bc0fbd9212c2aaf86c0adca70c084", [branch: "master"]},
"sobelow": {:hex, :sobelow, "0.7.4", "228cc6185b448b63ecc88429b43e864e8dd570e8e09f2d04b3aa71894db1bdbb", [:mix], [], "hexpm"},