Merge pull request #3443 from poanetwork/va-staking-dapp-enhance-blocks-handling

Improve blocks handling in Staking DApp
pull/3450/head
Victor Baranov 4 years ago committed by GitHub
commit 4e520f7ec3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .dialyzer-ignore
  2. 1
      CHANGELOG.md
  3. 72
      apps/block_scout_web/assets/js/lib/queue.js
  4. 117
      apps/block_scout_web/assets/js/pages/stakes.js
  5. 2
      apps/block_scout_web/assets/js/pages/stakes/utils.js
  6. 3
      apps/block_scout_web/lib/block_scout_web/application.ex
  7. 36
      apps/block_scout_web/lib/block_scout_web/channels/stakes_channel.ex
  8. 31
      apps/block_scout_web/lib/block_scout_web/controllers/stakes_controller.ex
  9. 12
      apps/block_scout_web/lib/block_scout_web/notifier.ex
  10. 1
      apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex
  11. 26
      apps/block_scout_web/lib/block_scout_web/staking_event_handler.ex
  12. 18
      apps/block_scout_web/priv/gettext/default.pot
  13. 18
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  14. 12
      apps/block_scout_web/test/block_scout_web/channels/stakes_channel_test.exs
  15. 2
      apps/explorer/lib/explorer/chain/events/publisher.ex
  16. 4
      apps/explorer/lib/explorer/chain/events/subscriber.ex
  17. 34
      apps/explorer/lib/explorer/smart_contract/reader.ex
  18. 95
      apps/explorer/lib/explorer/staking/contract_reader.ex
  19. 128
      apps/explorer/lib/explorer/staking/contract_state.ex
  20. 16
      apps/explorer/lib/explorer/staking/stake_snapshotting.ex

@ -25,4 +25,4 @@ lib/explorer/exchange_rates/source.ex:107
lib/explorer/smart_contract/verifier.ex:89 lib/explorer/smart_contract/verifier.ex:89
lib/block_scout_web/templates/address_contract/index.html.eex:117 lib/block_scout_web/templates/address_contract/index.html.eex:117
lib/explorer/staking/stake_snapshotting.ex:14: Function do_snapshotting/6 has no local return lib/explorer/staking/stake_snapshotting.ex:14: Function do_snapshotting/6 has no local return
lib/explorer/staking/stake_snapshotting.ex:179 lib/explorer/staking/stake_snapshotting.ex:183

