Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce RPC requests and DB changes by Staking DApp #3583

Merged
merged 8 commits into from
Jan 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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