Merge pull request #3076 from poanetwork/vb-check-if-address-has-reward

Speedup tx list query on address page: check if an address has a reward, check if this is actual payout key of the validator - beneficiary, return only mined txs in tx list query
pull/3089/head
Victor Baranov 5 years ago committed by GitHub
commit da191518f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 3
      apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex
  3. 10
      apps/block_scout_web/lib/block_scout_web/notifier.ex
  4. 2
      apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex
  5. 18
      apps/block_scout_web/lib/block_scout_web/views/block_view.ex
  6. 12
      apps/block_scout_web/priv/gettext/default.pot
  7. 12
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  8. 4
      apps/explorer/config/config.exs
  9. 128
      apps/explorer/lib/explorer/chain.ex
  10. 2
      apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex
  11. 101
      apps/explorer/lib/explorer/chain/block/reward.ex
  12. 4
      apps/explorer/lib/explorer/chain/transaction.ex
  13. 11
      apps/explorer/priv/repo/migrations/20200424070607_drop_block_rewards_address_hash_address_type_block_hash_index.exs
  14. 201
      apps/explorer/test/explorer/chain_test.exs
  15. 39
      apps/indexer/lib/indexer/block/fetcher.ex

@ -8,6 +8,7 @@
### Fixes ### Fixes
- [#3077](https://github.com/poanetwork/blockscout/pull/3077) - Finally speedup pending tx list - [#3077](https://github.com/poanetwork/blockscout/pull/3077) - Finally speedup pending tx list
- [#3076](https://github.com/poanetwork/blockscout/pull/3076) - Speedup tx list query on address page: check if an address has a reward, check if this is actual payout key of the validator - beneficiary, return only mined txs in tx list query
- [#3071](https://github.com/poanetwork/blockscout/pull/3071) - Speedup list of token transfers per token query - [#3071](https://github.com/poanetwork/blockscout/pull/3071) - Speedup list of token transfers per token query
- [#3070](https://github.com/poanetwork/blockscout/pull/3070) - Index creation to blazingly speedup token holders query - [#3070](https://github.com/poanetwork/blockscout/pull/3070) - Index creation to blazingly speedup token holders query
- [#3064](https://github.com/poanetwork/blockscout/pull/3064) - Automatically define Block reward contract address in TokenBridge supply module - [#3064](https://github.com/poanetwork/blockscout/pull/3064) - Automatically define Block reward contract address in TokenBridge supply module

@ -36,7 +36,7 @@ defmodule BlockScoutWeb.AddressTransactionController do
|> Keyword.merge(paging_options(params)) |> Keyword.merge(paging_options(params))
|> Keyword.merge(current_filter(params)) |> Keyword.merge(current_filter(params))
results_plus_one = Chain.address_to_transactions_with_rewards(address_hash, options) results_plus_one = Chain.address_to_mined_transactions_with_rewards(address_hash, options)
{results, next_page} = split_list_by_page(results_plus_one) {results, next_page} = split_list_by_page(results_plus_one)
next_page_url = next_page_url =
@ -60,7 +60,6 @@ defmodule BlockScoutWeb.AddressTransactionController do
View.render_to_string( View.render_to_string(
TransactionView, TransactionView,
"_emission_reward_tile.html", "_emission_reward_tile.html",
conn: conn,
current_address: address, current_address: address,
emission_funds: emission_reward, emission_funds: emission_reward,
validator: validator_reward validator: validator_reward

@ -190,11 +190,15 @@ defmodule BlockScoutWeb.Notifier do
defp broadcast_rewards(rewards) do defp broadcast_rewards(rewards) do
preloaded_rewards = Repo.preload(rewards, [:address, :block]) preloaded_rewards = Repo.preload(rewards, [:address, :block])
emission_reward = Enum.find(preloaded_rewards, fn reward -> reward.address_type == :emission_funds end)
Enum.each(preloaded_rewards, fn reward -> preloaded_rewards_except_emission =
Enum.reject(preloaded_rewards, fn reward -> reward.address_type == :emission_funds end)
Enum.each(preloaded_rewards_except_emission, fn reward ->
Endpoint.broadcast("rewards:#{to_string(reward.address_hash)}", "new_reward", %{ Endpoint.broadcast("rewards:#{to_string(reward.address_hash)}", "new_reward", %{
emission_funds: Enum.at(preloaded_rewards, 1), emission_funds: emission_reward,
validator: Enum.at(preloaded_rewards, 0) validator: reward
}) })
end) end)
end end

@ -146,7 +146,7 @@
<h2 class="card-title balance-card-title"><%= gettext "Block Rewards" %></h2> <h2 class="card-title balance-card-title"><%= gettext "Block Rewards" %></h2>
<div class="text-right" style="margin-left: 50%;" data-toggle="tooltip" data-placement="top" title="" data-original-title="Amount of distributed reward. Miners receive a static block reward + Tx fees + uncle fees."> <div class="text-right" style="margin-left: 50%;" data-toggle="tooltip" data-placement="top" title="" data-original-title="Amount of distributed reward. Miners receive a static block reward + Tx fees + uncle fees.">
<%= for block_reward <- @block.rewards do %> <%= for block_reward <- @block.rewards do %>
<p class="address-current-balance"><%= block_reward_text(block_reward) %> <span class="text-muted"><%= format_wei_value(block_reward.reward, :ether) %></span></p> <p class="address-current-balance"><%= block_reward_text(block_reward, @block.miner.hash) %> <span class="text-muted"><%= format_wei_value(block_reward.reward, :ether) %></span></p>
<% end %> <% end %>
</div> </div>
<% else %> <% else %>

@ -52,15 +52,25 @@ defmodule BlockScoutWeb.BlockView do
def show_reward?([]), do: false def show_reward?([]), do: false
def show_reward?(_), do: true def show_reward?(_), do: true
def block_reward_text(%Reward{address_type: :validator}) do def block_reward_text(%Reward{address_hash: beneficiary_address, address_type: :validator}, block_miner_address) do
gettext("Miner Reward") if Application.get_env(:explorer, Explorer.Chain.Block.Reward, %{})[:keys_manager_contract_address] do
%{payout_key: block_miner_payout_address} = Reward.get_validator_payout_key_by_mining(block_miner_address)
if beneficiary_address == block_miner_payout_address do
gettext("Miner Reward")
else
gettext("Chore Reward")
end
else
gettext("Miner Reward")
end
end end
def block_reward_text(%Reward{address_type: :emission_funds}) do def block_reward_text(%Reward{address_type: :emission_funds}, _block_miner_address) do
gettext("Emission Reward") gettext("Emission Reward")
end end
def block_reward_text(%Reward{address_type: :uncle}) do def block_reward_text(%Reward{address_type: :uncle}, _block_miner_address) do
gettext("Uncle Reward") gettext("Uncle Reward")
end end

@ -594,7 +594,7 @@ msgid "Emission Contract"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/block_view.ex:60 #: lib/block_scout_web/views/block_view.ex:70
msgid "Emission Reward" msgid "Emission Reward"
msgstr "" msgstr ""
@ -1058,7 +1058,8 @@ msgid "Method Id"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/block_view.ex:56 #: lib/block_scout_web/views/block_view.ex:60
#: lib/block_scout_web/views/block_view.ex:65
msgid "Miner Reward" msgid "Miner Reward"
msgstr "" msgstr ""
@ -1524,7 +1525,7 @@ msgid "UTF-8"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/block_view.ex:64 #: lib/block_scout_web/views/block_view.ex:74
msgid "Uncle Reward" msgid "Uncle Reward"
msgstr "" msgstr ""
@ -1904,3 +1905,8 @@ msgstr ""
#: lib/block_scout_web/views/transaction_view.ex:316 #: lib/block_scout_web/views/transaction_view.ex:316
msgid "Token Minting" msgid "Token Minting"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/views/block_view.ex:62
msgid "Chore Reward"
msgstr ""

@ -594,7 +594,7 @@ msgid "Emission Contract"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/block_view.ex:60 #: lib/block_scout_web/views/block_view.ex:70
msgid "Emission Reward" msgid "Emission Reward"
msgstr "" msgstr ""
@ -1058,7 +1058,8 @@ msgid "Method Id"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/block_view.ex:56 #: lib/block_scout_web/views/block_view.ex:60
#: lib/block_scout_web/views/block_view.ex:65
msgid "Miner Reward" msgid "Miner Reward"
msgstr "" msgstr ""
@ -1524,7 +1525,7 @@ msgid "UTF-8"
msgstr "" msgstr ""
#, elixir-format #, elixir-format
#: lib/block_scout_web/views/block_view.ex:64 #: lib/block_scout_web/views/block_view.ex:74
msgid "Uncle Reward" msgid "Uncle Reward"
msgstr "" msgstr ""
@ -1904,3 +1905,8 @@ msgstr ""
#: lib/block_scout_web/views/transaction_view.ex:316 #: lib/block_scout_web/views/transaction_view.ex:316
msgid "Token Minting" msgid "Token Minting"
msgstr "" msgstr ""
#, elixir-format
#: lib/block_scout_web/views/block_view.ex:62
msgid "Chore Reward"
msgstr ""

@ -137,6 +137,10 @@ else
config :explorer, Explorer.Validator.MetadataProcessor, enabled: false config :explorer, Explorer.Validator.MetadataProcessor, enabled: false
end end
config :explorer, Explorer.Chain.Block.Reward,
validators_contract_address: System.get_env("VALIDATORS_CONTRACT"),
keys_manager_contract_address: System.get_env("KEYS_MANAGER_CONTRACT")
config :explorer, Explorer.Staking.PoolsReader, config :explorer, Explorer.Staking.PoolsReader,
validators_contract_address: System.get_env("POS_VALIDATORS_CONTRACT"), validators_contract_address: System.get_env("POS_VALIDATORS_CONTRACT"),
staking_contract_address: System.get_env("POS_STAKING_CONTRACT") staking_contract_address: System.get_env("POS_STAKING_CONTRACT")

@ -18,7 +18,8 @@ defmodule Explorer.Chain do
union: 2, union: 2,
union_all: 2, union_all: 2,
where: 2, where: 2,
where: 3 where: 3,
select: 3
] ]
import EthereumJSONRPC, only: [integer_to_quantity: 1, fetch_block_internal_transactions: 2] import EthereumJSONRPC, only: [integer_to_quantity: 1, fetch_block_internal_transactions: 2]
@ -314,44 +315,64 @@ defmodule Explorer.Chain do
the `block_number` and `index` that are passed. the `block_number` and `index` that are passed.
""" """
@spec address_to_transactions_with_rewards(Hash.Address.t(), [paging_options | necessity_by_association_option]) :: [ @spec address_to_mined_transactions_with_rewards(Hash.Address.t(), [paging_options | necessity_by_association_option]) ::
Transaction.t() [
] Transaction.t()
def address_to_transactions_with_rewards(address_hash, options \\ []) when is_list(options) do ]
def address_to_mined_transactions_with_rewards(address_hash, options \\ []) when is_list(options) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options) paging_options = Keyword.get(options, :paging_options, @default_paging_options)
if Application.get_env(:block_scout_web, BlockScoutWeb.Chain)[:has_emission_funds] do 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) cond do
Keyword.get(options, :direction) == :from ->
rewards_task = address_to_mined_transactions_without_rewards(address_hash, options)
Task.async(fn -> Reward.fetch_emission_rewards_tuples(address_hash, paging_options, blocks_range) end)
[rewards_task | address_to_transactions_tasks(address_hash, options)] address_has_rewards?(address_hash) ->
|> wait_for_address_transactions() %{payout_key: block_miner_payout_address} = Reward.get_validator_payout_key_by_mining(address_hash)
|> Enum.sort_by(fn item ->
case item do
{%Reward{} = emission_reward, _} ->
{-emission_reward.block.number, 1}
item -> if block_miner_payout_address && address_hash == block_miner_payout_address do
{-item.block_number, -item.index} transactions_with_rewards_results(address_hash, options, paging_options)
end else
end) address_to_mined_transactions_without_rewards(address_hash, options)
|> Enum.dedup_by(fn item -> end
case item do
{%Reward{} = emission_reward, _} ->
{emission_reward.block_hash, emission_reward.address_hash, emission_reward.address_type}
transaction -> true ->
transaction.hash address_to_mined_transactions_without_rewards(address_hash, options)
end end
end)
|> Enum.take(paging_options.page_size)
else else
address_to_transactions_without_rewards(address_hash, options) address_to_mined_transactions_without_rewards(address_hash, options)
end end
end end
defp transactions_with_rewards_results(address_hash, options, paging_options) 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, blocks_range) end)
[rewards_task | address_to_mined_transactions_tasks(address_hash, options)]
|> wait_for_address_transactions()
|> Enum.sort_by(fn item ->
case item do
{%Reward{} = emission_reward, _} ->
{-emission_reward.block.number, 1}
item ->
{-item.block_number, -item.index}
end
end)
|> Enum.dedup_by(fn item ->
case item do
{%Reward{} = emission_reward, _} ->
{emission_reward.block_hash, emission_reward.address_hash, emission_reward.address_type}
transaction ->
transaction.hash
end
end)
|> Enum.take(paging_options.page_size)
end
def address_to_transactions_without_rewards(address_hash, options) do def address_to_transactions_without_rewards(address_hash, options) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options) paging_options = Keyword.get(options, :paging_options, @default_paging_options)
@ -363,12 +384,33 @@ defmodule Explorer.Chain do
|> Enum.take(paging_options.page_size) |> Enum.take(paging_options.page_size)
end end
def address_to_mined_transactions_without_rewards(address_hash, options) do
paging_options = Keyword.get(options, :paging_options, @default_paging_options)
address_hash
|> address_to_mined_transactions_tasks(options)
|> wait_for_address_transactions()
|> Enum.sort_by(&{&1.block_number, &1.index}, &>=/2)
|> Enum.dedup_by(& &1.hash)
|> Enum.take(paging_options.page_size)
end
defp address_to_transactions_tasks_query(options) do defp address_to_transactions_tasks_query(options) do
options options
|> Keyword.get(:paging_options, @default_paging_options) |> Keyword.get(:paging_options, @default_paging_options)
|> fetch_transactions() |> fetch_transactions()
end end
defp transactions_block_numbers_at_address(address_hash, options) do
direction = Keyword.get(options, :direction)
options
|> address_to_transactions_tasks_query()
|> Transaction.not_pending_transactions()
|> select([t], t.block_number)
|> Transaction.matching_address_queries_list(direction, address_hash)
end
defp address_to_transactions_tasks(address_hash, options) do defp address_to_transactions_tasks(address_hash, options) do
direction = Keyword.get(options, :direction) direction = Keyword.get(options, :direction)
necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
@ -380,21 +422,30 @@ defmodule Explorer.Chain do
|> Enum.map(fn query -> Task.async(fn -> Repo.all(query) end) end) |> Enum.map(fn query -> Task.async(fn -> Repo.all(query) end) end)
end end
defp address_to_transactions_tasks_range_of_blocks(address_hash, options) do defp address_to_mined_transactions_tasks(address_hash, options) do
direction = Keyword.get(options, :direction) direction = Keyword.get(options, :direction)
necessity_by_association = Keyword.get(options, :necessity_by_association, %{})
options
|> address_to_transactions_tasks_query()
|> Transaction.not_pending_transactions()
|> 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
def address_to_transactions_tasks_range_of_blocks(address_hash, options) do
extremums_list = extremums_list =
options address_hash
|> address_to_transactions_tasks_query() |> transactions_block_numbers_at_address(options)
|> Transaction.matching_address_queries_list(direction, address_hash)
|> Enum.map(fn query -> |> Enum.map(fn query ->
max_query = extremum_query =
from( from(
q in subquery(query), q in subquery(query),
select: %{min_block_number: min(q.block_number), max_block_number: max(q.block_number)} select: %{min_block_number: min(q.block_number), max_block_number: max(q.block_number)}
) )
max_query extremum_query
|> Repo.one!() |> Repo.one!()
end) end)
@ -3216,6 +3267,13 @@ defmodule Explorer.Chain do
Repo.exists?(query) Repo.exists?(query)
end end
@spec address_has_rewards?(Address.t()) :: boolean()
def address_has_rewards?(address_hash) do
query = from(r in Reward, where: r.address_hash == ^address_hash)
Repo.exists?(query)
end
@spec address_tokens_with_balance(Hash.Address.t(), [any()]) :: [] @spec address_tokens_with_balance(Hash.Address.t(), [any()]) :: []
def address_tokens_with_balance(address_hash, paging_options \\ []) do def address_tokens_with_balance(address_hash, paging_options \\ []) do
address_hash address_hash

