diff --git a/CHANGELOG.md b/CHANGELOG.md index 321c3e9774..cc8a46741b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,26 +2,30 @@ ### Features - [#2044](https://github.com/poanetwork/blockscout/pull/2044) - New network selector. -- [#1963](https://github.com/poanetwork/blockscout/pull/1963), [#1959](https://github.com/poanetwork/blockscout/pull/1959), [#1948](https://github.com/poanetwork/blockscout/pull/1948), [#1936](https://github.com/poanetwork/blockscout/pull/1936), [#1925](https://github.com/poanetwork/blockscout/pull/1925), [#1922](https://github.com/poanetwork/blockscout/pull/1922), [#1903](https://github.com/poanetwork/blockscout/pull/1903), [#1874](https://github.com/poanetwork/blockscout/pull/1874), [#1895](https://github.com/poanetwork/blockscout/pull/1895), [#2031](https://github.com/poanetwork/blockscout/pull/2031) - added new themes and logos for poa, eth, rinkeby, goerli, ropsten, kovan, sokol, xdai, etc, rsk +- [#1963](https://github.com/poanetwork/blockscout/pull/1963), [#1959](https://github.com/poanetwork/blockscout/pull/1959), [#1948](https://github.com/poanetwork/blockscout/pull/1948), [#1936](https://github.com/poanetwork/blockscout/pull/1936), [#1925](https://github.com/poanetwork/blockscout/pull/1925), [#1922](https://github.com/poanetwork/blockscout/pull/1922), [#1903](https://github.com/poanetwork/blockscout/pull/1903), [#1874](https://github.com/poanetwork/blockscout/pull/1874), [#1895](https://github.com/poanetwork/blockscout/pull/1895), [#2031](https://github.com/poanetwork/blockscout/pull/2031), [#2073](https://github.com/poanetwork/blockscout/pull/2073), [#2074](https://github.com/poanetwork/blockscout/pull/2074), - added new themes and logos for poa, eth, rinkeby, goerli, ropsten, kovan, sokol, xdai, etc, rsk and default theme - [#2010](https://github.com/poanetwork/blockscout/pull/2010) - added "block not found" and "tx not found pages" - [#1928](https://github.com/poanetwork/blockscout/pull/1928) - pagination styles were updated - [#1940](https://github.com/poanetwork/blockscout/pull/1940) - qr modal button and background issue - [#1907](https://github.com/poanetwork/blockscout/pull/1907) - dropdown color bug fix (lukso theme) and tooltip color bug fix -- [#1857](https://github.com/poanetwork/blockscout/pull/1857) - Re-implement Geth JS internal transaction tracer in Elixir - [#1859](https://github.com/poanetwork/blockscout/pull/1859) - feat: show raw transaction traces - [#1941](https://github.com/poanetwork/blockscout/pull/1941) - feat: add on demand fetching and stale attr to rpc - [#1957](https://github.com/poanetwork/blockscout/pull/1957) - Calculate stakes ratio before insert pools - [#1956](https://github.com/poanetwork/blockscout/pull/1956) - add logs tab to address - [#1952](https://github.com/poanetwork/blockscout/pull/1952) - feat: exclude empty contracts by default -- [#1989](https://github.com/poanetwork/blockscout/pull/1989) - fix: consolidate address w/ balance one at a time - [#1954](https://github.com/poanetwork/blockscout/pull/1954) - feat: use creation init on self destruct +- [#2036](https://github.com/poanetwork/blockscout/pull/2036) - New tables for staking pools and delegators - [#1974](https://github.com/poanetwork/blockscout/pull/1974) - feat: previous page button logic - [#1999](https://github.com/poanetwork/blockscout/pull/1999) - load data async on addresses page -- [#2002](https://github.com/poanetwork/blockscout/pull/2002) - Get estimated count of blocks when cache is empty - [#1807](https://github.com/poanetwork/blockscout/pull/1807) - New theming capabilites. -- [#2040](https://github.com/poanetwork/blockscout/pull/2040) - Verification links to other explorers for ETH +- [#2040](https://github.com/poanetwork/blockscout/pull/2040) - Verification links to other explorers for ETH +- [#2037](https://github.com/poanetwork/blockscout/pull/2037) - add address logs search functionality +- [#2012](https://github.com/poanetwork/blockscout/pull/2012) - make all pages pagination async ### Fixes +- [#2077](https://github.com/poanetwork/blockscout/pull/2077) - ui issues +- [#2072](https://github.com/poanetwork/blockscout/pull/2072) - Fixed checkmarks not showing correctly in tabs. +- [#2066](https://github.com/poanetwork/blockscout/pull/2066) - fixed length of logs search input +- [#2056](https://github.com/poanetwork/blockscout/pull/2056) - log search form styles added - [#2043](https://github.com/poanetwork/blockscout/pull/2043) - Fixed modal dialog width for 'verify other explorers' - [#2025](https://github.com/poanetwork/blockscout/pull/2025) - Added a new color to display transactions' errors. - [#2033](https://github.com/poanetwork/blockscout/pull/2033) - Header nav. dropdown active element color issue @@ -30,7 +34,6 @@ - [#1944](https://github.com/poanetwork/blockscout/pull/1944) - fixed styles for token's dropdown. - [#1926](https://github.com/poanetwork/blockscout/pull/1926) - status label alignment - [#1849](https://github.com/poanetwork/blockscout/pull/1849) - Improve chains menu -- [#1869](https://github.com/poanetwork/blockscout/pull/1869) - Fix output and gas extraction in JS tracer for Geth - [#1868](https://github.com/poanetwork/blockscout/pull/1868) - fix: logs list endpoint performance - [#1822](https://github.com/poanetwork/blockscout/pull/1822) - Fix style breaks in decompiled contract code view - [#1885](https://github.com/poanetwork/blockscout/pull/1885) - highlight reserved words in decompiled code @@ -40,11 +43,17 @@ - [#1915](https://github.com/poanetwork/blockscout/pull/1915) - fallback to 2 latest evm versions - [#1937](https://github.com/poanetwork/blockscout/pull/1937) - Check the presence of overlap[i] object before retrieving properties from it - [#1960](https://github.com/poanetwork/blockscout/pull/1960) - do not remove bold text in decompiled contacts -- [#1992](https://github.com/poanetwork/blockscout/pull/1992) - fix: support https for wobserver polling - [#1966](https://github.com/poanetwork/blockscout/pull/1966) - fix: add fields for contract filter performance - [#2017](https://github.com/poanetwork/blockscout/pull/2017) - fix: fix to/from filters on tx list pages - [#2008](https://github.com/poanetwork/blockscout/pull/2008) - add new function clause for xDai network beneficiaries - [#2009](https://github.com/poanetwork/blockscout/pull/2009) - addresses page improvements +- [#2027](https://github.com/poanetwork/blockscout/pull/2027) - fix: `BlocksTransactionsMismatch` ignoring blocks without transactions +- [#2062](https://github.com/poanetwork/blockscout/pull/2062) - fix: uniq by hash, instead of transaction +- [#2052](https://github.com/poanetwork/blockscout/pull/2052) - allow bytes32 for name and symbol +- [#2047](https://github.com/poanetwork/blockscout/pull/2047) - fix: show creating internal transactions +- [#2014](https://github.com/poanetwork/blockscout/pull/2014) - fix: use better queries for listLogs endpoint +- [#2027](https://github.com/poanetwork/blockscout/pull/2027) - fix: `BlocksTransactionsMismatch` ignoring blocks without transactions +- [#2070](https://github.com/poanetwork/blockscout/pull/2070) - reduce `max_concurrency` of `BlocksTransactionsMismatch` fetcher ### Chore @@ -55,11 +64,26 @@ - [#1988](https://github.com/poanetwork/blockscout/pull/1988) - Fix wrong parity tasks names in Circle CI - [#2000](https://github.com/poanetwork/blockscout/pull/2000) - docker/Makefile: always set a container name - [#2018](https://github.com/poanetwork/blockscout/pull/2018) - Use PORT env variable in dev config +- [#2055](https://github.com/poanetwork/blockscout/pull/2055) - Increase timeout for geth indexers +- [#2069](https://github.com/poanetwork/blockscout/pull/2069) - Docsify integration: static docs page generation -## 1.3.14-beta +## 1.3.15-beta ### Features +- [#1857](https://github.com/poanetwork/blockscout/pull/1857) - Re-implement Geth JS internal transaction tracer in Elixir +- [#1989](https://github.com/poanetwork/blockscout/pull/1989) - fix: consolidate address w/ balance one at a time +- [#2002](https://github.com/poanetwork/blockscout/pull/2002) - Get estimated count of blocks when cache is empty + +### Fixes + +- [#1869](https://github.com/poanetwork/blockscout/pull/1869) - Fix output and gas extraction in JS tracer for Geth +- [#1992](https://github.com/poanetwork/blockscout/pull/1992) - fix: support https for wobserver polling +- [#2027](https://github.com/poanetwork/blockscout/pull/2027) - fix: `BlocksTransactionsMismatch` ignoring blocks without transactions + + +## 1.3.14-beta + - [#1812](https://github.com/poanetwork/blockscout/pull/1812) - add pagination to addresses page - [#1920](https://github.com/poanetwork/blockscout/pull/1920) - fix: remove source code fields from list endpoint - [#1876](https://github.com/poanetwork/blockscout/pull/1876) - async calculate a count of blocks diff --git a/apps/block_scout_web/assets/css/app.scss b/apps/block_scout_web/assets/css/app.scss index a95a318189..3b20f299b1 100644 --- a/apps/block_scout_web/assets/css/app.scss +++ b/apps/block_scout_web/assets/css/app.scss @@ -122,6 +122,7 @@ $fa-font-path: "~@fortawesome/fontawesome-free/webfonts"; @import "components/alerts"; @import "components/verify_other_explorers"; @import "components/errors"; +@import "components/log-search"; @import "components/radio"; @import "components/network-selector"; diff --git a/apps/block_scout_web/assets/css/components/_btn_dropdown_line.scss b/apps/block_scout_web/assets/css/components/_btn_dropdown_line.scss index 8f89b0700c..f076f14dff 100644 --- a/apps/block_scout_web/assets/css/components/_btn_dropdown_line.scss +++ b/apps/block_scout_web/assets/css/components/_btn_dropdown_line.scss @@ -3,4 +3,11 @@ $btn-dropdown-line-color: $primary !default; .btn-dropdown-line { @include btn-line($btn-dropdown-line-bg, $btn-dropdown-line-color); + outline: none !important; + color: #333; + border-color: #e2e5ec; + &:hover { + background-color: transparent; + color: #333; + } } \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/components/_card.scss b/apps/block_scout_web/assets/css/components/_card.scss index 6e1b6a9725..8b4f1a423c 100644 --- a/apps/block_scout_web/assets/css/components/_card.scss +++ b/apps/block_scout_web/assets/css/components/_card.scss @@ -5,6 +5,8 @@ $card-horizontal-padding: 30px; $card-vertical-padding: 30px; $card-background-1: $primary !default; $card-background-1-text-color: #fff !default; +$card-tab-icon-color: #20b760 !default; +$card-tab-icon-color-active: #20b760 !default; .card { background-color: $card-background-color; @@ -158,40 +160,52 @@ $card-background-1-text-color: #fff !default; justify-content: flex-start; overflow: hidden; - @include media-breakpoint-down(sm) { + @include media-breakpoint-down(md) { flex-direction: column; } } .card-tab { + align-items: center; background-color: $card-background-color; color: #333; cursor: pointer; + display: flex; font-size: 14px; font-weight: normal; height: 70px; - line-height: 70px; - padding: 0 30px; + padding: 0 25px; text-align: center; &:hover { - text-decoration: underline; + background-color: rgba($card-tab-active, .1); + color: $card-tab-active; + text-decoration: none; } - @include media-breakpoint-down(sm) { + @include media-breakpoint-down(md) { display: none; width: 100%; } + .fa-check-circle { + color: $card-tab-icon-color; + margin-left: 6px; + } + &.active { background-color: $card-tab-active; color: #fff; cursor: default; text-decoration: none; - @include media-breakpoint-down(sm) { + .fa-check-circle { + color: $card-tab-icon-color-active; + } + + @include media-breakpoint-down(md) { cursor: pointer; - display: block; + display: flex; order: -1; &::after { diff --git a/apps/block_scout_web/assets/css/components/_dropdown.scss b/apps/block_scout_web/assets/css/components/_dropdown.scss index c93231edbd..998f779ec5 100644 --- a/apps/block_scout_web/assets/css/components/_dropdown.scss +++ b/apps/block_scout_web/assets/css/components/_dropdown.scss @@ -1,6 +1,6 @@ // These styles extend the default Bootstrap styles .dropdown-menu { - border-radius: 8px !important; + border-radius: 0 0 8px 8px !important; border: none; box-shadow: $box-shadow; padding: 0; @@ -32,8 +32,8 @@ } &:first-child { - border-top-left-radius: 8px; - border-top-right-radius: 8px; + border-top-left-radius: 0; + border-top-right-radius: 0; } &:last-child { @@ -46,7 +46,7 @@ &:hover, &:active { padding-left: 10px; - background-color: transparent; + background-color: #fff !important; cursor: default; color: #333; font-weight: 700; diff --git a/apps/block_scout_web/assets/css/components/_log-search.scss b/apps/block_scout_web/assets/css/components/_log-search.scss new file mode 100644 index 0000000000..a31de73262 --- /dev/null +++ b/apps/block_scout_web/assets/css/components/_log-search.scss @@ -0,0 +1,64 @@ +.logs-topbar { + padding-bottom: 30px; + @media (min-width: 600px) { + display: flex; + justify-content: space-between; + } + .pagination-container.position-top { + padding-top: 0 !important; + } +} + +.logs-search { + display: flex; + position: relative; + @media (max-width: 599px) { + margin-bottom: 30px; + } +} + +.logs-search-input, .logs-search-btn, .logs-search-btn-cancel { + height: 24px; + background-color: #f5f6fa; + border: 1px solid #f5f6fa; + color: #333; + border-radius: 2px; + outline: none; + font-family: Nunito, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + font-size: 12px; + font-weight: 600; +} + +.logs-search-input { + padding-left: 6px; + display: inline-flex; + flex-grow: 2; + min-width: 160px; + &::placeholder { + color: #a3a9b5; + } +} + +.logs-search-btn { + margin-left: 6px; + color: #a3a9b5; + transition: .1s ease-in; + cursor: pointer; + &:hover { + background-color: $primary; + color: #fff; + border-color: $primary; + } +} + +.logs-search-btn-cancel { + color: #a3a9b5; + cursor: pointer; + transition: .1s ease-in; + position: absolute; + top: 0; + left: 136px; + &:hover { + color: #333; + } +} \ No newline at end of file diff --git a/apps/block_scout_web/assets/css/components/_modal.scss b/apps/block_scout_web/assets/css/components/_modal.scss index ab2aaab679..4c3cf7f8cf 100644 --- a/apps/block_scout_web/assets/css/components/_modal.scss +++ b/apps/block_scout_web/assets/css/components/_modal.scss @@ -17,12 +17,17 @@ $modal-gray-background: #f6f7f9 !default; padding: #{$modal-vertical-padding} #{$modal-horizontal-padding}; } -.close.close-modal { +.close.close-modal{ left: auto; opacity: 1; position: absolute; right: -35px; top: -35px; + outline: none !important; +} + +.close { + outline: none !important; } .modal-body { 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 515f5eafaf..c2232fd0a8 100644 --- a/apps/block_scout_web/assets/css/theme/_neutral_variables.scss +++ b/apps/block_scout_web/assets/css/theme/_neutral_variables.scss @@ -1,8 +1,71 @@ -$primary: #262d62; -$secondary: #687bf6; -$tertiary: #687bf6; +// $primary: #262d62; +// $secondary: #687bf6; +// $tertiary: #687bf6; $dashboard-line-color-price: #8286a9 !default; $base-border-color: #e2e5ec !default; -$common-container-margin: 50px !default; \ No newline at end of file +$common-container-margin: 50px !default; + +// general +$primary: #5c34a2; +$secondary: #87e1a9; +$tertiary: #bf9cff; +$additional-font: #fff; + +// footer +$footer-background-color: #3c226a; +$footer-title-color: #fff; +$footer-text-color: #bda6e7; +$footer-item-disc-color: $secondary; +.footer-logo { filter: brightness(0) invert(1); } + +// dashboard +$dashboard-line-color-price: $tertiary; // price left border + +$dashboard-banner-chart-legend-value-color: $additional-font; // chart labels + +$dashboard-stats-item-value-color: $additional-font; // stat values + +$dashboard-stats-item-border-color: $secondary; // stat border + +$dashboard-banner-gradient-start: $primary; // gradient begin + +$dashboard-banner-gradient-end: lighten($primary, 5); // gradient end + +$dashboard-banner-network-plain-container-background-color: #865bd4; // stats bg + + +// navigation +.navbar { box-shadow: 0px 0px 30px 0px rgba(21, 53, 80, 0.12); } // header shadow +$header-icon-border-color-hover: $primary; // top border on hover +$header-icon-color-hover: $primary; // nav icon on hover +.dropdown-item:hover, .dropdown-item:focus { background-color: $primary !important; } // dropdown item on hover + +// buttons +$btn-line-bg: #fff; // button bg +$btn-line-color: $primary; // button border and font color && hover bg color +$btn-copy-color: $primary; // btn copy +$btn-qr-color: $primary; // btn qr-code + +//links & tile +.tile a { color: $primary !important; } // links color for badges +.tile-type-block { + border-left: 4px solid $primary; +} // tab active bg + +// card +$card-background-1: $primary; +$card-tab-active: $primary; + +.footer { + .tooltip { + .tooltip-inner { + background-color: darken($footer-background-color, 10) !important; + } + .arrow::before { + border-top-color: darken($footer-background-color, 10) !important; + border-bottom-color: darken($footer-background-color, 10) !important; + } + } +} diff --git a/apps/block_scout_web/assets/js/app.js b/apps/block_scout_web/assets/js/app.js index f37659ccba..58c852821e 100644 --- a/apps/block_scout_web/assets/js/app.js +++ b/apps/block_scout_web/assets/js/app.js @@ -23,6 +23,7 @@ import './locale' import './pages/address' import './pages/address/coin_balances' import './pages/address/transactions' +import './pages/address/logs' import './pages/address/validations' import './pages/address/internal_transactions' import './pages/blocks' 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 e22f6e399d..3a3eb2fd6c 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 @@ -105,7 +105,9 @@ export function asyncReducer (state = asyncInitialState, action) { state.pagesStack.push(window.location.href.split('?')[0]) } - state.pagesStack.push(state.nextPagePath) + if (state.pagesStack[state.pagesStack.length - 1] !== state.nextPagePath) { + state.pagesStack.push(state.nextPagePath) + } return Object.assign({}, state, { beyondPageOne: true }) } @@ -279,12 +281,14 @@ function firstPageLoad (store) { event.preventDefault() loadItemsNext() store.dispatch({type: 'NAVIGATE_TO_OLDER'}) + event.stopImmediatePropagation() }) $element.on('click', '[data-prev-page-button]', (event) => { event.preventDefault() loadItemsPrev() store.dispatch({type: 'NAVIGATE_TO_NEWER'}) + event.stopImmediatePropagation() }) } diff --git a/apps/block_scout_web/assets/js/lib/card_tabs.js b/apps/block_scout_web/assets/js/lib/card_tabs.js index 49b82ad04f..24b08e641e 100644 --- a/apps/block_scout_web/assets/js/lib/card_tabs.js +++ b/apps/block_scout_web/assets/js/lib/card_tabs.js @@ -17,7 +17,7 @@ $(function () { const siblings = $(this).siblings() if (siblings.is(':hidden')) { - siblings.show() + siblings.css({ 'display': 'flex' }) } else { siblings.hide() } diff --git a/apps/block_scout_web/assets/js/pages/address/logs.js b/apps/block_scout_web/assets/js/pages/address/logs.js new file mode 100644 index 0000000000..ffe8ad5676 --- /dev/null +++ b/apps/block_scout_web/assets/js/pages/address/logs.js @@ -0,0 +1,75 @@ +import $ from 'jquery' +import _ from 'lodash' +import humps from 'humps' +import { connectElements } from '../../lib/redux_helpers.js' +import { createAsyncLoadStore } from '../../lib/async_listing_load' + +export const initialState = { + addressHash: null, + isSearch: false +} + +export function reducer (state, action) { + switch (action.type) { + case 'PAGE_LOAD': + case 'ELEMENTS_LOAD': { + return Object.assign({}, state, _.omit(action, 'type')) + } + case 'START_SEARCH': { + return Object.assign({}, state, {pagesStack: [], isSearch: true}) + } + default: + return state + } +} + +const elements = { + '[data-search-field]': { + render ($el, state) { + return $el + } + }, + '[data-search-button]': { + render ($el, state) { + return $el + } + }, + '[data-cancel-search-button]': { + render ($el, state) { + if (!state.isSearch) { + return $el.hide() + } + + return $el.show() + } + } +} + +if ($('[data-page="address-logs"]').length) { + const store = createAsyncLoadStore(reducer, initialState, 'dataset.identifierLog') + const addressHash = $('[data-page="address-details"]')[0].dataset.pageAddressHash + const $element = $('[data-async-listing]') + + connectElements({ store, elements }) + + store.dispatch({ + type: 'PAGE_LOAD', + addressHash: addressHash}) + + $element.on('click', '[data-search-button]', (event) => { + store.dispatch({ + type: 'START_SEARCH', + addressHash: addressHash}) + var topic = $('[data-search-field]').val() + var path = '/search_logs?topic=' + topic + '&address_id=' + store.getState().addressHash + store.dispatch({type: 'START_REQUEST'}) + $.getJSON(path, {type: 'JSON'}) + .done(response => store.dispatch(Object.assign({type: 'ITEMS_FETCHED'}, humps.camelizeKeys(response)))) + .fail(() => store.dispatch({type: 'REQUEST_ERROR'})) + .always(() => store.dispatch({type: 'FINISH_REQUEST'})) + }) + + $element.on('click', '[data-cancel-search-button]', (event) => { + window.location.replace(window.location.href.split('?')[0]) + }) +} diff --git a/apps/block_scout_web/assets/static/images/blockscout_logo.svg b/apps/block_scout_web/assets/static/images/blockscout_logo.svg index 6e9ac73aae..2abda8ba4b 100644 --- a/apps/block_scout_web/assets/static/images/blockscout_logo.svg +++ b/apps/block_scout_web/assets/static/images/blockscout_logo.svg @@ -1,32 +1,65 @@ - - - - - Artboard 1 - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 56be858452..db76b9d447 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 @@ -71,4 +71,48 @@ defmodule BlockScoutWeb.AddressLogsController do not_found(conn) end end + + 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 + 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) + + {results, next_page} = split_list_by_page(logs_plus_one) + + next_page_url = + case next_page_params(next_page, results, params) do + nil -> + nil + + next_page_params -> + address_logs_path(conn, :index, address, Map.delete(next_page_params, "type")) + end + + items = + results + |> Enum.map(fn log -> + View.render_to_string( + AddressLogsView, + "_logs.html", + log: log, + conn: conn + ) + end) + + json( + conn, + %{ + items: items, + next_page_path: next_page_url + } + ) + else + _ -> + not_found(conn) + 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 e318a74e78..7c24b8d3b9 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,18 +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, - necessity_by_association: %{ - :block => :optional, - [created_contract_address: :names] => :optional, - [from_address: :names] => :optional, - [to_address: :names] => :optional, - [to_address: :smart_contract] => :optional, - :token_transfers => :optional - } - ) do + {:ok, transaction} <- Chain.hash_to_transaction(hash) do full_options = Keyword.merge( [ 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 b2f639d763..aa19725811 100644 --- a/apps/block_scout_web/lib/block_scout_web/router.ex +++ b/apps/block_scout_web/lib/block_scout_web/router.ex @@ -238,6 +238,8 @@ defmodule BlockScoutWeb.Router do get("/search", ChainController, :search) + get("/search_logs", AddressLogsController, :search_logs) + get("/token_autocomplete", ChainController, :token_autocomplete) get("/chain_blocks", ChainController, :chain_blocks, as: :chain_blocks) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex index 0c2d339c38..339b2d67e7 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address/_tabs.html.eex @@ -41,7 +41,7 @@ class: "card-tab #{tab_status("contracts", @conn.request_path)}") do %> <%= gettext("Code") %> <%= if smart_contract_verified?(@address) do %> - + <% end %> <% end %> <% end %> @@ -50,7 +50,7 @@ to: address_decompiled_contract_path(@conn, :index, @address.hash), class: "card-tab #{tab_status("decompiled_contracts", @conn.request_path)}") do %> <%= gettext("Decompiled code") %> - + <% end %> <% end %> <%= if smart_contract_with_read_only_functions?(@address) do %> 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 e75634ee83..76387a9662 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 @@ -121,7 +121,9 @@ <% end %> + 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 9d85567233..1b5cb5c4fd 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 @@ -1,4 +1,4 @@ -
+
">
<%= gettext "Transaction" %>
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 9053158442..d232400431 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 @@ -1,12 +1,21 @@
<%= render BlockScoutWeb.AddressView, "overview.html", assigns %> +
<%= render BlockScoutWeb.AddressView, "_tabs.html", assigns %>

