From 24c607b28c4ebbb01faa81eae8fb8aacc1017f8a Mon Sep 17 00:00:00 2001 From: Vadim Date: Fri, 29 Nov 2019 17:33:56 +0300 Subject: [PATCH] Update BlockReward contract ERC balance --- .../lib/explorer/staking/contract_reader.ex | 39 ++--- .../lib/explorer/staking/contract_state.ex | 140 +++++++++++++----- .../explorer/staking/stake_snapshotting.ex | 26 +++- 3 files changed, 135 insertions(+), 70 deletions(-) diff --git a/apps/explorer/lib/explorer/staking/contract_reader.ex b/apps/explorer/lib/explorer/staking/contract_reader.ex index d93128f999..f1ccb77243 100644 --- a/apps/explorer/lib/explorer/staking/contract_reader.ex +++ b/apps/explorer/lib/explorer/staking/contract_reader.ex @@ -1,26 +1,25 @@ defmodule Explorer.Staking.ContractReader do @moduledoc """ - Routines for batched fetching of information from POSDAO contracts + Routines for batched fetching of information from POSDAO contracts. """ - alias Explorer.Chain alias Explorer.SmartContract.Reader def global_requests do [ - token_contract_address: {:staking, "erc677TokenContract", []}, - min_candidate_stake: {:staking, "candidateMinStake", []}, - min_delegator_stake: {:staking, "delegatorMinStake", []}, + active_pools: {:staking, "getPools", []}, + epoch_end_block: {:staking, "stakingEpochEndBlock", []}, epoch_number: {:staking, "stakingEpoch", []}, epoch_start_block: {:staking, "stakingEpochStartBlock", []}, - epoch_end_block: {:staking, "stakingEpochEndBlock", []}, - staking_allowed: {:staking, "areStakeAndWithdrawAllowed", []}, - active_pools: {:staking, "getPools", []}, inactive_pools: {:staking, "getPoolsInactive", []}, - pools_to_be_elected: {:staking, "getPoolsToBeElected", []}, + min_candidate_stake: {:staking, "candidateMinStake", []}, + min_delegator_stake: {:staking, "delegatorMinStake", []}, pools_likelihood: {:staking, "getPoolsLikelihood", []}, - validators: {:validator_set, "getValidators", []}, + pools_to_be_elected: {:staking, "getPoolsToBeElected", []}, + staking_allowed: {:staking, "areStakeAndWithdrawAllowed", []}, + token_contract_address: {:staking, "erc677TokenContract", []}, unremovable_validator: {:validator_set, "unremovableValidator", []}, + validators: {:validator_set, "getValidators", []}, validator_set_apply_block: {:validator_set, "validatorSetApplyBlock", []} ] end @@ -28,23 +27,23 @@ defmodule Explorer.Staking.ContractReader do def pool_staking_requests(staking_address) do [ active_delegators: {:staking, "poolDelegators", [staking_address]}, - validator_reward_percent: {:block_reward, "validatorRewardPercent", [staking_address]}, inactive_delegators: {:staking, "poolDelegatorsInactive", [staking_address]}, is_active: {:staking, "isPoolActive", [staking_address]}, mining_address_hash: {:validator_set, "miningByStakingAddress", [staking_address]}, self_staked_amount: {:staking, "stakeAmount", [staking_address, staking_address]}, - total_staked_amount: {:staking, "stakeAmountTotal", [staking_address]} + total_staked_amount: {:staking, "stakeAmountTotal", [staking_address]}, + validator_reward_percent: {:block_reward, "validatorRewardPercent", [staking_address]} ] end def pool_mining_requests(mining_address) do [ - was_validator_count: {:validator_set, "validatorCounter", [mining_address]}, - is_banned: {:validator_set, "isValidatorBanned", [mining_address]}, are_delegators_banned: {:validator_set, "areDelegatorsBanned", [mining_address]}, ban_reason: {:validator_set, "banReason", [mining_address]}, banned_until: {:validator_set, "bannedUntil", [mining_address]}, banned_delegators_until: {:validator_set, "bannedDelegatorsUntil", [mining_address]}, + is_banned: {:validator_set, "isValidatorBanned", [mining_address]}, + was_validator_count: {:validator_set, "validatorCounter", [mining_address]}, was_banned_count: {:validator_set, "banCounter", [mining_address]} ] end @@ -85,18 +84,6 @@ defmodule Explorer.Staking.ContractReader do ] end - def decode_data(address_hash_string) do - { - :ok, - %Chain.Hash{ - byte_count: _, - bytes: bytes - } - } = Chain.string_to_address_hash(address_hash_string) - - bytes - end - def perform_requests(requests, contracts, abi) do requests |> generate_requests(contracts) diff --git a/apps/explorer/lib/explorer/staking/contract_state.ex b/apps/explorer/lib/explorer/staking/contract_state.ex index 95ac6e318c..49ddde3746 100644 --- a/apps/explorer/lib/explorer/staking/contract_state.ex +++ b/apps/explorer/lib/explorer/staking/contract_state.ex @@ -1,7 +1,7 @@ defmodule Explorer.Staking.ContractState do @moduledoc """ Fetches all information from POSDAO staking contracts. - All contract calls are batched into four requests, according to their dependencies. + All contract calls are batched into requests, according to their dependencies. Subscribes to new block notifications and refreshes when previously unseen block arrives. """ @@ -13,25 +13,28 @@ defmodule Explorer.Staking.ContractState do alias Explorer.Chain.Events.{Publisher, Subscriber} alias Explorer.SmartContract.Reader alias Explorer.Staking.{ContractReader, StakeSnapshotting} + alias Explorer.Token.{BalanceReader, MetadataRetriever} @table_name __MODULE__ @table_keys [ - :token_contract_address, - :token, - :min_candidate_stake, - :min_delegator_stake, + :block_reward_contract, + :epoch_end_block, :epoch_number, :epoch_start_block, - :epoch_end_block, + :is_snapshotted, + :min_candidate_stake, + :min_delegator_stake, :staking_allowed, :staking_contract, - :validator_set_contract, - :block_reward_contract, - :validator_set_apply_block, + :token_contract_address, + :token, :validator_min_reward_percent, - :is_snapshotted + :validator_set_apply_block, + :validator_set_contract ] + @token_renew_frequency 10 # frequency in blocks + defstruct [ :seen_block, :contracts, @@ -91,13 +94,15 @@ defmodule Explorer.Staking.ContractState do abi: staking_abi ++ validator_set_abi ++ block_reward_abi } + token = get_token(token_contract_address) + :ets.insert(@table_name, - staking_contract: %{abi: staking_abi, address: staking_contract_address}, - validator_set_contract: %{abi: validator_set_abi, address: validator_set_contract_address}, block_reward_contract: %{abi: block_reward_abi, address: block_reward_contract_address}, + is_snapshotted: false, + staking_contract: %{abi: staking_abi, address: staking_contract_address}, token_contract_address: token_contract_address, - token: get_token(token_contract_address), - is_snapshotted: false + token: token, + validator_set_contract: %{abi: validator_set_abi, address: validator_set_contract_address} ) {:ok, state, {:continue, []}} @@ -123,14 +128,13 @@ defmodule Explorer.Staking.ContractState do defp fetch_state(contracts, abi, block_number) do # read general info from the contracts (including pool list and validator list) global_responses = ContractReader.perform_requests(ContractReader.global_requests(), contracts, abi) - token = get_token(global_responses.token_contract_address) + validator_min_reward_percent = ContractReader.perform_requests( ContractReader.validator_min_reward_percent_request(global_responses.epoch_number), contracts, abi ).value - start_snapshotting = (global_responses.epoch_start_block == block_number + 1) - is_validator = Enum.into(global_responses.validators, %{}, &{hash_to_string(&1), true}) - unremovable_validator = global_responses.unremovable_validator + epoch_finished = (global_responses.epoch_start_block == block_number + 1) + is_validator = Enum.into(global_responses.validators, %{}, &{address_bytes_to_string(&1), true}) # save the general info to ETS (excluding pool list and validator list) settings = @@ -146,23 +150,32 @@ defmodule Explorer.Staking.ContractState do :validator_set_apply_block ]) |> Map.to_list() - |> Enum.concat(token: token) |> Enum.concat(validator_min_reward_percent: validator_min_reward_percent) + update_token = + get(:token) == nil or + get(:token_contract_address) != global_responses.token_contract_address or + rem(block_number, @token_renew_frequency) == 0 + settings = if update_token do + Enum.concat(settings, token: get_token(global_responses.token_contract_address)) + else + settings + end + :ets.insert(@table_name, settings) # form the list of all pools - validators = if start_snapshotting do + validators = if epoch_finished do %{ - "getPendingValidators" => {:ok, [pending_validators]}, - "validatorsToBeFinalized" => {:ok, [to_be_finalized_validators]} + "getPendingValidators" => {:ok, [validators_pending]}, + "validatorsToBeFinalized" => {:ok, [validators_to_be_finalized]} } = Reader.query_contract(contracts.validator_set, abi, %{ "getPendingValidators" => [], "validatorsToBeFinalized" => [] }) - validators_pending = Enum.uniq(pending_validators ++ to_be_finalized_validators) - # get the list of all validators (the current and pending) + validators_pending = Enum.uniq(validators_pending ++ validators_to_be_finalized) %{ + # get the list of all validators (the current and pending) all: Enum.uniq(global_responses.validators ++ validators_pending), pending: validators_pending } @@ -170,30 +183,33 @@ defmodule Explorer.Staking.ContractState do %{all: global_responses.validators} end + # miningToStakingAddress mapping mining_to_staking_address = validators.all |> Enum.map(&ContractReader.staking_by_mining_requests/1) |> ContractReader.perform_grouped_requests(validators.all, contracts, abi) - |> Map.new(fn {mining_address, resp} -> {mining_address, ContractReader.decode_data(resp.staking_address)} end) + |> Map.new(fn {mining_address, resp} -> {mining_address, address_string_to_bytes(resp.staking_address)} end) + # the list of all pools (validators + active pools + inactive pools) pools = Enum.uniq( Map.values(mining_to_staking_address) ++ global_responses.active_pools ++ global_responses.inactive_pools ) - # read info about each pool from the contracts (including delegator list) + # read pool info from the contracts by its staking address pool_staking_responses = pools |> Enum.map(&ContractReader.pool_staking_requests/1) |> ContractReader.perform_grouped_requests(pools, contracts, abi) + # read pool info from the contracts by its mining address pool_mining_responses = pools |> Enum.map(&ContractReader.pool_mining_requests(pool_staking_responses[&1].mining_address_hash)) |> ContractReader.perform_grouped_requests(pools, contracts, abi) - # form a flat list of all stakers in the form {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} stakers = Enum.flat_map(pool_staking_responses, fn {pool_staking_address, resp} -> [{pool_staking_address, pool_staking_address, true}] ++ @@ -201,7 +217,7 @@ defmodule Explorer.Staking.ContractState do Enum.map(resp.inactive_delegators, &{pool_staking_address, &1, false}) end) - # get amounts for each of the stakers + # read info of each staker from the contracts staker_responses = stakers |> Enum.map(fn {pool_staking_address, staker_address, _is_active} -> @@ -209,9 +225,11 @@ defmodule Explorer.Staking.ContractState do end) |> ContractReader.perform_grouped_requests(stakers, contracts, abi) - # to keep sort order - pool_staking_keys = Enum.map(pool_staking_responses, fn {key, _} -> key end) + # to keep sort order when using `perform_grouped_requests` (see below) + pool_staking_keys = Enum.map(pool_staking_responses, fn {pool_staking_address, _} -> pool_staking_address end) + # call `BlockReward.validatorShare` function for each pool + # to get validator's reward share of the pool (needed for the `Delegators` list in UI) candidate_reward_responses = pool_staking_responses |> Enum.map(fn {_pool_staking_address, resp} -> @@ -224,9 +242,11 @@ defmodule Explorer.Staking.ContractState do end) |> ContractReader.perform_grouped_requests(pool_staking_keys, contracts, abi) - # to keep sort order + # to keep sort order when using `perform_grouped_requests` (see below) delegator_keys = Enum.map(staker_responses, fn {key, _} -> key end) + # call `BlockReward.delegatorShare` function for each delegator + # to get their reward share of the pool (needed for the `Delegators` list in UI) delegator_reward_responses = staker_responses |> Enum.map(fn {{pool_staking_address, _staker_address, _is_active}, resp} -> @@ -247,7 +267,6 @@ defmodule Explorer.Staking.ContractState do # calculate likelihood of becoming a validator on the next epoch [likelihood_values, total_likelihood] = global_responses.pools_likelihood - likelihood = global_responses.pools_to_be_elected # array of pool addresses (staking addresses) |> Enum.zip(likelihood_values) @@ -283,7 +302,7 @@ defmodule Explorer.Staking.ContractState do validator_reward_percent: staking_resp.validator_reward_percent / 10_000, is_deleted: false, is_validator: is_validator, - is_unremovable: hash_to_string(pool_staking_address) == unremovable_validator, + is_unremovable: address_bytes_to_string(pool_staking_address) == global_responses.unremovable_validator, ban_reason: binary_to_string(mining_resp.ban_reason) } |> Map.merge( @@ -319,6 +338,7 @@ defmodule Explorer.Staking.ContractState do }) end) + # perform SQL queries {:ok, _} = Chain.import(%{ staking_pools: %{params: pool_entries}, @@ -326,9 +346,44 @@ defmodule Explorer.Staking.ContractState do timeout: :infinity }) - if start_snapshotting do + if epoch_finished do + # update ERC balance of the BlockReward contract + token = get(:token) + if token != nil do + block_reward_address = address_string_to_bytes(get(:block_reward_contract).address) + token_contract_address_hash = token.contract_address_hash + + block_reward_balance = BalanceReader.get_balances_of([%{ + token_contract_address_hash: token_contract_address_hash, + address_hash: block_reward_address, + block_number: block_number + }])[:ok] + + token_params = + token_contract_address_hash + |> MetadataRetriever.get_functions_of() + |> Map.merge(%{ + contract_address_hash: token_contract_address_hash, + type: "ERC-20" + }) + + Chain.import(%{ + addresses: %{params: [%{hash: block_reward_address}], on_conflict: :nothing}, + address_current_token_balances: %{params: [%{ + address_hash: block_reward_address, + token_contract_address_hash: token_contract_address_hash, + block_number: block_number, + value: block_reward_balance, + value_fetched_at: DateTime.utc_now() + }]}, + tokens: %{params: [token_params]} + }) + end + + # start snapshotting at the beginning of the staking epoch spawn(StakeSnapshotting, :do_snapshotting, [ - %{contracts: contracts, abi: abi, epoch_number: global_responses.epoch_number, ets_table_name: @table_name}, + %{contracts: contracts, abi: abi, ets_table_name: @table_name}, + global_responses.epoch_number, pool_staking_responses, pool_mining_responses, Map.new(Enum.map(staker_responses, fn {key, resp} -> {pool_staking_address, staker_address, _} = key; {{pool_staking_address, staker_address}, resp} end)), @@ -338,6 +393,7 @@ defmodule Explorer.Staking.ContractState do ]) end + # notify the UI about new block Publisher.broadcast(:staking_update) end @@ -367,7 +423,19 @@ defmodule Explorer.Staking.ContractState do defp ratio(_numerator, 0), do: 0 defp ratio(numerator, denominator), do: numerator / denominator * 100 - defp hash_to_string(hash), do: "0x" <> Base.encode16(hash, case: :lower) + defp address_bytes_to_string(hash), do: "0x" <> Base.encode16(hash, case: :lower) + + defp address_string_to_bytes(address_string) do + { + :ok, + %Chain.Hash{ + byte_count: _, + bytes: bytes + } + } = Chain.string_to_address_hash(address_string) + + bytes + end # sobelow_skip ["Traversal"] defp abi(file_name) do diff --git a/apps/explorer/lib/explorer/staking/stake_snapshotting.ex b/apps/explorer/lib/explorer/staking/stake_snapshotting.ex index 10678db538..07c40cc525 100644 --- a/apps/explorer/lib/explorer/staking/stake_snapshotting.ex +++ b/apps/explorer/lib/explorer/staking/stake_snapshotting.ex @@ -1,6 +1,6 @@ defmodule Explorer.Staking.StakeSnapshotting do @moduledoc """ - Makes snapshots of staked amounts + Makes snapshots of staked amounts. """ import Ecto.Query, only: [from: 2] @@ -12,7 +12,8 @@ defmodule Explorer.Staking.StakeSnapshotting do alias Explorer.Staking.ContractReader def do_snapshotting( - %{contracts: contracts, abi: abi, epoch_number: epoch_number, ets_table_name: ets_table_name}, + %{contracts: contracts, abi: abi, ets_table_name: ets_table_name}, + epoch_number, cached_pool_staking_responses, cached_pool_mining_responses, cached_staker_responses, @@ -27,7 +28,8 @@ defmodule Explorer.Staking.StakeSnapshotting do pending_validators_mining_addresses |> Enum.map(&mining_to_staking_address[&1]) - # get snapshotted amounts and other pool info for each pending validator. + # get snapshotted amounts and other pool info for each + # pending validator by their staking address. # use `cached_pool_staking_responses` when possible pool_staking_responses = pool_staking_addresses @@ -46,7 +48,7 @@ defmodule Explorer.Staking.StakeSnapshotting do |> Enum.zip(pool_staking_addresses) |> Map.new(fn {key, val} -> {val, key} end) - # get pool info by its mining address. + # read pool info from the contracts by its mining address. # use `cached_pool_mining_responses` when possible pool_mining_responses = pool_staking_addresses @@ -63,14 +65,15 @@ defmodule Explorer.Staking.StakeSnapshotting do |> Enum.zip(pool_staking_addresses) |> Map.new(fn {key, val} -> {val, key} end) - # form a flat list of all active stakers in the form {pool_staking_address, staker_address} + # get a flat list of all stakers of each validator + # in the form of {pool_staking_address, staker_address} stakers = Enum.flat_map(pool_staking_responses, fn {pool_staking_address, resp} -> [{pool_staking_address, pool_staking_address}] ++ Enum.map(resp.active_delegators, &{pool_staking_address, &1}) end) - # get amounts for each of the stakers + # read info of each staker from the contracts. # use `cached_staker_responses` when possible staker_responses = stakers @@ -96,9 +99,11 @@ defmodule Explorer.Staking.StakeSnapshotting do |> Enum.zip(stakers) |> Map.new(fn {key, val} -> {val, key} end) - # to keep sort order + # to keep sort order when using `perform_grouped_requests` (see below) pool_staking_keys = Enum.map(pool_staking_responses, fn {key, _} -> key end) + # call `BlockReward.validatorShare` function for each pool + # to get validator's reward share of the pool (needed for the `Delegators` list in UI) validator_reward_responses = pool_staking_responses |> Enum.map(fn {_pool_staking_address, resp} -> @@ -111,9 +116,11 @@ defmodule Explorer.Staking.StakeSnapshotting do end) |> ContractReader.perform_grouped_requests(pool_staking_keys, contracts, abi) - # to keep sort order + # to keep sort order when using `perform_grouped_requests` (see below) delegator_keys = Enum.map(staker_responses, fn {key, _} -> key end) + # call `BlockReward.delegatorShare` function for each delegator + # to get their reward share of the pool (needed for the `Delegators` list in UI) delegator_reward_responses = staker_responses |> Enum.map(fn {{pool_staking_address, _staker_address}, resp} -> @@ -129,6 +136,7 @@ defmodule Explorer.Staking.StakeSnapshotting do end) |> ContractReader.perform_grouped_requests(delegator_keys, contracts, abi) + # form entries for updating the `staking_pools` table in DB pool_entries = Enum.map(pool_staking_addresses, fn pool_staking_address -> staking_resp = pool_staking_responses[pool_staking_address] @@ -158,6 +166,7 @@ defmodule Explorer.Staking.StakeSnapshotting do ) end) + # form entries for updating the `staking_pools_delegators` table in DB delegator_entries = Enum.map(staker_responses, fn {{pool_staking_address, staker_address}, resp} -> delegator_reward_resp = delegator_reward_responses[{pool_staking_address, staker_address}] @@ -169,6 +178,7 @@ defmodule Explorer.Staking.StakeSnapshotting do }) end) + # perform SQL queries case Chain.import(%{ staking_pools: %{params: pool_entries, on_conflict: staking_pools_update(), clear_snapshotted_values: true}, staking_pools_delegators: %{params: delegator_entries, on_conflict: staking_pools_delegators_update(), clear_snapshotted_values: true},