@ -36,7 +36,7 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporter do
transactions = transactions =
address_hash address_hash
|> Chain.address_to_transactions_with_rewards(options) |> Chain.address_to_mined_transactions_with_rewards(options)
|> Enum.filter(fn transaction -> Enum.count(transaction.token_transfers) > 0 end) |> Enum.filter(fn transaction -> Enum.count(transaction.token_transfers) > 0 end)
new_acc = transactions ++ acc new_acc = transactions ++ acc

@ -5,12 +5,36 @@ defmodule Explorer.Chain.Block.Reward do
use Explorer.Schema use Explorer.Schema
alias Explorer.Chain
alias Explorer.Chain.Block.Reward.AddressType alias Explorer.Chain.Block.Reward.AddressType
alias Explorer.Chain.{Address, Block, Hash, Wei} alias Explorer.Chain.{Address, Block, Hash, Wei}
alias Explorer.{PagingOptions, Repo} alias Explorer.{PagingOptions, Repo}
alias Explorer.SmartContract.Reader
@required_attrs ~w(address_hash address_type block_hash reward)a @required_attrs ~w(address_hash address_type block_hash reward)a
@get_payout_by_mining_abi %{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "address", "name" => ""}],
"name" => "getPayoutByMining",
"inputs" => [%{"type" => "address", "name" => ""}],
"constant" => true
}
@is_validator_abi %{
"type" => "function",
"stateMutability" => "view",
"payable" => false,
"outputs" => [%{"type" => "bool", "name" => ""}],
"name" => "isValidator",
"inputs" => [%{"type" => "address", "name" => ""}],
"constant" => true
}
@empty_address "0x0000000000000000000000000000000000000000"
@typedoc """ @typedoc """
The validation reward given related to a block. The validation reward given related to a block.
@ -114,6 +138,83 @@ defmodule Explorer.Chain.Block.Reward do
end end
end end
defp is_validator(mining_key) do
validators_contract_address =
Application.get_env(:explorer, Explorer.Chain.Block.Reward, %{})[:validators_contract_address]
if validators_contract_address do
is_validator_params = %{"isValidator" => [mining_key.bytes]}
call_contract(validators_contract_address, @is_validator_abi, is_validator_params)
else
nil
end
end
def get_validator_payout_key_by_mining(mining_key) do
is_validator = is_validator(mining_key)
if is_validator do
keys_manager_contract_address =
Application.get_env(:explorer, Explorer.Chain.Block.Reward, %{})[:keys_manager_contract_address]
if keys_manager_contract_address do
payout_key =
if keys_manager_contract_address do
get_payout_by_mining_params = %{"getPayoutByMining" => [mining_key.bytes]}
payout_key_hash =
call_contract(keys_manager_contract_address, @get_payout_by_mining_abi, get_payout_by_mining_params)
if payout_key_hash == @empty_address do
mining_key
else
{:ok, payout_key} = Chain.string_to_address_hash(payout_key_hash)
payout_key
end
else
mining_key
end
%{is_validator: is_validator, payout_key: payout_key}
else
%{is_validator: is_validator, payout_key: mining_key}
end
else
%{is_validator: is_validator, payout_key: mining_key}
end
end
defp call_contract(address, abi, params) do
abi = [abi]
method_name =
params
|> Enum.map(fn {key, _value} -> key end)
|> List.first()
value =
case Reader.query_contract(address, abi, params) do
%{^method_name => {:ok, [result]}} -> result
_ -> @empty_address
end
type =
abi
|> Enum.at(0)
|> Map.get("outputs", [])
|> Enum.at(0)
|> Map.get("type", "")
case type do
"address" ->
"0x" <> Base.encode16(value, case: :lower)
_ ->
value
end
end
defp join_associations(query) do defp join_associations(query) do
query query
|> preload(:address) |> preload(:address)

