Skip to content

Commit

Permalink
Merge pull request #3583 from poanetwork/va-staking-dapp-light-optimi…
Browse files Browse the repository at this point in the history
…zation

Reduce RPC requests and DB changes by Staking DApp
  • Loading branch information
vbaranov committed Jan 21, 2021
2 parents 81d3a81 + fed392f commit 3cd59ed
Show file tree
Hide file tree
Showing 10 changed files with 183 additions and 86 deletions.
4 changes: 2 additions & 2 deletions .dialyzer-ignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ lib/explorer/exchange_rates/source.ex:104
lib/explorer/exchange_rates/source.ex:107
lib/explorer/smart_contract/verifier.ex:89
lib/block_scout_web/templates/address_contract/index.html.eex:118
lib/explorer/staking/stake_snapshotting.ex:14: Function do_snapshotting/6 has no local return
lib/explorer/staking/stake_snapshotting.ex:183
lib/explorer/staking/stake_snapshotting.ex:15: Function do_snapshotting/6 has no local return
lib/explorer/staking/stake_snapshotting.ex:184
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- [#3564](https://github.com/poanetwork/blockscout/pull/3564) - Staking welcome message

### Fixes
- [#3583](https://github.com/poanetwork/blockscout/pull/3583) - Reduce RPC requests and DB changes by Staking DApp

### Chore
- [#3574](https://github.com/poanetwork/blockscout/pull/3574) - Correct UNI token price
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<%= raw(@top) %>
</div>
<%= render BlockScoutWeb.StakesView, "_learn-more.html", conn: @conn %>
<%= render BlockScoutWeb.StakesView, "_warning.html", conn: @conn %>
<%= # render BlockScoutWeb.StakesView, "_warning.html", conn: @conn %>
<section data-page="stakes" class="container" data-refresh-interval="<%= @refresh_interval %>">
<div class="card" data-async-load data-async-listing="<%= @current_path %>" data-no-first-loading>
<%= render BlockScoutWeb.StakesView, "_stakes_tabs.html", conn: @conn %>
Expand Down
2 changes: 1 addition & 1 deletion apps/explorer/lib/explorer/chain/events/subscriber.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ defmodule Explorer.Chain.Events.Subscriber do

@allowed_broadcast_types ~w(catchup realtime on_demand contract_verification_result)a

@allowed_events ~w(exchange_rate transaction_stats)a
@allowed_events ~w(exchange_rate stake_snapshotting_finished transaction_stats)a

@type broadcast_type :: :realtime | :catchup | :on_demand

Expand Down
6 changes: 5 additions & 1 deletion apps/explorer/lib/explorer/staking/contract_reader.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,18 @@ defmodule Explorer.Staking.ContractReader do
pools_to_be_elected: {:staking, "a5d54f65", [], block_number},
# f4942501 = keccak256(areStakeAndWithdrawAllowed())
staking_allowed: {:staking, "f4942501", [], block_number},
# 74bdb372 = keccak256(lastChangeBlock())
staking_last_change_block: {:staking, "74bdb372", [], block_number},
# 2d21d217 = keccak256(erc677TokenContract())
token_contract_address: {:staking, "2d21d217", [], block_number},
# 704189ca = keccak256(unremovableValidator())
unremovable_validator: {:validator_set, "704189ca", [], block_number},
# b7ab4db5 = keccak256(getValidators())
validators: {:validator_set, "b7ab4db5", [], block_number},
# b927ef43 = keccak256(validatorSetApplyBlock())
validator_set_apply_block: {:validator_set, "b927ef43", [], block_number}
validator_set_apply_block: {:validator_set, "b927ef43", [], block_number},
# 74bdb372 = keccak256(lastChangeBlock())
validator_set_last_change_block: {:validator_set, "74bdb372", [], block_number}
]
end

Expand Down
133 changes: 95 additions & 38 deletions apps/explorer/lib/explorer/staking/contract_state.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ defmodule Explorer.Staking.ContractState do
:epoch_number,
:epoch_start_block,
:is_snapshotting,
:last_change_block,
:max_candidates,
:min_candidate_stake,
:min_delegator_stake,
Expand All @@ -41,6 +42,7 @@ defmodule Explorer.Staking.ContractState do

defstruct [
:seen_block,
:snapshotting_finished,
:contracts,
:abi
]
Expand Down Expand Up @@ -69,6 +71,7 @@ defmodule Explorer.Staking.ContractState do
])