<%= gettext "Logs" %>

+
+ + - <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %> + <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "top", 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/layout/_footer.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/_footer.html.eex index b03b9f964f..ed22191b1f 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/_footer.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/_footer.html.eex @@ -16,13 +16,13 @@
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex index c323998e3a..7c22762e49 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex @@ -71,18 +71,18 @@
<%= gettext "Block Confirmations" %>
- <%= confirmations(@transaction, block_height: @block_height) %> + <%= confirmations(@transaction, block_height: @block_height) %>
<%= gettext "Nonce" %>
-
<%= @transaction.nonce %>
+
<%= @transaction.nonce %>
<%= gettext "TX Fee" %>
-
+
<%= formatted_fee(@transaction, denomination: :ether) %> <%= if !empty_exchange_rate?(@exchange_rate) do %> diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index f75e678e2a..2caf439866 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -112,7 +112,6 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:27 #: lib/block_scout_web/templates/address_transaction/index.html.eex:23 -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:20 #: lib/block_scout_web/views/address_internal_transaction_view.ex:8 #: lib/block_scout_web/views/address_transaction_view.ex:8 msgid "All" @@ -208,8 +207,8 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37 -#: lib/block_scout_web/templates/address/overview.html.eex:142 -#: lib/block_scout_web/templates/address/overview.html.eex:150 +#: lib/block_scout_web/templates/address/overview.html.eex:144 +#: lib/block_scout_web/templates/address/overview.html.eex:152 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:106 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:114 msgid "Close" @@ -501,7 +500,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:26 -#: lib/block_scout_web/templates/address_logs/index.html.eex:7 +#: lib/block_scout_web/templates/address_logs/index.html.eex:8 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:17 #: lib/block_scout_web/templates/transaction_log/index.html.eex:8 #: lib/block_scout_web/views/address_view.ex:314 @@ -635,7 +634,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/overview.html.eex:33 -#: lib/block_scout_web/templates/address/overview.html.eex:141 +#: lib/block_scout_web/templates/address/overview.html.eex:143 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:35 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:105 msgid "QR Code" @@ -675,8 +674,9 @@ msgid "Responses" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:97 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:114 +#: lib/block_scout_web/templates/address_logs/index.html.eex:14 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:111 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:128 msgid "Search" msgstr "" @@ -1218,7 +1218,7 @@ 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 -#: lib/block_scout_web/templates/address_logs/index.html.eex:12 +#: lib/block_scout_web/templates/address_logs/index.html.eex:21 #: lib/block_scout_web/templates/address_token/index.html.eex:13 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:20 #: lib/block_scout_web/templates/address_transaction/index.html.eex:57 @@ -1541,8 +1541,8 @@ msgid "Error: Could not determine contract creator." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:91 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:95 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:105 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:109 msgid "Search by address, token symbol name, transaction hash, or block number" msgstr "" @@ -1683,7 +1683,7 @@ msgid "Displaying the init data provided of the creating transaction." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_logs/index.html.eex:17 +#: lib/block_scout_web/templates/address_logs/index.html.eex:26 msgid "There are no logs for this address." msgstr "" @@ -1723,36 +1723,6 @@ msgid "There are no token transfers for this transaction" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:10 -msgid "Change Network" -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:23 -msgid "Favorites" -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:11 -msgid "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore." -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:21 -msgid "Mainnet" -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:17 -msgid "Search network" -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:51 -msgid "Show More Networks" -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:22 -msgid "Testnet" +#: lib/block_scout_web/templates/address_logs/index.html.eex:12 +msgid "Topic" 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 4794851acc..6bbaa0f4b7 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 @@ -112,7 +112,6 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:27 #: lib/block_scout_web/templates/address_transaction/index.html.eex:23 -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:20 #: lib/block_scout_web/views/address_internal_transaction_view.ex:8 #: lib/block_scout_web/views/address_transaction_view.ex:8 msgid "All" @@ -146,7 +145,7 @@ msgid "Block %{block_number} - %{subnetwork} Explorer" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:73 +#: lib/block_scout_web/templates/transaction/overview.html.eex:72 msgid "Block Confirmations" msgstr "" @@ -161,7 +160,7 @@ msgid "Block Mined, awaiting import..." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:59 +#: lib/block_scout_web/templates/transaction/overview.html.eex:58 msgid "Block Number" msgstr "" @@ -191,7 +190,7 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:32 #: lib/block_scout_web/templates/address/overview.html.eex:95 #: lib/block_scout_web/templates/address_validation/index.html.eex:13 -#: lib/block_scout_web/views/address_view.ex:308 +#: lib/block_scout_web/views/address_view.ex:313 msgid "Blocks Validated" msgstr "" @@ -208,8 +207,8 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37 -#: lib/block_scout_web/templates/address/overview.html.eex:141 -#: lib/block_scout_web/templates/address/overview.html.eex:149 +#: lib/block_scout_web/templates/address/overview.html.eex:144 +#: lib/block_scout_web/templates/address/overview.html.eex:152 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:106 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:114 msgid "Close" @@ -219,7 +218,7 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:42 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:165 #: lib/block_scout_web/templates/api_docs/_action_tile.html.eex:187 -#: lib/block_scout_web/views/address_view.ex:304 +#: lib/block_scout_web/views/address_view.ex:309 msgid "Code" msgstr "" @@ -383,7 +382,7 @@ msgstr "" #: lib/block_scout_web/templates/layout/app.html.eex:55 #: 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:210 +#: lib/block_scout_web/templates/transaction/overview.html.eex:209 #: lib/block_scout_web/views/wei_helpers.ex:72 msgid "Ether" msgstr "POA" @@ -453,7 +452,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:39 -#: lib/block_scout_web/templates/transaction/_tile.html.eex:76 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:74 msgid "IN" msgstr "" @@ -477,7 +476,7 @@ msgstr "" #: lib/block_scout_web/templates/address_internal_transaction/index.html.eex:19 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:11 #: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6 -#: lib/block_scout_web/views/address_view.ex:303 +#: lib/block_scout_web/views/address_view.ex:308 #: lib/block_scout_web/views/transaction_view.ex:339 msgid "Internal Transactions" msgstr "" @@ -495,16 +494,16 @@ msgid "Less than" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:238 +#: lib/block_scout_web/templates/transaction/overview.html.eex:237 msgid "Limit" msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:26 -#: lib/block_scout_web/templates/address_logs/index.html.eex:7 +#: lib/block_scout_web/templates/address_logs/index.html.eex:8 #: lib/block_scout_web/templates/transaction/_tabs.html.eex:17 #: lib/block_scout_web/templates/transaction_log/index.html.eex:8 -#: lib/block_scout_web/views/address_view.ex:309 +#: lib/block_scout_web/views/address_view.ex:314 #: lib/block_scout_web/views/transaction_view.ex:340 msgid "Logs" msgstr "" @@ -575,13 +574,13 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/block/overview.html.eex:73 -#: lib/block_scout_web/templates/transaction/overview.html.eex:80 +#: lib/block_scout_web/templates/transaction/overview.html.eex:79 msgid "Nonce" msgstr "" #, elixir-format #: lib/block_scout_web/templates/internal_transaction/_tile.html.eex:37 -#: lib/block_scout_web/templates/transaction/_tile.html.eex:72 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:70 msgid "OUT" msgstr "" @@ -635,7 +634,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/overview.html.eex:33 -#: lib/block_scout_web/templates/address/overview.html.eex:140 +#: lib/block_scout_web/templates/address/overview.html.eex:143 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:35 #: lib/block_scout_web/templates/tokens/overview/_details.html.eex:105 msgid "QR Code" @@ -649,7 +648,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:58 #: lib/block_scout_web/templates/tokens/overview/_tabs.html.eex:25 -#: lib/block_scout_web/views/address_view.ex:306 +#: lib/block_scout_web/views/address_view.ex:311 #: lib/block_scout_web/views/tokens/overview_view.ex:37 msgid "Read Contract" msgstr "" @@ -675,8 +674,9 @@ msgid "Responses" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:97 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:114 +#: lib/block_scout_web/templates/address_logs/index.html.eex:14 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:111 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:128 msgid "Search" msgstr "" @@ -706,7 +706,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/transaction/_pending_tile.html.eex:21 #: lib/block_scout_web/templates/transaction/_tile.html.eex:34 -#: lib/block_scout_web/templates/transaction/overview.html.eex:85 +#: lib/block_scout_web/templates/transaction/overview.html.eex:84 msgid "TX Fee" msgstr "" @@ -803,8 +803,8 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex:5 -#: lib/block_scout_web/templates/transaction/overview.html.eex:180 -#: lib/block_scout_web/templates/transaction/overview.html.eex:194 +#: lib/block_scout_web/templates/transaction/overview.html.eex:179 +#: lib/block_scout_web/templates/transaction/overview.html.eex:193 #: lib/block_scout_web/templates/transaction_token_transfer/_token_transfer.html.eex:4 #: lib/block_scout_web/views/transaction_view.ex:284 msgid "Token Transfer" @@ -824,7 +824,7 @@ msgstr "" #: lib/block_scout_web/templates/address/_tabs.html.eex:8 #: lib/block_scout_web/templates/address_token/index.html.eex:8 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:9 -#: lib/block_scout_web/views/address_view.ex:301 +#: lib/block_scout_web/views/address_view.ex:306 msgid "Tokens" msgstr "" @@ -882,7 +882,7 @@ msgstr "" #: 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/views/address_view.ex:302 +#: lib/block_scout_web/views/address_view.ex:307 msgid "Transactions" msgstr "" @@ -918,7 +918,7 @@ msgid "Unique Token" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:232 +#: lib/block_scout_web/templates/transaction/overview.html.eex:231 msgid "Used" msgstr "" @@ -938,7 +938,7 @@ msgid "Validations" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:210 +#: lib/block_scout_web/templates/transaction/overview.html.eex:209 msgid "Value" msgstr "" @@ -959,12 +959,12 @@ msgid "View Contract" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/_tile.html.eex:56 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:55 msgid "View Less Transfers" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/_tile.html.eex:55 +#: lib/block_scout_web/templates/transaction/_tile.html.eex:54 msgid "View More Transfers" msgstr "" @@ -1102,7 +1102,7 @@ msgid "This API is provided for developers transitioning their applications from msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:110 +#: lib/block_scout_web/templates/transaction/overview.html.eex:109 msgid "Raw Input" msgstr "" @@ -1218,7 +1218,7 @@ 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 -#: lib/block_scout_web/templates/address_logs/index.html.eex:12 +#: lib/block_scout_web/templates/address_logs/index.html.eex:21 #: lib/block_scout_web/templates/address_token/index.html.eex:13 #: lib/block_scout_web/templates/address_token_transfer/index.html.eex:20 #: lib/block_scout_web/templates/address_transaction/index.html.eex:57 @@ -1258,7 +1258,7 @@ msgstr "" #, elixir-format #: lib/block_scout_web/templates/address/_tabs.html.eex:20 -#: lib/block_scout_web/views/address_view.ex:307 +#: lib/block_scout_web/views/address_view.ex:312 msgid "Coin Balance History" msgstr "" @@ -1500,18 +1500,18 @@ msgid "Transactions Sent" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:102 +#: lib/block_scout_web/templates/transaction/overview.html.eex:101 msgid "Transaction Speed" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:116 -#: lib/block_scout_web/templates/transaction/overview.html.eex:120 +#: lib/block_scout_web/templates/transaction/overview.html.eex:115 +#: lib/block_scout_web/templates/transaction/overview.html.eex:119 msgid "Hex (Default)" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:123 +#: lib/block_scout_web/templates/transaction/overview.html.eex:122 msgid "UTF-8" msgstr "" @@ -1541,8 +1541,8 @@ msgid "Error: Could not determine contract creator." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_topnav.html.eex:91 -#: lib/block_scout_web/templates/layout/_topnav.html.eex:95 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:105 +#: lib/block_scout_web/templates/layout/_topnav.html.eex:109 msgid "Search by address, token symbol name, transaction hash, or block number" msgstr "" @@ -1562,7 +1562,7 @@ msgid "Copy Decompiled Contract Code" msgstr "" #, elixir-format -#: lib/block_scout_web/views/address_view.ex:305 +#: lib/block_scout_web/views/address_view.ex:310 msgid "Decompiled Code" msgstr "" @@ -1587,12 +1587,12 @@ msgid "Optimization runs" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:180 +#: lib/block_scout_web/templates/transaction/overview.html.eex:179 msgid "ERC-20" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:194 +#: lib/block_scout_web/templates/transaction/overview.html.eex:193 msgid "ERC-721" msgstr "" @@ -1612,7 +1612,7 @@ msgid "View All Transactions" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/transaction/overview.html.eex:228 +#: lib/block_scout_web/templates/transaction/overview.html.eex:227 msgid "Gas" msgstr "" @@ -1683,7 +1683,7 @@ msgid "Displaying the init data provided of the creating transaction." msgstr "" #, elixir-format -#: lib/block_scout_web/templates/address_logs/index.html.eex:17 +#: lib/block_scout_web/templates/address_logs/index.html.eex:26 msgid "There are no logs for this address." msgstr "" @@ -1723,36 +1723,6 @@ msgid "There are no token transfers for this transaction" msgstr "" #, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:10 -msgid "Change Network" -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:23 -msgid "Favorites" -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:11 -msgid "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore." -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:21 -msgid "Mainnet" -msgstr "" - -#, elixir-format, fuzzy -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:17 -msgid "Search network" -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:51 -msgid "Show More Networks" -msgstr "" - -#, elixir-format -#: lib/block_scout_web/templates/layout/_network_selector.html.eex:22 -msgid "Testnet" +#: lib/block_scout_web/templates/address_logs/index.html.eex:12 +msgid "Topic" msgstr "" diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_internal_transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_internal_transaction_controller_test.exs index 6ce66a1e2e..8f5bc9a667 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_internal_transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_internal_transaction_controller_test.exs @@ -153,6 +153,49 @@ defmodule BlockScoutWeb.AddressInternalTransactionControllerTest do end) end + test "returns internal an transaction that created the address", %{conn: conn} do + address = insert(:address) + + transaction = + :transaction + |> insert() + |> with_block(insert(:block, number: 1)) + + from_internal_transaction = + insert(:internal_transaction, + transaction: transaction, + from_address: address, + index: 1, + block_number: transaction.block_number, + transaction_index: transaction.index + ) + + to_internal_transaction = + insert(:internal_transaction, + transaction: transaction, + to_address: nil, + created_contract_address: address, + index: 2, + block_number: transaction.block_number, + transaction_index: transaction.index + ) + + path = address_internal_transaction_path(conn, :index, address, %{"filter" => "to", "type" => "JSON"}) + conn = get(conn, path) + + internal_transaction_tiles = json_response(conn, 200)["items"] + + assert Enum.any?(internal_transaction_tiles, fn tile -> + String.contains?(tile, to_string(to_internal_transaction.transaction_hash)) && + String.contains?(tile, "data-internal-transaction-index=\"#{to_internal_transaction.index}\"") + end) + + refute Enum.any?(internal_transaction_tiles, fn tile -> + String.contains?(tile, to_string(from_internal_transaction.transaction_hash)) && + String.contains?(tile, "data-internal-transaction-index=\"#{from_internal_transaction.index}\"") + end) + end + test "returns next page of results based on last seen internal transaction", %{conn: conn} do address = insert(:address) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/logs_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/logs_controller_test.exs index 758e37e801..454db88e86 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/logs_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/logs_controller_test.exs @@ -266,7 +266,7 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do transaction = %Transaction{block: block} = :transaction - |> insert() + |> insert(to_address: contract_address) |> with_block() log = insert(:log, address: contract_address, transaction: transaction) @@ -313,13 +313,13 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do transaction_block1 = %Transaction{} = :transaction - |> insert() + |> insert(to_address: contract_address) |> with_block(first_block) transaction_block2 = %Transaction{} = :transaction - |> insert() + |> insert(to_address: contract_address) |> with_block(second_block) insert(:log, address: contract_address, transaction: transaction_block1) @@ -356,13 +356,13 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do transaction_block1 = %Transaction{} = :transaction - |> insert() + |> insert(to_address: contract_address) |> with_block(first_block) transaction_block2 = %Transaction{} = :transaction - |> insert() + |> insert(to_address: contract_address) |> with_block(second_block) insert(:log, address: contract_address, transaction: transaction_block1) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex index c372c0c979..e1c60f7352 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/contract.ex @@ -28,10 +28,11 @@ defmodule EthereumJSONRPC.Contract do @spec execute_contract_functions([call()], [map()], EthereumJSONRPC.json_rpc_named_arguments()) :: [call_result()] def execute_contract_functions(requests, abi, json_rpc_named_arguments) do - functions = + parsed_abi = abi |> ABI.parse_specification() - |> Enum.into(%{}, &{&1.function, &1}) + + functions = Enum.into(parsed_abi, %{}, &{&1.function, &1}) requests_with_index = Enum.with_index(requests) @@ -52,13 +53,15 @@ defmodule EthereumJSONRPC.Contract do |> Enum.into(%{}, &{&1.id, &1}) Enum.map(requests_with_index, fn {%{function_name: function_name}, index} -> + selectors = Enum.filter(parsed_abi, fn p_abi -> p_abi.function == function_name end) + indexed_responses[index] |> case do nil -> {:error, "No result"} response -> - {^index, result} = Encoder.decode_result(response, functions[function_name]) + {^index, result} = Encoder.decode_result(response, selectors) result end end) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex index c43737cfdb..8cefe10850 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex @@ -35,12 +35,29 @@ defmodule EthereumJSONRPC.Encoder do @doc """ Given a result from the blockchain, and the function selector, returns the result decoded. """ - @spec decode_result(map(), %ABI.FunctionSelector{}) :: + @spec decode_result(map(), %ABI.FunctionSelector{} | [%ABI.FunctionSelector{}]) :: {String.t(), {:ok, any()} | {:error, String.t() | :invalid_data}} def decode_result(%{error: %{code: code, message: message}, id: id}, _selector) do {id, {:error, "(#{code}) #{message}"}} end + def decode_result(result, selectors) when is_list(selectors) do + selectors + |> Enum.map(fn selector -> + try do + decode_result(result, selector) + rescue + _ -> :error + end + end) + |> Enum.find(fn decode -> + case decode do + {_id, {:ok, _}} -> true + _ -> false + end + end) + end + def decode_result(%{id: id, result: result}, function_selector) do types_list = List.wrap(function_selector.returns) diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 9446388531..841084b251 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -39,6 +39,7 @@ defmodule Explorer.Chain do InternalTransaction, Log, SmartContract, + StakingPool, Token, TokenTransfer, Transaction, @@ -266,7 +267,7 @@ defmodule Explorer.Chain do queries |> Stream.flat_map(&Repo.all/1) - |> Stream.uniq() + |> Stream.uniq_by(& &1.hash) |> Stream.concat(rewards_list) |> Enum.sort_by(fn item -> case item do @@ -280,7 +281,7 @@ defmodule Explorer.Chain do |> Enum.take(paging_options.page_size) end - @spec address_to_logs(Address.t(), [paging_options]) :: [ + @spec address_to_logs(Address.t(), Keyword.t()) :: [ Log.t() ] def address_to_logs( @@ -292,7 +293,7 @@ defmodule Explorer.Chain do {block_number, transaction_index, log_index} = paging_options.key || {BlockNumberCache.max_number(), 0, 0} - query = + base_query = from(log in Log, inner_join: transaction in assoc(log, :transaction), order_by: [desc: transaction.block_number, desc: transaction.index], @@ -307,11 +308,22 @@ defmodule Explorer.Chain do select: log ) - query + base_query + |> filter_topic(options) |> Repo.all() |> Enum.take(paging_options.page_size) end + defp filter_topic(base_query, topic: topic) do + from(log in base_query, + where: + log.first_topic == ^topic or log.second_topic == ^topic or log.third_topic == ^topic or + log.fourth_topic == ^topic + ) + end + + defp filter_topic(base_query, _), do: base_query + @doc """ Finds all `t:Explorer.Chain.Transaction.t/0`s given the address_hash and the token contract address hash. @@ -2925,7 +2937,7 @@ defmodule Explorer.Chain do def staking_pools(filter, %PagingOptions{page_size: page_size, page_number: page_number} \\ @default_paging_options) do off = page_size * (page_number - 1) - Address.Name + StakingPool |> staking_pool_filter(filter) |> limit(^page_size) |> offset(^off) @@ -2935,55 +2947,36 @@ defmodule Explorer.Chain do @doc "Get count of staking pools from the DB" @spec staking_pools_count(filter :: :validator | :active | :inactive) :: integer def staking_pools_count(filter) do - Address.Name + StakingPool |> staking_pool_filter(filter) - |> Repo.aggregate(:count, :address_hash) + |> Repo.aggregate(:count, :staking_address_hash) end defp staking_pool_filter(query, :validator) do where( query, - [address], - fragment( - """ - (?->>'is_active')::boolean = true and - (?->>'deleted')::boolean is not true and - (?->>'is_validator')::boolean = true - """, - address.metadata, - address.metadata, - address.metadata - ) + [pool], + pool.is_active == true and + pool.is_deleted == false and + pool.is_validator == true ) end defp staking_pool_filter(query, :active) do where( query, - [address], - fragment( - """ - (?->>'is_active')::boolean = true and - (?->>'deleted')::boolean is not true - """, - address.metadata, - address.metadata - ) + [pool], + pool.is_active == true and + pool.is_deleted == false ) end defp staking_pool_filter(query, :inactive) do where( query, - [address], - fragment( - """ - (?->>'is_active')::boolean = false and - (?->>'deleted')::boolean is not true - """, - address.metadata, - address.metadata - ) + [pool], + pool.is_active == false and + pool.is_deleted == false ) end diff --git a/apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex b/apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex index b21c0c4441..ca83fa62ee 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex @@ -1,12 +1,12 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do @moduledoc """ - Bulk imports staking pools to Address.Name tabe. + Bulk imports staking pools to StakingPool tabe. """ require Ecto.Query alias Ecto.{Changeset, Multi, Repo} - alias Explorer.Chain.{Address, Import} + alias Explorer.Chain.{Import, StakingPool} import Ecto.Query, only: [from: 2] @@ -15,10 +15,10 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do # milliseconds @timeout 60_000 - @type imported :: [Address.Name.t()] + @type imported :: [StakingPool.t()] @impl Import.Runner - def ecto_schema_module, do: Address.Name + def ecto_schema_module, do: StakingPool @impl Import.Runner def option_key, do: :staking_pools @@ -47,23 +47,25 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do |> Multi.run(:insert_staking_pools, fn repo, _ -> insert(repo, changes_list, insert_options) end) + |> Multi.run(:calculate_stakes_ratio, fn repo, _ -> + calculate_stakes_ratio(repo, insert_options) + end) end @impl Import.Runner def timeout, do: @timeout defp mark_as_deleted(repo, changes_list, %{timeout: timeout}) when is_list(changes_list) do - addresses = Enum.map(changes_list, & &1.address_hash) + addresses = Enum.map(changes_list, & &1.staking_address_hash) query = from( - address_name in Address.Name, - where: - address_name.address_hash not in ^addresses and - fragment("(?->>'is_pool')::boolean = true", address_name.metadata), + pool in StakingPool, + where: pool.staking_address_hash not in ^addresses, update: [ set: [ - metadata: fragment("? || '{\"deleted\": true}'::jsonb", address_name.metadata) + is_deleted: true, + is_active: false ] ] ) @@ -83,7 +85,7 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do required(:timeout) => timeout, required(:timestamps) => Import.timestamps() }) :: - {:ok, [Address.Name.t()]} + {:ok, [StakingPool.t()]} | {:error, [Changeset.t()]} defp insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) @@ -91,11 +93,11 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do {:ok, _} = Import.insert_changes_list( repo, - stakes_ratio(changes_list), - conflict_target: {:unsafe_fragment, "(address_hash) where \"primary\" = true"}, + changes_list, + conflict_target: :staking_address_hash, on_conflict: on_conflict, - for: Address.Name, - returning: [:address_hash], + for: StakingPool, + returning: [:staking_address_hash], timeout: timeout, timestamps: timestamps ) @@ -103,31 +105,58 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do defp default_on_conflict do from( - name in Address.Name, + pool in StakingPool, update: [ set: [ - name: fragment("EXCLUDED.name"), - metadata: fragment("EXCLUDED.metadata"), - inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", name.inserted_at), - updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", name.updated_at) + mining_address_hash: fragment("EXCLUDED.mining_address_hash"), + delegators_count: fragment("EXCLUDED.delegators_count"), + is_active: fragment("EXCLUDED.is_active"), + is_banned: fragment("EXCLUDED.is_banned"), + is_validator: fragment("EXCLUDED.is_validator"), + likelihood: fragment("EXCLUDED.likelihood"), + staked_ratio: fragment("EXCLUDED.staked_ratio"), + self_staked_amount: fragment("EXCLUDED.self_staked_amount"), + staked_amount: fragment("EXCLUDED.staked_amount"), + was_banned_count: fragment("EXCLUDED.was_banned_count"), + was_validator_count: fragment("EXCLUDED.was_validator_count"), + is_deleted: fragment("EXCLUDED.is_deleted"), + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", pool.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", pool.updated_at) ] ] ) end - # Calculates staked ratio for each pool - defp stakes_ratio(pools) do - active_pools = Enum.filter(pools, & &1.metadata[:is_active]) - - stakes_total = - Enum.reduce(pools, 0, fn pool, acc -> - acc + pool.metadata[:staked_amount] - end) + defp calculate_stakes_ratio(repo, %{timeout: timeout}) do + total_query = + from( + pool in StakingPool, + where: pool.is_active == true, + select: sum(pool.staked_amount) + ) - Enum.map(active_pools, fn pool -> - staked_ratio = if stakes_total > 0, do: pool.metadata[:staked_amount] / stakes_total, else: 0 + total = repo.one!(total_query) + + if total > Decimal.new(0) do + query = + from( + p in StakingPool, + where: p.is_active == true, + update: [ + set: [ + staked_ratio: p.staked_amount / ^total * 100, + likelihood: p.staked_amount / ^total * 100 + ] + ] + ) - put_in(pool, [:metadata, :staked_ratio], staked_ratio) - end) + {count, _} = repo.update_all(query, [], timeout: timeout) + {:ok, count} + else + {:ok, 1} + end + rescue + postgrex_error in Postgrex.Error -> + {:error, %{exception: postgrex_error}} end end diff --git a/apps/explorer/lib/explorer/chain/import/runner/staking_pools_delegators.ex b/apps/explorer/lib/explorer/chain/import/runner/staking_pools_delegators.ex new file mode 100644 index 0000000000..420882a833 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/runner/staking_pools_delegators.ex @@ -0,0 +1,91 @@ +defmodule Explorer.Chain.Import.Runner.StakingPoolsDelegators do + @moduledoc """ + Bulk imports delegators to StakingPoolsDelegator tabe. + """ + + require Ecto.Query + + alias Ecto.{Changeset, Multi, Repo} + alias Explorer.Chain.{Import, StakingPoolsDelegator} + + import Ecto.Query, only: [from: 2] + + @behaviour Import.Runner + + # milliseconds + @timeout 60_000 + + @type imported :: [StakingPoolsDelegator.t()] + + @impl Import.Runner + def ecto_schema_module, do: StakingPoolsDelegator + + @impl Import.Runner + def option_key, do: :staking_pools_delegators + + @impl Import.Runner + def imported_table_row do + %{ + value_type: "[#{ecto_schema_module()}.t()]", + value_description: "List of `t:#{ecto_schema_module()}.t/0`s" + } + end + + @impl Import.Runner + def run(multi, changes_list, %{timestamps: timestamps} = options) do + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) + + multi + |> Multi.run(:insert_staking_pools_delegators, fn repo, _ -> + insert(repo, changes_list, insert_options) + end) + end + + @impl Import.Runner + def timeout, do: @timeout + + @spec insert(Repo.t(), [map()], %{ + optional(:on_conflict) => Import.Runner.on_conflict(), + required(:timeout) => timeout, + required(:timestamps) => Import.timestamps() + }) :: + {:ok, [StakingPoolsDelegator.t()]} + | {:error, [Changeset.t()]} + defp insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do + on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0) + + {:ok, _} = + Import.insert_changes_list( + repo, + changes_list, + conflict_target: [:pool_address_hash, :delegator_address_hash], + on_conflict: on_conflict, + for: StakingPoolsDelegator, + returning: [:pool_address_hash, :delegator_address_hash], + timeout: timeout, + timestamps: timestamps + ) + end + + defp default_on_conflict do + from( + delegator in StakingPoolsDelegator, + update: [ + set: [ + stake_amount: fragment("EXCLUDED.stake_amount"), + ordered_withdraw: fragment("EXCLUDED.ordered_withdraw"), + max_withdraw_allowed: fragment("EXCLUDED.max_withdraw_allowed"), + max_ordered_withdraw_allowed: fragment("EXCLUDED.max_ordered_withdraw_allowed"), + ordered_withdraw_epoch: fragment("EXCLUDED.ordered_withdraw_epoch"), + inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", delegator.inserted_at), + updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", delegator.updated_at) + ] + ] + ) + end +end diff --git a/apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex b/apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex index 0e42bdc1f9..b5037f3b04 100644 --- a/apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex +++ b/apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex @@ -25,7 +25,8 @@ defmodule Explorer.Chain.Import.Stage.AddressReferencing do Runner.TokenTransfers, Runner.Address.CurrentTokenBalances, Runner.Address.TokenBalances, - Runner.StakingPools + Runner.StakingPools, + Runner.StakingPoolsDelegators ] @impl Stage diff --git a/apps/explorer/lib/explorer/chain/internal_transaction.ex b/apps/explorer/lib/explorer/chain/internal_transaction.ex index 60a0f3ed08..1231677a57 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction.ex @@ -470,7 +470,12 @@ defmodule Explorer.Chain.InternalTransaction do from_address_hash, created_contract_address_hash from internal_transactions' table. """ def where_address_fields_match(query, address_hash, :to) do - where(query, [t], t.to_address_hash == ^address_hash) + where( + query, + [t], + t.to_address_hash == ^address_hash or + (is_nil(t.to_address_hash) and t.created_contract_address_hash == ^address_hash) + ) end def where_address_fields_match(query, address_hash, :from) do diff --git a/apps/explorer/lib/explorer/chain/staking_pool.ex b/apps/explorer/lib/explorer/chain/staking_pool.ex new file mode 100644 index 0000000000..7949661c23 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/staking_pool.ex @@ -0,0 +1,97 @@ +defmodule Explorer.Chain.StakingPool do + @moduledoc """ + The representation of staking pool from POSDAO network. + Staking pools might be candidate or validator. + """ + use Ecto.Schema + import Ecto.Changeset + + alias Explorer.Chain.{ + Address, + Hash, + StakingPoolsDelegator, + Wei + } + + @type t :: %__MODULE__{ + staking_address_hash: Hash.Address.t(), + mining_address_hash: Hash.Address.t(), + banned_until: boolean, + delegators_count: integer, + is_active: boolean, + is_banned: boolean, + is_validator: boolean, + likelihood: integer, + staked_ratio: Decimal.t(), + self_staked_amount: Wei.t(), + staked_amount: Wei.t(), + was_banned_count: integer, + was_validator_count: integer, + is_deleted: boolean + } + + @attrs ~w( + is_active delegators_count staked_amount self_staked_amount is_validator + was_validator_count is_banned was_banned_count banned_until likelihood + staked_ratio staking_address_hash mining_address_hash + )a + @req_attrs ~w( + is_active delegators_count staked_amount self_staked_amount is_validator + was_validator_count is_banned was_banned_count banned_until + staking_address_hash mining_address_hash + )a + + schema "staking_pools" do + field(:banned_until, :integer) + field(:delegators_count, :integer) + field(:is_active, :boolean, default: false) + field(:is_banned, :boolean, default: false) + field(:is_validator, :boolean, default: false) + field(:likelihood, :decimal) + field(:staked_ratio, :decimal) + field(:self_staked_amount, Wei) + field(:staked_amount, Wei) + field(:was_banned_count, :integer) + field(:was_validator_count, :integer) + field(:is_deleted, :boolean, default: false) + has_many(:delegators, StakingPoolsDelegator, foreign_key: :pool_address_hash) + + belongs_to( + :staking_address, + Address, + foreign_key: :staking_address_hash, + references: :hash, + type: Hash.Address + ) + + belongs_to( + :mining_address, + Address, + foreign_key: :mining_address_hash, + references: :hash, + type: Hash.Address + ) + + timestamps(null: false, type: :utc_datetime_usec) + end + + @doc false + def changeset(staking_pool, attrs) do + staking_pool + |> cast(attrs, @attrs) + |> cast_assoc(:delegators) + |> validate_required(@req_attrs) + |> validate_staked_amount() + |> unique_constraint(:staking_address_hash) + end + + defp validate_staked_amount(%{valid?: false} = c), do: c + + defp validate_staked_amount(changeset) do + if get_field(changeset, :staked_amount) < get_field(changeset, :self_staked_amount) do + add_error(changeset, :staked_amount, "must be greater than self_staked_amount") + else + changeset + end + end +end diff --git a/apps/explorer/lib/explorer/chain/staking_pools_delegator.ex b/apps/explorer/lib/explorer/chain/staking_pools_delegator.ex new file mode 100644 index 0000000000..8fabc6c447 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/staking_pools_delegator.ex @@ -0,0 +1,64 @@ +defmodule Explorer.Chain.StakingPoolsDelegator do + @moduledoc """ + The representation of delegators from POSDAO network. + Delegators make stakes on staking pools and withdraw from them. + """ + use Ecto.Schema + import Ecto.Changeset + + alias Explorer.Chain.{ + Address, + Hash, + StakingPool, + Wei + } + + @type t :: %__MODULE__{ + pool_address_hash: Hash.Address.t(), + delegator_address_hash: Hash.Address.t(), + max_ordered_withdraw_allowed: Wei.t(), + max_withdraw_allowed: Wei.t(), + ordered_withdraw: Wei.t(), + stake_amount: Wei.t(), + ordered_withdraw_epoch: integer() + } + + @attrs ~w( + pool_address_hash delegator_address_hash max_ordered_withdraw_allowed + max_withdraw_allowed ordered_withdraw stake_amount ordered_withdraw_epoch + )a + + schema "staking_pools_delegators" do + field(:max_ordered_withdraw_allowed, Wei) + field(:max_withdraw_allowed, Wei) + field(:ordered_withdraw, Wei) + field(:ordered_withdraw_epoch, :integer) + field(:stake_amount, Wei) + + belongs_to( + :staking_pool, + StakingPool, + foreign_key: :pool_address_hash, + references: :staking_address_hash, + type: Hash.Address + ) + + belongs_to( + :delegator_address, + Address, + foreign_key: :delegator_address_hash, + references: :hash, + type: Hash.Address + ) + + timestamps(null: false, type: :utc_datetime_usec) + end + + @doc false + def changeset(staking_pools_delegator, attrs) do + staking_pools_delegator + |> cast(attrs, @attrs) + |> validate_required(@attrs) + |> unique_constraint(:pool_address_hash, name: :pools_delegator_index) + end +end diff --git a/apps/explorer/lib/explorer/chain/token_transfer.ex b/apps/explorer/lib/explorer/chain/token_transfer.ex index fa7ba99347..905d576aa1 100644 --- a/apps/explorer/lib/explorer/chain/token_transfer.ex +++ b/apps/explorer/lib/explorer/chain/token_transfer.ex @@ -204,59 +204,17 @@ defmodule Explorer.Chain.TokenTransfer do transaction_hashes_from_token_transfers_sql(address_bytes, paging_options) end - defp transaction_hashes_from_token_transfers_sql(address_bytes, %PagingOptions{key: nil, page_size: page_size}) do - {:ok, %Postgrex.Result{rows: transaction_hashes_from_token_transfers}} = - Repo.query( - """ - SELECT transaction_hash - FROM - ( - SELECT transaction_hash - FROM token_transfers - WHERE from_address_hash = $1 - - UNION - - SELECT transaction_hash - FROM token_transfers - WHERE to_address_hash = $1 - ) as token_transfers_transaction_hashes - LIMIT $2 - """, - [address_bytes, page_size] - ) - - List.flatten(transaction_hashes_from_token_transfers) - end - - defp transaction_hashes_from_token_transfers_sql(address_bytes, %PagingOptions{ - key: {block_number, _index}, - page_size: page_size - }) do - {:ok, %Postgrex.Result{rows: transaction_hashes_from_token_transfers}} = - Repo.query( - """ - SELECT transaction_hash - FROM - ( - SELECT transaction_hash - FROM token_transfers - WHERE from_address_hash = $1 - AND block_number < $2 - - UNION - - SELECT transaction_hash - FROM token_transfers - WHERE to_address_hash = $1 - AND block_number < $2 - ) as token_transfers_transaction_hashes - LIMIT $3 - """, - [address_bytes, block_number, page_size] + defp transaction_hashes_from_token_transfers_sql(address_bytes, %PagingOptions{page_size: page_size} = paging_options) do + query = + from(token_transfer in TokenTransfer, + where: token_transfer.to_address_hash == ^address_bytes or token_transfer.from_address_hash == ^address_bytes, + select: type(token_transfer.transaction_hash, :binary), + limit: ^page_size ) - List.flatten(transaction_hashes_from_token_transfers) + query + |> page_transaction_hashes_from_token_transfers(paging_options) + |> Repo.all() end defp page_transaction_hashes_from_token_transfers(query, %PagingOptions{key: nil}), do: query diff --git a/apps/explorer/lib/explorer/etherscan/logs.ex b/apps/explorer/lib/explorer/etherscan/logs.ex index e8cfb7404c..124199be72 100644 --- a/apps/explorer/lib/explorer/etherscan/logs.ex +++ b/apps/explorer/lib/explorer/etherscan/logs.ex @@ -5,9 +5,9 @@ defmodule Explorer.Etherscan.Logs do """ - import Ecto.Query, only: [from: 2, where: 3, subquery: 1, order_by: 3, limit: 2] + import Ecto.Query, only: [from: 2, where: 3, subquery: 1] - alias Explorer.Chain.Log + alias Explorer.Chain.{Block, InternalTransaction, Log, Transaction} alias Explorer.Repo @base_filter %{ @@ -67,33 +67,101 @@ defmodule Explorer.Etherscan.Logs do """ @spec list_logs(map()) :: [map()] - def list_logs(filter) do + def list_logs(%{address_hash: address_hash} = filter) when not is_nil(address_hash) do prepared_filter = Map.merge(@base_filter, filter) - query = - from( - l in Log, - inner_join: t in assoc(l, :transaction), - inner_join: b in assoc(t, :block), - where: b.number >= ^prepared_filter.from_block, - where: b.number <= ^prepared_filter.to_block, + logs_query = where_topic_match(Log, prepared_filter) + + internal_transaction_log_query = + from(internal_transaction in InternalTransaction, + join: transaction in assoc(internal_transaction, :transaction), + join: log in ^logs_query, + on: log.transaction_hash == internal_transaction.transaction_hash, + where: internal_transaction.block_number >= ^prepared_filter.from_block, + where: internal_transaction.block_number <= ^prepared_filter.to_block, + where: + internal_transaction.to_address_hash == ^address_hash or + internal_transaction.from_address_hash == ^address_hash or + internal_transaction.created_contract_address_hash == ^address_hash, select: - merge(map(l, ^@log_fields), %{ - gas_price: t.gas_price, - gas_used: t.gas_used, - transaction_index: t.index, - block_number: b.number, - block_timestamp: b.timestamp + merge(map(log, ^@log_fields), %{ + gas_price: transaction.gas_price, + gas_used: transaction.gas_used, + transaction_index: transaction.index, + block_number: transaction.block_number }) ) - query - |> subquery() - |> where_address_match(prepared_filter) - |> where_topic_match(prepared_filter) - |> order_by([l], l.block_number) - |> limit(1_000) - |> Repo.all() + all_transaction_logs_query = + from(transaction in Transaction, + join: log in ^logs_query, + on: log.transaction_hash == transaction.hash, + where: transaction.block_number >= ^prepared_filter.from_block, + where: transaction.block_number <= ^prepared_filter.to_block, + where: + transaction.to_address_hash == ^address_hash or + transaction.from_address_hash == ^address_hash or + transaction.created_contract_address_hash == ^address_hash, + select: map(log, ^@log_fields), + select_merge: map(transaction, [:gas_price, :gas_used, :block_number]), + select_merge: %{ + transaction_index: transaction.index + }, + union: ^internal_transaction_log_query + ) + + query_with_blocks = + from(log_transaction_data in subquery(all_transaction_logs_query), + join: block in Block, + on: block.number == log_transaction_data.block_number, + where: block.consensus == true, + where: log_transaction_data.address_hash == ^address_hash, + order_by: block.number, + limit: 1000, + select_merge: %{ + block_timestamp: block.timestamp + } + ) + + Repo.all(query_with_blocks) + 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 + prepared_filter = Map.merge(@base_filter, filter) + + logs_query = where_topic_match(Log, prepared_filter) + + block_transaction_query = + from(transaction in Transaction, + join: block in assoc(transaction, :block), + where: block.number >= ^prepared_filter.from_block, + where: block.number <= ^prepared_filter.to_block, + where: block.consensus == true, + select: %{ + transaction_hash: transaction.hash, + gas_price: transaction.gas_price, + gas_used: transaction.gas_used, + transaction_index: transaction.index, + block_number: block.number, + block_timestamp: block.timestamp + } + ) + + query_with_block_transaction_data = + from(log in logs_query, + join: block_transaction_data in subquery(block_transaction_query), + on: block_transaction_data.transaction_hash == log.transaction_hash, + order_by: block_transaction_data.block_number, + limit: 1000, + select: block_transaction_data, + select_merge: map(log, ^@log_fields) + ) + + Repo.all(query_with_block_transaction_data) end @topics [ @@ -112,12 +180,6 @@ defmodule Explorer.Etherscan.Logs do topic2_3_opr: {:third_topic, :fourth_topic} } - defp where_address_match(query, %{address_hash: address_hash}) when not is_nil(address_hash) do - where(query, [l], l.address_hash == ^address_hash) - end - - defp where_address_match(query, _), do: query - defp where_topic_match(query, filter) do case Enum.filter(@topics, &filter[&1]) do [] -> diff --git a/apps/explorer/lib/explorer/staking/pools_reader.ex b/apps/explorer/lib/explorer/staking/pools_reader.ex index 608fea3863..0c04c8e570 100644 --- a/apps/explorer/lib/explorer/staking/pools_reader.ex +++ b/apps/explorer/lib/explorer/staking/pools_reader.ex @@ -24,10 +24,11 @@ defmodule Explorer.Staking.PoolsReader do @spec pool_data(String.t()) :: {:ok, map()} | :error def pool_data(staking_address) do with {:ok, [mining_address]} <- call_validators_method("miningByStakingAddress", [staking_address]), - data = fetch_data(staking_address, mining_address), + data = fetch_pool_data(staking_address, mining_address), {:ok, [is_active]} <- data["isPoolActive"], {:ok, [delegator_addresses]} <- data["poolDelegators"], delegators_count = Enum.count(delegator_addresses), + delegators = delegators_data(delegator_addresses, staking_address), {:ok, [staked_amount]} <- data["stakeAmountTotalMinusOrderedWithdraw"], {:ok, [self_staked_amount]} <- data["stakeAmountMinusOrderedWithdraw"], {:ok, [is_validator]} <- data["isValidator"], @@ -38,8 +39,8 @@ defmodule Explorer.Staking.PoolsReader do { :ok, %{ - staking_address: staking_address, - mining_address: mining_address, + staking_address_hash: staking_address, + mining_address_hash: mining_address, is_active: is_active, delegators_count: delegators_count, staked_amount: staked_amount, @@ -48,7 +49,8 @@ defmodule Explorer.Staking.PoolsReader do was_validator_count: was_validator_count, is_banned: is_banned, banned_until: banned_until, - was_banned_count: was_banned_count + was_banned_count: was_banned_count, + delegators: delegators } } else @@ -57,6 +59,35 @@ defmodule Explorer.Staking.PoolsReader do end end + defp delegators_data(delegators, pool_address) do + Enum.map(delegators, fn address -> + data = + call_methods([ + {:staking, "stakeAmount", [pool_address, address]}, + {:staking, "orderedWithdrawAmount", [pool_address, address]}, + {:staking, "maxWithdrawAllowed", [pool_address, address]}, + {:staking, "maxWithdrawOrderAllowed", [pool_address, address]}, + {:staking, "orderWithdrawEpoch", [pool_address, address]} + ]) + + {:ok, [stake_amount]} = data["stakeAmount"] + {:ok, [ordered_withdraw]} = data["orderedWithdrawAmount"] + {:ok, [max_withdraw_allowed]} = data["maxWithdrawAllowed"] + {:ok, [max_ordered_withdraw_allowed]} = data["maxWithdrawOrderAllowed"] + {:ok, [ordered_withdraw_epoch]} = data["orderWithdrawEpoch"] + + %{ + delegator_address_hash: address, + pool_address_hash: pool_address, + stake_amount: stake_amount, + ordered_withdraw: ordered_withdraw, + max_withdraw_allowed: max_withdraw_allowed, + max_ordered_withdraw_allowed: max_ordered_withdraw_allowed, + ordered_withdraw_epoch: ordered_withdraw_epoch + } + end) + end + defp call_staking_method(method, params) do %{^method => resp} = Reader.query_contract(config(:staking_contract_address), abi("staking.json"), %{ @@ -75,10 +106,8 @@ defmodule Explorer.Staking.PoolsReader do resp end - defp fetch_data(staking_address, mining_address) do - contract_abi = abi("staking.json") ++ abi("validators.json") - - methods = [ + defp fetch_pool_data(staking_address, mining_address) do + call_methods([ {:staking, "isPoolActive", [staking_address]}, {:staking, "poolDelegators", [staking_address]}, {:staking, "stakeAmountTotalMinusOrderedWithdraw", [staking_address]}, @@ -88,7 +117,11 @@ defmodule Explorer.Staking.PoolsReader do {:validators, "isValidatorBanned", [mining_address]}, {:validators, "bannedUntil", [mining_address]}, {:validators, "banCounter", [mining_address]} - ] + ]) + end + + defp call_methods(methods) do + contract_abi = abi("staking.json") ++ abi("validators.json") methods |> Enum.map(&format_request/1) diff --git a/apps/explorer/lib/explorer/token/metadata_retriever.ex b/apps/explorer/lib/explorer/token/metadata_retriever.ex index 0e718954d0..2a03358248 100644 --- a/apps/explorer/lib/explorer/token/metadata_retriever.ex +++ b/apps/explorer/lib/explorer/token/metadata_retriever.ex @@ -22,6 +22,16 @@ defmodule Explorer.Token.MetadataRetriever do "payable" => false, "type" => "function" }, + %{ + "constant" => true, + "inputs" => [], + "name" => "name", + "outputs" => [ + %{"name" => "", "type" => "bytes32"} + ], + "payable" => false, + "type" => "function" + }, %{ "constant" => true, "inputs" => [], @@ -60,6 +70,19 @@ defmodule Explorer.Token.MetadataRetriever do ], "payable" => false, "type" => "function" + }, + %{ + "constant" => true, + "inputs" => [], + "name" => "symbol", + "outputs" => [ + %{ + "name" => "", + "type" => "bytes32" + } + ], + "payable" => false, + "type" => "function" } ] diff --git a/apps/explorer/priv/repo/migrations/20190513134025_add_refetch_needed_to_block.exs b/apps/explorer/priv/repo/migrations/20190513134025_add_refetch_needed_to_block.exs index 70ddac3e03..5380e2df14 100644 --- a/apps/explorer/priv/repo/migrations/20190513134025_add_refetch_needed_to_block.exs +++ b/apps/explorer/priv/repo/migrations/20190513134025_add_refetch_needed_to_block.exs @@ -6,6 +6,6 @@ defmodule Explorer.Repo.Migrations.AddRefetchNeededToBlock do add(:refetch_needed, :boolean, default: false) end - execute("UPDATE blocks SET refetch_needed = TRUE;", "") + execute("UPDATE blocks SET refetch_needed = TRUE WHERE consensus", "") end end diff --git a/apps/explorer/priv/repo/migrations/20190521104412_create_staking_pools.exs b/apps/explorer/priv/repo/migrations/20190521104412_create_staking_pools.exs new file mode 100644 index 0000000000..94483112f2 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20190521104412_create_staking_pools.exs @@ -0,0 +1,27 @@ +defmodule Explorer.Repo.Migrations.CreateStakingPools do + use Ecto.Migration + + def change do + create table(:staking_pools) do + add(:is_active, :boolean, default: false, null: false) + add(:is_deleted, :boolean, default: false, null: false) + add(:delegators_count, :integer) + add(:staked_amount, :numeric, precision: 100) + add(:self_staked_amount, :numeric, precision: 100) + add(:is_validator, :boolean, default: false, null: false) + add(:was_validator_count, :integer) + add(:is_banned, :boolean, default: false, null: false) + add(:was_banned_count, :integer) + add(:banned_until, :bigint) + add(:likelihood, :decimal, precision: 5, scale: 2) + add(:staked_ratio, :decimal, precision: 5, scale: 2) + add(:staking_address_hash, :bytea) + add(:mining_address_hash, :bytea) + + timestamps(null: false, type: :utc_datetime_usec) + end + + create(index(:staking_pools, [:staking_address_hash], unique: true)) + create(index(:staking_pools, [:mining_address_hash])) + end +end diff --git a/apps/explorer/priv/repo/migrations/20190523112839_create_staking_pools_delegators.exs b/apps/explorer/priv/repo/migrations/20190523112839_create_staking_pools_delegators.exs new file mode 100644 index 0000000000..ccf9c8c372 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20190523112839_create_staking_pools_delegators.exs @@ -0,0 +1,26 @@ +defmodule Explorer.Repo.Migrations.CreateStakingPoolsDelegator do + use Ecto.Migration + + def change do + create table(:staking_pools_delegators) do + add(:delegator_address_hash, :bytea) + add(:pool_address_hash, :bytea) + add(:stake_amount, :numeric, precision: 100) + add(:ordered_withdraw, :numeric, precision: 100) + add(:max_withdraw_allowed, :numeric, precision: 100) + add(:max_ordered_withdraw_allowed, :numeric, precision: 100) + add(:ordered_withdraw_epoch, :integer) + + timestamps(null: false, type: :utc_datetime_usec) + end + + create(index(:staking_pools_delegators, [:delegator_address_hash])) + + create( + index(:staking_pools_delegators, [:delegator_address_hash, :pool_address_hash], + unique: true, + name: :pools_delegator_index + ) + ) + end +end diff --git a/apps/explorer/test/explorer/chain/import/runner/staking_pools_delegators_test.exs b/apps/explorer/test/explorer/chain/import/runner/staking_pools_delegators_test.exs new file mode 100644 index 0000000000..9a11e3edc8 --- /dev/null +++ b/apps/explorer/test/explorer/chain/import/runner/staking_pools_delegators_test.exs @@ -0,0 +1,32 @@ +defmodule Explorer.Chain.Import.Runner.StakingPoolsDelegatorsTest do + use Explorer.DataCase + + import Explorer.Factory + + alias Ecto.Multi + alias Explorer.Chain.Import.Runner.StakingPoolsDelegators + alias Explorer.Chain.StakingPoolsDelegator + + describe "run/1" do + test "insert new pools list" do + delegators = + [params_for(:staking_pools_delegator), params_for(:staking_pools_delegator)] + |> Enum.map(fn param -> + changeset = StakingPoolsDelegator.changeset(%StakingPoolsDelegator{}, param) + changeset.changes + end) + + assert {:ok, %{insert_staking_pools_delegators: list}} = run_changes(delegators) + assert Enum.count(list) == Enum.count(delegators) + end + end + + defp run_changes(changes) do + Multi.new() + |> StakingPoolsDelegators.run(changes, %{ + 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/import/runner/staking_pools_test.exs b/apps/explorer/test/explorer/chain/import/runner/staking_pools_test.exs index af25368679..1c56fe294c 100644 --- a/apps/explorer/test/explorer/chain/import/runner/staking_pools_test.exs +++ b/apps/explorer/test/explorer/chain/import/runner/staking_pools_test.exs @@ -5,25 +5,30 @@ defmodule Explorer.Chain.Import.Runner.StakingPoolsTest do alias Ecto.Multi alias Explorer.Chain.Import.Runner.StakingPools + alias Explorer.Chain.StakingPool describe "run/1" do test "insert new pools list" do - pools = [pool1, pool2, pool3, pool4] = build_list(4, :staking_pool) + pools = + [pool1, pool2] = + [params_for(:staking_pool), params_for(:staking_pool)] + |> Enum.map(fn param -> + changeset = StakingPool.changeset(%StakingPool{}, param) + changeset.changes + end) assert {:ok, %{insert_staking_pools: list}} = run_changes(pools) assert Enum.count(list) == Enum.count(pools) saved_list = - Explorer.Chain.Address.Name + Explorer.Chain.StakingPool |> Repo.all() |> Enum.reduce(%{}, fn pool, acc -> - Map.put(acc, pool.address_hash, pool) + Map.put(acc, pool.staking_address_hash, pool) end) - assert saved_list[pool1.address_hash].metadata["staked_ratio"] == 0.25 - assert saved_list[pool2.address_hash].metadata["staked_ratio"] == 0.25 - assert saved_list[pool3.address_hash].metadata["staked_ratio"] == 0.25 - assert saved_list[pool4.address_hash].metadata["staked_ratio"] == 0.25 + assert saved_list[pool1.staking_address_hash].staked_ratio == Decimal.new("50.00") + assert saved_list[pool2.staking_address_hash].staked_ratio == Decimal.new("50.00") end end diff --git a/apps/explorer/test/explorer/chain/staking_pool_test.exs b/apps/explorer/test/explorer/chain/staking_pool_test.exs new file mode 100644 index 0000000000..22450071a5 --- /dev/null +++ b/apps/explorer/test/explorer/chain/staking_pool_test.exs @@ -0,0 +1,18 @@ +defmodule Explorer.Chain.StakingPoolTest do + use Explorer.DataCase + + alias Explorer.Chain.StakingPool + + describe "changeset/2" do + test "with valid attributes" do + params = params_for(:staking_pool) + changeset = StakingPool.changeset(%StakingPool{}, params) + assert changeset.valid? + end + + test "with invalid attributes" do + changeset = StakingPool.changeset(%StakingPool{}, %{staking_address_hash: 0}) + refute changeset.valid? + end + end +end diff --git a/apps/explorer/test/explorer/chain/staking_pools_delegator_test.exs b/apps/explorer/test/explorer/chain/staking_pools_delegator_test.exs new file mode 100644 index 0000000000..222658e92e --- /dev/null +++ b/apps/explorer/test/explorer/chain/staking_pools_delegator_test.exs @@ -0,0 +1,18 @@ +defmodule Explorer.Chain.StakingPoolsDelegatorTest do + use Explorer.DataCase + + alias Explorer.Chain.StakingPoolsDelegator + + describe "changeset/2" do + test "with valid attributes" do + params = params_for(:staking_pools_delegator) + changeset = StakingPoolsDelegator.changeset(%StakingPoolsDelegator{}, params) + assert changeset.valid? + end + + test "with invalid attributes" do + changeset = StakingPoolsDelegator.changeset(%StakingPoolsDelegator{}, %{pool_address_hash: 0}) + refute changeset.valid? + end + end +end diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 0234672183..1cccd21e1d 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -93,6 +93,50 @@ defmodule Explorer.ChainTest do assert Enum.count(Chain.address_to_logs(address, paging_options: paging_options2)) == 50 end + + test "searches logs by topic when the first topic matches" do + address = insert(:address) + + transaction1 = + :transaction + |> insert(to_address: address) + |> with_block() + + insert(:log, transaction: transaction1, index: 1, address: address) + + transaction2 = + :transaction + |> insert(from_address: address) + |> with_block() + + insert(:log, transaction: transaction2, index: 2, address: address, first_topic: "test") + + [found_log] = Chain.address_to_logs(address, topic: "test") + + assert found_log.transaction.hash == transaction2.hash + end + + test "searches logs by topic when the fourth topic matches" do + address = insert(:address) + + transaction1 = + :transaction + |> insert(to_address: address) + |> with_block() + + insert(:log, transaction: transaction1, index: 1, address: address, fourth_topic: "test") + + transaction2 = + :transaction + |> insert(from_address: address) + |> with_block() + + insert(:log, transaction: transaction2, index: 2, address: address) + + [found_log] = Chain.address_to_logs(address, topic: "test") + + assert found_log.transaction.hash == transaction1.hash + end end describe "address_to_transactions_with_rewards/2" do @@ -3957,54 +4001,54 @@ defmodule Explorer.ChainTest do describe "staking_pools/3" do test "validators staking pools" do - inserted_validator = insert(:address_name, primary: true, metadata: %{is_active: true, is_validator: true}) - insert(:address_name, primary: true, metadata: %{is_active: true, is_validator: false}) + inserted_validator = insert(:staking_pool, is_active: true, is_validator: true) + insert(:staking_pool, is_active: true, is_validator: false) options = %PagingOptions{page_size: 20, page_number: 1} assert [gotten_validator] = Chain.staking_pools(:validator, options) - assert inserted_validator.address_hash == gotten_validator.address_hash + assert inserted_validator.staking_address_hash == gotten_validator.staking_address_hash end test "active staking pools" do - inserted_validator = insert(:address_name, primary: true, metadata: %{is_active: true}) - insert(:address_name, primary: true, metadata: %{is_active: false}) + inserted_pool = insert(:staking_pool, is_active: true) + insert(:staking_pool, is_active: false) options = %PagingOptions{page_size: 20, page_number: 1} - assert [gotten_validator] = Chain.staking_pools(:active, options) - assert inserted_validator.address_hash == gotten_validator.address_hash + assert [gotten_pool] = Chain.staking_pools(:active, options) + assert inserted_pool.staking_address_hash == gotten_pool.staking_address_hash end test "inactive staking pools" do - insert(:address_name, primary: true, metadata: %{is_active: true}) - inserted_validator = insert(:address_name, primary: true, metadata: %{is_active: false}) + insert(:staking_pool, is_active: true) + inserted_pool = insert(:staking_pool, is_active: false) options = %PagingOptions{page_size: 20, page_number: 1} - assert [gotten_validator] = Chain.staking_pools(:inactive, options) - assert inserted_validator.address_hash == gotten_validator.address_hash + assert [gotten_pool] = Chain.staking_pools(:inactive, options) + assert inserted_pool.staking_address_hash == gotten_pool.staking_address_hash end end describe "staking_pools_count/1" do test "validators staking pools" do - insert(:address_name, primary: true, metadata: %{is_active: true, is_validator: true}) - insert(:address_name, primary: true, metadata: %{is_active: true, is_validator: false}) + insert(:staking_pool, is_active: true, is_validator: true) + insert(:staking_pool, is_active: true, is_validator: false) assert Chain.staking_pools_count(:validator) == 1 end test "active staking pools" do - insert(:address_name, primary: true, metadata: %{is_active: true}) - insert(:address_name, primary: true, metadata: %{is_active: false}) + insert(:staking_pool, is_active: true) + insert(:staking_pool, is_active: false) assert Chain.staking_pools_count(:active) == 1 end test "inactive staking pools" do - insert(:address_name, primary: true, metadata: %{is_active: true}) - insert(:address_name, primary: true, metadata: %{is_active: false}) + insert(:staking_pool, is_active: true) + insert(:staking_pool, is_active: false) assert Chain.staking_pools_count(:inactive) == 1 end diff --git a/apps/explorer/test/explorer/etherscan/logs_test.exs b/apps/explorer/test/explorer/etherscan/logs_test.exs index 65c62175f6..00050db6f4 100644 --- a/apps/explorer/test/explorer/etherscan/logs_test.exs +++ b/apps/explorer/test/explorer/etherscan/logs_test.exs @@ -24,7 +24,7 @@ defmodule Explorer.Etherscan.LogsTest do %Transaction{block: block} = :transaction - |> insert() + |> insert(to_address: contract_address) |> with_block() filter = %{ @@ -42,7 +42,7 @@ defmodule Explorer.Etherscan.LogsTest do transaction = %Transaction{block: block} = :transaction - |> insert() + |> insert(to_address: contract_address) |> with_block() log = insert(:log, address: contract_address, transaction: transaction) @@ -76,7 +76,7 @@ defmodule Explorer.Etherscan.LogsTest do transaction = %Transaction{block: block} = :transaction - |> insert() + |> insert(to_address: contract_address) |> with_block() insert_list(2, :log, address: contract_address, transaction: transaction) @@ -101,13 +101,13 @@ defmodule Explorer.Etherscan.LogsTest do transaction_block1 = %Transaction{} = :transaction - |> insert() + |> insert(to_address: contract_address) |> with_block(first_block) transaction_block2 = %Transaction{} = :transaction - |> insert() + |> insert(to_address: contract_address) |> with_block(second_block) insert(:log, address: contract_address, transaction: transaction_block1) @@ -134,13 +134,13 @@ defmodule Explorer.Etherscan.LogsTest do transaction_block1 = %Transaction{} = :transaction - |> insert() + |> insert(to_address: contract_address) |> with_block(first_block) transaction_block2 = %Transaction{} = :transaction - |> insert() + |> insert(to_address: contract_address) |> with_block(second_block) insert(:log, address: contract_address, transaction: transaction_block1) @@ -164,7 +164,7 @@ defmodule Explorer.Etherscan.LogsTest do transaction = %Transaction{block: block} = :transaction - |> insert() + |> insert(to_address: contract_address) |> with_block() log1_details = [ @@ -200,7 +200,7 @@ defmodule Explorer.Etherscan.LogsTest do transaction = %Transaction{block: block} = :transaction - |> insert() + |> insert(to_address: contract_address) |> with_block() log1_details = [ @@ -241,7 +241,7 @@ defmodule Explorer.Etherscan.LogsTest do transaction = %Transaction{block: block} = :transaction - |> insert() + |> insert(to_address: contract_address) |> with_block() log1_details = [ @@ -280,7 +280,7 @@ defmodule Explorer.Etherscan.LogsTest do transaction = %Transaction{block: block} = :transaction - |> insert() + |> insert(to_address: contract_address) |> with_block() log1_details = [ @@ -317,7 +317,7 @@ defmodule Explorer.Etherscan.LogsTest do transaction = %Transaction{block: block} = :transaction - |> insert() + |> insert(to_address: contract_address) |> with_block() log1_details = [ @@ -358,7 +358,7 @@ defmodule Explorer.Etherscan.LogsTest do transaction = %Transaction{block: block} = :transaction - |> insert() + |> insert(to_address: contract_address) |> with_block() log1_details = [ @@ -415,7 +415,7 @@ defmodule Explorer.Etherscan.LogsTest do transaction = %Transaction{block: block} = :transaction - |> insert() + |> insert(to_address: contract_address) |> with_block() log1_details = [ @@ -472,7 +472,7 @@ defmodule Explorer.Etherscan.LogsTest do transaction = %Transaction{block: block} = :transaction - |> insert() + |> insert(to_address: contract_address) |> with_block() log1_details = [ @@ -529,7 +529,7 @@ defmodule Explorer.Etherscan.LogsTest do transaction = %Transaction{block: block} = :transaction - |> insert() + |> insert(to_address: contract_address) |> with_block() log1_details = [ @@ -595,19 +595,19 @@ defmodule Explorer.Etherscan.LogsTest do transaction_block1 = %Transaction{} = :transaction - |> insert() + |> insert(to_address: contract_address) |> with_block(first_block) transaction_block2 = %Transaction{} = :transaction - |> insert() + |> insert(to_address: contract_address) |> with_block(second_block) transaction_block3 = %Transaction{} = :transaction - |> insert() + |> insert(to_address: contract_address) |> with_block(third_block) insert(:log, address: contract_address, transaction: transaction_block3) diff --git a/apps/explorer/test/explorer/staking/pools_reader_test.exs b/apps/explorer/test/explorer/staking/pools_reader_test.exs index bb3af9fbcc..6fb5940010 100644 --- a/apps/explorer/test/explorer/staking/pools_reader_test.exs +++ b/apps/explorer/test/explorer/staking/pools_reader_test.exs @@ -30,25 +30,38 @@ defmodule Explorer.Token.PoolsReaderTest do test "get_pool_data success" do get_pool_data_from_blockchain() - address = <<11, 47, 94, 47, 60, 189, 134, 78, 170, 44, 100, 46, 55, 105, 193, 88, 35, 97, 202, 246>> + address = <<219, 156, 178, 71, 141, 145, 119, 25, 197, 56, 98, 0, 134, 114, 22, 104, 8, 37, 133, 119>> - response = { - :ok, - %{ - banned_until: 0, - delegators_count: 0, - is_active: true, - is_banned: false, - is_validator: true, - mining_address: - <<187, 202, 168, 212, 130, 137, 187, 31, 252, 249, 128, 141, 154, 164, 177, 210, 21, 5, 76, 120>>, - staked_amount: 0, - self_staked_amount: 0, - staking_address: <<11, 47, 94, 47, 60, 189, 134, 78, 170, 44, 100, 46, 55, 105, 193, 88, 35, 97, 202, 246>>, - was_banned_count: 0, - was_validator_count: 2 - } - } + response = + {:ok, + %{ + banned_until: 0, + is_active: true, + is_banned: false, + is_validator: true, + was_banned_count: 0, + was_validator_count: 2, + delegators: [ + %{ + delegator_address_hash: + <<243, 231, 124, 74, 245, 235, 47, 51, 175, 255, 118, 25, 216, 209, 231, 81, 215, 24, 164, 145>>, + max_ordered_withdraw_allowed: 1_000_000_000_000_000_000, + max_withdraw_allowed: 1_000_000_000_000_000_000, + ordered_withdraw: 0, + ordered_withdraw_epoch: 0, + pool_address_hash: + <<219, 156, 178, 71, 141, 145, 119, 25, 197, 56, 98, 0, 134, 114, 22, 104, 8, 37, 133, 119>>, + stake_amount: 1_000_000_000_000_000_000 + } + ], + delegators_count: 1, + mining_address_hash: + <<190, 105, 235, 9, 104, 34, 106, 24, 8, 151, 94, 26, 31, 33, 39, 102, 127, 43, 255, 179>>, + self_staked_amount: 2_000_000_000_000_000_000, + staked_amount: 3_000_000_000_000_000_000, + staking_address_hash: + <<219, 156, 178, 71, 141, 145, 119, 25, 197, 56, 98, 0, 134, 114, 22, 104, 8, 37, 133, 119>> + }} assert PoolsReader.pool_data(address) == response end @@ -101,7 +114,7 @@ defmodule Explorer.Token.PoolsReaderTest do expect( EthereumJSONRPC.Mox, :json_rpc, - 2, + 3, fn requests, _opts -> {:ok, Enum.map(requests, fn @@ -110,13 +123,13 @@ defmodule Explorer.Token.PoolsReaderTest do id: id, method: "eth_call", params: [ - %{data: "0x005351750000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _}, + %{data: "0x00535175000000000000000000000000db9cb2478d917719c53862008672166808258577", to: _}, "latest" ] } -> %{ id: id, - result: "0x000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78" + result: "0x000000000000000000000000be69eb0968226a1808975e1a1f2127667f2bffb3" } # isPoolActive @@ -124,7 +137,7 @@ defmodule Explorer.Token.PoolsReaderTest do id: id, method: "eth_call", params: [ - %{data: "0xa711e6a10000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _}, + %{data: "0xa711e6a1000000000000000000000000db9cb2478d917719c53862008672166808258577", to: _}, "latest" ] } -> @@ -138,14 +151,14 @@ defmodule Explorer.Token.PoolsReaderTest do id: id, method: "eth_call", params: [ - %{data: "0x9ea8082b0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _}, + %{data: "0x9ea8082b000000000000000000000000db9cb2478d917719c53862008672166808258577", to: _}, "latest" ] } -> %{ id: id, result: - "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000" + "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f3e77c4af5eb2f33afff7619d8d1e751d718a491" } # stakeAmountTotalMinusOrderedWithdraw @@ -153,13 +166,13 @@ defmodule Explorer.Token.PoolsReaderTest do id: id, method: "eth_call", params: [ - %{data: "0x234fbf2b0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _}, + %{data: "0x234fbf2b000000000000000000000000db9cb2478d917719c53862008672166808258577", to: _}, "latest" ] } -> %{ id: id, - result: "0x0000000000000000000000000000000000000000000000000000000000000000" + result: "0x00000000000000000000000000000000000000000000000029a2241af62c0000" } # stakeAmountMinusOrderedWithdraw @@ -170,7 +183,7 @@ defmodule Explorer.Token.PoolsReaderTest do params: [ %{ data: - "0x58daab6a0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf60000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", + "0x58daab6a000000000000000000000000db9cb2478d917719c53862008672166808258577000000000000000000000000db9cb2478d917719c53862008672166808258577", to: _ }, "latest" @@ -178,7 +191,7 @@ defmodule Explorer.Token.PoolsReaderTest do } -> %{ id: id, - result: "0x0000000000000000000000000000000000000000000000000000000000000000" + result: "0x0000000000000000000000000000000000000000000000001bc16d674ec80000" } # isValidator @@ -186,7 +199,7 @@ defmodule Explorer.Token.PoolsReaderTest do id: id, method: "eth_call", params: [ - %{data: "0xfacd743b000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, + %{data: "0xfacd743b000000000000000000000000be69eb0968226a1808975e1a1f2127667f2bffb3", to: _}, "latest" ] } -> @@ -200,7 +213,7 @@ defmodule Explorer.Token.PoolsReaderTest do id: id, method: "eth_call", params: [ - %{data: "0xb41832e4000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, + %{data: "0xb41832e4000000000000000000000000be69eb0968226a1808975e1a1f2127667f2bffb3", to: _}, "latest" ] } -> @@ -214,7 +227,7 @@ defmodule Explorer.Token.PoolsReaderTest do id: id, method: "eth_call", params: [ - %{data: "0xa92252ae000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, + %{data: "0xa92252ae000000000000000000000000be69eb0968226a1808975e1a1f2127667f2bffb3", to: _}, "latest" ] } -> @@ -228,7 +241,7 @@ defmodule Explorer.Token.PoolsReaderTest do id: id, method: "eth_call", params: [ - %{data: "0x5836d08a000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, + %{data: "0x5836d08a000000000000000000000000be69eb0968226a1808975e1a1f2127667f2bffb3", to: _}, "latest" ] } -> @@ -242,7 +255,98 @@ defmodule Explorer.Token.PoolsReaderTest do id: id, method: "eth_call", params: [ - %{data: "0x1d0cd4c6000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _}, + %{data: "0x1d0cd4c6000000000000000000000000be69eb0968226a1808975e1a1f2127667f2bffb3", to: _}, + "latest" + ] + } -> + %{ + id: id, + result: "0x0000000000000000000000000000000000000000000000000000000000000000" + } + + # DELEGATOR + # stakeAmount + %{ + id: id, + method: "eth_call", + params: [ + %{ + data: + "0xa697ecff000000000000000000000000db9cb2478d917719c53862008672166808258577000000000000000000000000f3e77c4af5eb2f33afff7619d8d1e751d718a491", + to: _ + }, + "latest" + ] + } -> + %{ + id: id, + result: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000" + } + + # orderedWithdrawAmount + %{ + id: id, + method: "eth_call", + params: [ + %{ + data: + "0xe9ab0300000000000000000000000000db9cb2478d917719c53862008672166808258577000000000000000000000000f3e77c4af5eb2f33afff7619d8d1e751d718a491", + to: _ + }, + "latest" + ] + } -> + %{ + id: id, + result: "0x0000000000000000000000000000000000000000000000000000000000000000" + } + + # maxWithdrawAllowed + %{ + id: id, + method: "eth_call", + params: [ + %{ + data: + "0x6bda1577000000000000000000000000db9cb2478d917719c53862008672166808258577000000000000000000000000f3e77c4af5eb2f33afff7619d8d1e751d718a491", + to: _ + }, + "latest" + ] + } -> + %{ + id: id, + result: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000" + } + + # maxWithdrawOrderAllowed + %{ + id: id, + method: "eth_call", + params: [ + %{ + data: + "0x950a6513000000000000000000000000db9cb2478d917719c53862008672166808258577000000000000000000000000f3e77c4af5eb2f33afff7619d8d1e751d718a491", + to: _ + }, + "latest" + ] + } -> + %{ + id: id, + result: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000" + } + + # orderWithdrawEpoch + %{ + id: id, + method: "eth_call", + params: [ + %{ + data: + "0xa4205967000000000000000000000000db9cb2478d917719c53862008672166808258577000000000000000000000000f3e77c4af5eb2f33afff7619d8d1e751d718a491", + to: _ + }, "latest" ] } -> diff --git a/apps/explorer/test/explorer/token/metadata_retriever_test.exs b/apps/explorer/test/explorer/token/metadata_retriever_test.exs index 71b3f5ef2e..4ef10ea05e 100644 --- a/apps/explorer/test/explorer/token/metadata_retriever_test.exs +++ b/apps/explorer/test/explorer/token/metadata_retriever_test.exs @@ -426,4 +426,51 @@ defmodule Explorer.Token.MetadataRetrieverTest do on_exit(fn -> Application.put_env(:explorer, :token_functions_reader_max_retries, original) end) end end + + test "returns name and symbol when they are bytes32" do + token = insert(:token, contract_address: build(:contract_address)) + + expect( + EthereumJSONRPC.Mox, + :json_rpc, + 1, + fn requests, _opts -> + {:ok, + Enum.map(requests, fn + %{id: id, method: "eth_call", params: [%{data: "0x313ce567", to: _}, "latest"]} -> + %{ + id: id, + result: "0x0000000000000000000000000000000000000000000000000000000000000012" + } + + %{id: id, method: "eth_call", params: [%{data: "0x06fdde03", to: _}, "latest"]} -> + %{ + id: id, + result: "0x4d616b6572000000000000000000000000000000000000000000000000000000" + } + + %{id: id, method: "eth_call", params: [%{data: "0x95d89b41", to: _}, "latest"]} -> + %{ + id: id, + result: "0x4d4b520000000000000000000000000000000000000000000000000000000000" + } + + %{id: id, method: "eth_call", params: [%{data: "0x18160ddd", to: _}, "latest"]} -> + %{ + id: id, + result: "0x00000000000000000000000000000000000000000000d3c21bcecceda1000000" + } + end)} + end + ) + + expected = %{ + decimals: 18, + name: "Maker", + symbol: "MKR", + total_supply: 1_000_000_000_000_000_000_000_000 + } + + assert MetadataRetriever.get_functions_of(token.contract_address_hash) == expected + end end diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index fcde1e13f2..2189b8f601 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -26,7 +26,9 @@ defmodule Explorer.Factory do SmartContract, Token, TokenTransfer, - Transaction + Transaction, + StakingPool, + StakingPoolsDelegator } alias Explorer.Market.MarketHistory @@ -612,22 +614,34 @@ defmodule Explorer.Factory do end def staking_pool_factory do - %{ - address_hash: address_hash(), - metadata: %{ - banned_unitil: 0, - delegators_count: 0, - is_active: true, - is_banned: false, - is_validator: true, - mining_address: address_hash(), - retries_count: 1, - staked_amount: 25, - was_banned_count: 0, - was_validator_count: 1 - }, - name: "anonymous", - primary: true + wei_per_ether = 1_000_000_000_000_000_000 + + %StakingPool{ + staking_address_hash: address_hash(), + mining_address_hash: address_hash(), + banned_until: 0, + delegators_count: 0, + is_active: true, + is_banned: false, + is_validator: true, + staked_amount: wei_per_ether * 500, + self_staked_amount: wei_per_ether * 300, + was_banned_count: 0, + was_validator_count: 1 + } + end + + def staking_pools_delegator_factory do + wei_per_ether = 1_000_000_000_000_000_000 + + %StakingPoolsDelegator{ + pool_address_hash: address_hash(), + delegator_address_hash: address_hash(), + max_ordered_withdraw_allowed: wei_per_ether * 100, + max_withdraw_allowed: wei_per_ether * 50, + ordered_withdraw: wei_per_ether * 600, + stake_amount: wei_per_ether * 200, + ordered_withdraw_epoch: 2 } end end diff --git a/apps/indexer/config/prod/geth.exs b/apps/indexer/config/prod/geth.exs index cf1113119d..859d94319c 100644 --- a/apps/indexer/config/prod/geth.exs +++ b/apps/indexer/config/prod/geth.exs @@ -7,7 +7,7 @@ config :indexer, transport_options: [ http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "https://mainnet.infura.io/8lTvJTKmHPCHazkneJsY", - http_options: [recv_timeout: :timer.minutes(1), timeout: :timer.minutes(1), hackney: [pool: :ethereum_jsonrpc]] + http_options: [recv_timeout: :timer.minutes(10), timeout: :timer.minutes(10), hackney: [pool: :ethereum_jsonrpc]] ], variant: EthereumJSONRPC.Geth ], diff --git a/apps/indexer/lib/indexer/fetcher/staking_pools.ex b/apps/indexer/lib/indexer/fetcher/staking_pools.ex index fe4ab84c28..1576eb56e2 100644 --- a/apps/indexer/lib/indexer/fetcher/staking_pools.ex +++ b/apps/indexer/lib/indexer/fetcher/staking_pools.ex @@ -9,6 +9,7 @@ defmodule Indexer.Fetcher.StakingPools do require Logger alias Explorer.Chain + alias Explorer.Chain.StakingPool alias Explorer.Staking.PoolsReader alias Indexer.BufferedTask alias Indexer.Fetcher.StakingPools.Supervisor, as: StakingPoolsSupervisor @@ -71,7 +72,7 @@ defmodule Indexer.Fetcher.StakingPools do def entry(pool_address) do %{ - staking_address: pool_address, + staking_address_hash: pool_address, retries_count: 0 } end @@ -79,7 +80,7 @@ defmodule Indexer.Fetcher.StakingPools do defp fetch_from_blockchain(addresses) do addresses |> Enum.filter(&(&1.retries_count <= @max_retries)) - |> Enum.map(fn %{staking_address: staking_address} = pool -> + |> Enum.map(fn %{staking_address_hash: staking_address} = pool -> case PoolsReader.pool_data(staking_address) do {:ok, data} -> Map.merge(pool, data) @@ -93,15 +94,22 @@ defmodule Indexer.Fetcher.StakingPools do defp import_pools(pools) do {failed, success} = Enum.reduce(pools, {[], []}, fn - %{error: _error, staking_address: address}, {failed, success} -> - {[address | failed], success} + %{error: _error} = pool, {failed, success} -> + {[pool | failed], success} pool, {failed, success} -> - {failed, [changeset(pool) | success]} + changeset = StakingPool.changeset(%StakingPool{}, pool) + + if changeset.valid? do + {failed, [changeset.changes | success]} + else + {[pool | failed], success} + end end) import_params = %{ - staking_pools: %{params: success}, + staking_pools: %{params: remove_assoc(success)}, + staking_pools_delegators: %{params: delegators_list(success)}, timeout: :infinity } @@ -118,20 +126,15 @@ defmodule Indexer.Fetcher.StakingPools do failed end - defp changeset(%{staking_address: staking_address} = pool) do - {:ok, mining_address} = Chain.Hash.Address.cast(pool[:mining_address]) - - data = - pool - |> Map.delete(:staking_address) - |> Map.put(:mining_address, mining_address) - |> Map.put(:is_pool, true) + defp delegators_list(pools) do + Enum.reduce(pools, [], fn pool, acc -> + pool.delegators + |> Enum.map(&Map.get(&1, :changes)) + |> Enum.concat(acc) + end) + end - %{ - name: "anonymous", - primary: true, - address_hash: staking_address, - metadata: data - } + defp remove_assoc(pools) do + Enum.map(pools, &Map.delete(&1, :delegators)) end end diff --git a/apps/indexer/lib/indexer/temporary/blocks_transactions_mismatch.ex b/apps/indexer/lib/indexer/temporary/blocks_transactions_mismatch.ex index 76f4322379..49b33b7c44 100644 --- a/apps/indexer/lib/indexer/temporary/blocks_transactions_mismatch.ex +++ b/apps/indexer/lib/indexer/temporary/blocks_transactions_mismatch.ex @@ -24,7 +24,7 @@ defmodule Indexer.Temporary.BlocksTransactionsMismatch do @defaults [ flush_interval: :timer.seconds(3), max_batch_size: 10, - max_concurrency: 4, + max_concurrency: 1, task_supervisor: Indexer.Temporary.BlocksTransactionsMismatch.TaskSupervisor, metadata: [fetcher: :blocks_transactions_mismatch] ] @@ -51,7 +51,7 @@ defmodule Indexer.Temporary.BlocksTransactionsMismatch do def init(initial, reducer, _) do query = from(block in Block, - join: transactions in assoc(block, :transactions), + left_join: transactions in assoc(block, :transactions), where: block.consensus and block.refetch_needed, group_by: block.hash, select: {block, count(transactions.hash)} @@ -81,14 +81,19 @@ defmodule Indexer.Temporary.BlocksTransactionsMismatch do defp run_blocks(%Blocks{blocks_params: []}, blocks_data), do: {:retry, blocks_data} defp run_blocks( - %Blocks{transactions_params: transactions_params}, + %Blocks{transactions_params: transactions_params, blocks_params: blocks_params}, blocks_data ) do - found_blocks_map = + blocks_with_transactions_map = transactions_params |> Enum.group_by(&Map.fetch!(&1, :block_hash)) |> Map.new(fn {block_hash, trans_lst} -> {block_hash, Enum.count(trans_lst)} end) + found_blocks_map = + blocks_params + |> Map.new(&{Map.fetch!(&1, :hash), 0}) + |> Map.merge(blocks_with_transactions_map) + {found_blocks_data, missing_blocks_data} = Enum.split_with(blocks_data, fn {block, _trans_num} -> Map.has_key?(found_blocks_map, to_string(block.hash)) diff --git a/apps/indexer/test/indexer/fetcher/staking_pools_test.exs b/apps/indexer/test/indexer/fetcher/staking_pools_test.exs index 13e2c0d7ee..257c9e558c 100644 --- a/apps/indexer/test/indexer/fetcher/staking_pools_test.exs +++ b/apps/indexer/test/indexer/fetcher/staking_pools_test.exs @@ -6,7 +6,7 @@ defmodule Indexer.Fetcher.StakingPoolsTest do alias Indexer.Fetcher.StakingPools alias Explorer.Staking.PoolsReader - alias Explorer.Chain.Address + alias Explorer.Chain.StakingPool @moduletag :capture_log @@ -33,15 +33,15 @@ defmodule Indexer.Fetcher.StakingPoolsTest do success_address = list |> List.first() - |> Map.get(:staking_address) + |> Map.get(:staking_address_hash) get_pool_data_from_blockchain() assert {:retry, retry_list} = StakingPools.run(list, nil) assert Enum.count(retry_list) == 2 - pool = Explorer.Repo.get_by(Address.Name, address_hash: success_address) - assert pool.name == "anonymous" + pool = Explorer.Repo.get_by(StakingPool, staking_address_hash: success_address) + assert pool.is_active == true end end diff --git a/docs/.gitkeep b/docs/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000..f53a43e897 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,323 @@ +