@ -18,6 +18,7 @@
### Fixes ### Fixes
- [#3449](https://github.com/poanetwork/blockscout/pull/3449) - Correct avg time calculation - [#3449](https://github.com/poanetwork/blockscout/pull/3449) - Correct avg time calculation
- [#3443](https://github.com/poanetwork/blockscout/pull/3443) - Improve blocks handling in Staking DApp
- [#3440](https://github.com/poanetwork/blockscout/pull/3440) - Rewrite missing blocks range query - [#3440](https://github.com/poanetwork/blockscout/pull/3440) - Rewrite missing blocks range query
- [#3439](https://github.com/poanetwork/blockscout/pull/3439) - Dark mode color fixes (search, charts) - [#3439](https://github.com/poanetwork/blockscout/pull/3439) - Dark mode color fixes (search, charts)
- [#3437](https://github.com/poanetwork/blockscout/pull/3437) - Fix Postgres Docker container - [#3437](https://github.com/poanetwork/blockscout/pull/3437) - Fix Postgres Docker container

@ -0,0 +1,72 @@
/*
Queue.js
A function to represent a queue
Created by Kate Morley - http://code.iamkate.com/ - and released under the terms
of the CC0 1.0 Universal legal code:
http://creativecommons.org/publicdomain/zero/1.0/legalcode
*/
/* eslint-disable */
/* Creates a new queue. A queue is a first-in-first-out (FIFO) data structure -
* items are added to the end of the queue and removed from the front.
*/
module.exports = function Queue(){
// initialise the queue and offset
var queue = [];
var offset = 0;
// Returns the length of the queue.
this.getLength = function(){
return (queue.length - offset);
}
// Returns true if the queue is empty, and false otherwise.
this.isEmpty = function(){
return (queue.length == 0);
}
/* Enqueues the specified item. The parameter is:
*
* item - the item to enqueue
*/
this.enqueue = function(item){
queue.push(item);
}
/* Dequeues an item and returns it. If the queue is empty, the value
* 'undefined' is returned.
*/
this.dequeue = function(){
// if the queue is empty, return immediately
if (queue.length == 0) return undefined;
// store the item at the front of the queue
var item = queue[offset];
// increment the offset and remove the free space if necessary
if (++ offset * 2 >= queue.length){
queue = queue.slice(offset);
offset = 0;
}
// return the dequeued item
return item;
}
/* Returns the item at the front of the queue (without dequeuing it). If the
* queue is empty then undefined is returned.
*/
this.peek = function(){
return (queue.length > 0 ? queue[offset] : undefined);
}
}

@ -5,6 +5,7 @@ import _ from 'lodash'
import { subscribeChannel } from '../socket' import { subscribeChannel } from '../socket'
import { connectElements } from '../lib/redux_helpers.js' import { connectElements } from '../lib/redux_helpers.js'
import { createAsyncLoadStore, refreshPage } from '../lib/async_listing_load' import { createAsyncLoadStore, refreshPage } from '../lib/async_listing_load'
import Queue from '../lib/queue'
import Web3 from 'web3' import Web3 from 'web3'
import { openPoolInfoModal } from './stakes/validator_info' import { openPoolInfoModal } from './stakes/validator_info'
import { openDelegatorsListModal } from './stakes/delegators_list' import { openDelegatorsListModal } from './stakes/delegators_list'
@ -25,6 +26,7 @@ export const initialState = {
blockRewardContract: null, blockRewardContract: null,
channel: null, channel: null,
currentBlockNumber: 0, // current block number currentBlockNumber: 0, // current block number
finishRequestResolve: null,
lastEpochNumber: 0, lastEpochNumber: 0,
network: null, network: null,
refreshBlockNumber: 0, // last page refresh block number refreshBlockNumber: 0, // last page refresh block number
@ -86,7 +88,8 @@ export function reducer (state = initialState, action) {
} }
case 'PAGE_REFRESHED': { case 'PAGE_REFRESHED': {
return Object.assign({}, state, { return Object.assign({}, state, {
refreshBlockNumber: action.refreshBlockNumber refreshBlockNumber: action.refreshBlockNumber,
finishRequestResolve: action.finishRequestResolve
}) })
} }
case 'RECEIVED_UPDATE': { case 'RECEIVED_UPDATE': {
@ -108,6 +111,12 @@ export function reducer (state = initialState, action) {
} }
case 'FINISH_REQUEST': { case 'FINISH_REQUEST': {
$(stakesPageSelector).fadeTo(0, 1) $(stakesPageSelector).fadeTo(0, 1)
if (state.finishRequestResolve) {
state.finishRequestResolve()
return Object.assign({}, state, {
finishRequestResolve: null
})
}
return state return state
} }
default: default:
@ -139,10 +148,17 @@ if ($stakesPage.length) {
const channel = subscribeChannel('stakes:staking_update') const channel = subscribeChannel('stakes:staking_update')
store.dispatch({ type: 'CHANNEL_CONNECTED', channel }) store.dispatch({ type: 'CHANNEL_CONNECTED', channel })
channel.on('staking_update', msg => { let updating = false
async function onStakingUpdate (msg) { // eslint-disable-line no-inner-declarations
const state = store.getState() const state = store.getState()
if (state.finishRequestResolve || updating) {
return
}
updating = true
const firstMsg = (state.currentBlockNumber === 0) const firstMsg = (state.currentBlockNumber === 0)
const accountChanged = (msg.account !== state.account)
store.dispatch({ type: 'BLOCK_CREATED', currentBlockNumber: msg.block_number }) store.dispatch({ type: 'BLOCK_CREATED', currentBlockNumber: msg.block_number })
@ -153,7 +169,7 @@ if ($stakesPage.length) {
$stakesTop.html(msg.top_html) $stakesTop.html(msg.top_html)
if (accountChanged) { if (accountChanged(msg.account, state)) {
store.dispatch({ type: 'ACCOUNT_UPDATED', account: msg.account }) store.dispatch({ type: 'ACCOUNT_UPDATED', account: msg.account })
resetFilterMy(store) resetFilterMy(store)
} }
@ -162,16 +178,15 @@ if ($stakesPage.length) {
msg.staking_allowed !== state.stakingAllowed || msg.staking_allowed !== state.stakingAllowed ||
msg.epoch_number > state.lastEpochNumber || msg.epoch_number > state.lastEpochNumber ||
msg.validator_set_apply_block !== state.validatorSetApplyBlock || msg.validator_set_apply_block !== state.validatorSetApplyBlock ||
(state.refreshInterval && msg.block_number >= state.refreshBlockNumber + state.refreshInterval) (state.refreshInterval && msg.block_number >= state.refreshBlockNumber + state.refreshInterval) ||
accountChanged(msg.account, state) || msg.by_set_account
) { ) {
if (firstMsg || accountChanged) { if (firstMsg) {
// Don't refresh the page for the first load // Don't refresh the page for the first load
// as it is already refreshed by `initialize` function. // as it is already refreshed by the `initialize` function.
// Also, don't refresh that after reconnect
// as it is already refreshed by `setAccount` function.
msg.dont_refresh_page = true msg.dont_refresh_page = true
} }
reloadPoolList(msg, store) await reloadPoolList(msg, store)
} }
const refreshBlockNumber = store.getState().refreshBlockNumber const refreshBlockNumber = store.getState().refreshBlockNumber
@ -185,13 +200,36 @@ if ($stakesPage.length) {
const $refreshInformerLink = $refreshInformer.find('a') const $refreshInformerLink = $refreshInformer.find('a')
$refreshInformerLink.off('click') $refreshInformerLink.off('click')
$refreshInformerLink.on('click', (event) => { $refreshInformerLink.on('click', async (event) => {
event.preventDefault() event.preventDefault()
if (!store.getState().finishRequestResolve) {
$refreshInformer.hide() $refreshInformer.hide()
$stakesPage.fadeTo(0, 0.5) $stakesPage.fadeTo(0, 0.5)
delete msg.dont_refresh_page // refresh anyway delete msg.dont_refresh_page // refresh anyway
reloadPoolList(msg, store) await reloadPoolList(msg, store)
}
}) })
updating = false
}
const messagesQueue = new Queue()
setTimeout(async () => {
while (true) {
const msg = messagesQueue.dequeue()
if (msg) {
// Synchronously handle the message
await onStakingUpdate(msg)
} else {
// Wait for the next message
await new Promise(resolve => setTimeout(resolve, 10))
}
}
}, 0)
channel.on('staking_update', msg => {
messagesQueue.enqueue(msg)
}) })
channel.on('contracts', msg => { channel.on('contracts', msg => {
@ -266,6 +304,10 @@ if ($stakesPage.length) {
initialize(store) initialize(store)
} }
function accountChanged (account, state) {
return account !== state.account
}
function hideCurrentModal () { function hideCurrentModal () {
const $modal = currentModal() const $modal = currentModal()
if ($modal) $modal.modal('hide') if ($modal) $modal.modal('hide')
@ -298,10 +340,12 @@ async function checkNetworkAndAccount (store, web3) {
const accounts = await web3.eth.getAccounts() const accounts = await web3.eth.getAccounts()
const account = accounts[0] ? accounts[0].toLowerCase() : null const account = accounts[0] ? accounts[0].toLowerCase() : null
if (account !== state.account) { if (accountChanged(account, state) && await setAccount(account, store)) {
setAccount(account, store) refresh = false // because refreshing will be done by `onStakingUpdate`
} else if (refresh) { }
refreshPageWrapper(store)
if (refresh) {
await refreshPageWrapper(store)
} }
setTimeout(() => { setTimeout(() => {
@ -320,21 +364,29 @@ async function loginByMetamask () {
} }
} }
function refreshPageWrapper (store) { async function refreshPageWrapper (store) {
while (store.getState().finishRequestResolve) {
// Don't let anything simultaneously refresh the page
await new Promise(resolve => setTimeout(resolve, 10))
}
let currentBlockNumber = store.getState().currentBlockNumber let currentBlockNumber = store.getState().currentBlockNumber
if (!currentBlockNumber) { if (!currentBlockNumber) {
currentBlockNumber = $('[data-block-number]', $stakesTop).data('blockNumber') currentBlockNumber = $('[data-block-number]', $stakesTop).data('blockNumber')
} }
refreshPage(store) await new Promise(resolve => {
store.dispatch({ store.dispatch({
type: 'PAGE_REFRESHED', type: 'PAGE_REFRESHED',
refreshBlockNumber: currentBlockNumber refreshBlockNumber: currentBlockNumber,
finishRequestResolve: resolve
}) })
$refreshInformer.hide() $refreshInformer.hide()
refreshPage(store)
})
} }
function reloadPoolList (msg, store) { async function reloadPoolList (msg, store) {
store.dispatch({ store.dispatch({
type: 'RECEIVED_UPDATE', type: 'RECEIVED_UPDATE',
lastEpochNumber: msg.epoch_number, lastEpochNumber: msg.epoch_number,
@ -343,7 +395,7 @@ function reloadPoolList (msg, store) {
validatorSetApplyBlock: msg.validator_set_apply_block validatorSetApplyBlock: msg.validator_set_apply_block
}) })
if (!msg.dont_refresh_page) { if (!msg.dont_refresh_page) {
refreshPageWrapper(store) await refreshPageWrapper(store)
} }
} }
@ -353,6 +405,7 @@ function resetFilterMy (store) {
} }
function setAccount (account, store) { function setAccount (account, store) {
return new Promise(resolve => {
store.dispatch({ type: 'ACCOUNT_UPDATED', account }) store.dispatch({ type: 'ACCOUNT_UPDATED', account })
if (!account) { if (!account) {
resetFilterMy(store) resetFilterMy(store)
@ -364,13 +417,20 @@ function setAccount (account, store) {
store.getState().channel.push( store.getState().channel.push(
'set_account', account 'set_account', account
).receive('ok', () => { ).receive('ok', () => {
$addressField.html(account) $addressField.html(`
refreshPageWrapper(store) <div data-placement="bottom" data-toggle="tooltip" title="${account}">
${account}
</div>
`)
hideCurrentModal() hideCurrentModal()
resolve(true)
}).receive('error', () => { }).receive('error', () => {
openErrorModal('Change account', errorMsg, true) openErrorModal('Change account', errorMsg, true)
resolve(false)
}).receive('timeout', () => { }).receive('timeout', () => {
openErrorModal('Change account', errorMsg, true) openErrorModal('Change account', errorMsg, true)
resolve(false)
})
}) })
} }
@ -395,6 +455,17 @@ function updateFilters (store, filterType) {
const filterBanned = $stakesPage.find('[pool-filter-banned]') const filterBanned = $stakesPage.find('[pool-filter-banned]')
const filterMy = $stakesPage.find('[pool-filter-my]') const filterMy = $stakesPage.find('[pool-filter-my]')
const state = store.getState() const state = store.getState()
if (state.finishRequestResolve) {
if (filterType === 'my') {
filterMy.prop('checked', !filterMy.prop('checked'))
} else {
filterBanned.prop('checked', !filterBanned.prop('checked'))
}
openWarningModal('Still loading', 'The previous request to load pool list is not yet finished. Please, wait...')
return
}
if (filterType === 'my' && !state.account) { if (filterType === 'my' && !state.account) {
filterMy.prop('checked', false) filterMy.prop('checked', false)
openWarningModal('Unauthorized', 'Please login with MetaMask') openWarningModal('Unauthorized', 'Please login with MetaMask')

@ -10,8 +10,8 @@ export async function makeContractCall (call, store, gasLimit, callbackFunc) {
if (!callbackFunc) { if (!callbackFunc) {
callbackFunc = function (errorMessage) { callbackFunc = function (errorMessage) {
if (!errorMessage) { if (!errorMessage) {
state.refreshPageFunc(store)
openSuccessModal('Success', 'Transaction is confirmed.') openSuccessModal('Success', 'Transaction is confirmed.')
state.refreshPageFunc(store)
} else { } else {
openErrorModal('Error', errorMessage) openErrorModal('Error', errorMessage)
} }

@ -7,7 +7,7 @@ defmodule BlockScoutWeb.Application do
alias BlockScoutWeb.Counters.BlocksIndexedCounter alias BlockScoutWeb.Counters.BlocksIndexedCounter
alias BlockScoutWeb.{Endpoint, Prometheus} alias BlockScoutWeb.{Endpoint, Prometheus}
alias BlockScoutWeb.RealtimeEventHandler alias BlockScoutWeb.{RealtimeEventHandler, StakingEventHandler}
def start(_type, _args) do def start(_type, _args) do
import Supervisor.Spec import Supervisor.Spec
@ -22,6 +22,7 @@ defmodule BlockScoutWeb.Application do
supervisor(Endpoint, []), supervisor(Endpoint, []),
{Absinthe.Subscription, Endpoint}, {Absinthe.Subscription, Endpoint},
{RealtimeEventHandler, name: RealtimeEventHandler}, {RealtimeEventHandler, name: RealtimeEventHandler},
{StakingEventHandler, name: StakingEventHandler},
{BlocksIndexedCounter, name: BlocksIndexedCounter} {BlocksIndexedCounter, name: BlocksIndexedCounter}
] ]

@ -63,18 +63,22 @@ defmodule BlockScoutWeb.StakesChannel do
|> assign(:mining_address, mining_address) |> assign(:mining_address, mining_address)
|> push_contracts() |> push_contracts()
handle_out( data =
"staking_update", case Map.fetch(socket.assigns, :staking_update_data) do
{:ok, staking_update_data} ->
staking_update_data
_ ->
%{ %{
block_number: BlockNumber.get_max(), block_number: BlockNumber.get_max(),
dont_refresh_page: true,
epoch_number: ContractState.get(:epoch_number, 0), epoch_number: ContractState.get(:epoch_number, 0),
staking_allowed: ContractState.get(:staking_allowed, false), staking_allowed: ContractState.get(:staking_allowed, false),
staking_token_defined: ContractState.get(:token, nil) != nil, staking_token_defined: ContractState.get(:token, nil) != nil,
validator_set_apply_block: ContractState.get(:validator_set_apply_block, 0) validator_set_apply_block: ContractState.get(:validator_set_apply_block, 0)
}, }
socket end
)
handle_out("staking_update", Map.merge(data, %{by_set_account: true}), socket)
{:reply, :ok, socket} {:reply, :ok, socket}
end end
@ -399,16 +403,30 @@ defmodule BlockScoutWeb.StakesChannel do
end end
def handle_out("staking_update", data, socket) do def handle_out("staking_update", data, socket) do
dont_refresh_page = by_set_account =
case Map.fetch(data, :dont_refresh_page) do case Map.fetch(data, :by_set_account) do
{:ok, value} -> value {:ok, value} -> value
_ -> false _ -> false
end end
socket =
if by_set_account do
# if :by_set_account is in the `data`,
# it means that this function was called by
# handle_in("set_account", ...), so we
# shouldn't assign the incoming data to the socket
socket
else
# otherwise, we should do the assignment
# to use the incoming data later by
# handle_in("set_account", ...) and StakesController.render_top
assign(socket, :staking_update_data, data)
end
push(socket, "staking_update", %{ push(socket, "staking_update", %{
account: socket.assigns[:account], account: socket.assigns[:account],
block_number: data.block_number, block_number: data.block_number,
dont_refresh_page: dont_refresh_page, by_set_account: by_set_account,
epoch_number: data.epoch_number, epoch_number: data.epoch_number,
staking_allowed: data.staking_allowed, staking_allowed: data.staking_allowed,
staking_token_defined: data.staking_token_defined, staking_token_defined: data.staking_token_defined,

@ -19,11 +19,21 @@ defmodule BlockScoutWeb.StakesController do
# when a new block appears (see `staking_update` event handled in `StakesChannel`), # when a new block appears (see `staking_update` event handled in `StakesChannel`),
# or when the page is loaded for the first time or reloaded by a user (i.e. it is called by the `render_template(filter, conn, _)`) # or when the page is loaded for the first time or reloaded by a user (i.e. it is called by the `render_template(filter, conn, _)`)
def render_top(conn) do def render_top(conn) do
active_pools_length = ContractState.get(:active_pools_length, 0) staking_data =
block_number = BlockNumber.get_max() case Map.fetch(conn.assigns, :staking_update_data) do
epoch_end_block = ContractState.get(:epoch_end_block, 0) {:ok, data} ->
epoch_number = ContractState.get(:epoch_number, 0) data
max_candidates = ContractState.get(:max_candidates, 0)
_ ->
%{
active_pools_length: ContractState.get(:active_pools_length, 0),
block_number: BlockNumber.get_max(),
epoch_end_block: ContractState.get(:epoch_end_block, 0),
epoch_number: ContractState.get(:epoch_number, 0),
max_candidates: ContractState.get(:max_candidates, 0)
}
end
token = ContractState.get(:token, %Token{}) token = ContractState.get(:token, %Token{})
account = account =
@ -38,14 +48,17 @@ defmodule BlockScoutWeb.StakesController do
}) })
end end
epoch_end_in = if epoch_end_block - block_number >= 0, do: epoch_end_block - block_number, else: 0 epoch_end_in =
if staking_data.epoch_end_block - staking_data.block_number >= 0,
do: staking_data.epoch_end_block - staking_data.block_number,
else: 0
View.render_to_string(StakesView, "_stakes_top.html", View.render_to_string(StakesView, "_stakes_top.html",
account: account, account: account,
block_number: block_number, block_number: staking_data.block_number,
candidates_limit_reached: active_pools_length >= max_candidates, candidates_limit_reached: staking_data.active_pools_length >= staking_data.max_candidates,
epoch_end_in: epoch_end_in, epoch_end_in: epoch_end_in,
epoch_number: epoch_number, epoch_number: staking_data.epoch_number,
token: token token: token
) )
end end

@ -7,13 +7,11 @@ defmodule BlockScoutWeb.Notifier do
alias BlockScoutWeb.{AddressContractVerificationView, Endpoint} alias BlockScoutWeb.{AddressContractVerificationView, Endpoint}
alias Explorer.{Chain, Market, Repo} alias Explorer.{Chain, Market, Repo}
alias Explorer.Chain.{Address, InternalTransaction, TokenTransfer, Transaction} alias Explorer.Chain.{Address, InternalTransaction, TokenTransfer, Transaction}
alias Explorer.Chain.Cache.BlockNumber
alias Explorer.Chain.Supply.RSK alias Explorer.Chain.Supply.RSK
alias Explorer.Chain.Transaction.History.TransactionStats alias Explorer.Chain.Transaction.History.TransactionStats
alias Explorer.Counters.AverageBlockTime alias Explorer.Counters.AverageBlockTime
alias Explorer.ExchangeRates.Token alias Explorer.ExchangeRates.Token
alias Explorer.SmartContract.{Solidity.CodeCompiler, Solidity.CompilerVersion} alias Explorer.SmartContract.{Solidity.CodeCompiler, Solidity.CompilerVersion}
alias Explorer.Staking.ContractState
alias Phoenix.View alias Phoenix.View
def handle_event({:chain_event, :addresses, type, addresses}) when type in [:realtime, :on_demand] do def handle_event({:chain_event, :addresses, type, addresses}) when type in [:realtime, :on_demand] do
@ -107,16 +105,6 @@ defmodule BlockScoutWeb.Notifier do
}) })
end end
def handle_event({:chain_event, :staking_update}) do
Endpoint.broadcast("stakes:staking_update", "staking_update", %{
block_number: BlockNumber.get_max(),
epoch_number: ContractState.get(:epoch_number, 0),
staking_allowed: ContractState.get(:staking_allowed, false),
staking_token_defined: ContractState.get(:token, nil) != nil,
validator_set_apply_block: ContractState.get(:validator_set_apply_block, 0)
})
end
def handle_event({:chain_event, :internal_transactions, :realtime, internal_transactions}) do def handle_event({:chain_event, :internal_transactions, :realtime, internal_transactions}) do
internal_transactions internal_transactions
|> Stream.map( |> Stream.map(

@ -28,7 +28,6 @@ defmodule BlockScoutWeb.RealtimeEventHandler do
# Does not come from the indexer # Does not come from the indexer
Subscriber.to(:exchange_rate) Subscriber.to(:exchange_rate)
Subscriber.to(:transaction_stats) Subscriber.to(:transaction_stats)
Subscriber.to(:staking_update)
{:ok, []} {:ok, []}
end end

@ -0,0 +1,26 @@
defmodule BlockScoutWeb.StakingEventHandler do
@moduledoc """
Subscribing process for broadcast events from staking app.
"""
use GenServer
alias BlockScoutWeb.Endpoint
alias Explorer.Chain.Events.Subscriber
def start_link(_) do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
@impl true
def init([]) do
Subscriber.to(:staking_update, :realtime)
{:ok, []}
end
@impl true
def handle_info({:chain_event, :staking_update, :realtime, data}, state) do
Endpoint.broadcast("stakes:staking_update", "staking_update", data)
{:noreply, state}
end
end

@ -2224,7 +2224,7 @@ msgid "It's me!"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/channels/stakes_channel.ex:680 #: lib/block_scout_web/channels/stakes_channel.ex:698
msgid "JSON RPC error" msgid "JSON RPC error"
msgstr "" msgstr ""
@ -2299,7 +2299,7 @@ msgid "Pool"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/channels/stakes_channel.ex:735 #: lib/block_scout_web/channels/stakes_channel.ex:753
msgid "Pools searching is already in progress for this address" msgid "Pools searching is already in progress for this address"
msgstr "" msgstr ""
@ -2332,7 +2332,7 @@ msgid "Remove My Pool"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/channels/stakes_channel.ex:776 #: lib/block_scout_web/channels/stakes_channel.ex:794
msgid "Reward calculating is already in progress for this address" msgid "Reward calculating is already in progress for this address"
msgstr "" msgstr ""
@ -2412,7 +2412,7 @@ msgid "Stakes Ratio"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/channels/stakes_channel.ex:779 #: lib/block_scout_web/channels/stakes_channel.ex:797
msgid "Staking epochs are not specified or not in the allowed range" msgid "Staking epochs are not specified or not in the allowed range"
msgstr "" msgstr ""
@ -2507,19 +2507,19 @@ msgid "Unable to find any pools you could claim a reward from."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/channels/stakes_channel.ex:742 #: lib/block_scout_web/channels/stakes_channel.ex:760
#: lib/block_scout_web/channels/stakes_channel.ex:789 #: lib/block_scout_web/channels/stakes_channel.ex:807
msgid "Unknown address of Staking contract. Please, contact support" msgid "Unknown address of Staking contract. Please, contact support"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/channels/stakes_channel.ex:782 #: lib/block_scout_web/channels/stakes_channel.ex:800
msgid "Unknown pool staking address. Please, contact support" msgid "Unknown pool staking address. Please, contact support"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/channels/stakes_channel.ex:738 #: lib/block_scout_web/channels/stakes_channel.ex:756
#: lib/block_scout_web/channels/stakes_channel.ex:785 #: lib/block_scout_web/channels/stakes_channel.ex:803
msgid "Unknown staker address. Please, choose your account in MetaMask" msgid "Unknown staker address. Please, choose your account in MetaMask"
msgstr "" msgstr ""

@ -2224,7 +2224,7 @@ msgid "It's me!"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/channels/stakes_channel.ex:680 #: lib/block_scout_web/channels/stakes_channel.ex:698
msgid "JSON RPC error" msgid "JSON RPC error"
msgstr "" msgstr ""
@ -2299,7 +2299,7 @@ msgid "Pool"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/channels/stakes_channel.ex:735 #: lib/block_scout_web/channels/stakes_channel.ex:753
msgid "Pools searching is already in progress for this address" msgid "Pools searching is already in progress for this address"
msgstr "" msgstr ""
@ -2332,7 +2332,7 @@ msgid "Remove My Pool"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/channels/stakes_channel.ex:776 #: lib/block_scout_web/channels/stakes_channel.ex:794
msgid "Reward calculating is already in progress for this address" msgid "Reward calculating is already in progress for this address"
msgstr "" msgstr ""
@ -2412,7 +2412,7 @@ msgid "Stakes Ratio"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/channels/stakes_channel.ex:779 #: lib/block_scout_web/channels/stakes_channel.ex:797
msgid "Staking epochs are not specified or not in the allowed range" msgid "Staking epochs are not specified or not in the allowed range"
msgstr "" msgstr ""
@ -2507,19 +2507,19 @@ msgid "Unable to find any pools you could claim a reward from."
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/channels/stakes_channel.ex:742 #: lib/block_scout_web/channels/stakes_channel.ex:760
#: lib/block_scout_web/channels/stakes_channel.ex:789 #: lib/block_scout_web/channels/stakes_channel.ex:807
msgid "Unknown address of Staking contract. Please, contact support" msgid "Unknown address of Staking contract. Please, contact support"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/channels/stakes_channel.ex:782 #: lib/block_scout_web/channels/stakes_channel.ex:800
msgid "Unknown pool staking address. Please, contact support" msgid "Unknown pool staking address. Please, contact support"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/channels/stakes_channel.ex:738 #: lib/block_scout_web/channels/stakes_channel.ex:756
#: lib/block_scout_web/channels/stakes_channel.ex:785 #: lib/block_scout_web/channels/stakes_channel.ex:803
msgid "Unknown staker address. Please, choose your account in MetaMask" msgid "Unknown staker address. Please, choose your account in MetaMask"
msgstr "" msgstr ""

@ -1,13 +1,21 @@
defmodule BlockScoutWeb.StakesChannelTest do defmodule BlockScoutWeb.StakesChannelTest do
use BlockScoutWeb.ChannelCase use BlockScoutWeb.ChannelCase
alias BlockScoutWeb.Notifier alias BlockScoutWeb.StakingEventHandler
test "subscribed user is notified of staking_update event" do test "subscribed user is notified of staking_update event" do
topic = "stakes:staking_update" topic = "stakes:staking_update"
@endpoint.subscribe(topic) @endpoint.subscribe(topic)
Notifier.handle_event({:chain_event, :staking_update}) data = %{
block_number: 76,
epoch_number: 0,
staking_allowed: false,
staking_token_defined: false,
validator_set_apply_block: 0
}
StakingEventHandler.handle_info({:chain_event, :staking_update, :realtime, data}, nil)
receive do receive do
%Phoenix.Socket.Broadcast{topic: ^topic, event: "staking_update", payload: %{epoch_number: _}} -> %Phoenix.Socket.Broadcast{topic: ^topic, event: "staking_update", payload: %{epoch_number: _}} ->

@ -3,7 +3,7 @@ defmodule Explorer.Chain.Events.Publisher do
Publishes events related to the Chain context. Publishes events related to the Chain context.
""" """
@allowed_events ~w(addresses address_coin_balances address_token_balances blocks block_rewards internal_transactions last_block_number token_transfers transactions contract_verification_result)a @allowed_events ~w(addresses address_coin_balances address_token_balances blocks block_rewards internal_transactions last_block_number staking_update token_transfers transactions contract_verification_result)a
def broadcast(_data, false), do: :ok def broadcast(_data, false), do: :ok

@ -3,11 +3,11 @@ defmodule Explorer.Chain.Events.Subscriber do
Subscribes to events related to the Chain context. Subscribes to events related to the Chain context.
""" """
@allowed_broadcast_events ~w(addresses address_coin_balances address_token_balances blocks block_rewards internal_transactions last_block_number token_transfers transactions contract_verification_result)a @allowed_broadcast_events ~w(addresses address_coin_balances address_token_balances blocks block_rewards internal_transactions last_block_number staking_update token_transfers transactions contract_verification_result)a
@allowed_broadcast_types ~w(catchup realtime on_demand contract_verification_result)a @allowed_broadcast_types ~w(catchup realtime on_demand contract_verification_result)a
@allowed_events ~w(exchange_rate transaction_stats staking_update)a @allowed_events ~w(exchange_rate transaction_stats)a
@type broadcast_type :: :realtime | :catchup | :on_demand @type broadcast_type :: :realtime | :catchup | :on_demand

@ -94,22 +94,7 @@ defmodule Explorer.SmartContract.Reader do
functions() functions()
) :: functions_results() ) :: functions_results()
def query_contract(contract_address, abi, functions) do def query_contract(contract_address, abi, functions) do
requests = query_contract_inner(contract_address, abi, functions, nil, nil)
functions
|> Enum.map(fn {method_id, args} ->
%{
contract_address: contract_address,
method_id: method_id,
args: args
}
end)
requests
|> query_contracts(abi)
|> Enum.zip(requests)
|> Enum.into(%{}, fn {response, request} ->
{request.method_id, response}
end)
end end
@spec query_contract( @spec query_contract(
@ -119,6 +104,20 @@ defmodule Explorer.SmartContract.Reader do
functions() functions()
) :: functions_results() ) :: functions_results()
def query_contract(contract_address, from, abi, functions) do def query_contract(contract_address, from, abi, functions) do
query_contract_inner(contract_address, abi, functions, nil, from)
end
@spec query_contract_by_block_number(
String.t(),
term(),
functions(),
non_neg_integer()
) :: functions_results()
def query_contract_by_block_number(contract_address, abi, functions, block_number) do
query_contract_inner(contract_address, abi, functions, block_number, nil)
end
defp query_contract_inner(contract_address, abi, functions, block_number, from) do
requests = requests =
functions functions
|> Enum.map(fn {method_id, args} -> |> Enum.map(fn {method_id, args} ->
@ -126,7 +125,8 @@ defmodule Explorer.SmartContract.Reader do
contract_address: contract_address, contract_address: contract_address,
from: from, from: from,
method_id: method_id, method_id: method_id,
args: args args: args,
block_number: block_number
} }
end) end)

@ -5,38 +5,38 @@ defmodule Explorer.Staking.ContractReader do
alias Explorer.SmartContract.Reader alias Explorer.SmartContract.Reader
def global_requests do def global_requests(block_number) do
[ [
# 673a2a1f = keccak256(getPools()) # 673a2a1f = keccak256(getPools())
active_pools: {:staking, "673a2a1f", []}, active_pools: {:staking, "673a2a1f", [], block_number},
# 8c2243ae = keccak256(stakingEpochEndBlock()) # 8c2243ae = keccak256(stakingEpochEndBlock())
epoch_end_block: {:staking, "8c2243ae", []}, epoch_end_block: {:staking, "8c2243ae", [], block_number},
# 794c0c68 = keccak256(stakingEpoch()) # 794c0c68 = keccak256(stakingEpoch())
epoch_number: {:staking, "794c0c68", []}, epoch_number: {:staking, "794c0c68", [], block_number},
# 7069e746 = keccak256(stakingEpochStartBlock()) # 7069e746 = keccak256(stakingEpochStartBlock())
epoch_start_block: {:staking, "7069e746", []}, epoch_start_block: {:staking, "7069e746", [], block_number},
# df6f55f5 = keccak256(getPoolsInactive()) # df6f55f5 = keccak256(getPoolsInactive())
inactive_pools: {:staking, "df6f55f5", []}, inactive_pools: {:staking, "df6f55f5", [], block_number},
# f0786096 = keccak256(MAX_CANDIDATES()) # f0786096 = keccak256(MAX_CANDIDATES())
max_candidates: {:staking, "f0786096", []}, max_candidates: {:staking, "f0786096", [], block_number},
# 5fef7643 = keccak256(candidateMinStake()) # 5fef7643 = keccak256(candidateMinStake())
min_candidate_stake: {:staking, "5fef7643", []}, min_candidate_stake: {:staking, "5fef7643", [], block_number},
# da7a9b6a = keccak256(delegatorMinStake()) # da7a9b6a = keccak256(delegatorMinStake())
min_delegator_stake: {:staking, "da7a9b6a", []}, min_delegator_stake: {:staking, "da7a9b6a", [], block_number},
# 957950a7 = keccak256(getPoolsLikelihood()) # 957950a7 = keccak256(getPoolsLikelihood())
pools_likelihood: {:staking, "957950a7", []}, pools_likelihood: {:staking, "957950a7", [], block_number},
# a5d54f65 = keccak256(getPoolsToBeElected()) # a5d54f65 = keccak256(getPoolsToBeElected())
pools_to_be_elected: {:staking, "a5d54f65", []}, pools_to_be_elected: {:staking, "a5d54f65", [], block_number},
# f4942501 = keccak256(areStakeAndWithdrawAllowed()) # f4942501 = keccak256(areStakeAndWithdrawAllowed())
staking_allowed: {:staking, "f4942501", []}, staking_allowed: {:staking, "f4942501", [], block_number},
# 2d21d217 = keccak256(erc677TokenContract()) # 2d21d217 = keccak256(erc677TokenContract())
token_contract_address: {:staking, "2d21d217", []}, token_contract_address: {:staking, "2d21d217", [], block_number},
# 704189ca = keccak256(unremovableValidator()) # 704189ca = keccak256(unremovableValidator())
unremovable_validator: {:validator_set, "704189ca", []}, unremovable_validator: {:validator_set, "704189ca", [], block_number},
# b7ab4db5 = keccak256(getValidators()) # b7ab4db5 = keccak256(getValidators())
validators: {:validator_set, "b7ab4db5", []}, validators: {:validator_set, "b7ab4db5", [], block_number},
# b927ef43 = keccak256(validatorSetApplyBlock()) # b927ef43 = keccak256(validatorSetApplyBlock())
validator_set_apply_block: {:validator_set, "b927ef43", []} validator_set_apply_block: {:validator_set, "b927ef43", [], block_number}
] ]
end end
@ -199,10 +199,10 @@ defmodule Explorer.Staking.ContractReader do
end end
# args = [staking_epoch, delegator_staked, validator_staked, total_staked, pool_reward \\ 10_00000] # args = [staking_epoch, delegator_staked, validator_staked, total_staked, pool_reward \\ 10_00000]
def delegator_reward_request(args) do def delegator_reward_request(args, block_number) do
[ [
# 5fba554e = keccak256(delegatorShare(uint256,uint256,uint256,uint256,uint256)) # 5fba554e = keccak256(delegatorShare(uint256,uint256,uint256,uint256,uint256))
delegator_share: {:block_reward, "5fba554e", args} delegator_share: {:block_reward, "5fba554e", args, block_number}
] ]
end end
@ -234,76 +234,83 @@ defmodule Explorer.Staking.ContractReader do
] ]
end end
def mining_by_staking_request(staking_address, block_number) do
[
# 00535175 = keccak256(miningByStakingAddress(address))
mining_address: {:validator_set, "00535175", [staking_address], block_number}
]
end
def pool_staking_requests(staking_address, block_number) do def pool_staking_requests(staking_address, block_number) do
[ [
active_delegators: active_delegators_request(staking_address, block_number)[:active_delegators], active_delegators: active_delegators_request(staking_address, block_number)[:active_delegators],
# 73c21803 = keccak256(poolDelegatorsInactive(address)) # 73c21803 = keccak256(poolDelegatorsInactive(address))
inactive_delegators: {:staking, "73c21803", [staking_address]}, inactive_delegators: {:staking, "73c21803", [staking_address], block_number},
# a711e6a1 = keccak256(isPoolActive(address)) # a711e6a1 = keccak256(isPoolActive(address))
is_active: {:staking, "a711e6a1", [staking_address]}, is_active: {:staking, "a711e6a1", [staking_address], block_number},
mining_address_hash: mining_by_staking_request(staking_address)[:mining_address], mining_address_hash: mining_by_staking_request(staking_address, block_number)[:mining_address],
# a697ecff = keccak256(stakeAmount(address,address)) # a697ecff = keccak256(stakeAmount(address,address))
self_staked_amount: {:staking, "a697ecff", [staking_address, staking_address]}, self_staked_amount: {:staking, "a697ecff", [staking_address, staking_address], block_number},
# 5267e1d6 = keccak256(stakeAmountTotal(address)) # 5267e1d6 = keccak256(stakeAmountTotal(address))
total_staked_amount: {:staking, "5267e1d6", [staking_address]}, total_staked_amount: {:staking, "5267e1d6", [staking_address], block_number},
# 527d8bc4 = keccak256(validatorRewardPercent(address)) # 527d8bc4 = keccak256(validatorRewardPercent(address))
validator_reward_percent: {:block_reward, "527d8bc4", [staking_address]} validator_reward_percent: {:block_reward, "527d8bc4", [staking_address], block_number}
] ]
end end
def pool_mining_requests(mining_address) do def pool_mining_requests(mining_address, block_number) do
[ [
# a881c5fd = keccak256(areDelegatorsBanned(address)) # a881c5fd = keccak256(areDelegatorsBanned(address))
are_delegators_banned: {:validator_set, "a881c5fd", [mining_address]}, are_delegators_banned: {:validator_set, "a881c5fd", [mining_address], block_number},
# c9e9694d = keccak256(banReason(address)) # c9e9694d = keccak256(banReason(address))
ban_reason: {:validator_set, "c9e9694d", [mining_address]}, ban_reason: {:validator_set, "c9e9694d", [mining_address], block_number},
# 5836d08a = keccak256(bannedUntil(address)) # 5836d08a = keccak256(bannedUntil(address))
banned_until: {:validator_set, "5836d08a", [mining_address]}, banned_until: {:validator_set, "5836d08a", [mining_address], block_number},
# 1a7fa237 = keccak256(bannedDelegatorsUntil(address)) # 1a7fa237 = keccak256(bannedDelegatorsUntil(address))
banned_delegators_until: {:validator_set, "1a7fa237", [mining_address]}, banned_delegators_until: {:validator_set, "1a7fa237", [mining_address], block_number},
# a92252ae = keccak256(isValidatorBanned(address)) # a92252ae = keccak256(isValidatorBanned(address))
is_banned: {:validator_set, "a92252ae", [mining_address]}, is_banned: {:validator_set, "a92252ae", [mining_address], block_number},
# b41832e4 = keccak256(validatorCounter(address)) # b41832e4 = keccak256(validatorCounter(address))
was_validator_count: {:validator_set, "b41832e4", [mining_address]}, was_validator_count: {:validator_set, "b41832e4", [mining_address], block_number},
# 1d0cd4c6 = keccak256(banCounter(address)) # 1d0cd4c6 = keccak256(banCounter(address))
was_banned_count: {:validator_set, "1d0cd4c6", [mining_address]} was_banned_count: {:validator_set, "1d0cd4c6", [mining_address], block_number}
] ]
end end
def staker_requests(pool_staking_address, staker_address) do def staker_requests(pool_staking_address, staker_address, block_number) do
[ [
# 950a6513 = keccak256(maxWithdrawOrderAllowed(address,address)) # 950a6513 = keccak256(maxWithdrawOrderAllowed(address,address))
max_ordered_withdraw_allowed: {:staking, "950a6513", [pool_staking_address, staker_address]}, max_ordered_withdraw_allowed: {:staking, "950a6513", [pool_staking_address, staker_address], block_number},
# 6bda1577 = keccak256(maxWithdrawAllowed(address,address)) # 6bda1577 = keccak256(maxWithdrawAllowed(address,address))
max_withdraw_allowed: {:staking, "6bda1577", [pool_staking_address, staker_address]}, max_withdraw_allowed: {:staking, "6bda1577", [pool_staking_address, staker_address], block_number},
# e9ab0300 = keccak256(orderedWithdrawAmount(address,address)) # e9ab0300 = keccak256(orderedWithdrawAmount(address,address))
ordered_withdraw: {:staking, "e9ab0300", [pool_staking_address, staker_address]}, ordered_withdraw: {:staking, "e9ab0300", [pool_staking_address, staker_address], block_number},
# a4205967 = keccak256(orderWithdrawEpoch(address,address)) # a4205967 = keccak256(orderWithdrawEpoch(address,address))
ordered_withdraw_epoch: {:staking, "a4205967", [pool_staking_address, staker_address]}, ordered_withdraw_epoch: {:staking, "a4205967", [pool_staking_address, staker_address], block_number},
# a697ecff = keccak256(stakeAmount(address,address)) # a697ecff = keccak256(stakeAmount(address,address))
stake_amount: {:staking, "a697ecff", [pool_staking_address, staker_address]} stake_amount: {:staking, "a697ecff", [pool_staking_address, staker_address], block_number}
] ]
end end
def staking_by_mining_request(mining_address) do def staking_by_mining_request(mining_address, block_number) do
[ [
# 1ee4d0bc = keccak256(stakingByMiningAddress(address)) # 1ee4d0bc = keccak256(stakingByMiningAddress(address))
staking_address: {:validator_set, "1ee4d0bc", [mining_address]} staking_address: {:validator_set, "1ee4d0bc", [mining_address], block_number}
] ]
end end
def validator_min_reward_percent_request(epoch_number) do def validator_min_reward_percent_request(epoch_number, block_number) do
[ [
# cdf7a090 = keccak256(validatorMinRewardPercent(uint256)) # cdf7a090 = keccak256(validatorMinRewardPercent(uint256))
value: {:block_reward, "cdf7a090", [epoch_number]} value: {:block_reward, "cdf7a090", [epoch_number], block_number}
] ]
end end
# args = [staking_epoch, validator_staked, total_staked, pool_reward \\ 10_00000] # args = [staking_epoch, validator_staked, total_staked, pool_reward \\ 10_00000]
def validator_reward_request(args) do def validator_reward_request(args, block_number) do
[ [
# 8737929a = keccak256(validatorShare(uint256,uint256,uint256,uint256)) # 8737929a = keccak256(validatorShare(uint256,uint256,uint256,uint256))
validator_share: {:block_reward, "8737929a", args} validator_share: {:block_reward, "8737929a", args, block_number}
] ]
end end

@ -128,31 +128,50 @@ defmodule Explorer.Staking.ContractState do
@doc "Handles new blocks and decides to fetch fresh chain info" @doc "Handles new blocks and decides to fetch fresh chain info"
def handle_info({:chain_event, :last_block_number, :realtime, block_number}, state) do def handle_info({:chain_event, :last_block_number, :realtime, block_number}, state) do
if block_number > state.seen_block do if block_number > state.seen_block do
fetch_state(state.contracts, state.abi, block_number) # read general info from the contracts (including pool list and validator list)
global_responses =
ContractReader.perform_requests(ContractReader.global_requests(block_number), state.contracts, state.abi)
epoch_very_beginning = global_responses.epoch_start_block == block_number + 1
if global_responses.epoch_number > get(:epoch_number) and not epoch_very_beginning and state.seen_block > 0 do
# if the previous staking epoch finished and we have blocks gap,
# call fetch_state in a loop until the blocks gap is closed
loop_block_start = state.seen_block + 1
loop_block_end = block_number - 1
if loop_block_end >= loop_block_start do
for bn <- loop_block_start..loop_block_end do
gr = ContractReader.perform_requests(ContractReader.global_requests(bn), state.contracts, state.abi)
fetch_state(state.contracts, state.abi, gr, bn, gr.epoch_start_block == bn + 1)
end
end
end
fetch_state(state.contracts, state.abi, global_responses, block_number, epoch_very_beginning)
{:noreply, %{state | seen_block: block_number}} {:noreply, %{state | seen_block: block_number}}
else else
{:noreply, state} {:noreply, state}
end end
end end
defp fetch_state(contracts, abi, block_number) do defp fetch_state(contracts, abi, global_responses, block_number, epoch_very_beginning) do
# read general info from the contracts (including pool list and validator list) validator_min_reward_percent =
global_responses = ContractReader.perform_requests(ContractReader.global_requests(), contracts, abi) get_validator_min_reward_percent(global_responses.epoch_number, block_number, contracts, abi)
validator_min_reward_percent = get_validator_min_reward_percent(global_responses, contracts, abi)
epoch_very_beginning = global_responses.epoch_start_block == block_number + 1
is_validator = Enum.into(global_responses.validators, %{}, &{address_bytes_to_string(&1), true}) is_validator = Enum.into(global_responses.validators, %{}, &{address_bytes_to_string(&1), true})
start_snapshotting = start_snapshotting =
global_responses.epoch_number > get(:snapshotted_epoch_number) && global_responses.epoch_number > 0 && global_responses.epoch_number > get(:snapshotted_epoch_number) && global_responses.epoch_number > 0 &&
not get(:is_snapshotting) not get(:is_snapshotting)
active_pools_length = Enum.count(global_responses.active_pools)
# save the general info to ETS (excluding pool list and validator list) # save the general info to ETS (excluding pool list and validator list)
settings = settings =
global_responses global_responses
|> get_settings(validator_min_reward_percent, block_number) |> get_settings(validator_min_reward_percent, block_number)
|> Enum.concat(active_pools_length: Enum.count(global_responses.active_pools)) |> Enum.concat(active_pools_length: active_pools_length)
:ets.insert(@table_name, settings) :ets.insert(@table_name, settings)
@ -162,11 +181,12 @@ defmodule Explorer.Staking.ContractState do
start_snapshotting, start_snapshotting,
global_responses, global_responses,
contracts, contracts,
abi abi,
block_number
) )
# miningToStakingAddress mapping # miningToStakingAddress mapping
mining_to_staking_address = get_mining_to_staking_address(validators, contracts, abi) mining_to_staking_address = get_mining_to_staking_address(validators, contracts, abi, block_number)
# the list of all pools (validators + active pools + inactive pools) # the list of all pools (validators + active pools + inactive pools)
pools = pools =
@ -188,7 +208,14 @@ defmodule Explorer.Staking.ContractState do
# call `BlockReward.validatorShare` function for each pool # call `BlockReward.validatorShare` function for each pool
# to get validator's reward share of the pool (needed for the `Delegators` list in UI) # to get validator's reward share of the pool (needed for the `Delegators` list in UI)
candidate_reward_responses = candidate_reward_responses =
get_candidate_reward_responses(pool_staking_responses, global_responses, pool_staking_keys, contracts, abi) get_candidate_reward_responses(
pool_staking_responses,
global_responses,
pool_staking_keys,
contracts,
abi,
block_number
)
# call `BlockReward.delegatorShare` function for each delegator # call `BlockReward.delegatorShare` function for each delegator
# to get their reward share of the pool (needed for the `Delegators` list in UI) # to get their reward share of the pool (needed for the `Delegators` list in UI)
@ -207,7 +234,8 @@ defmodule Explorer.Staking.ContractState do
pool_staking_responses, pool_staking_responses,
global_responses, global_responses,
contracts, contracts,
abi abi,
block_number
) )
# calculate total amount staked into all active pools # calculate total amount staked into all active pools
@ -265,8 +293,19 @@ defmodule Explorer.Staking.ContractState do
) )
end end
# notify the UI about new block # notify the UI about a new block
Publisher.broadcast(:staking_update) data = %{
active_pools_length: active_pools_length,
block_number: block_number,
epoch_end_block: global_responses.epoch_end_block,
epoch_number: global_responses.epoch_number,
max_candidates: global_responses.max_candidates,
staking_allowed: global_responses.staking_allowed,
staking_token_defined: get(:token, nil) != nil,
validator_set_apply_block: global_responses.validator_set_apply_block
}
Publisher.broadcast([{:staking_update, data}], :realtime)
end end
defp get_settings(global_responses, validator_min_reward_percent, block_number) do defp get_settings(global_responses, validator_min_reward_percent, block_number) do
@ -284,9 +323,9 @@ defmodule Explorer.Staking.ContractState do
end end
end end
defp get_mining_to_staking_address(validators, contracts, abi) do defp get_mining_to_staking_address(validators, contracts, abi, block_number) do
validators.all validators.all
|> Enum.map(&ContractReader.staking_by_mining_request/1) |> Enum.map(&ContractReader.staking_by_mining_request(&1, block_number))
|> ContractReader.perform_grouped_requests(validators.all, contracts, abi) |> ContractReader.perform_grouped_requests(validators.all, contracts, abi)
|> Map.new(fn {mining_address, resp} -> {mining_address, address_string_to_bytes(resp.staking_address).bytes} end) |> Map.new(fn {mining_address, resp} -> {mining_address, address_string_to_bytes(resp.staking_address).bytes} end)
end end
@ -295,15 +334,13 @@ defmodule Explorer.Staking.ContractState do
# read pool info from the contracts by its staking address # read pool info from the contracts by its staking address
pool_staking_responses = pool_staking_responses =
pools pools
|> Enum.map(fn staking_address_hash -> |> Enum.map(&ContractReader.pool_staking_requests(&1, block_number))
ContractReader.pool_staking_requests(staking_address_hash, block_number)
end)
|> ContractReader.perform_grouped_requests(pools, contracts, abi) |> ContractReader.perform_grouped_requests(pools, contracts, abi)
# read pool info from the contracts by its mining address # read pool info from the contracts by its mining address
pool_mining_responses = pool_mining_responses =
pools pools
|> Enum.map(&ContractReader.pool_mining_requests(pool_staking_responses[&1].mining_address_hash)) |> Enum.map(&ContractReader.pool_mining_requests(pool_staking_responses[&1].mining_address_hash, block_number))
|> ContractReader.perform_grouped_requests(pools, contracts, abi) |> ContractReader.perform_grouped_requests(pools, contracts, abi)
# get a flat list of all stakers in the form of {pool_staking_address, staker_address, is_active} # get a flat list of all stakers in the form of {pool_staking_address, staker_address, is_active}
@ -318,7 +355,7 @@ defmodule Explorer.Staking.ContractState do
staker_responses = staker_responses =
stakers stakers
|> Enum.map(fn {pool_staking_address, staker_address, _is_active} -> |> Enum.map(fn {pool_staking_address, staker_address, _is_active} ->
ContractReader.staker_requests(pool_staking_address, staker_address) ContractReader.staker_requests(pool_staking_address, staker_address, block_number)
end) end)
|> ContractReader.perform_grouped_requests(stakers, contracts, abi) |> ContractReader.perform_grouped_requests(stakers, contracts, abi)
@ -329,15 +366,25 @@ defmodule Explorer.Staking.ContractState do
} }
end end
defp get_candidate_reward_responses(pool_staking_responses, global_responses, pool_staking_keys, contracts, abi) do defp get_candidate_reward_responses(
pool_staking_responses,
global_responses,
pool_staking_keys,
contracts,
abi,
block_number
) do
pool_staking_responses pool_staking_responses
|> Enum.map(fn {_pool_staking_address, resp} -> |> Enum.map(fn {_pool_staking_address, resp} ->
ContractReader.validator_reward_request([ ContractReader.validator_reward_request(
[
global_responses.epoch_number, global_responses.epoch_number,
resp.self_staked_amount, resp.self_staked_amount,
resp.total_staked_amount, resp.total_staked_amount,
1000_000 1000_000
]) ],
block_number
)
end) end)
|> ContractReader.perform_grouped_requests(pool_staking_keys, contracts, abi) |> ContractReader.perform_grouped_requests(pool_staking_keys, contracts, abi)
end end
@ -347,7 +394,8 @@ defmodule Explorer.Staking.ContractState do
pool_staking_responses, pool_staking_responses,
global_responses, global_responses,
contracts, contracts,
abi abi,
block_number
) do ) do
# to keep sort order when using `perform_grouped_requests` (see below) # to keep sort order when using `perform_grouped_requests` (see below)
delegator_keys = Enum.map(delegator_responses, fn {key, _} -> key end) delegator_keys = Enum.map(delegator_responses, fn {key, _} -> key end)
@ -356,13 +404,16 @@ defmodule Explorer.Staking.ContractState do
|> Enum.map(fn {{pool_staking_address, _staker_address, _is_active}, resp} -> |> Enum.map(fn {{pool_staking_address, _staker_address, _is_active}, resp} ->
staking_resp = pool_staking_responses[pool_staking_address] staking_resp = pool_staking_responses[pool_staking_address]
ContractReader.delegator_reward_request([ ContractReader.delegator_reward_request(
[
global_responses.epoch_number, global_responses.epoch_number,
resp.stake_amount, resp.stake_amount,
staking_resp.self_staked_amount, staking_resp.self_staked_amount,
staking_resp.total_staked_amount, staking_resp.total_staked_amount,
1000_000 1000_000
]) ],
block_number
)
end) end)
|> ContractReader.perform_grouped_requests(delegator_keys, contracts, abi) |> ContractReader.perform_grouped_requests(delegator_keys, contracts, abi)
end end
@ -385,9 +436,9 @@ defmodule Explorer.Staking.ContractState do
end) end)
end end
defp get_validator_min_reward_percent(global_responses, contracts, abi) do defp get_validator_min_reward_percent(epoch_number, block_number, contracts, abi) do
ContractReader.perform_requests( ContractReader.perform_requests(
ContractReader.validator_min_reward_percent_request(global_responses.epoch_number), ContractReader.validator_min_reward_percent_request(epoch_number, block_number),
contracts, contracts,
abi abi
).value ).value
@ -414,7 +465,8 @@ defmodule Explorer.Staking.ContractState do
start_snapshotting, start_snapshotting,
global_responses, global_responses,
contracts, contracts,
abi abi,
block_number
) do ) do
if start_snapshotting do if start_snapshotting do
# eebc7a39 = keccak256(getPendingValidators()) # eebc7a39 = keccak256(getPendingValidators())
@ -424,10 +476,15 @@ defmodule Explorer.Staking.ContractState do
"eebc7a39" => {:ok, [validators_pending]}, "eebc7a39" => {:ok, [validators_pending]},
"004a8803" => {:ok, [validators_to_be_finalized]} "004a8803" => {:ok, [validators_to_be_finalized]}
} = } =
Reader.query_contract(contracts.validator_set, abi, %{ Reader.query_contract_by_block_number(
contracts.validator_set,
abi,
%{
"eebc7a39" => [], "eebc7a39" => [],
"004a8803" => [] "004a8803" => []
}) },
block_number
)
validators_pending = Enum.uniq(validators_pending ++ validators_to_be_finalized) validators_pending = Enum.uniq(validators_pending ++ validators_to_be_finalized)
@ -613,6 +670,8 @@ defmodule Explorer.Staking.ContractState do
mining_to_staking_address mining_to_staking_address
) do ) do
# start snapshotting at the beginning of the staking epoch # start snapshotting at the beginning of the staking epoch
:ets.insert(@table_name, is_snapshotting: true)
cached_pool_staking_responses = cached_pool_staking_responses =
if epoch_very_beginning do if epoch_very_beginning do
pool_staking_responses pool_staking_responses
@ -659,7 +718,10 @@ defmodule Explorer.Staking.ContractState do
defp fetch_token(address, address_hash) do defp fetch_token(address, address_hash) do
# the token doesn't exist in DB, so try # the token doesn't exist in DB, so try
# to read it from a contract and then write to DB # to read it from a contract and then write to DB.
# Since the Staking DApp doesn't use the token fields
# which dinamically change (such as totalSupply),
# we don't pass the current block_number to the RPC request
token_functions = MetadataRetriever.get_functions_of(address) token_functions = MetadataRetriever.get_functions_of(address)
if map_size(token_functions) > 0 do if map_size(token_functions) > 0 do