@ -501,6 +501,10 @@ defmodule Explorer.Chain.Transaction do
] ]
end end
def not_pending_transactions(query) do
where(query, [t], not is_nil(t.block_number))
end
@collated_fields ~w(block_number cumulative_gas_used gas_used index)a @collated_fields ~w(block_number cumulative_gas_used gas_used index)a
@collated_message "can't be blank when the transaction is collated into a block" @collated_message "can't be blank when the transaction is collated into a block"

@ -0,0 +1,11 @@
defmodule Explorer.Repo.Migrations.DropBlockRewardsAddressHashAddressTypeBlockHashIndex do
use Ecto.Migration
def change do
drop_if_exists(
index(:internal_transactions, [:address_hash, :block_hash, :address_type],
name: "block_rewards_address_hash_address_type_block_hash_index"
)
)
end
end

@ -383,13 +383,13 @@ defmodule Explorer.ChainTest do
end end
end end
describe "address_to_transactions_with_rewards/2" do describe "address_to_mined_transactions_with_rewards/2" do
test "without transactions" do test "without transactions" do
%Address{hash: address_hash} = insert(:address) %Address{hash: address_hash} = insert(:address)
assert Repo.aggregate(Transaction, :count, :hash) == 0 assert Repo.aggregate(Transaction, :count, :hash) == 0
assert [] == Chain.address_to_transactions_with_rewards(address_hash) assert [] == Chain.address_to_mined_transactions_with_rewards(address_hash)
end end
test "with from transactions" do test "with from transactions" do
@ -401,7 +401,7 @@ defmodule Explorer.ChainTest do
|> with_block() |> with_block()
assert [transaction] == assert [transaction] ==
Chain.address_to_transactions_with_rewards(address_hash, direction: :from) Chain.address_to_mined_transactions_with_rewards(address_hash, direction: :from)
|> Repo.preload([:block, :to_address, :from_address]) |> Repo.preload([:block, :to_address, :from_address])
end end
@ -414,7 +414,7 @@ defmodule Explorer.ChainTest do
|> with_block() |> with_block()
assert [transaction] == assert [transaction] ==
Chain.address_to_transactions_with_rewards(address_hash, direction: :to) Chain.address_to_mined_transactions_with_rewards(address_hash, direction: :to)
|> Repo.preload([:block, :to_address, :from_address]) |> Repo.preload([:block, :to_address, :from_address])
end end
@ -428,7 +428,7 @@ defmodule Explorer.ChainTest do
# only contains "from" transaction # only contains "from" transaction
assert [transaction] == assert [transaction] ==
Chain.address_to_transactions_with_rewards(address_hash, direction: :from) Chain.address_to_mined_transactions_with_rewards(address_hash, direction: :from)
|> Repo.preload([:block, :to_address, :from_address]) |> Repo.preload([:block, :to_address, :from_address])
end end
@ -441,7 +441,7 @@ defmodule Explorer.ChainTest do
|> with_block() |> with_block()
assert [transaction] == assert [transaction] ==
Chain.address_to_transactions_with_rewards(address_hash, direction: :to) Chain.address_to_mined_transactions_with_rewards(address_hash, direction: :to)
|> Repo.preload([:block, :to_address, :from_address]) |> Repo.preload([:block, :to_address, :from_address])
end end
@ -460,7 +460,7 @@ defmodule Explorer.ChainTest do
|> with_block(block) |> with_block(block)
assert [transaction2, transaction1] == assert [transaction2, transaction1] ==
Chain.address_to_transactions_with_rewards(address_hash) Chain.address_to_mined_transactions_with_rewards(address_hash)
|> Repo.preload([:block, :to_address, :from_address]) |> Repo.preload([:block, :to_address, :from_address])
end end
@ -481,7 +481,7 @@ defmodule Explorer.ChainTest do
transaction_index: transaction.index transaction_index: transaction.index
) )
assert [] == Chain.address_to_transactions_with_rewards(address.hash) assert [] == Chain.address_to_mined_transactions_with_rewards(address.hash)
end end
test "returns transactions that have token transfers for the given to_address" do test "returns transactions that have token transfers for the given to_address" do
@ -499,7 +499,7 @@ defmodule Explorer.ChainTest do
) )
assert [transaction.hash] == assert [transaction.hash] ==
Chain.address_to_transactions_with_rewards(address_hash) Chain.address_to_mined_transactions_with_rewards(address_hash)
|> Enum.map(& &1.hash) |> Enum.map(& &1.hash)
end end
@ -519,7 +519,7 @@ defmodule Explorer.ChainTest do
assert second_page_hashes == assert second_page_hashes ==
address_hash address_hash
|> Chain.address_to_transactions_with_rewards( |> Chain.address_to_mined_transactions_with_rewards(
paging_options: %PagingOptions{ paging_options: %PagingOptions{
key: {block_number, index}, key: {block_number, index},
page_size: 2 page_size: 2
@ -568,7 +568,7 @@ defmodule Explorer.ChainTest do
result = result =
address_hash address_hash
|> Chain.address_to_transactions_with_rewards() |> Chain.address_to_mined_transactions_with_rewards()
|> Enum.map(& &1.hash) |> Enum.map(& &1.hash)
assert [fourth, third, second, first, sixth, fifth] == result assert [fourth, third, second, first, sixth, fifth] == result
@ -577,8 +577,15 @@ defmodule Explorer.ChainTest do
test "with emission rewards" do test "with emission rewards" do
Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true) Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true)
Application.put_env(:explorer, Explorer.Chain.Block.Reward,
validators_contract_address: "0x0000000000000000000000000000000000000005",
keys_manager_contract_address: "0x0000000000000000000000000000000000000006"
)
block = insert(:block) block = insert(:block)
block_miner_hash_string = Base.encode16(block.miner_hash.bytes, case: :lower)
insert( insert(
:reward, :reward,
address_hash: block.miner_hash, address_hash: block.miner_hash,
@ -593,16 +600,51 @@ defmodule Explorer.ChainTest do
address_type: :emission_funds address_type: :emission_funds
) )
assert [{_, _}] = Chain.address_to_transactions_with_rewards(block.miner.hash) # isValidator => true
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
{:ok,
[%{id: id, jsonrpc: "2.0", result: "0x0000000000000000000000000000000000000000000000000000000000000001"}]}
end
)
Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) # getPayoutByMining => 0x0000000000000000000000000000000000000001
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
{:ok, [%{id: id, result: "0x000000000000000000000000" <> block_miner_hash_string}]}
end
)
res = Chain.address_to_mined_transactions_with_rewards(block.miner.hash)
assert [{_, _}] = res
on_exit(fn ->
Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false)
Application.put_env(:explorer, Explorer.Chain.Block.Reward,
validators_contract_address: nil,
keys_manager_contract_address: nil
)
end)
end end
test "with emission rewards and transactions" do test "with emission rewards and transactions" do
Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true) Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true)
Application.put_env(:explorer, Explorer.Chain.Block.Reward,
validators_contract_address: "0x0000000000000000000000000000000000000005",
keys_manager_contract_address: "0x0000000000000000000000000000000000000006"
)
block = insert(:block) block = insert(:block)
block_miner_hash_string = Base.encode16(block.miner_hash.bytes, case: :lower)
insert( insert(
:reward, :reward,
address_hash: block.miner_hash, address_hash: block.miner_hash,
@ -618,13 +660,39 @@ defmodule Explorer.ChainTest do
) )
:transaction :transaction
|> insert(from_address: block.miner) |> insert(to_address: block.miner)
|> with_block(block) |> with_block(block)
|> Repo.preload(:token_transfers) |> Repo.preload(:token_transfers)
assert [_, {_, _}] = Chain.address_to_transactions_with_rewards(block.miner.hash, direction: :from) # isValidator => true
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
{:ok,
[%{id: id, jsonrpc: "2.0", result: "0x0000000000000000000000000000000000000000000000000000000000000001"}]}
end
)
Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) # getPayoutByMining => 0x0000000000000000000000000000000000000001
expect(
EthereumJSONRPC.Mox,
:json_rpc,
fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options ->
{:ok, [%{id: id, result: "0x000000000000000000000000" <> block_miner_hash_string}]}
end
)
assert [_, {_, _}] = Chain.address_to_mined_transactions_with_rewards(block.miner.hash, direction: :to)
on_exit(fn ->
Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false)
Application.put_env(:explorer, Explorer.Chain.Block.Reward,
validators_contract_address: nil,
keys_manager_contract_address: nil
)
end)
end end
test "with transactions if rewards are not in the range of blocks" do test "with transactions if rewards are not in the range of blocks" do
@ -651,7 +719,7 @@ defmodule Explorer.ChainTest do
|> with_block() |> with_block()
|> Repo.preload(:token_transfers) |> Repo.preload(:token_transfers)
assert [_] = Chain.address_to_transactions_with_rewards(block.miner.hash, direction: :from) assert [_] = Chain.address_to_mined_transactions_with_rewards(block.miner.hash, direction: :from)
Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false)
end end
@ -675,7 +743,104 @@ defmodule Explorer.ChainTest do
address_type: :emission_funds address_type: :emission_funds
) )
assert [] == Chain.address_to_transactions_with_rewards(block.miner.hash) assert [] == Chain.address_to_mined_transactions_with_rewards(block.miner.hash)
end
end
describe "address_to_transactions_tasks_range_of_blocks/2" do
test "returns empty extremums if no transactions" do
address = insert(:address)
extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, [])
assert extremums == %{
:min_block_number => nil,
:max_block_number => 0
}
end
test "returns correct extremums for from_address" do
address = insert(:address)
:transaction
|> insert(from_address: address)
|> with_block(insert(:block, number: 1000))
extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, [])
assert extremums == %{
:min_block_number => 1000,
:max_block_number => 1000
}
end
test "returns correct extremums for to_address" do
address = insert(:address)
:transaction
|> insert(to_address: address)
|> with_block(insert(:block, number: 1000))
extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, [])
assert extremums == %{
:min_block_number => 1000,
:max_block_number => 1000
}
end
test "returns correct extremums for created_contract_address" do
address = insert(:address)
:transaction
|> insert(created_contract_address: address)
|> with_block(insert(:block, number: 1000))
extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, [])
assert extremums == %{
:min_block_number => 1000,
:max_block_number => 1000
}
end
test "returns correct extremums for multiple number of transactions" do
address = insert(:address)
:transaction
|> insert(created_contract_address: address)
|> with_block(insert(:block, number: 1000))
:transaction
|> insert(created_contract_address: address)
|> with_block(insert(:block, number: 999))
:transaction
|> insert(created_contract_address: address)
|> with_block(insert(:block, number: 1003))
:transaction
|> insert(from_address: address)
|> with_block(insert(:block, number: 1001))
:transaction
|> insert(from_address: address)
|> with_block(insert(:block, number: 1004))
:transaction
|> insert(to_address: address)
|> with_block(insert(:block, number: 1002))
:transaction
|> insert(to_address: address)
|> with_block(insert(:block, number: 998))
extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, [])
assert extremums == %{
:min_block_number => 998,
:max_block_number => 1004
}
end end
end end

