diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bb5120ae1..9bf659d556 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - [#2924](https://github.com/poanetwork/blockscout/pull/2924) - Speedup address to logs query - [#2915](https://github.com/poanetwork/blockscout/pull/2915) - Speedup of blocks_without_reward_query - [#2914](https://github.com/poanetwork/blockscout/pull/2914) - Reduce execution time of stream_unfetched_token_instances query +- [#2908](https://github.com/poanetwork/blockscout/pull/2908) - Fix performance of address page - [#2906](https://github.com/poanetwork/blockscout/pull/2906) - fix address sum cache - [#2902](https://github.com/poanetwork/blockscout/pull/2902) - Offset in blocks retrieval for average block time - [#2900](https://github.com/poanetwork/blockscout/pull/2900) - check fetched instance metadata in multiple places diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 7d26ed6105..1b19a439f1 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -263,10 +263,10 @@ defmodule Explorer.Chain do paging_options = Keyword.get(options, :paging_options, @default_paging_options) if Application.get_env(:block_scout_web, BlockScoutWeb.Chain)[:has_emission_funds] do + blocks_range = address_to_transactions_tasks_range_of_blocks(address_hash, options) + rewards_task = - Task.async(fn -> - Reward.fetch_emission_rewards_tuples(address_hash, paging_options) - end) + Task.async(fn -> Reward.fetch_emission_rewards_tuples(address_hash, paging_options, blocks_range) end) [rewards_task | address_to_transactions_tasks(address_hash, options)] |> wait_for_address_transactions() @@ -305,21 +305,72 @@ defmodule Explorer.Chain do |> Enum.take(paging_options.page_size) end + defp address_to_transactions_tasks_query(options) do + options + |> Keyword.get(:paging_options, @default_paging_options) + |> fetch_transactions() + end + defp address_to_transactions_tasks(address_hash, options) do - paging_options = Keyword.get(options, :paging_options, @default_paging_options) direction = Keyword.get(options, :direction) necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) - base_query = - paging_options - |> fetch_transactions() - |> join_associations(necessity_by_association) - - base_query + options + |> address_to_transactions_tasks_query() + |> join_associations(necessity_by_association) |> Transaction.matching_address_queries_list(direction, address_hash) |> Enum.map(fn query -> Task.async(fn -> Repo.all(query) end) end) end + defp address_to_transactions_tasks_range_of_blocks(address_hash, options) do + direction = Keyword.get(options, :direction) + + extremums_list = + options + |> address_to_transactions_tasks_query() + |> Transaction.matching_address_queries_list(direction, address_hash) + |> Enum.map(fn query -> + max_query = + from( + q in subquery(query), + select: %{min_block_number: min(q.block_number), max_block_number: max(q.block_number)} + ) + + max_query + |> Repo.one!() + end) + + extremums_list + |> Enum.reduce(%{min_block_number: nil, max_block_number: 0}, fn %{ + min_block_number: min_number, + max_block_number: max_number + }, + extremums_result -> + current_min_number = Map.get(extremums_result, :min_block_number) + current_max_number = Map.get(extremums_result, :max_block_number) + + extremums_result = + if is_number(current_min_number) do + if is_number(min_number) and min_number > 0 and min_number < current_min_number do + extremums_result + |> Map.put(:min_block_number, min_number) + else + extremums_result + end + else + extremums_result + |> Map.put(:min_block_number, min_number) + end + + if is_number(max_number) and max_number > 0 and max_number > current_max_number do + extremums_result + |> Map.put(:max_block_number, max_number) + else + extremums_result + end + end) + end + defp wait_for_address_transactions(tasks) do tasks |> Task.yield_many(:timer.seconds(20)) diff --git a/apps/explorer/lib/explorer/chain/block/reward.ex b/apps/explorer/lib/explorer/chain/block/reward.ex index bc38f1ad08..0a33de7153 100644 --- a/apps/explorer/lib/explorer/chain/block/reward.ex +++ b/apps/explorer/lib/explorer/chain/block/reward.ex @@ -68,8 +68,10 @@ defmodule Explorer.Chain.Block.Reward do Returns a list of tuples representing rewards by the EmissionFunds on POA chains. The tuples have the format {EmissionFunds, Validator} """ - @spec fetch_emission_rewards_tuples(Hash.Address.t(), PagingOptions.t()) :: [{t(), t()}] - def fetch_emission_rewards_tuples(address_hash, paging_options) do + def fetch_emission_rewards_tuples(address_hash, paging_options, %{ + min_block_number: min_block_number, + max_block_number: max_block_number + }) do address_rewards = __MODULE__ |> join_associations() @@ -77,6 +79,7 @@ defmodule Explorer.Chain.Block.Reward do |> limit(^paging_options.page_size) |> order_by([_, block], desc: block.number) |> where([reward], reward.address_hash == ^address_hash) + |> address_rewards_blocks_ranges_clause(min_block_number, max_block_number, paging_options) |> Repo.all() case List.first(address_rewards) do @@ -117,4 +120,25 @@ defmodule Explorer.Chain.Block.Reward do |> join(:inner, [reward], block in assoc(reward, :block)) |> preload(:block) end + + defp address_rewards_blocks_ranges_clause(query, min_block_number, max_block_number, paging_options) do + if is_number(min_block_number) and max_block_number > 0 and min_block_number > 0 do + cond do + paging_options.page_number == 1 -> + query + |> where([_, block], block.number >= ^min_block_number) + + min_block_number == max_block_number -> + query + |> where([_, block], block.number == ^min_block_number) + + true -> + query + |> where([_, block], block.number >= ^min_block_number) + |> where([_, block], block.number <= ^max_block_number) + end + else + query + end + end end diff --git a/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs b/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs index 02a62cab2b..e0c135904b 100644 --- a/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs +++ b/apps/explorer/test/explorer/chain/import/runner/blocks_test.exs @@ -312,7 +312,7 @@ defmodule Explorer.Chain.Import.Runner.BlocksTest do end test "removes duplicate blocks (by hash) before inserting", - %{consensus_block: %{number: block_number, hash: block_hash, miner_hash: miner_hash}, options: options} do + %{consensus_block: %{number: _, hash: block_hash, miner_hash: miner_hash}, options: options} do new_block = params_for(:block, miner_hash: miner_hash, consensus: true) %Ecto.Changeset{valid?: true, changes: block_changes} = Block.changeset(%Block{}, new_block) diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 52cc84549c..3ed8ac74ac 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -615,7 +615,7 @@ defmodule Explorer.ChainTest do :transaction |> insert(from_address: block.miner) - |> with_block() + |> with_block(block) |> Repo.preload(:token_transfers) assert [_, {_, _}] = Chain.address_to_transactions_with_rewards(block.miner.hash, direction: :from) @@ -623,6 +623,35 @@ defmodule Explorer.ChainTest do Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) end + test "with transactions if rewards are not in the range of blocks" do + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true) + + block = insert(:block) + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :validator + ) + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :emission_funds + ) + + :transaction + |> insert(from_address: block.miner) + |> with_block() + |> Repo.preload(:token_transfers) + + assert [_] = Chain.address_to_transactions_with_rewards(block.miner.hash, direction: :from) + + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) + end + test "with emissions rewards, but feature disabled" do Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false)