@ -19,8 +19,6 @@ defmodule Explorer.Staking.StakeSnapshotting do
mining_to_staking_address, mining_to_staking_address,
block_number block_number
) do ) do
:ets.insert(ets_table_name, is_snapshotting: true)
# get staking addresses for the pending validators # get staking addresses for the pending validators
pool_staking_addresses = pool_staking_addresses =
pools_mining_addresses pools_mining_addresses
@ -89,12 +87,15 @@ defmodule Explorer.Staking.StakeSnapshotting do
validator_reward_responses = validator_reward_responses =
pool_staking_responses pool_staking_responses
|> Enum.map(fn {_pool_staking_address, resp} -> |> Enum.map(fn {_pool_staking_address, resp} ->
ContractReader.validator_reward_request([ ContractReader.validator_reward_request(
[
epoch_number, epoch_number,
resp.snapshotted_self_staked_amount, resp.snapshotted_self_staked_amount,
resp.snapshotted_total_staked_amount, resp.snapshotted_total_staked_amount,
1000_000 1000_000
]) ],
block_number
)
end) end)
|> ContractReader.perform_grouped_requests(pool_staking_keys, contracts, abi) |> ContractReader.perform_grouped_requests(pool_staking_keys, contracts, abi)
@ -116,13 +117,16 @@ defmodule Explorer.Staking.StakeSnapshotting do
|> Enum.map(fn {{pool_staking_address, _staker_address}, resp} -> |> Enum.map(fn {{pool_staking_address, _staker_address}, resp} ->
staking_resp = pool_staking_responses[pool_staking_address] staking_resp = pool_staking_responses[pool_staking_address]
ContractReader.delegator_reward_request([ ContractReader.delegator_reward_request(
[
epoch_number, epoch_number,
resp.snapshotted_stake_amount, resp.snapshotted_stake_amount,
staking_resp.snapshotted_self_staked_amount, staking_resp.snapshotted_self_staked_amount,
staking_resp.snapshotted_total_staked_amount, staking_resp.snapshotted_total_staked_amount,
1000_000 1000_000
]) ],
block_number
)
end) end)
|> ContractReader.perform_grouped_requests(delegator_keys, contracts, abi) |> ContractReader.perform_grouped_requests(delegator_keys, contracts, abi)

Loading…
Cancel
Save