Subscriber.to(:last_block_number, :realtime)
Subscriber.to(:stake_snapshotting_finished)

staking_abi = abi("StakingAuRa")
validator_set_abi = abi("ValidatorSetAuRa")
Expand Down Expand Up @@ -101,6 +104,7 @@ defmodule Explorer.Staking.ContractState do

state = %__MODULE__{
seen_block: 0,
snapshotting_finished: false,
contracts: %{
staking: staking_contract_address,
validator_set: validator_set_contract_address,
Expand All @@ -112,6 +116,7 @@ defmodule Explorer.Staking.ContractState do
:ets.insert(@table_name,
block_reward_contract: %{abi: block_reward_abi, address: block_reward_contract_address},
is_snapshotting: false,
last_change_block: 0,
snapshotted_epoch_number: -1,
staking_contract: %{abi: staking_abi, address: staking_contract_address},
token_contract: %{abi: token_abi, address: token_contract_address},
Expand All @@ -126,7 +131,12 @@ defmodule Explorer.Staking.ContractState do
{:noreply, state}
end

@doc "Handles new blocks and decides to fetch fresh chain info"
# handles an event about snapshotting finishing
def handle_info({:chain_event, :stake_snapshotting_finished}, state) do
{:noreply, %{state | snapshotting_finished: true}}
end

# handles new blocks and decides to fetch fresh chain info
def handle_info({:chain_event, :last_block_number, :realtime, block_number}, state) do
if block_number > state.seen_block do
# read general info from the contracts (including pool list and validator list)
Expand All @@ -144,38 +154,91 @@ defmodule Explorer.Staking.ContractState do
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)
fetch_state(state, gr, bn, gr.epoch_start_block == bn + 1)
end
end
end

fetch_state(state.contracts, state.abi, global_responses, block_number, epoch_very_beginning)
fetch_state(state, global_responses, block_number, epoch_very_beginning)

state =
if state.snapshotting_finished do
%{state | snapshotting_finished: false}
else
state
end

{:noreply, %{state | seen_block: block_number}}
else
{:noreply, state}
end
end

defp fetch_state(contracts, abi, global_responses, block_number, epoch_very_beginning) do
defp fetch_state(state, global_responses, block_number, epoch_very_beginning) do
contracts = state.contracts
abi = state.abi
snapshotting_finished = state.snapshotting_finished
first_fetch = get(:epoch_end_block, 0) == 0

validator_min_reward_percent =
get_validator_min_reward_percent(global_responses.epoch_number, block_number, contracts, abi)

is_validator = Enum.into(global_responses.validators, %{}, &{address_bytes_to_string(&1), true})

start_snapshotting =
global_responses.epoch_number > get(:snapshotted_epoch_number) && global_responses.epoch_number > 0 &&
not get(:is_snapshotting)

active_pools_length = Enum.count(global_responses.active_pools)

# determine if something changed in contracts state since the previous seen block.
# if something changed or the `fetch_state` function is called for the first time
# or we are at the beginning of staking epoch or snapshotting recently finished
# then we should update database
last_change_block =
max(global_responses.staking_last_change_block, global_responses.validator_set_last_change_block)

should_update_db =
start_snapshotting or snapshotting_finished or first_fetch or last_change_block > get(:last_change_block)

# save the general info to ETS (excluding pool list and validator list)
settings =
global_responses
|> get_settings(validator_min_reward_percent, block_number)
|> Enum.concat(active_pools_length: active_pools_length)
|> Enum.concat(last_change_block: last_change_block)

:ets.insert(@table_name, settings)

if epoch_very_beginning or start_snapshotting do
# if the block_number is the latest block of the finished staking epoch
# or we are starting Blockscout server, the BlockRewardAuRa contract balance
# could increase before (without Mint/Transfer events),
# so we need to update its balance in database
update_block_reward_balance(block_number)
end

# we should update database as something changed in contracts state
if should_update_db do
update_database(epoch_very_beginning, start_snapshotting, global_responses, contracts, abi, block_number)
end

# notify the UI about a new block
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

defp update_database(epoch_very_beginning, start_snapshotting, global_responses, contracts, abi, block_number) do
is_validator = Enum.into(global_responses.validators, %{}, &{address_bytes_to_string(&1), true})

# form the list of validator pools
validators =
get_validators(
Expand Down Expand Up @@ -220,14 +283,7 @@ defmodule Explorer.Staking.ContractState do

# call `BlockReward.delegatorShare` function for each delegator
# to get their reward share of the pool (needed for the `Delegators` list in UI)
delegator_responses =
Enum.reduce(staker_responses, %{}, fn {{pool_staking_address, staker_address, _is_active} = key, value}, acc ->
if pool_staking_address != staker_address do
Map.put(acc, key, value)
else
acc
end
end)
delegator_responses = get_delegator_responses(staker_responses)

delegator_reward_responses =
get_delegator_reward_responses(
Expand All @@ -252,7 +308,20 @@ defmodule Explorer.Staking.ContractState do

snapshotted_epoch_number = get(:snapshotted_epoch_number)

# form entries for writing to the `staking_pools` table in DB
# form entries for writing to the `staking_pools_delegators` table in DB
delegator_entries = get_delegator_entries(staker_responses, delegator_reward_responses)

# perform SQL queries
{:ok, _} =
Chain.import(%{
staking_pools_delegators: %{params: delegator_entries},
timeout: :infinity
})

# form entries for writing to the `staking_pools` table in DB.
# !!! it's important to do this AFTER updating `staking_pools_delegators`
# !!! table because the `get_pool_entries` function requires fresh
# !!! info about delegators of validators from the `staking_pools_delegators` table
pool_entries =
get_pool_entries(%{
pools: pools,
Expand All @@ -267,21 +336,13 @@ defmodule Explorer.Staking.ContractState do
staked_total: staked_total
})

# form entries for writing to the `staking_pools_delegators` table in DB
delegator_entries = get_delegator_entries(staker_responses, delegator_reward_responses)

# perform SQL queries
{:ok, _} =
Chain.import(%{
staking_pools: %{params: pool_entries},
staking_pools_delegators: %{params: delegator_entries},
timeout: :infinity
})

if epoch_very_beginning or start_snapshotting do
at_start_snapshotting(block_number)
end

if start_snapshotting do
do_start_snapshotting(
epoch_very_beginning,
Expand All @@ -293,20 +354,6 @@ defmodule Explorer.Staking.ContractState do
mining_to_staking_address
)
end

# notify the UI about a new block
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

defp get_settings(global_responses, validator_min_reward_percent, block_number) do
Expand Down Expand Up @@ -390,6 +437,16 @@ defmodule Explorer.Staking.ContractState do
|> ContractReader.perform_grouped_requests(pool_staking_keys, contracts, abi)
end

defp get_delegator_responses(staker_responses) do
Enum.reduce(staker_responses, %{}, fn {{pool_staking_address, staker_address, _is_active} = key, value}, acc ->
if pool_staking_address != staker_address do
Map.put(acc, key, value)
else
acc
end
end)
end

defp get_delegator_reward_responses(
delegator_responses,
pool_staking_responses,
Expand Down Expand Up @@ -603,7 +660,7 @@ defmodule Explorer.Staking.ContractState do
end)
end

defp at_start_snapshotting(block_number) do
defp update_block_reward_balance(block_number) do
# update ERC balance of the BlockReward contract
token = get(:token)

Expand Down
3 changes: 3 additions & 0 deletions apps/explorer/lib/explorer/staking/stake_snapshotting.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ defmodule Explorer.Staking.StakeSnapshotting do
require Logger

alias Explorer.Chain
alias Explorer.Chain.Events.Publisher
alias Explorer.Chain.{StakingPool, StakingPoolsDelegator}
alias Explorer.Staking.ContractReader

Expand Down Expand Up @@ -194,6 +195,8 @@ defmodule Explorer.Staking.StakeSnapshotting do
end

:ets.insert(ets_table_name, is_snapshotting: false)

Publisher.broadcast(:stake_snapshotting_finished)
end

defp address_bytes_to_string(hash), do: "0x" <> Base.encode16(hash, case: :lower)
Expand Down
14 changes: 14 additions & 0 deletions apps/explorer/priv/contracts_abi/posdao/StakingAuRa.json
Original file line number Diff line number Diff line change
Expand Up @@ -1074,5 +1074,19 @@
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "lastChangeBlock",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
}
]
14 changes: 14 additions & 0 deletions apps/explorer/priv/contracts_abi/posdao/ValidatorSetAuRa.json
Original file line number Diff line number Diff line change
Expand Up @@ -694,5 +694,19 @@
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "lastChangeBlock",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
}
]
Loading

0 comments on commit 3cd59ed

Please sign in to comment.