Enhance blocks handling in Staking DApp

pull/3443/head
PoaMan 4 years ago committed by Victor Baranov
parent 93198f2acc
commit 72d9006717
  1. 72
      apps/block_scout_web/assets/js/lib/queue.js
  2. 155
      apps/block_scout_web/assets/js/pages/stakes.js
  3. 2
      apps/block_scout_web/assets/js/pages/stakes/utils.js
  4. 2
      apps/block_scout_web/lib/block_scout_web/application.ex
  5. 48
      apps/block_scout_web/lib/block_scout_web/channels/stakes_channel.ex
  6. 31
      apps/block_scout_web/lib/block_scout_web/controllers/stakes_controller.ex
  7. 12
      apps/block_scout_web/lib/block_scout_web/notifier.ex
  8. 1
      apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex
  9. 26
      apps/block_scout_web/lib/block_scout_web/staking_event_handler.ex
  10. 12
      apps/block_scout_web/test/block_scout_web/channels/stakes_channel_test.exs
  11. 2
      apps/explorer/lib/explorer/chain/events/publisher.ex
  12. 4
      apps/explorer/lib/explorer/chain/events/subscriber.ex
  13. 26
      apps/explorer/lib/explorer/smart_contract/reader.ex
  14. 95
      apps/explorer/lib/explorer/staking/contract_reader.ex
  15. 153
      apps/explorer/lib/explorer/staking/contract_state.ex
  16. 34
      apps/explorer/lib/explorer/staking/stake_snapshotting.ex

@ -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 (msg.account !== state.account) {
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) ||
msg.account !== state.account || 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()
$refreshInformer.hide() if (!store.getState().finishRequestResolve) {
$stakesPage.fadeTo(0, 0.5) $refreshInformer.hide()
delete msg.dont_refresh_page // refresh anyway $stakesPage.fadeTo(0, 0.5)
reloadPoolList(msg, store) delete msg.dont_refresh_page // refresh anyway
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 => {
@ -298,10 +336,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 (account !== state.account && 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 +360,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()
refreshPage(store)
}) })
$refreshInformer.hide()
} }
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 +391,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,24 +401,32 @@ function resetFilterMy (store) {
} }
function setAccount (account, store) { function setAccount (account, store) {
store.dispatch({ type: 'ACCOUNT_UPDATED', account }) return new Promise(resolve => {
if (!account) { store.dispatch({ type: 'ACCOUNT_UPDATED', account })
resetFilterMy(store) if (!account) {
} resetFilterMy(store)
}
const errorMsg = 'Cannot properly set account due to connection loss. Please, reload the page.' const errorMsg = 'Cannot properly set account due to connection loss. Please, reload the page.'
const $addressField = $('.stakes-top-stats-item-address .stakes-top-stats-value') const $addressField = $('.stakes-top-stats-item-address .stakes-top-stats-value')
$addressField.html('Loading...') $addressField.html('Loading...')
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}">
hideCurrentModal() ${account}
}).receive('error', () => { </div>
openErrorModal('Change account', errorMsg, true) `)
}).receive('timeout', () => { hideCurrentModal()
openErrorModal('Change account', errorMsg, true) resolve(true)
}).receive('error', () => {
openErrorModal('Change account', errorMsg, true)
resolve(false)
}).receive('timeout', () => {
openErrorModal('Change account', errorMsg, true)
resolve(false)
})
}) })
} }
@ -395,6 +451,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)
} }

@ -8,6 +8,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
alias BlockScoutWeb.StakingEventHandler
def start(_type, _args) do def start(_type, _args) do
import Supervisor.Spec import Supervisor.Spec
@ -22,6 +23,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} ->
block_number: BlockNumber.get_max(), staking_update_data
dont_refresh_page: true,
epoch_number: ContractState.get(:epoch_number, 0), _ ->
staking_allowed: ContractState.get(:staking_allowed, false), %{
staking_token_defined: ContractState.get(:token, nil) != nil, block_number: BlockNumber.get_max(),
validator_set_apply_block: ContractState.get(:validator_set_apply_block, 0) epoch_number: ContractState.get(:epoch_number, 0),
}, staking_allowed: ContractState.get(:staking_allowed, false),
socket staking_token_defined: ContractState.get(:token, nil) != nil,
) validator_set_apply_block: ContractState.get(:validator_set_apply_block, 0)
}
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

@ -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

@ -138,6 +138,32 @@ defmodule Explorer.SmartContract.Reader do
end) end)
end 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
requests =
functions
|> Enum.map(fn {method_id, args} ->
%{
contract_address: contract_address,
method_id: method_id,
args: args,
block_number: block_number
}
end)
requests
|> query_contracts(abi)
|> Enum.zip(requests)
|> Enum.into(%{}, fn {response, request} ->
{request.method_id, response}
end)
end
@doc """ @doc """
Runs batch of contract functions on given addresses for smart contract with an expected ABI and functions. Runs batch of contract functions on given addresses for smart contract with an expected ABI and functions.

@ -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,11 @@ 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(fn mining_address ->
ContractReader.staking_by_mining_request(mining_address, block_number)
end)
|> 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
@ -303,7 +344,12 @@ defmodule Explorer.Staking.ContractState do
# 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(fn staking_address_hash ->
ContractReader.pool_mining_requests(
pool_staking_responses[staking_address_hash].mining_address_hash,
block_number
)
end)
|> 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 +364,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 +375,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, [
resp.self_staked_amount, global_responses.epoch_number,
resp.total_staked_amount, resp.self_staked_amount,
1000_000 resp.total_staked_amount,
]) 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 +403,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 +413,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, [
resp.stake_amount, global_responses.epoch_number,
staking_resp.self_staked_amount, resp.stake_amount,
staking_resp.total_staked_amount, staking_resp.self_staked_amount,
1000_000 staking_resp.total_staked_amount,
]) 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 +445,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 +474,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 +485,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(
"eebc7a39" => [], contracts.validator_set,
"004a8803" => [] abi,
}) %{
"eebc7a39" => [],
"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 +679,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 +727,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, [
resp.snapshotted_self_staked_amount, epoch_number,
resp.snapshotted_total_staked_amount, resp.snapshotted_self_staked_amount,
1000_000 resp.snapshotted_total_staked_amount,
]) 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, [
resp.snapshotted_stake_amount, epoch_number,
staking_resp.snapshotted_self_staked_amount, resp.snapshotted_stake_amount,
staking_resp.snapshotted_total_staked_amount, staking_resp.snapshotted_self_staked_amount,
1000_000 staking_resp.snapshotted_total_staked_amount,
]) 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