From 81d3a8162fe381a96af110ace9df582f00f7d03b Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Wed, 20 Jan 2021 19:35:11 +0300 Subject: [PATCH 01/22] Correct Staking Dapp warning message --- .../lib/block_scout_web/templates/stakes/_warning.html.eex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/stakes/_warning.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/stakes/_warning.html.eex index 9cdf42903a..53803c4d3f 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/stakes/_warning.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/stakes/_warning.html.eex @@ -8,7 +8,7 @@ - Due to high loading, Staking Dapp may show data with delay. Because of that after making transactions the result in UI will be visible not right away. We're working on optimization. + Due to high load volumes, current staking data display may lag behind actual transactions. Transactions are being processed correctly on-chain. We are currently working to address this UI display issue. From 7570d58ef6c7a28f3b403e3cd0e8ff4e2a9405ea Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Tue, 19 Jan 2021 14:24:18 +0300 Subject: [PATCH 02/22] Add Staking DApp light optimizations --- .../lib/explorer/staking/contract_reader.ex | 6 +- .../lib/explorer/staking/contract_state.ex | 244 ++++++++++-------- .../contracts_abi/posdao/StakingAuRa.json | 14 + .../posdao/ValidatorSetAuRa.json | 14 + .../explorer/staking/contract_state_test.exs | 90 ++++--- 5 files changed, 214 insertions(+), 154 deletions(-) diff --git a/apps/explorer/lib/explorer/staking/contract_reader.ex b/apps/explorer/lib/explorer/staking/contract_reader.ex index 5307646c95..3cf659d8d3 100644 --- a/apps/explorer/lib/explorer/staking/contract_reader.ex +++ b/apps/explorer/lib/explorer/staking/contract_reader.ex @@ -29,6 +29,8 @@ 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()) @@ -36,7 +38,9 @@ defmodule Explorer.Staking.ContractReader do # 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 diff --git a/apps/explorer/lib/explorer/staking/contract_state.ex b/apps/explorer/lib/explorer/staking/contract_state.ex index c41e1cf60b..b8af3675aa 100644 --- a/apps/explorer/lib/explorer/staking/contract_state.ex +++ b/apps/explorer/lib/explorer/staking/contract_state.ex @@ -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, @@ -112,6 +113,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}, @@ -168,131 +170,143 @@ defmodule Explorer.Staking.ContractState do 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, 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 || get(:epoch_end_block, 0) == 0 || 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) - # form the list of validator pools - validators = - get_validators( - start_snapshotting, - global_responses, - contracts, - abi, - block_number - ) - - # miningToStakingAddress mapping - mining_to_staking_address = get_mining_to_staking_address(validators, contracts, abi, block_number) - - # 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 - ) - - %{ - pool_staking_responses: pool_staking_responses, - pool_mining_responses: pool_mining_responses, - staker_responses: staker_responses - } = get_responses(pools, block_number, contracts, abi) - - # 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 = - get_candidate_reward_responses( - pool_staking_responses, - global_responses, - pool_staking_keys, - contracts, - abi, - block_number - ) - - # 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_reward_responses = - get_delegator_reward_responses( - delegator_responses, - pool_staking_responses, - global_responses, - contracts, - abi, - block_number - ) - - # calculate total amount staked into all active pools - staked_total = Enum.sum(for {_, pool} <- pool_staking_responses, pool.is_active, do: pool.total_staked_amount) - - # calculate likelihood of becoming a validator on the next epoch - [likelihood_values, total_likelihood] = global_responses.pools_likelihood - # array of pool addresses (staking addresses) - likelihood = - global_responses.pools_to_be_elected - |> Enum.zip(likelihood_values) - |> Enum.into(%{}) + 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 - snapshotted_epoch_number = get(:snapshotted_epoch_number) + # we should update database as something changed in contracts state + if should_update_db do + # form the list of validator pools + validators = + get_validators( + start_snapshotting, + global_responses, + contracts, + abi, + block_number + ) + + # miningToStakingAddress mapping + mining_to_staking_address = get_mining_to_staking_address(validators, contracts, abi, block_number) + + # 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 + ) - # form entries for writing to the `staking_pools` table in DB - pool_entries = - get_pool_entries(%{ - pools: pools, - pool_mining_responses: pool_mining_responses, + %{ pool_staking_responses: pool_staking_responses, - is_validator: is_validator, - candidate_reward_responses: candidate_reward_responses, - global_responses: global_responses, - snapshotted_epoch_number: snapshotted_epoch_number, - likelihood: likelihood, - total_likelihood: total_likelihood, - staked_total: staked_total - }) + pool_mining_responses: pool_mining_responses, + staker_responses: staker_responses + } = get_responses(pools, block_number, contracts, abi) + + # 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 = + get_candidate_reward_responses( + pool_staking_responses, + global_responses, + pool_staking_keys, + contracts, + abi, + block_number + ) + + # call `BlockReward.delegatorShare` function for each delegator + # to get their reward share of the pool (needed for the `Delegators` list in UI) + delegator_responses = get_delegator_responses(staker_responses) + + delegator_reward_responses = + get_delegator_reward_responses( + delegator_responses, + pool_staking_responses, + global_responses, + contracts, + abi, + block_number + ) + + # calculate total amount staked into all active pools + staked_total = Enum.sum(for {_, pool} <- pool_staking_responses, pool.is_active, do: pool.total_staked_amount) + + # calculate likelihood of becoming a validator on the next epoch + [likelihood_values, total_likelihood] = global_responses.pools_likelihood + # array of pool addresses (staking addresses) + likelihood = + global_responses.pools_to_be_elected + |> Enum.zip(likelihood_values) + |> Enum.into(%{}) + + snapshotted_epoch_number = get(:snapshotted_epoch_number) + + # form entries for writing to the `staking_pools` table in DB + pool_entries = + get_pool_entries(%{ + pools: pools, + pool_mining_responses: pool_mining_responses, + pool_staking_responses: pool_staking_responses, + is_validator: is_validator, + candidate_reward_responses: candidate_reward_responses, + global_responses: global_responses, + snapshotted_epoch_number: snapshotted_epoch_number, + likelihood: likelihood, + total_likelihood: total_likelihood, + 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) + # 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 - }) + # 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) + if start_snapshotting do + do_start_snapshotting( + epoch_very_beginning, + pool_staking_responses, + global_responses, + contracts, + abi, + validators, + mining_to_staking_address + ) + end end - if start_snapshotting do - do_start_snapshotting( - epoch_very_beginning, - pool_staking_responses, - global_responses, - contracts, - abi, - validators, - mining_to_staking_address - ) - end + # if should_update_db # notify the UI about a new block data = %{ @@ -390,6 +404,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, @@ -603,7 +627,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) diff --git a/apps/explorer/priv/contracts_abi/posdao/StakingAuRa.json b/apps/explorer/priv/contracts_abi/posdao/StakingAuRa.json index bf9cebbb58..ad2599813c 100644 --- a/apps/explorer/priv/contracts_abi/posdao/StakingAuRa.json +++ b/apps/explorer/priv/contracts_abi/posdao/StakingAuRa.json @@ -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" } ] diff --git a/apps/explorer/priv/contracts_abi/posdao/ValidatorSetAuRa.json b/apps/explorer/priv/contracts_abi/posdao/ValidatorSetAuRa.json index 7906f27317..0a3d2fc4a4 100644 --- a/apps/explorer/priv/contracts_abi/posdao/ValidatorSetAuRa.json +++ b/apps/explorer/priv/contracts_abi/posdao/ValidatorSetAuRa.json @@ -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" } ] diff --git a/apps/explorer/test/explorer/staking/contract_state_test.exs b/apps/explorer/test/explorer/staking/contract_state_test.exs index 0fcc564b18..281cefe3b8 100644 --- a/apps/explorer/test/explorer/staking/contract_state_test.exs +++ b/apps/explorer/test/explorer/staking/contract_state_test.exs @@ -99,7 +99,7 @@ defmodule Explorer.Staking.ContractStateTest do EthereumJSONRPC.Mox, :json_rpc, fn requests, _opts -> - assert length(requests) == 15 + assert length(requests) == 17 {:ok, format_responses([ @@ -125,13 +125,17 @@ defmodule Explorer.Staking.ContractStateTest do "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000b916e7e1f4bcb13549602ed042d36746fd0d96c9000000000000000000000000db9cb2478d917719c53862008672166808258577000000000000000000000000b6695f5c2e3f5eff8036b5f5f3a9d83a5310e51e", # 11 StakingAuRa.areStakeAndWithdrawAllowed "0x0000000000000000000000000000000000000000000000000000000000000000", - # 12 StakingAuRa.erc677TokenContract + # 12 StakingAuRa.lastChangeBlock + "0x0000000000000000000000000000000000000000000000000000000000000000", + # 13 StakingAuRa.erc677TokenContract "0x0000000000000000000000006f7a73c96bd56f8b0debc795511eda135e105ea3", - # 13 ValidatorSetAuRa.unremovableValidator + # 14 ValidatorSetAuRa.unremovableValidator "0x0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", - # 14 ValidatorSetAuRa.getValidators + # 15 ValidatorSetAuRa.getValidators "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c7800000000000000000000000075df42383afe6bf5194aa8fa0e9b3d5f9e869441000000000000000000000000522df396ae70a058bd69778408630fdb023389b2", - # 15 ValidatorSetAuRa.validatorSetApplyBlock + # 16 ValidatorSetAuRa.validatorSetApplyBlock + "0x0000000000000000000000000000000000000000000000000000000000000000", + # 17 ValidatorSetAuRa.lastChangeBlock "0x0000000000000000000000000000000000000000000000000000000000000000" ])} end @@ -152,6 +156,44 @@ defmodule Explorer.Staking.ContractStateTest do end ) + # invoke update_block_reward_balance() + + ## BalanceReader.get_balances_of + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn requests, _opts -> + assert length(requests) == 1 + + {:ok, + format_responses([ + # ERC677BridgeTokenRewardable.balanceOf(BlockRewardAuRa) + "0x0000000000000000000000000000000000000000000000000000000000000000" + ])} + end + ) + + ## MetadataRetriever.get_functions_of + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn requests, _opts -> + assert length(requests) == 4 + + {:ok, + format_responses([ + # ERC677BridgeTokenRewardable.decimals + "0x0000000000000000000000000000000000000000000000000000000000000012", + # ERC677BridgeTokenRewardable.name + "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000055354414b45000000000000000000000000000000000000000000000000000000", + # ERC677BridgeTokenRewardable.symbol + "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000055354414b45000000000000000000000000000000000000000000000000000000", + # ERC677BridgeTokenRewardable.totalSupply + "0x000000000000000000000000000000000000000000000001f399b1438a100000" + ])} + end + ) + # get_validators expect( EthereumJSONRPC.Mox, @@ -644,44 +686,6 @@ defmodule Explorer.Staking.ContractStateTest do end ) - # invoke at_start_snapshotting() - - ## BalanceReader.get_balances_of - expect( - EthereumJSONRPC.Mox, - :json_rpc, - fn requests, _opts -> - assert length(requests) == 1 - - {:ok, - format_responses([ - # ERC677BridgeTokenRewardable.balanceOf(BlockRewardAuRa) - "0x0000000000000000000000000000000000000000000000000000000000000000" - ])} - end - ) - - ## MetadataRetriever.get_functions_of - expect( - EthereumJSONRPC.Mox, - :json_rpc, - fn requests, _opts -> - assert length(requests) == 4 - - {:ok, - format_responses([ - # ERC677BridgeTokenRewardable.decimals - "0x0000000000000000000000000000000000000000000000000000000000000012", - # ERC677BridgeTokenRewardable.name - "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000055354414b45000000000000000000000000000000000000000000000000000000", - # ERC677BridgeTokenRewardable.symbol - "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000055354414b45000000000000000000000000000000000000000000000000000000", - # ERC677BridgeTokenRewardable.totalSupply - "0x000000000000000000000000000000000000000000000001f399b1438a100000" - ])} - end - ) - # invoke do_snapshotting() ## 1 snapshotted_pool_amounts_requests From 94265dc15cb57e25752ded634944fe0282cee452 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Tue, 19 Jan 2021 16:38:36 +0300 Subject: [PATCH 03/22] Fix Staking DApp light optimizations --- .../lib/explorer/chain/events/subscriber.ex | 2 +- .../lib/explorer/staking/contract_state.ex | 52 ++++++++++++++----- .../explorer/staking/stake_snapshotting.ex | 3 ++ 3 files changed, 44 insertions(+), 13 deletions(-) diff --git a/apps/explorer/lib/explorer/chain/events/subscriber.ex b/apps/explorer/lib/explorer/chain/events/subscriber.ex index c363e459d7..21e68ff827 100644 --- a/apps/explorer/lib/explorer/chain/events/subscriber.ex +++ b/apps/explorer/lib/explorer/chain/events/subscriber.ex @@ -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 diff --git a/apps/explorer/lib/explorer/staking/contract_state.ex b/apps/explorer/lib/explorer/staking/contract_state.ex index b8af3675aa..be8250a5e2 100644 --- a/apps/explorer/lib/explorer/staking/contract_state.ex +++ b/apps/explorer/lib/explorer/staking/contract_state.ex @@ -42,6 +42,7 @@ defmodule Explorer.Staking.ContractState do defstruct [ :seen_block, + :snapshotting_finished, :contracts, :abi ] @@ -70,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") @@ -102,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, @@ -128,6 +131,11 @@ defmodule Explorer.Staking.ContractState do {:noreply, state} end + @doc "Handles an event about snapshotting finishing" + def handle_info({:chain_event, :stake_snapshotting_finished}, state) do + {:noreply, %{state | snapshotting_finished: true}} + end + @doc "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 @@ -146,19 +154,31 @@ 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) @@ -172,12 +192,13 @@ defmodule Explorer.Staking.ContractState do # 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, we should update database + # 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 || get(:epoch_end_block, 0) == 0 || last_change_block > get(:last_change_block) + 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 = @@ -267,7 +288,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, @@ -282,14 +316,10 @@ 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 }) @@ -306,8 +336,6 @@ defmodule Explorer.Staking.ContractState do end end - # if should_update_db - # notify the UI about a new block data = %{ active_pools_length: active_pools_length, diff --git a/apps/explorer/lib/explorer/staking/stake_snapshotting.ex b/apps/explorer/lib/explorer/staking/stake_snapshotting.ex index 4ce07417b7..c2a439ae9a 100644 --- a/apps/explorer/lib/explorer/staking/stake_snapshotting.ex +++ b/apps/explorer/lib/explorer/staking/stake_snapshotting.ex @@ -9,6 +9,7 @@ defmodule Explorer.Staking.StakeSnapshotting do alias Explorer.Chain alias Explorer.Chain.{StakingPool, StakingPoolsDelegator} + alias Explorer.Chain.Events.Publisher alias Explorer.Staking.ContractReader def do_snapshotting( @@ -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) From 5cf046c13ac72050a254d444aaa2467b0a1c3b6c Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Tue, 19 Jan 2021 16:40:27 +0300 Subject: [PATCH 04/22] mix format --- apps/explorer/lib/explorer/staking/contract_state.ex | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/explorer/lib/explorer/staking/contract_state.ex b/apps/explorer/lib/explorer/staking/contract_state.ex index be8250a5e2..d5e4ca9212 100644 --- a/apps/explorer/lib/explorer/staking/contract_state.ex +++ b/apps/explorer/lib/explorer/staking/contract_state.ex @@ -161,11 +161,12 @@ defmodule Explorer.Staking.ContractState do fetch_state(state, global_responses, block_number, epoch_very_beginning) - state = if state.snapshotting_finished do - %{state | snapshotting_finished: false} - else - state - end + state = + if state.snapshotting_finished do + %{state | snapshotting_finished: false} + else + state + end {:noreply, %{state | seen_block: block_number}} else From 4712f6f06fb9d0f61c4647aa999c8e66bdca988c Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Wed, 20 Jan 2021 11:26:02 +0300 Subject: [PATCH 05/22] Comment out Staking DApp warning --- .../lib/block_scout_web/templates/stakes/index.html.eex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/stakes/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/stakes/index.html.eex index 30dca52715..7c783f0a9e 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/stakes/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/stakes/index.html.eex @@ -2,7 +2,7 @@ <%= raw(@top) %> <%= render BlockScoutWeb.StakesView, "_learn-more.html", conn: @conn %> -<%= render BlockScoutWeb.StakesView, "_warning.html", conn: @conn %> +<%= # render BlockScoutWeb.StakesView, "_warning.html", conn: @conn %>
<%= render BlockScoutWeb.StakesView, "_stakes_tabs.html", conn: @conn %> From 1de2970e7356842d318f48d174bed5166518968e Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Wed, 20 Jan 2021 11:28:33 +0300 Subject: [PATCH 06/22] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cab29720da..7de1de155b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - [#3564](https://github.com/poanetwork/blockscout/pull/3564) - Staking welcome message ### Fixes +- [#3581](https://github.com/poanetwork/blockscout/pull/3581) - Reduce RPC requests and DB changes by Staking DApp ### Chore - [#3574](https://github.com/poanetwork/blockscout/pull/3574) - Correct UNI token price From 6ab00e76a1a5e57ed86899c0174394b037c52a48 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Wed, 20 Jan 2021 11:34:57 +0300 Subject: [PATCH 07/22] Update CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7de1de155b..2b226a1f23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - [#3564](https://github.com/poanetwork/blockscout/pull/3564) - Staking welcome message ### Fixes -- [#3581](https://github.com/poanetwork/blockscout/pull/3581) - Reduce RPC requests and DB changes by Staking DApp +- [#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 From 0be2e854751cb8fe2fe67d68456fa2f5b548fe04 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Wed, 20 Jan 2021 12:07:53 +0300 Subject: [PATCH 08/22] Fixes --- .dialyzer-ignore | 4 ++-- apps/explorer/lib/explorer/staking/contract_state.ex | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.dialyzer-ignore b/.dialyzer-ignore index 1324d90572..5d621ddb40 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -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 diff --git a/apps/explorer/lib/explorer/staking/contract_state.ex b/apps/explorer/lib/explorer/staking/contract_state.ex index d5e4ca9212..99d3bc3d24 100644 --- a/apps/explorer/lib/explorer/staking/contract_state.ex +++ b/apps/explorer/lib/explorer/staking/contract_state.ex @@ -131,12 +131,12 @@ defmodule Explorer.Staking.ContractState do {:noreply, state} end - @doc "Handles an event about snapshotting finishing" + # handles an event about snapshotting finishing def handle_info({:chain_event, :stake_snapshotting_finished}, state) do {:noreply, %{state | snapshotting_finished: true}} end - @doc "Handles new blocks and decides to fetch fresh chain info" + # 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) From fed392f6b441d8daf91f34d422bcf1379cf52757 Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Wed, 20 Jan 2021 13:23:56 +0300 Subject: [PATCH 09/22] Refactoring --- .../lib/explorer/staking/contract_state.ex | 238 +++++++++--------- .../explorer/staking/stake_snapshotting.ex | 2 +- 2 files changed, 122 insertions(+), 118 deletions(-) diff --git a/apps/explorer/lib/explorer/staking/contract_state.ex b/apps/explorer/lib/explorer/staking/contract_state.ex index 99d3bc3d24..8313525fdc 100644 --- a/apps/explorer/lib/explorer/staking/contract_state.ex +++ b/apps/explorer/lib/explorer/staking/contract_state.ex @@ -183,8 +183,6 @@ defmodule Explorer.Staking.ContractState do 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) @@ -220,121 +218,7 @@ defmodule Explorer.Staking.ContractState do # we should update database as something changed in contracts state if should_update_db do - # form the list of validator pools - validators = - get_validators( - start_snapshotting, - global_responses, - contracts, - abi, - block_number - ) - - # miningToStakingAddress mapping - mining_to_staking_address = get_mining_to_staking_address(validators, contracts, abi, block_number) - - # 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 - ) - - %{ - pool_staking_responses: pool_staking_responses, - pool_mining_responses: pool_mining_responses, - staker_responses: staker_responses - } = get_responses(pools, block_number, contracts, abi) - - # 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 = - get_candidate_reward_responses( - pool_staking_responses, - global_responses, - pool_staking_keys, - contracts, - abi, - block_number - ) - - # call `BlockReward.delegatorShare` function for each delegator - # to get their reward share of the pool (needed for the `Delegators` list in UI) - delegator_responses = get_delegator_responses(staker_responses) - - delegator_reward_responses = - get_delegator_reward_responses( - delegator_responses, - pool_staking_responses, - global_responses, - contracts, - abi, - block_number - ) - - # calculate total amount staked into all active pools - staked_total = Enum.sum(for {_, pool} <- pool_staking_responses, pool.is_active, do: pool.total_staked_amount) - - # calculate likelihood of becoming a validator on the next epoch - [likelihood_values, total_likelihood] = global_responses.pools_likelihood - # array of pool addresses (staking addresses) - likelihood = - global_responses.pools_to_be_elected - |> Enum.zip(likelihood_values) - |> Enum.into(%{}) - - snapshotted_epoch_number = get(:snapshotted_epoch_number) - - # 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, - pool_mining_responses: pool_mining_responses, - pool_staking_responses: pool_staking_responses, - is_validator: is_validator, - candidate_reward_responses: candidate_reward_responses, - global_responses: global_responses, - snapshotted_epoch_number: snapshotted_epoch_number, - likelihood: likelihood, - total_likelihood: total_likelihood, - staked_total: staked_total - }) - - # perform SQL queries - {:ok, _} = - Chain.import(%{ - staking_pools: %{params: pool_entries}, - timeout: :infinity - }) - - if start_snapshotting do - do_start_snapshotting( - epoch_very_beginning, - pool_staking_responses, - global_responses, - contracts, - abi, - validators, - mining_to_staking_address - ) - end + update_database(epoch_very_beginning, start_snapshotting, global_responses, contracts, abi, block_number) end # notify the UI about a new block @@ -352,6 +236,126 @@ defmodule Explorer.Staking.ContractState do 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( + start_snapshotting, + global_responses, + contracts, + abi, + block_number + ) + + # miningToStakingAddress mapping + mining_to_staking_address = get_mining_to_staking_address(validators, contracts, abi, block_number) + + # 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 + ) + + %{ + pool_staking_responses: pool_staking_responses, + pool_mining_responses: pool_mining_responses, + staker_responses: staker_responses + } = get_responses(pools, block_number, contracts, abi) + + # 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 = + get_candidate_reward_responses( + pool_staking_responses, + global_responses, + pool_staking_keys, + contracts, + abi, + block_number + ) + + # call `BlockReward.delegatorShare` function for each delegator + # to get their reward share of the pool (needed for the `Delegators` list in UI) + delegator_responses = get_delegator_responses(staker_responses) + + delegator_reward_responses = + get_delegator_reward_responses( + delegator_responses, + pool_staking_responses, + global_responses, + contracts, + abi, + block_number + ) + + # calculate total amount staked into all active pools + staked_total = Enum.sum(for {_, pool} <- pool_staking_responses, pool.is_active, do: pool.total_staked_amount) + + # calculate likelihood of becoming a validator on the next epoch + [likelihood_values, total_likelihood] = global_responses.pools_likelihood + # array of pool addresses (staking addresses) + likelihood = + global_responses.pools_to_be_elected + |> Enum.zip(likelihood_values) + |> Enum.into(%{}) + + snapshotted_epoch_number = get(:snapshotted_epoch_number) + + # 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, + pool_mining_responses: pool_mining_responses, + pool_staking_responses: pool_staking_responses, + is_validator: is_validator, + candidate_reward_responses: candidate_reward_responses, + global_responses: global_responses, + snapshotted_epoch_number: snapshotted_epoch_number, + likelihood: likelihood, + total_likelihood: total_likelihood, + staked_total: staked_total + }) + + # perform SQL queries + {:ok, _} = + Chain.import(%{ + staking_pools: %{params: pool_entries}, + timeout: :infinity + }) + + if start_snapshotting do + do_start_snapshotting( + epoch_very_beginning, + pool_staking_responses, + global_responses, + contracts, + abi, + validators, + mining_to_staking_address + ) + end + end + defp get_settings(global_responses, validator_min_reward_percent, block_number) do base_settings = get_base_settings(global_responses, validator_min_reward_percent) diff --git a/apps/explorer/lib/explorer/staking/stake_snapshotting.ex b/apps/explorer/lib/explorer/staking/stake_snapshotting.ex index c2a439ae9a..751da24aa2 100644 --- a/apps/explorer/lib/explorer/staking/stake_snapshotting.ex +++ b/apps/explorer/lib/explorer/staking/stake_snapshotting.ex @@ -8,8 +8,8 @@ defmodule Explorer.Staking.StakeSnapshotting do require Logger alias Explorer.Chain - alias Explorer.Chain.{StakingPool, StakingPoolsDelegator} alias Explorer.Chain.Events.Publisher + alias Explorer.Chain.{StakingPool, StakingPoolsDelegator} alias Explorer.Staking.ContractReader def do_snapshotting( From 123d488af03c31166c3354650e54a4bff90a876e Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Fri, 22 Jan 2021 13:59:25 +0300 Subject: [PATCH 10/22] Token holders API endpoint --- CHANGELOG.md | 1 + .../controllers/api/rpc/token_controller.ex | 31 ++++++- .../lib/block_scout_web/etherscan.ex | 84 ++++++++++++++++++- .../views/api/rpc/token_view.ex | 12 +++ apps/explorer/lib/explorer/chain.ex | 2 +- 5 files changed, 127 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b226a1f23..c3ebb44d87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## Current ### Features +- [#3584](https://github.com/poanetwork/blockscout/pull/3584) - Token holders API endpoint - [#3564](https://github.com/poanetwork/blockscout/pull/3564) - Staking welcome message ### Fixes diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/token_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/token_controller.ex index ee965fd9b9..e24caee0d1 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/token_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/token_controller.ex @@ -1,7 +1,8 @@ defmodule BlockScoutWeb.API.RPC.TokenController do use BlockScoutWeb, :controller - alias Explorer.Chain + alias BlockScoutWeb.API.RPC.Helpers + alias Explorer.{Chain, PagingOptions} def gettoken(conn, params) do with {:contractaddress_param, {:ok, contractaddress_param}} <- fetch_contractaddress(params), @@ -20,6 +21,34 @@ defmodule BlockScoutWeb.API.RPC.TokenController do end end + def gettokenholders(conn, params) do + with pagination_options <- Helpers.put_pagination_options(%{}, params), + {:contractaddress_param, {:ok, contractaddress_param}} <- fetch_contractaddress(params), + {:format, {:ok, address_hash}} <- to_address_hash(contractaddress_param) do + options_with_defaults = + pagination_options + |> Map.put_new(:page_number, 0) + |> Map.put_new(:page_size, 10) + + options = [ + paging_options: %PagingOptions{ + key: nil, + page_number: options_with_defaults.page_number, + page_size: options_with_defaults.page_size + } + ] + + token_holders = Chain.fetch_token_holders_from_token_hash(address_hash, options) + render(conn, "gettokenholders.json", %{token_holders: token_holders}) + else + {:contractaddress_param, :error} -> + render(conn, :error, error: "Query parameter contract address is required") + + {:format, :error} -> + render(conn, :error, error: "Invalid contract address hash") + end + end + defp fetch_contractaddress(params) do {:contractaddress_param, Map.fetch(params, "contractaddress")} end diff --git a/apps/block_scout_web/lib/block_scout_web/etherscan.ex b/apps/block_scout_web/lib/block_scout_web/etherscan.ex index 2b2d28b85d..f761a8e766 100644 --- a/apps/block_scout_web/lib/block_scout_web/etherscan.ex +++ b/apps/block_scout_web/lib/block_scout_web/etherscan.ex @@ -276,6 +276,23 @@ defmodule BlockScoutWeb.Etherscan do "result" => nil } + @token_gettokenholders_example_value %{ + "status" => "1", + "message" => "OK", + "result" => [ + %{ + "address" => "0x0000000000000000000000000000000000000000", + "value" => "965208500001258757122850" + } + ] + } + + @token_gettokenholders_example_value_error %{ + "status" => "0", + "message" => "Invalid contract address format", + "result" => nil + } + @stats_tokensupply_example_value %{ "status" => "1", "message" => "OK", @@ -664,6 +681,18 @@ defmodule BlockScoutWeb.Etherscan do } } + @token_holder_details %{ + name: "Token holder Detail", + fields: %{ + address: @address_hash_type, + value: %{ + type: "value", + definition: "A nonnegative number used to identify the balance of the target token.", + example: ~s("1000000000000000000") + } + } + } + @address_balance %{ name: "AddressBalance", fields: %{ @@ -1825,6 +1854,56 @@ defmodule BlockScoutWeb.Etherscan do ] } + @token_gettokenholders_action %{ + name: "getTokenHolders", + description: "Get token holders by contract address.", + required_params: [ + %{ + key: "contractaddress", + placeholder: "contractAddressHash", + type: "string", + description: "A 160-bit code used for identifying contracts." + } + ], + optional_params: [ + %{ + key: "page", + type: "integer", + description: + "A nonnegative integer that represents the page number to be used for pagination. 'offset' must be provided in conjunction." + }, + %{ + key: "offset", + type: "integer", + description: + "A nonnegative integer that represents the maximum number of records to return when paginating. 'page' must be provided in conjunction." + } + ], + responses: [ + %{ + code: "200", + description: "successful operation", + example_value: Jason.encode!(@token_gettokenholders_example_value), + model: %{ + name: "Result", + fields: %{ + status: @status_type, + message: @message_type, + result: %{ + type: "array", + array_type: @token_holder_details + } + } + } + }, + %{ + code: "200", + description: "error", + example_value: Jason.encode!(@token_gettokenholders_example_value_error) + } + ] + } + @stats_tokensupply_action %{ name: "tokensupply", description: @@ -2446,7 +2525,10 @@ defmodule BlockScoutWeb.Etherscan do @token_module %{ name: "token", - actions: [@token_gettoken_action] + actions: [ + @token_gettoken_action, + @token_gettokenholders_action + ] } @stats_module %{ diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/token_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/token_view.ex index a29b0e7796..9ccab7c9d1 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/token_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/token_view.ex @@ -7,6 +7,11 @@ defmodule BlockScoutWeb.API.RPC.TokenView do RPCView.render("show.json", data: prepare_token(token)) end + def render("gettokenholders.json", %{token_holders: token_holders}) do + data = Enum.map(token_holders, &prepare_token_holder/1) + RPCView.render("show.json", data: data) + end + def render("error.json", assigns) do RPCView.render("error.json", assigns) end @@ -22,4 +27,11 @@ defmodule BlockScoutWeb.API.RPC.TokenView do "cataloged" => token.cataloged } end + + defp prepare_token_holder(token_holder) do + %{ + "address" => to_string(token_holder.address_hash), + "value" => token_holder.value + } + end end diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 89e0e5d89d..4bcd063030 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -4601,7 +4601,7 @@ defmodule Explorer.Chain do end @spec fetch_token_holders_from_token_hash(Hash.Address.t(), [paging_options]) :: [TokenBalance.t()] - def fetch_token_holders_from_token_hash(contract_address_hash, options) do + def fetch_token_holders_from_token_hash(contract_address_hash, options \\ []) do contract_address_hash |> CurrentTokenBalance.token_holders_ordered_by_value(options) |> Repo.all() From c5cb97dc54e7da63bf903b870b697503fdbc67d2 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Fri, 22 Jan 2021 15:52:03 +0300 Subject: [PATCH 11/22] Cut CSS export from master branch --- .../address_transaction_controller.ex | 45 ------ .../templates/address_token/index.html.eex | 7 - .../address_transaction/index.html.eex | 9 -- .../views/address_token_view.ex | 2 - .../views/address_transaction_view.ex | 1 - .../lib/block_scout_web/web_router.ex | 4 - apps/block_scout_web/priv/gettext/default.pot | 12 +- .../priv/gettext/en/LC_MESSAGES/default.po | 12 +- .../address_token_transfer_csv_exporter.ex | 119 --------------- .../chain/address_transaction_csv_exporter.ex | 139 ------------------ 10 files changed, 6 insertions(+), 344 deletions(-) delete mode 100644 apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex delete mode 100644 apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex index f35d3b508b..010c6d441a 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex @@ -9,7 +9,6 @@ defmodule BlockScoutWeb.AddressTransactionController do alias BlockScoutWeb.{AccessHelpers, TransactionView} alias Explorer.{Chain, Market} - alias Explorer.Chain.{AddressTokenTransferCsvExporter, AddressTransactionCsvExporter} alias Explorer.ExchangeRates.Token alias Indexer.Fetcher.CoinBalanceOnDemand alias Phoenix.View @@ -140,48 +139,4 @@ defmodule BlockScoutWeb.AddressTransactionController do end end end - - def token_transfers_csv(conn, %{"address_id" => address_hash_string}) when is_binary(address_hash_string) do - with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), - {:ok, address} <- Chain.hash_to_address(address_hash) do - address - |> AddressTokenTransferCsvExporter.export() - |> Enum.into( - conn - |> put_resp_content_type("application/csv") - |> put_resp_header("content-disposition", "attachment; filename=token_transfers.csv") - |> send_chunked(200) - ) - else - :error -> - unprocessable_entity(conn) - - {:error, :not_found} -> - not_found(conn) - end - end - - def token_transfers_csv(conn, _), do: not_found(conn) - - def transactions_csv(conn, %{"address_id" => address_hash_string}) do - with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), - {:ok, address} <- Chain.hash_to_address(address_hash) do - address - |> AddressTransactionCsvExporter.export() - |> Enum.into( - conn - |> put_resp_content_type("application/csv") - |> put_resp_header("content-disposition", "attachment; filename=transactions.csv") - |> send_chunked(200) - ) - else - :error -> - unprocessable_entity(conn) - - {:error, :not_found} -> - not_found(conn) - end - end - - def transactions_csv(conn, _), do: not_found(conn) end diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex index c31e4a0a7b..d765ea3ff7 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_token/index.html.eex @@ -26,13 +26,6 @@
- <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex index 7f7a1b7db2..10d7611e62 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_transaction/index.html.eex @@ -59,15 +59,6 @@
- <%= render BlockScoutWeb.CommonComponentsView, "_pagination_container.html", position: "bottom", cur_page_number: "1", show_pagination_limit: true, data_next_page_button: true, data_prev_page_button: true %>
diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_token_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_token_view.ex index 75d5c126d8..bf41494b38 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_token_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_token_view.ex @@ -1,5 +1,3 @@ defmodule BlockScoutWeb.AddressTokenView do use BlockScoutWeb, :view - - alias Explorer.Chain.Address end diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_transaction_view.ex index 64ba591cec..2985d42b2c 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_transaction_view.ex @@ -2,7 +2,6 @@ defmodule BlockScoutWeb.AddressTransactionView do use BlockScoutWeb, :view alias BlockScoutWeb.AccessHelpers - alias Explorer.Chain.Address def format_current_filter(filter) do case filter do diff --git a/apps/block_scout_web/lib/block_scout_web/web_router.ex b/apps/block_scout_web/lib/block_scout_web/web_router.ex index b97d764b5e..91360fe475 100644 --- a/apps/block_scout_web/lib/block_scout_web/web_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/web_router.ex @@ -256,12 +256,8 @@ defmodule BlockScoutWeb.WebRouter do get("/search-logs", AddressLogsController, :search_logs) - get("/transactions_csv", AddressTransactionController, :transactions_csv) - get("/token-autocomplete", ChainController, :token_autocomplete) - get("/token-transfers-csv", AddressTransactionController, :token_transfers_csv) - get("/chain-blocks", ChainController, :chain_blocks, as: :chain_blocks) get("/token-counters", Tokens.TokenController, :token_counters) diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index f9fdbdeede..35afcd3684 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -141,7 +141,7 @@ msgstr "" #: lib/block_scout_web/templates/layout/_topnav.html.eex:91 #: lib/block_scout_web/views/address_internal_transaction_view.ex:10 #: lib/block_scout_web/views/address_token_transfer_view.ex:10 -#: lib/block_scout_web/views/address_transaction_view.ex:11 +#: lib/block_scout_web/views/address_transaction_view.ex:10 msgid "All" msgstr "" @@ -252,12 +252,6 @@ msgstr "" msgid "Blockscout is a tool for inspecting and analyzing EVM based blockchains. Blockchain explorer for Ethereum Networks." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_token/index.html.eex:30 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:63 -msgid "CSV" -msgstr "" - #, elixir-format #: lib/block_scout_web/views/internal_transaction_view.ex:21 msgid "Call" @@ -706,7 +700,7 @@ msgstr "" #: lib/block_scout_web/templates/address_transaction/index.html.eex:36 #: lib/block_scout_web/views/address_internal_transaction_view.ex:9 #: lib/block_scout_web/views/address_token_transfer_view.ex:9 -#: lib/block_scout_web/views/address_transaction_view.ex:10 +#: lib/block_scout_web/views/address_transaction_view.ex:9 msgid "From" msgstr "" @@ -856,7 +850,7 @@ msgstr "" #: lib/block_scout_web/templates/address_transaction/index.html.eex:30 #: lib/block_scout_web/views/address_internal_transaction_view.ex:8 #: lib/block_scout_web/views/address_token_transfer_view.ex:8 -#: lib/block_scout_web/views/address_transaction_view.ex:9 +#: lib/block_scout_web/views/address_transaction_view.ex:8 msgid "To" msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index f9fdbdeede..35afcd3684 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -141,7 +141,7 @@ msgstr "" #: lib/block_scout_web/templates/layout/_topnav.html.eex:91 #: lib/block_scout_web/views/address_internal_transaction_view.ex:10 #: lib/block_scout_web/views/address_token_transfer_view.ex:10 -#: lib/block_scout_web/views/address_transaction_view.ex:11 +#: lib/block_scout_web/views/address_transaction_view.ex:10 msgid "All" msgstr "" @@ -252,12 +252,6 @@ msgstr "" msgid "Blockscout is a tool for inspecting and analyzing EVM based blockchains. Blockchain explorer for Ethereum Networks." msgstr "" -#, elixir-format -#: lib/block_scout_web/templates/address_token/index.html.eex:30 -#: lib/block_scout_web/templates/address_transaction/index.html.eex:63 -msgid "CSV" -msgstr "" - #, elixir-format #: lib/block_scout_web/views/internal_transaction_view.ex:21 msgid "Call" @@ -706,7 +700,7 @@ msgstr "" #: lib/block_scout_web/templates/address_transaction/index.html.eex:36 #: lib/block_scout_web/views/address_internal_transaction_view.ex:9 #: lib/block_scout_web/views/address_token_transfer_view.ex:9 -#: lib/block_scout_web/views/address_transaction_view.ex:10 +#: lib/block_scout_web/views/address_transaction_view.ex:9 msgid "From" msgstr "" @@ -856,7 +850,7 @@ msgstr "" #: lib/block_scout_web/templates/address_transaction/index.html.eex:30 #: lib/block_scout_web/views/address_internal_transaction_view.ex:8 #: lib/block_scout_web/views/address_token_transfer_view.ex:8 -#: lib/block_scout_web/views/address_transaction_view.ex:9 +#: lib/block_scout_web/views/address_transaction_view.ex:8 msgid "To" msgstr "" diff --git a/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex b/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex deleted file mode 100644 index 184b391d9c..0000000000 --- a/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex +++ /dev/null @@ -1,119 +0,0 @@ -defmodule Explorer.Chain.AddressTokenTransferCsvExporter do - @moduledoc """ - Exports token transfers to a csv file. - """ - - alias Explorer.{Chain, PagingOptions} - alias Explorer.Chain.{TokenTransfer, Transaction} - alias NimbleCSV.RFC4180 - - @necessity_by_association [ - necessity_by_association: %{ - [created_contract_address: :names] => :optional, - [from_address: :names] => :optional, - [to_address: :names] => :optional, - [token_transfers: :token] => :optional, - [token_transfers: :to_address] => :optional, - [token_transfers: :from_address] => :optional, - [token_transfers: :token_contract_address] => :optional, - :block => :required - } - ] - - @page_size 150 - @paging_options %PagingOptions{page_size: @page_size + 1} - - def export(address) do - address.hash - |> fetch_all_transactions(@paging_options) - |> to_token_transfers() - |> to_csv_format(address) - |> dump_to_stream() - end - - defp fetch_all_transactions(address_hash, paging_options, acc \\ []) do - options = Keyword.merge(@necessity_by_association, paging_options: paging_options) - - transactions = - address_hash - |> Chain.address_to_mined_transactions_with_rewards(options) - |> Enum.filter(fn transaction -> Enum.count(transaction.token_transfers) > 0 end) - - new_acc = transactions ++ acc - - case Enum.split(transactions, @page_size) do - {_transactions, [%Transaction{block_number: block_number, index: index}]} -> - new_paging_options = %{@paging_options | key: {block_number, index}} - fetch_all_transactions(address_hash, new_paging_options, new_acc) - - {_, []} -> - new_acc - end - end - - defp to_token_transfers(transactions) do - transactions - |> Enum.flat_map(fn transaction -> - transaction.token_transfers - |> Enum.map(fn transfer -> %{transfer | transaction: transaction} end) - end) - end - - defp dump_to_stream(transactions) do - transactions - |> RFC4180.dump_to_stream() - end - - defp to_csv_format(token_transfers, address) do - row_names = [ - "TxHash", - "BlockNumber", - "UnixTimestamp", - "FromAddress", - "ToAddress", - "TokenContractAddress", - "Type", - "TokenSymbol", - "TokensTransferred", - "TransactionFee", - "Status", - "ErrCode" - ] - - token_transfer_lists = - token_transfers - |> Stream.map(fn token_transfer -> - [ - to_string(token_transfer.transaction_hash), - token_transfer.transaction.block_number, - token_transfer.transaction.block.timestamp, - token_transfer.from_address |> to_string() |> String.downcase(), - token_transfer.to_address |> to_string() |> String.downcase(), - token_transfer.token_contract_address |> to_string() |> String.downcase(), - type(token_transfer, address.hash), - token_transfer.token.symbol, - token_transfer.amount, - fee(token_transfer.transaction), - token_transfer.transaction.status, - token_transfer.transaction.error - ] - end) - - Stream.concat([row_names], token_transfer_lists) - end - - defp type(%TokenTransfer{from_address_hash: address_hash}, address_hash), do: "OUT" - - defp type(%TokenTransfer{to_address_hash: address_hash}, address_hash), do: "IN" - - defp type(_, _), do: "" - - defp fee(transaction) do - transaction - |> Chain.fee(:wei) - |> case do - {:actual, value} -> value - {:maximum, value} -> "Max of #{value}" - end - end -end diff --git a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex deleted file mode 100644 index d608cc55e3..0000000000 --- a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex +++ /dev/null @@ -1,139 +0,0 @@ -defmodule Explorer.Chain.AddressTransactionCsvExporter do - @moduledoc """ - Exports transactions to a csv file. - """ - - import Ecto.Query, - only: [ - from: 2 - ] - - alias Explorer.{Chain, Market, PagingOptions, Repo} - alias Explorer.Market.MarketHistory - alias Explorer.Chain.{Address, Transaction, Wei} - alias Explorer.ExchangeRates.Token - alias NimbleCSV.RFC4180 - - @necessity_by_association [ - necessity_by_association: %{ - [created_contract_address: :names] => :optional, - [from_address: :names] => :optional, - [to_address: :names] => :optional, - [token_transfers: :token] => :optional, - [token_transfers: :to_address] => :optional, - [token_transfers: :from_address] => :optional, - [token_transfers: :token_contract_address] => :optional, - :block => :required - } - ] - - @page_size 150 - - @paging_options %PagingOptions{page_size: @page_size + 1} - - @spec export(Address.t()) :: Enumerable.t() - def export(address) do - exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null() - - address.hash - |> fetch_all_transactions(@paging_options) - |> to_csv_format(address, exchange_rate) - |> dump_to_stream() - end - - defp fetch_all_transactions(address_hash, paging_options, acc \\ []) do - options = Keyword.put(@necessity_by_association, :paging_options, paging_options) - - transactions = Chain.address_to_transactions_without_rewards(address_hash, options) - - new_acc = transactions ++ acc - - case Enum.split(transactions, @page_size) do - {_transactions, [%Transaction{block_number: block_number, index: index}]} -> - new_paging_options = %{@paging_options | key: {block_number, index}} - fetch_all_transactions(address_hash, new_paging_options, new_acc) - - {_, []} -> - new_acc - end - end - - defp dump_to_stream(transactions) do - transactions - |> RFC4180.dump_to_stream() - end - - defp to_csv_format(transactions, address, exchange_rate) do - row_names = [ - "TxHash", - "BlockNumber", - "UnixTimestamp", - "FromAddress", - "ToAddress", - "ContractAddress", - "Type", - "Value", - "Fee", - "Status", - "ErrCode", - "CurrentPrice", - "TxDateOpeningPrice", - "TxDateClosingPrice" - ] - - transaction_lists = - transactions - |> Stream.map(fn transaction -> - {opening_price, closing_price} = price_at_date(transaction.block.timestamp) - - [ - to_string(transaction.hash), - transaction.block_number, - transaction.block.timestamp, - to_string(transaction.from_address), - to_string(transaction.to_address), - to_string(transaction.created_contract_address), - type(transaction, address.hash), - Wei.to(transaction.value, :wei), - fee(transaction), - transaction.status, - transaction.error, - exchange_rate.usd_value, - opening_price, - closing_price - ] - end) - - Stream.concat([row_names], transaction_lists) - end - - defp type(%Transaction{from_address_hash: address_hash}, address_hash), do: "OUT" - - defp type(%Transaction{to_address_hash: address_hash}, address_hash), do: "IN" - - defp type(_, _), do: "" - - defp fee(transaction) do - transaction - |> Chain.fee(:wei) - |> case do - {:actual, value} -> value - {:maximum, value} -> "Max of #{value}" - end - end - - defp price_at_date(datetime) do - date = DateTime.to_date(datetime) - - query = - from( - mh in MarketHistory, - where: mh.date == ^date - ) - - case Repo.one(query) do - nil -> {nil, nil} - price -> {price.opening_price, price.closing_price} - end - end -end From cbff8a452872b61adebc7d14e03c302648f76112 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Fri, 22 Jan 2021 17:16:08 +0300 Subject: [PATCH 12/22] Cut CSS export tests --- .../address_transaction_controller_test.exs | 36 ------ ...dress_token_transfer_csv_exporter_test.exs | 72 ------------ .../address_transaction_csv_exporter_test.exs | 105 ------------------ 3 files changed, 213 deletions(-) delete mode 100644 apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs delete mode 100644 apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs index 6e523f8b42..fc56421061 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/address_transaction_controller_test.exs @@ -157,40 +157,4 @@ defmodule BlockScoutWeb.AddressTransactionControllerTest do end) end end - - describe "GET token-transfers-csv/2" do - test "exports token transfers to csv", %{conn: conn} do - address = insert(:address) - - transaction = - :transaction - |> insert(from_address: address) - |> with_block() - - insert(:token_transfer, transaction: transaction, from_address: address) - insert(:token_transfer, transaction: transaction, to_address: address) - - conn = get(conn, "/token-transfers-csv", %{"address_id" => Address.checksum(address.hash)}) - - assert conn.resp_body |> String.split("\n") |> Enum.count() == 4 - end - end - - describe "GET transactions_csv/2" do - test "download csv file with transactions", %{conn: conn} do - address = insert(:address) - - :transaction - |> insert(from_address: address) - |> with_block() - - :transaction - |> insert(from_address: address) - |> with_block() - - conn = get(conn, "/transactions_csv", %{"address_id" => Address.checksum(address.hash)}) - - assert conn.resp_body |> String.split("\n") |> Enum.count() == 4 - end - end end diff --git a/apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs deleted file mode 100644 index 24bd9d7f04..0000000000 --- a/apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs +++ /dev/null @@ -1,72 +0,0 @@ -defmodule Explorer.Chain.AddressTokenTransferCsvExporterTest do - use Explorer.DataCase - - alias Explorer.Chain.AddressTokenTransferCsvExporter - - describe "export/1" do - test "exports token transfers to csv" do - address = insert(:address) - - transaction = - :transaction - |> insert(from_address: address) - |> with_block() - - token_transfer = insert(:token_transfer, transaction: transaction, from_address: address) - - [result] = - address - |> AddressTokenTransferCsvExporter.export() - |> Enum.to_list() - |> Enum.drop(1) - |> Enum.map(fn [ - tx_hash, - _, - block_number, - _, - timestamp, - _, - from_address, - _, - to_address, - _, - token_contract_address, - _, - type, - _, - token_symbol, - _, - tokens_transferred, - _, - transaction_fee, - _, - status, - _, - err_code, - _ - ] -> - %{ - tx_hash: tx_hash, - block_number: block_number, - timestamp: timestamp, - from_address: from_address, - to_address: to_address, - token_contract_address: token_contract_address, - type: type, - token_symbol: token_symbol, - tokens_transferred: tokens_transferred, - transaction_fee: transaction_fee, - status: status, - err_code: err_code - } - end) - - assert result.block_number == to_string(transaction.block_number) - assert result.tx_hash == to_string(transaction.hash) - assert result.from_address == token_transfer.from_address_hash |> to_string() |> String.downcase() - assert result.to_address == token_transfer.to_address_hash |> to_string() |> String.downcase() - assert result.timestamp == to_string(transaction.block.timestamp) - assert result.type == "OUT" - end - end -end diff --git a/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs deleted file mode 100644 index 1f722abf87..0000000000 --- a/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs +++ /dev/null @@ -1,105 +0,0 @@ -defmodule Explorer.Chain.AddressTransactionCsvExporterTest do - use Explorer.DataCase - - alias Explorer.Chain.{AddressTransactionCsvExporter, Wei} - - describe "export/1" do - test "exports address transactions to csv" do - address = insert(:address) - - transaction = - :transaction - |> insert(from_address: address) - |> with_block() - |> Repo.preload(:token_transfers) - - [result] = - address - |> AddressTransactionCsvExporter.export() - |> Enum.to_list() - |> Enum.drop(1) - |> Enum.map(fn [ - hash, - _, - block_number, - _, - timestamp, - _, - from_address, - _, - to_address, - _, - created_address, - _, - type, - _, - value, - _, - fee, - _, - status, - _, - error, - _, - cur_price, - _, - op_price, - _, - cl_price, - _ - ] -> - %{ - hash: hash, - block_number: block_number, - timestamp: timestamp, - from_address: from_address, - to_address: to_address, - created_address: created_address, - type: type, - value: value, - fee: fee, - status: status, - error: error, - current_price: cur_price, - opening_price: op_price, - closing_price: cl_price - } - end) - - assert result.block_number == to_string(transaction.block_number) - assert result.timestamp - assert result.created_address == to_string(transaction.created_contract_address_hash) - assert result.from_address == to_string(transaction.from_address) - assert result.to_address == to_string(transaction.to_address) - assert result.hash == to_string(transaction.hash) - assert result.type == "OUT" - assert result.value == transaction.value |> Wei.to(:wei) |> to_string() - assert result.fee - assert result.status == to_string(transaction.status) - assert result.error == to_string(transaction.error) - assert result.current_price - assert result.opening_price - assert result.closing_price - end - - test "fetches all transactions" do - address = insert(:address) - - 1..200 - |> Enum.map(fn _ -> - :transaction - |> insert(from_address: address) - |> with_block() - end) - |> Enum.count() - - result = - address - |> AddressTransactionCsvExporter.export() - |> Enum.to_list() - |> Enum.drop(1) - - assert Enum.count(result) == 200 - end - end -end From 240c3c4378038485e6e6e2a1fcc079300c0d788e Mon Sep 17 00:00:00 2001 From: POA <33550681+poa@users.noreply.github.com> Date: Fri, 22 Jan 2021 15:32:07 +0300 Subject: [PATCH 13/22] Add Staking DApp autoswitching to eth_blockNumber --- .../_stakes_modal_delegators_list.html.eex | 10 +- .../lib/explorer/staking/contract_reader.ex | 2 + .../lib/explorer/staking/contract_state.ex | 244 +++++++++++------- .../explorer/staking/contract_state_test.exs | 28 +- .../lib/indexer/block/realtime/fetcher.ex | 6 +- 5 files changed, 175 insertions(+), 115 deletions(-) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex index 67f81ff442..76ae952269 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/stakes/_stakes_modal_delegators_list.html.eex @@ -2,7 +2,7 @@ <% else %> - <%= raw(values_only(output["value"], output["type"])) %> + <%= raw(values_only(output["value"], output["type"], output["components"])) %> <% end %> <% end %> <% end %> diff --git a/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex index 35b9b9347f..7470b5cc54 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex @@ -2,6 +2,7 @@ defmodule BlockScoutWeb.SmartContractView do use BlockScoutWeb, :view alias Explorer.Chain + alias Explorer.Chain.Hash.Address def queryable?(inputs) when not is_nil(inputs), do: Enum.any?(inputs) @@ -41,12 +42,19 @@ defmodule BlockScoutWeb.SmartContractView do def named_argument?(%{"name" => _}), do: true def named_argument?(_), do: false - def values_with_type(value, type) when is_list(value) do + def values_with_type(value, type, components \\ nil) + + def values_with_type(value, type, components) when is_list(value) do cond do String.starts_with?(type, "tuple") -> + tuple_types = + type + |> String.slice(0..-3) + |> supplement_type_with_components(components) + values = value - |> tuple_array_to_array(type) + |> tuple_array_to_array(tuple_types) |> Enum.join(", ") render_array_type_value(type, values) @@ -54,7 +62,7 @@ defmodule BlockScoutWeb.SmartContractView do String.starts_with?(type, "address") -> values = value - |> Enum.map(&to_string(&1)) + |> Enum.map(&binary_to_utf_string(&1)) |> Enum.join(", ") render_array_type_value(type, values) @@ -76,7 +84,7 @@ defmodule BlockScoutWeb.SmartContractView do end end - def values_with_type(value, type) when is_tuple(value) do + def values_with_type(value, type, _components) when is_tuple(value) do values = value |> tuple_to_array(type) @@ -85,23 +93,28 @@ defmodule BlockScoutWeb.SmartContractView do render_type_value(type, values) end - def values_with_type(value, type) when type in ["address", "address payable"] do - {:ok, address} = Explorer.Chain.Hash.Address.cast(value) + def values_with_type(value, type, _components) when type in ["address", "address payable"] do + {:ok, address} = Address.cast(value) render_type_value("address", to_string(address)) end - def values_with_type(value, "string"), do: render_type_value("string", value) + def values_with_type(value, "string", _components), do: render_type_value("string", value) - def values_with_type(value, type), do: render_type_value(type, binary_to_utf_string(value)) + def values_with_type(value, "bool", _components), do: render_type_value("bool", to_string(value)) - def values_only(value, type) when is_list(value) do - with_type? = false + def values_with_type(value, type, _components), do: render_type_value(type, binary_to_utf_string(value)) + def values_only(value, type, components) when is_list(value) do cond do String.starts_with?(type, "tuple") -> + tuple_types = + type + |> String.slice(0..-3) + |> supplement_type_with_components(components) + values = value - |> tuple_array_to_array(type, with_type?) + |> tuple_array_to_array(tuple_types) |> Enum.join(", ") render_array_value(values) @@ -131,36 +144,48 @@ defmodule BlockScoutWeb.SmartContractView do end end - def values_only(value, "address") do - {:ok, address} = Explorer.Chain.Hash.Address.cast(value) - to_string(address) - end + def values_only(value, type, _components) when is_tuple(value) do + values = + value + |> tuple_to_array(type) + |> Enum.join(", ") - def values_only(value, "string") do - value + values end - def values_only(value, _type) do - binary_to_utf_string(value) + def values_only(value, type, _components) when type in ["address", "address payable"] do + {:ok, address} = Address.cast(value) + to_string(address) end - defp tuple_array_to_array(value, type, with_type? \\ true) do - type = type |> String.slice(0..-3) + def values_only(value, "string", _components), do: value + def values_only(value, "bool", _components), do: to_string(value) + + def values_only(value, _type, _components), do: binary_to_utf_string(value) + + defp tuple_array_to_array(value, type) do value |> Enum.map(fn item -> - tuple_to_array(item, type, with_type?) + tuple_to_array(item, type) end) end - defp tuple_to_array(value, type, with_type? \\ true) do + defp tuple_to_array(value, type) do types_string = type |> String.slice(6..-2) - |> String.split(",") + + types = + if String.trim(types_string) == "" do + [] + else + types_string + |> String.split(",") + end {tuple_types, _} = - types_string + types |> Enum.reduce({[], nil}, fn val, acc -> {arr, to_merge} = acc @@ -191,11 +216,7 @@ defmodule BlockScoutWeb.SmartContractView do values_types_list |> Enum.map(fn {type, value} -> - if with_type? do - values_with_type(value, type) - else - values_only(value, type) - end + values_with_type(value, type) end) end @@ -220,7 +241,15 @@ defmodule BlockScoutWeb.SmartContractView do end defp binary_to_utf_string(item) do - if is_binary(item), do: "0x" <> Base.encode16(item, case: :lower), else: item + if is_binary(item) do + if String.starts_with?(item, "0x") do + item + else + "0x" <> Base.encode16(item, case: :lower) + end + else + item + end end defp render_type_value(type, value) do @@ -238,4 +267,19 @@ defmodule BlockScoutWeb.SmartContractView do value_to_display end + + defp supplement_type_with_components(type, components) do + if type == "tuple" && components do + types = + components + |> Enum.map(fn component -> + Map.get(component, "type") + end) + |> Enum.join(",") + + "tuple[" <> types <> "]" + else + type + end + end end diff --git a/apps/block_scout_web/test/block_scout_web/views/tokens/smart_contract_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/tokens/smart_contract_view_test.exs index d6026b16c2..9896546114 100644 --- a/apps/block_scout_web/test/block_scout_web/views/tokens/smart_contract_view_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/tokens/smart_contract_view_test.exs @@ -244,17 +244,19 @@ defmodule BlockScoutWeb.SmartContractViewTest do test "joins the values when it is a list of a given type" do values = [8, 6, 9, 2, 2, 37] - assert SmartContractView.values_only(values, "type") == "[8, 6, 9, 2, 2, 37]" + assert SmartContractView.values_only(values, "type", nil) == "[8, 6, 9, 2, 2, 37]" end test "convert the value to string receiving a value and the 'address' type" do value = <<95, 38, 9, 115, 52, 182, 163, 43, 121, 81, 223, 97, 253, 12, 88, 3, 236, 93, 131, 84>> - assert SmartContractView.values_only(value, "address") == "0x5f26097334b6a32b7951df61fd0c5803ec5d8354" + assert SmartContractView.values_only(value, "address", nil) == "0x5f26097334b6a32b7951df61fd0c5803ec5d8354" end test "convert the value to string receiving a value and the 'address payable' type" do value = <<95, 38, 9, 115, 52, 182, 163, 43, 121, 81, 223, 97, 253, 12, 88, 3, 236, 93, 131, 84>> - assert SmartContractView.values_only(value, "address payable") == "0x5f26097334b6a32b7951df61fd0c5803ec5d8354" + + assert SmartContractView.values_only(value, "address payable", nil) == + "0x5f26097334b6a32b7951df61fd0c5803ec5d8354" end test "convert each value to string and join them when receiving 'address[]' as the type" do @@ -263,14 +265,35 @@ defmodule BlockScoutWeb.SmartContractViewTest do <<207, 38, 14, 163, 23, 85, 86, 55, 197, 95, 112, 229, 93, 186, 141, 90, 216, 65, 76, 176>> ] - assert SmartContractView.values_only(value, "address[]") == + assert SmartContractView.values_only(value, "address[]", nil) == "[0x5f26097334b6a32b7951df61fd0c5803ec5d8354, 0xcf260ea317555637c55f70e55dba8d5ad8414cb0]" end test "returns the value when the type is neither 'address' nor 'address payable'" do value = "POA" - assert SmartContractView.values_only(value, "string") == "POA" + assert SmartContractView.values_only(value, "string", nil) == "POA" + end + + test "returns the value when the type is boolean" do + value = "true" + + assert SmartContractView.values_only(value, "bool", nil) == "true" + end + + test "returns the value when the type is bytes4" do + value = <<228, 184, 12, 77>> + + assert SmartContractView.values_only(value, "bytes4", nil) == "0xe4b80c4d" + end + + test "returns the value when the type is bytes32" do + value = + <<156, 209, 70, 119, 249, 170, 85, 105, 179, 187, 179, 81, 252, 214, 125, 17, 21, 170, 86, 58, 225, 98, 66, 118, + 211, 212, 230, 127, 179, 214, 249, 38>> + + assert SmartContractView.values_only(value, "bytes32", nil) == + "0x9cd14677f9aa5569b3bbb351fcd67d1115aa563ae1624276d3d4e67fb3d6f926" end end end From 7e5a2a40c9577b6a4bcd889f48090eb268895e03 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Mon, 1 Feb 2021 22:22:48 +0300 Subject: [PATCH 21/22] Prevent update validator with empty name from contract --- CHANGELOG.md | 1 + apps/explorer/lib/explorer/validator/metadata_importer.ex | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfb61e9ec7..8582ddf55f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - [#3564](https://github.com/poanetwork/blockscout/pull/3564) - Staking welcome message ### Fixes +- [#3600](https://github.com/poanetwork/blockscout/pull/3600) - Prevent update validator metadata with empty name from contract - [#3592](https://github.com/poanetwork/blockscout/pull/3592) - Contract interaction: fix nested tuples in the output view, add formatting - [#3583](https://github.com/poanetwork/blockscout/pull/3583) - Reduce RPC requests and DB changes by Staking DApp diff --git a/apps/explorer/lib/explorer/validator/metadata_importer.ex b/apps/explorer/lib/explorer/validator/metadata_importer.ex index 5c6757ffbd..8cc3792a77 100644 --- a/apps/explorer/lib/explorer/validator/metadata_importer.ex +++ b/apps/explorer/lib/explorer/validator/metadata_importer.ex @@ -9,7 +9,12 @@ defmodule Explorer.Validator.MetadataImporter do def import_metadata(metadata_maps) do # Enforce Name ShareLocks order (see docs: sharelocks.md) - ordered_metadata_maps = Enum.sort_by(metadata_maps, &{&1.address_hash, &1.name}) + ordered_metadata_maps = + metadata_maps + |> Enum.filter(fn metadata -> + String.trim(metadata.name) !== "" + end) + |> Enum.sort_by(&{&1.address_hash, &1.name}) Repo.transaction(fn -> Enum.each(ordered_metadata_maps, &upsert_validator_metadata(&1)) end) end From 2d161bb17d3694105e696098e6087e8ad042a1d4 Mon Sep 17 00:00:00 2001 From: Victor Baranov Date: Tue, 2 Feb 2021 10:34:57 +0300 Subject: [PATCH 22/22] Fix contract reader --- CHANGELOG.md | 2 +- .../views/smart_contract_view.ex | 20 +++++++++++++++---- .../views/tokens/smart_contract_view_test.exs | 11 ++++++++++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8582ddf55f..e8d53aa8e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ ### Fixes - [#3600](https://github.com/poanetwork/blockscout/pull/3600) - Prevent update validator metadata with empty name from contract -- [#3592](https://github.com/poanetwork/blockscout/pull/3592) - Contract interaction: fix nested tuples in the output view, add formatting +- [#3592](https://github.com/poanetwork/blockscout/pull/3592), [#3601](https://github.com/poanetwork/blockscout/pull/3601) - Contract interaction: fix nested tuples in the output view, add formatting - [#3583](https://github.com/poanetwork/blockscout/pull/3583) - Reduce RPC requests and DB changes by Staking DApp ### Chore diff --git a/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex index 7470b5cc54..fd75ee25df 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/smart_contract_view.ex @@ -93,7 +93,7 @@ defmodule BlockScoutWeb.SmartContractView do render_type_value(type, values) end - def values_with_type(value, type, _components) when type in ["address", "address payable"] do + def values_with_type(value, type, _components) when type in [:address, "address", "address payable"] do {:ok, address} = Address.cast(value) render_type_value("address", to_string(address)) end @@ -102,7 +102,13 @@ defmodule BlockScoutWeb.SmartContractView do def values_with_type(value, "bool", _components), do: render_type_value("bool", to_string(value)) - def values_with_type(value, type, _components), do: render_type_value(type, binary_to_utf_string(value)) + def values_with_type(value, type, _components) do + if String.starts_with?(type, "uint") do + render_type_value(type, to_string(value)) + else + render_type_value(type, binary_to_utf_string(value)) + end + end def values_only(value, type, components) when is_list(value) do cond do @@ -153,7 +159,7 @@ defmodule BlockScoutWeb.SmartContractView do values end - def values_only(value, type, _components) when type in ["address", "address payable"] do + def values_only(value, type, _components) when type in [:address, "address", "address payable"] do {:ok, address} = Address.cast(value) to_string(address) end @@ -162,7 +168,13 @@ defmodule BlockScoutWeb.SmartContractView do def values_only(value, "bool", _components), do: to_string(value) - def values_only(value, _type, _components), do: binary_to_utf_string(value) + def values_only(value, type, _components) do + if String.starts_with?(type, "uint") do + to_string(value) + else + binary_to_utf_string(value) + end + end defp tuple_array_to_array(value, type) do value diff --git a/apps/block_scout_web/test/block_scout_web/views/tokens/smart_contract_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/tokens/smart_contract_view_test.exs index 9896546114..66dd911eb9 100644 --- a/apps/block_scout_web/test/block_scout_web/views/tokens/smart_contract_view_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/tokens/smart_contract_view_test.exs @@ -252,6 +252,11 @@ defmodule BlockScoutWeb.SmartContractViewTest do assert SmartContractView.values_only(value, "address", nil) == "0x5f26097334b6a32b7951df61fd0c5803ec5d8354" end + test "convert the value to string receiving a value and the :address type" do + value = <<95, 38, 9, 115, 52, 182, 163, 43, 121, 81, 223, 97, 253, 12, 88, 3, 236, 93, 131, 84>> + assert SmartContractView.values_only(value, :address, nil) == "0x5f26097334b6a32b7951df61fd0c5803ec5d8354" + end + test "convert the value to string receiving a value and the 'address payable' type" do value = <<95, 38, 9, 115, 52, 182, 163, 43, 121, 81, 223, 97, 253, 12, 88, 3, 236, 93, 131, 84>> @@ -295,5 +300,11 @@ defmodule BlockScoutWeb.SmartContractViewTest do assert SmartContractView.values_only(value, "bytes32", nil) == "0x9cd14677f9aa5569b3bbb351fcd67d1115aa563ae1624276d3d4e67fb3d6f926" end + + test "returns the value when the type is uint(n) and value is 0" do + value = "0" + + assert SmartContractView.values_only(value, "uint64", nil) == "0" + end end end