+ + + +

+ +

BlockScout

+

Blockchain Explorer for inspecting and analyzing EVM Chains.

+
+ +[![CircleCI](https://circleci.com/gh/poanetwork/blockscout.svg?style=svg&circle-token=f8823a3d0090407c11f87028c73015a331dbf604)](https://circleci.com/gh/poanetwork/blockscout) [![Coverage Status](https://coveralls.io/repos/github/poanetwork/blockscout/badge.svg?branch=master)](https://coveralls.io/github/poanetwork/blockscout?branch=master) [![Join the chat at https://gitter.im/poanetwork/blockscout](https://badges.gitter.im/poanetwork/blockscout.svg)](https://gitter.im/poanetwork/blockscout?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +
+ +BlockScout provides a comprehensive, easy-to-use interface for users to view, confirm, and inspect transactions on **all EVM** (Ethereum Virtual Machine) blockchains. This includes the Ethereum main and test networks as well as **Ethereum forks and sidechains**. + +Following is an overview of the project and instructions for [getting started](#getting-started). + +Visit the [POA BlockScout forum](https://forum.poa.network/c/blockscout) for additional deployment instructions, FAQs, troubleshooting, and other BlockScout related items. You can also post and answer questions here. + +You can also access the dev chatroom on our [Gitter Channel](https://gitter.im/poanetwork/blockscout). + +## About BlockScout + +BlockScout is an Elixir application that allows users to search transactions, view accounts and balances, and verify smart contracts on the entire Ethereum network including all forks and sidechains. + +Currently available block explorers (i.e. Etherscan and Etherchain) are closed systems which are not independently verifiable. As Ethereum sidechains continue to proliferate in both private and public settings, transparent tools are needed to analyze and validate transactions. + + +### Features + +- [x] **Open source development**: The code is community driven and available for anyone to use, explore and improve. + +- [x] **Real time transaction tracking**: Transactions are updated in real time - no page refresh required. Infinite scrolling is also enabled. + +- [x] **Smart contract interaction**: Users can read and verify Solidity smart contracts and access pre-existing contracts to fast-track development. Support for Vyper, LLL, and Web Assembly contracts is in progress. + +- [x] **Token support**: ERC20 and ERC721 tokens are supported. Future releases will support additional token types including ERC223 and ERC1155. + +- [x] **User customization**: Users can easily deploy on a network and customize the Bootstrap interface. + +- [x] **Ethereum sidechain networks**: BlockScout supports the Ethereum mainnet, Ethereum testnets, POA network, and forks like Ethereum Classic, xDAI, additional sidechains, and private EVM networks. + +### Supported Projects + +| **Hosted Mainnets** | **Hosted Testnets** | **Additional Chains using BlockScout** | +|--------------------------------------------------------|-------------------------------------------------------|----------------------------------------------------| +| [Aerum](https://blockscout.com/aerum/mainnet) | [Goerli Testnet](https://blockscout.com/eth/goerli) | [ARTIS](https://explorer.sigma1.artis.network) | +| [Callisto](https://blockscout.com/callisto/mainnet) | [Kovan Testnet](https://blockscout.com/eth/kovan) | [Ether-1](https://blocks.ether1.wattpool.net/) | +| [Ethereum Classic](https://blockscout.com/etc/mainnet) | [POA Sokol Testnet](https://blockscout.com/poa/sokol) | [Fuse Network](https://explorer.fuse.io/) | +| [Ethereum Mainnet](https://blockscout.com/eth/mainnet) | [Rinkeby Testnet](https://blockscout.com/eth/rinkeby) | [Oasis Labs](https://blockexplorer.oasiscloud.io/) | +| [POA Core Network](https://blockscout.com/poa/core) | [Ropsten Testnet](https://blockscout.com/eth/ropsten) | [Petrichor](https://explorer.petrachor.com/) | +| [RSK](https://blockscout.com/rsk/mainnet) | | [PIRL](http://pirl.es/) | +| [xDai Chain](https://blockscout.com/poa/dai) | | [SafeChain](https://explorer.safechain.io) | +| | | [SpringChain](https://explorer.springrole.com/) | +| | | [Kotti Testnet](https://kottiexplorer.ethernode.io/) | + + +### Visual Interface + +Interface for the POA network _updated 02/2019_ + +![BlockScout Example](explorer_example_2_2019.gif) + + +### Umbrella Project Organization + +This repository is an [umbrella project](https://elixir-lang.org/getting-started/mix-otp/dependencies-and-umbrella-projects.html). Each directory under `apps/` is a separate [Mix](https://hexdocs.pm/mix/Mix.html) project and [OTP application](https://hexdocs.pm/elixir/Application.html), but the projects can use each other as a dependency in their `mix.exs`. + +Each OTP application has a restricted domain. + +| Directory | OTP Application | Namespace | Purpose | +|:------------------------|:--------------------|:------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `apps/ethereum_jsonrpc` | `:ethereum_jsonrpc` | `EthereumJSONRPC` | Ethereum JSONRPC client. It is allowed to know `Explorer`'s param format, but it cannot directly depend on `:explorer` | +| `apps/explorer` | `:explorer` | `Explorer` | Storage for the indexed chain. Can read and write to the backing storage. MUST be able to boot in a read-only mode when run independently from `:indexer`, so cannot depend on `:indexer` as that would start `:indexer` indexing. | +| `apps/block_scout_web` | `:block_scout_web` | `BlockScoutWeb` | Phoenix interface to `:explorer`. The minimum interface to allow web access should go in `:block_scout_web`. Any business rules or interface not tied directly to `Phoenix` or `Plug` should go in `:explorer`. MUST be able to boot in a read-only mode when run independently from `:indexer`, so cannot depend on `:indexer` as that would start `:indexer` indexing. | +| `apps/indexer` | `:indexer` | `Indexer` | Uses `:ethereum_jsonrpc` to index chain and batch import data into `:explorer`. Any process, `Task`, or `GenServer` that automatically reads from the chain and writes to `:explorer` should be in `:indexer`. This restricts automatic writes to `:indexer` and read-only mode can be achieved by not running `:indexer`. | + + +## Getting Started + +### Requirements + +| 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) | +| [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) | +| [Libtool](https://www.gnu.org/software/libtool/) | `brew install libtool` | [Libtool Install Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L62) | +| [Inotify-tools](https://github.com/rvoicilas/inotify-tools/wiki) | Not Required | Ubuntu - `apt-get install inotify-tools` | +| [GCC Compiler](https://gcc.gnu.org/) | `brew install gcc` | [GCC Compiler Example](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L70) | +| [GMP](https://gmplib.org/) | `brew install gmp` | [Install GMP Devel](https://github.com/poanetwork/blockscout-terraform/blob/33f68e816e36dc2fb055911fa0372531f0e956e7/modules/stack/libexec/init.sh#L74) | + +### Build and Run + +#### Playbook Deployment + +We use [Ansible](https://docs.ansible.com/ansible/latest/index.html) & [Terraform](https://www.terraform.io/intro/getting-started/install.html) to build the correct infrastructure to run BlockScout. See [https://github.com/poanetwork/blockscout-terraform](https://github.com/poanetwork/blockscout-terraform) for details and instructions. + +#### Manual Deployment + +See [Manual BlockScout Deployment](https://forum.poa.network/t/manual-blockscout-deployment/2458) for instructions. + +#### Environment Variables + +Our forum contains a [full list of BlockScout environment variables](https://forum.poa.network/t/faq-blockscout-environment-variables/1814). + +#### Configuring EVM Chains + +* **CSS:** Update the import instruction in `apps/block_scout_web/assets/css/theme/_variables.scss` to select a preset css file. This is reflected in the `production-${chain}` branch for each instance. For example, in the `production-xdai` branch, it is set to `@import "dai-variables"`. + +* **ENV:** Update the [environment variables](https://forum.poa.network/t/faq-blockscout-environment-variables/1814) to match the chain specs. + +#### Automating Restarts + +By default `blockscout` does not restart if it crashes. To enable automated +restarts, set the environment variable `HEART_COMMAND` to whatever command you run to start `blockscout`. Configure the heart beat timeout to change how long it waits before considering the application unresponsive. At that point, it will kill the current blockscout instance and execute the `HEART_COMMAND`. By default a crash dump is not written unless you set `ERL_CRASH_DUMP_SECONDS` to a positive or negative integer. See the [heart](http://erlang.org/doc/man/heart.html) documentation for more information. + + +#### CircleCI Updates + +To monitor build status, configure your local [CCMenu](http://ccmenu.org/) with the following url: [`https://circleci.com/gh/poanetwork/blockscout.cc.xml?circle-token=f8823a3d0090407c11f87028c73015a331dbf604`](https://circleci.com/gh/poanetwork/blockscout.cc.xml?circle-token=f8823a3d0090407c11f87028c73015a331dbf604) + + +## Testing + +### Requirements + + * PhantomJS (for wallaby) + +### Running the tests + + 1. Build the assets. + `cd apps/block_scout_web/assets && npm run build; cd -` + + 2. Format the Elixir code. + `mix format` + + 3. Run the test suite with coverage for whole umbrella project. This step can be run with different configuration outlined below. + `mix coveralls.html --umbrella` + + 4. Lint the Elixir code. + `mix credo --strict` + + 5. Run the dialyzer. + `mix dialyzer --halt-exit-status` + + 6. Check the Elixir code for vulnerabilities. + `cd apps/explorer && mix sobelow --config; cd -` + `cd apps/block_scout_web && mix sobelow --config; cd -` + + 7. Lint the JavaScript code. + `cd apps/block_scout_web/assets && npm run eslint; cd -` + + 8. Test the JavaScript code. + `cd apps/block_scout_web/assets && npm run test; cd -` + +#### Parity + +##### Mox + +**This is the default setup. `mix coveralls.html --umbrella` will work on its own, but to be explicit, use the following setup**: + +```shell +export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Parity.Mox +export ETHEREUM_JSONRPC_WEB_SOCKET_CASE=EthereumJSONRPC.WebSocket.Case.Mox +mix coveralls.html --umbrella --exclude no_parity +``` + +##### HTTP / WebSocket + +```shell +export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Parity.HTTPWebSocket +export ETHEREUM_JSONRPC_WEB_SOCKET_CASE=EthereumJSONRPC.WebSocket.Case.Parity +mix coveralls.html --umbrella --exclude no_parity +``` + +| Protocol | URL | +|:----------|:-----------------------------------| +| HTTP | `http://localhost:8545` | +| WebSocket | `ws://localhost:8546` | + +#### Geth + +##### Mox + +```shell +export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Geth.Mox +export ETHEREUM_JSONRPC_WEB_SOCKET_CASE=EthereumJSONRPC.WebSocket.Case.Mox +mix coveralls.html --umbrella --exclude no_geth +``` + +##### HTTP / WebSocket + +```shell +export ETHEREUM_JSONRPC_CASE=EthereumJSONRPC.Case.Geth.HTTPWebSocket +export ETHEREUM_JSONRPC_WEB_SOCKET_CASE=EthereumJSONRPC.WebSocket.Case.Geth +mix coveralls.html --umbrella --exclude no_geth +``` + +| Protocol | URL | +|:----------|:--------------------------------------------------| +| HTTP | `https://mainnet.infura.io/8lTvJTKmHPCHazkneJsY` | +| WebSocket | `wss://mainnet.infura.io/ws/8lTvJTKmHPCHazkneJsY` | + +### API Documentation + +To view Modules and API Reference documentation: + +1. Generate documentation. +`mix docs` +2. View the generated docs. +`open doc/index.html` + +## Front-end + +### Javascript + +All Javascript files are under [apps/block_scout_web/assets/js](https://github.com/poanetwork/blockscout/tree/master/apps/block_scout_web/assets/js) and the main file is [app.js](https://github.com/poanetwork/blockscout/blob/master/apps/block_scout_web/assets/js/app.js). This file imports all javascript used in the application. If you want to create a new JS file consider creating into [/js/pages](https://github.com/poanetwork/blockscout/tree/master/apps/block_scout_web/assets/js/pages) or [/js/lib](https://github.com/poanetwork/blockscout/tree/master/apps/block_scout_web/assets/js/lib), as follows: + +#### js/lib +This folder contains all scripts that can be reused in any page or can be used as a helper to some component. + +#### js/pages +This folder contains the scripts that are specific for some page. + +#### Redux +This project uses Redux to control the state in some pages. There are pages that have things happening in real-time thanks to the Phoenix channels, e.g. Address page, so the page state changes a lot depending on which events it is listening. The redux is also used to load some contents asynchronous, see [async_listing_load.js](https://github.com/poanetwork/blockscout/blob/master/apps/block_scout_web/assets/js/lib/async_listing_load.js). + +To understand how to build new pages that need redux in this project, see the [redux_helpers.js](https://github.com/poanetwork/blockscout/blob/master/apps/block_scout_web/assets/js/lib/redux_helpers.js) + +## Internationalization + +The app is currently internationalized. It is only localized to U.S. English. To translate new strings. + +1. To setup translation file. +`cd apps/block_scout_web; mix gettext.extract --merge; cd -` +2. To edit the new strings, go to `apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po`. + +## Metrics + +BlockScout is setup to export [Prometheus](https://prometheus.io/) metrics at `/metrics`. + +### Prometheus + +1. Install prometheus: `brew install prometheus` +2. Start the web server `iex -S mix phx.server` +3. Start prometheus: `prometheus --config.file=prometheus.yml` + +### Grafana + +1. Install grafana: `brew install grafana` +2. Install Pie Chart panel plugin: `grafana-cli plugins install grafana-piechart-panel` +3. Start grafana: `brew services start grafana` +4. Add Prometheus as a Data Source + 1. `open http://localhost:3000/datasources` + 2. Click "+ Add data source" + 3. Put "Prometheus" for "Name" + 4. Change "Type" to "Prometheus" + 5. Set "URL" to "http://localhost:9090" + 6. Set "Scrape Interval" to "10s" +5. Add the dashboards from https://github.com/deadtrickster/beam-dashboards: + For each `*.json` file in the repo. + 1. `open http://localhost:3000/dashboard/import` + 2. Copy the contents of the JSON file in the "Or paste JSON" entry + 3. Click "Load" +6. View the dashboards. (You will need to click-around and use BlockScout for the web-related metrics to show up.) + +## Tracing + +Blockscout supports tracing via +[Spandex](http://git@github.com:spandex-project/spandex.git). Each application +has its own tracer, that is configured internally to that application. In order +to enable it, visit each application's `config/.ex` and update its tracer +configuration to change `disabled?: true` to `disabled?: false`. Do this for +each application you'd like included in your trace data. + +Currently, only [Datadog](https://www.datadoghq.com/) is supported as a +tracing backend, but more will be added soon. + +### DataDog + +If you would like to use DataDog, after enabling `Spandex`, set +`"DATADOG_HOST"` and `"DATADOG_PORT"` environment variables to the +host/port that your Datadog agent is running on. For more information on +Datadog and the Datadog agent, see their +[documentation](https://docs.datadoghq.com/). + +### Other + +If you want to use a different backend, remove the +`SpandexDatadog.ApiServer` `Supervisor.child_spec` from +`Explorer.Application` and follow any instructions provided in `Spandex` +for setting up that backend. + +## Memory Usage + +The work queues for building the index of all blocks, balances (coin and token), and internal transactions can grow quite large. By default, the soft-limit is 1 GiB, which can be changed in `apps/indexer/config/config.exs`: + +``` +config :indexer, memory_limit: 1 <<< 30 +``` + +Memory usage is checked once per minute. If the soft-limit is reached, the shrinkable work queues will shed half their load. The shed load will be restored from the database, the same as when a restart of the server occurs, so rebuilding the work queue will be slower, but use less memory. + +If all queues are at their minimum size, then no more memory can be reclaimed and an error will be logged. + +## Acknowledgements + +We would like to thank the [EthPrize foundation](http://ethprize.io/) for their funding support. + +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution and pull request protocol. We expect contributors to follow our [code of conduct](CODE_OF_CONDUCT.md) when submitting code or comments. + + +## License + +[![License: GPL v3.0](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) + +This project is licensed under the GNU General Public License v3.0. See the [LICENSE](LICENSE) file for details. diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000000..f48a914259 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,21 @@ + + + + + Document + + + + + + +
+ + + +