@ -10,7 +10,9 @@ defmodule Indexer.Block.Fetcher do
import EthereumJSONRPC, only: [quantity_to_integer: 1] import EthereumJSONRPC, only: [quantity_to_integer: 1]
alias EthereumJSONRPC.{Blocks, FetchedBeneficiaries} alias EthereumJSONRPC.{Blocks, FetchedBeneficiaries}
alias Explorer.Chain
alias Explorer.Chain.{Address, Block, Hash, Import, Transaction} alias Explorer.Chain.{Address, Block, Hash, Import, Transaction}
alias Explorer.Chain.Block.Reward
alias Explorer.Chain.Cache.Blocks, as: BlocksCache alias Explorer.Chain.Cache.Blocks, as: BlocksCache
alias Explorer.Chain.Cache.{Accounts, BlockNumber, PendingTransactions, Transactions, Uncles} alias Explorer.Chain.Cache.{Accounts, BlockNumber, PendingTransactions, Transactions, Uncles}
alias Indexer.Block.Fetcher.Receipts alias Indexer.Block.Fetcher.Receipts
@ -153,7 +155,7 @@ defmodule Indexer.Block.Fetcher do
|> AddressCoinBalances.params_set(), |> AddressCoinBalances.params_set(),
beneficiaries_with_gas_payment <- beneficiaries_with_gas_payment <-
beneficiary_params_set beneficiary_params_set
|> add_gas_payments(transactions_with_receipts) |> add_gas_payments(transactions_with_receipts, blocks)
|> BlockReward.reduce_uncle_rewards(), |> BlockReward.reduce_uncle_rewards(),
address_token_balances = AddressTokenBalances.params_set(%{token_transfers_params: token_transfers}), address_token_balances = AddressTokenBalances.params_set(%{token_transfers_params: token_transfers}),
{:ok, inserted} <- {:ok, inserted} <-
@ -396,18 +398,22 @@ defmodule Indexer.Block.Fetcher do
|> Enum.into(MapSet.new()) |> Enum.into(MapSet.new())
end end
defp add_gas_payments(beneficiaries, transactions) do defp add_gas_payments(beneficiaries, transactions, blocks) do
transactions_by_block_number = Enum.group_by(transactions, & &1.block_number) transactions_by_block_number = Enum.group_by(transactions, & &1.block_number)
Enum.map(beneficiaries, fn beneficiary -> Enum.map(beneficiaries, fn beneficiary ->
case beneficiary.address_type do case beneficiary.address_type do
:validator -> :validator ->
gas_payment = gas_payment(beneficiary, transactions_by_block_number) block_hash = beneficiary.block_hash
"0x" <> minted_hex = beneficiary.reward block = find_block(blocks, block_hash)
{minted, _} = Integer.parse(minted_hex, 16)
%{beneficiary | reward: minted + gas_payment} block_miner_hash = block.miner_hash
{:ok, block_miner} = Chain.string_to_address_hash(block_miner_hash)
%{payout_key: block_miner_payout_address} = Reward.get_validator_payout_key_by_mining(block_miner)
reward_with_gas(block_miner_payout_address, beneficiary, transactions_by_block_number)
_ -> _ ->
beneficiary beneficiary
@ -415,6 +421,27 @@ defmodule Indexer.Block.Fetcher do
end) end)
end end
defp reward_with_gas(block_miner_payout_address, beneficiary, transactions_by_block_number) do
{:ok, beneficiary_address} = Chain.string_to_address_hash(beneficiary.address_hash)
"0x" <> minted_hex = beneficiary.reward
{minted, _} = Integer.parse(minted_hex, 16)
if block_miner_payout_address && beneficiary_address.bytes == block_miner_payout_address.bytes do
gas_payment = gas_payment(beneficiary, transactions_by_block_number)
%{beneficiary | reward: minted + gas_payment}
else
%{beneficiary | reward: minted}
end
end
defp find_block(blocks, block_hash) do
blocks
|> Enum.filter(fn block -> block.hash == block_hash end)
|> Enum.at(0)
end
defp gas_payment(transactions) when is_list(transactions) do defp gas_payment(transactions) when is_list(transactions) do
transactions transactions
|> Stream.map(&(&1.gas_used * &1.gas_price)) |> Stream.map(&(&1.gas_used * &1.gas_price))

Loading…
Cancel
Save