Fix tests: mix format and mix credo

staking
Victor Baranov 5 years ago
parent 930ffbf6e4
commit 415ffa4208
  1. 3
      apps/block_scout_web/config/config.exs
  2. 307
      apps/block_scout_web/lib/block_scout_web/channels/stakes_channel.ex
  3. 4
      apps/block_scout_web/lib/block_scout_web/controllers/stakes_controller.ex
  4. 17
      apps/explorer/lib/explorer/chain.ex
  5. 4
      apps/explorer/lib/explorer/chain/import.ex
  6. 14
      apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex
  7. 6
      apps/explorer/lib/explorer/chain/import/runner/staking_pools_delegators.ex
  8. 80
      apps/explorer/lib/explorer/staking/contract_reader.ex
  9. 483
      apps/explorer/lib/explorer/staking/contract_state.ex
  10. 21
      apps/explorer/lib/explorer/staking/stake_snapshotting.ex
  11. 4
      apps/explorer/priv/repo/migrations/20190807111216_remove_duplicate_indexes.exs

@ -24,7 +24,8 @@ config :block_scout_web, BlockScoutWeb.Chain,
logo_text: System.get_env("LOGO_TEXT"), logo_text: System.get_env("LOGO_TEXT"),
has_emission_funds: false, has_emission_funds: false,
staking_enabled: not is_nil(System.get_env("POS_STAKING_CONTRACT")), staking_enabled: not is_nil(System.get_env("POS_STAKING_CONTRACT")),
staking_pool_list_refresh_interval: 5 # how often (in blocks) the list of pools should autorefresh in UI (zero turns off autorefreshing) # how often (in blocks) the list of pools should autorefresh in UI (zero turns off autorefreshing)
staking_pool_list_refresh_interval: 5
config :block_scout_web, config :block_scout_web,
link_to_other_explorers: System.get_env("LINK_TO_OTHER_EXPLORERS") == "true", link_to_other_explorers: System.get_env("LINK_TO_OTHER_EXPLORERS") == "true",

@ -28,6 +28,7 @@ defmodule BlockScoutWeb.StakesChannel do
# apps/block_scout_web/lib/block_scout_web/endpoint.ex # apps/block_scout_web/lib/block_scout_web/endpoint.ex
def terminate(_reason, socket) do def terminate(_reason, socket) do
s = socket.assigns[@claim_reward_long_op] s = socket.assigns[@claim_reward_long_op]
if s != nil do if s != nil do
:ets.delete(ContractState, claim_reward_long_op_key(s.staker)) :ets.delete(ContractState, claim_reward_long_op_key(s.staker))
end end
@ -251,55 +252,60 @@ defmodule BlockScoutWeb.StakesChannel do
def handle_in("render_claim_reward", data, socket) do def handle_in("render_claim_reward", data, socket) do
staker = socket.assigns[:account] staker = socket.assigns[:account]
staking_contract_address = try do ContractState.get(:staking_contract).address after end
cond do staking_contract_address =
claim_reward_long_op_active(socket) == true -> try do
{:reply, {:error, %{reason: gettext("Pools searching is already in progress for this address")}}, socket} ContractState.get(:staking_contract).address
staker == nil || staker == "" || staker == "0x0000000000000000000000000000000000000000" -> after
{:reply, {:error, %{reason: gettext("Unknown staker address. Please, choose your account in MetaMask")}}, socket}
staking_contract_address == nil || staking_contract_address == "" || staking_contract_address == "0x0000000000000000000000000000000000000000" ->
{:reply, {:error, %{reason: gettext("Unknown address of Staking contract. Please, contact support")}}, socket}
true ->
result = if data["preload"] do
%{
html: View.render_to_string(StakesView, "_stakes_modal_claim_reward.html", %{}),
socket: socket
}
else
task = Task.async(__MODULE__, :find_claim_reward_pools, [socket, staker, staking_contract_address])
%{
html: "OK",
socket: assign(socket, @claim_reward_long_op, %{task: task, staker: staker})
}
end end
{:reply, {:ok, %{html: result.html}}, result.socket} empty_staker = staker == nil || staker == "" || staker == "0x0000000000000000000000000000000000000000"
end
empty_staking_contract_address =
staking_contract_address == nil || staking_contract_address == "" ||
staking_contract_address == "0x0000000000000000000000000000000000000000"
handle_in_render_claim_reward_result(
socket,
data,
staker,
staking_contract_address,
empty_staker,
empty_staking_contract_address
)
end end
def handle_in("recalc_claim_reward", data, socket) do def handle_in("recalc_claim_reward", data, socket) do
epochs = data["epochs"] epochs = data["epochs"]
pool_staking_address = data["pool_staking_address"] pool_staking_address = data["pool_staking_address"]
staker = socket.assigns[:account] staker = socket.assigns[:account]
staking_contract_address = try do ContractState.get(:staking_contract).address after end
cond do staking_contract_address =
claim_reward_long_op_active(socket) == true -> try do
{:reply, {:error, %{reason: gettext("Reward calculating is already in progress for this address")}}, socket} ContractState.get(:staking_contract).address
Enum.count(epochs) == 0 -> after
{:reply, {:error, %{reason: gettext("Staking epochs are not specified or not in the allowed range")}}, socket}
pool_staking_address == nil || pool_staking_address == "" || pool_staking_address == "0x0000000000000000000000000000000000000000" ->
{:reply, {:error, %{reason: gettext("Unknown pool staking address. Please, contact support")}}, socket}
staker == nil || staker == "" || staker == "0x0000000000000000000000000000000000000000" ->
{:reply, {:error, %{reason: gettext("Unknown staker address. Please, choose your account in MetaMask")}}, socket}
staking_contract_address == nil || staking_contract_address == "" || staking_contract_address == "0x0000000000000000000000000000000000000000" ->
{:reply, {:error, %{reason: gettext("Unknown address of Staking contract. Please, contact support")}}, socket}
true ->
task = Task.async(__MODULE__, :recalc_claim_reward, [socket, staking_contract_address, epochs, pool_staking_address, staker])
socket = assign(socket, @claim_reward_long_op, %{task: task, staker: staker})
{:reply, {:ok, %{html: "OK"}}, socket}
end end
empty_pool_staking_address =
pool_staking_address == nil || pool_staking_address == "" ||
pool_staking_address == "0x0000000000000000000000000000000000000000"
empty_staker = staker == nil || staker == "" || staker == "0x0000000000000000000000000000000000000000"
empty_staking_contract_address =
staking_contract_address == nil || staking_contract_address == "" ||
staking_contract_address == "0x0000000000000000000000000000000000000000"
handle_in_recalc_claim_reward_result(
socket,
epochs,
staking_contract_address,
pool_staking_address,
staker,
empty_pool_staking_address,
empty_staking_contract_address,
empty_staker
)
end end
def handle_in("render_claim_withdrawal", %{"address" => staking_address}, socket) do def handle_in("render_claim_withdrawal", %{"address" => staking_address}, socket) do
@ -325,12 +331,15 @@ defmodule BlockScoutWeb.StakesChannel do
def handle_info({:DOWN, ref, :process, pid, _reason}, socket) do def handle_info({:DOWN, ref, :process, pid, _reason}, socket) do
s = socket.assigns[@claim_reward_long_op] s = socket.assigns[@claim_reward_long_op]
socket = if s && s.task.ref == ref && s.task.pid == pid do
socket =
if s && s.task.ref == ref && s.task.pid == pid do
:ets.delete(ContractState, claim_reward_long_op_key(s.staker)) :ets.delete(ContractState, claim_reward_long_op_key(s.staker))
assign(socket, @claim_reward_long_op, nil) assign(socket, @claim_reward_long_op, nil)
else else
socket socket
end end
{:noreply, socket} {:noreply, socket}
end end
@ -339,7 +348,8 @@ defmodule BlockScoutWeb.StakesChannel do
end end
def handle_out("staking_update", data, socket) do def handle_out("staking_update", data, socket) do
dont_refresh_page = case Map.fetch(data, :dont_refresh_page) do dont_refresh_page =
case Map.fetch(data, :dont_refresh_page) do
{:ok, value} -> value {:ok, value} -> value
_ -> false _ -> false
end end
@ -360,23 +370,32 @@ defmodule BlockScoutWeb.StakesChannel do
def find_claim_reward_pools(socket, staker, staking_contract_address) do def find_claim_reward_pools(socket, staker, staking_contract_address) do
:ets.insert(ContractState, {claim_reward_long_op_key(staker), true}) :ets.insert(ContractState, {claim_reward_long_op_key(staker), true})
try do try do
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
staking_contract = ContractState.get(:staking_contract) staking_contract = ContractState.get(:staking_contract)
responses = responses =
ContractReader.get_staker_pools_length_request(staker) staker
|> ContractReader.get_staker_pools_length_request()
|> ContractReader.perform_requests(%{staking: staking_contract.address}, staking_contract.abi) |> ContractReader.perform_requests(%{staking: staking_contract.address}, staking_contract.abi)
staker_pools_length = responses[:length] staker_pools_length = responses[:length]
chunk_size = 100 chunk_size = 100
pools = if staker_pools_length > 0 do
pools =
if staker_pools_length > 0 do
chunks = 0..trunc(ceil(staker_pools_length / chunk_size) - 1) chunks = 0..trunc(ceil(staker_pools_length / chunk_size) - 1)
Enum.reduce(chunks, [], fn i, acc -> Enum.reduce(chunks, [], fn i, acc ->
responses = responses =
ContractReader.get_staker_pools_request(staker, i * chunk_size, chunk_size) staker
|> ContractReader.get_staker_pools_request(i * chunk_size, chunk_size)
|> ContractReader.perform_requests(%{staking: staking_contract.address}, staking_contract.abi) |> ContractReader.perform_requests(%{staking: staking_contract.address}, staking_contract.abi)
acc ++ Enum.map(responses[:pools], fn pool_staking_address ->
acc ++
Enum.map(responses[:pools], fn pool_staking_address ->
address_bytes_to_string(pool_staking_address) address_bytes_to_string(pool_staking_address)
end) end)
end) end)
@ -384,7 +403,8 @@ defmodule BlockScoutWeb.StakesChannel do
[] []
end end
pools_amounts = Enum.map(pools, fn pool_staking_address -> pools_amounts =
Enum.map(pools, fn pool_staking_address ->
ContractReader.call_get_reward_amount( ContractReader.call_get_reward_amount(
staking_contract_address, staking_contract_address,
[], [],
@ -394,14 +414,37 @@ defmodule BlockScoutWeb.StakesChannel do
) )
end) end)
error = Enum.find_value(pools_amounts, fn result -> error =
Enum.find_value(pools_amounts, fn result ->
case result do case result do
{:error, reason} -> error_reason_to_string(reason) {:error, reason} -> error_reason_to_string(reason)
_ -> nil _ -> nil
end end
end) end)
{error, pools} = if error != nil do {error, pools} =
get_pools(pools_amounts, pools, staking_contract_address, staker, json_rpc_named_arguments, error)
html =
View.render_to_string(
StakesView,
"_stakes_modal_claim_reward_content.html",
coin: %Token{symbol: Explorer.coin(), decimals: Decimal.new(18)},
error: error,
pools: pools,
token: ContractState.get(:token)
)
push(socket, "claim_reward_pools", %{
html: html
})
after
:ets.delete(ContractState, claim_reward_long_op_key(staker))
end
end
def get_pools(pools_amounts, pools, staking_contract_address, staker, json_rpc_named_arguments, error) do
if error != nil do
{error, %{}} {error, %{}}
else else
block_reward_contract = ContractState.get(:block_reward_contract) block_reward_contract = ContractState.get(:block_reward_contract)
@ -413,40 +456,52 @@ defmodule BlockScoutWeb.StakesChannel do
|> Enum.filter(fn {amounts, _} -> amounts.token_reward_sum > 0 || amounts.native_reward_sum > 0 end) |> Enum.filter(fn {amounts, _} -> amounts.token_reward_sum > 0 || amounts.native_reward_sum > 0 end)
|> Enum.map(fn {amounts, pool_staking_address} -> |> Enum.map(fn {amounts, pool_staking_address} ->
responses = responses =
ContractReader.epochs_to_claim_reward_from_request(pool_staking_address, staker) pool_staking_address
|> ContractReader.perform_requests(%{block_reward: block_reward_contract.address}, block_reward_contract.abi) |> ContractReader.epochs_to_claim_reward_from_request(staker)
|> ContractReader.perform_requests(
%{block_reward: block_reward_contract.address},
block_reward_contract.abi
)
epochs = epochs =
array_to_ranges(responses[:epochs]) responses[:epochs]
|> array_to_ranges()
|> Enum.map(fn {first, last} -> |> Enum.map(fn {first, last} ->
Integer.to_string(first) <> (if first != last, do: "-" <> Integer.to_string(last), else: "") Integer.to_string(first) <> if first != last, do: "-" <> Integer.to_string(last), else: ""
end) end)
data = Map.put(amounts, :epochs, Enum.join(epochs, ",")) data = Map.put(amounts, :epochs, Enum.join(epochs, ","))
{data, pool_staking_address} {data, pool_staking_address}
end) end)
|> Enum.filter(fn {data, _} -> data.epochs != "" end) |> Enum.filter(fn {data, _} -> data.epochs != "" end)
pools_gas_estimates = Enum.map(pools, fn {_data, pool_staking_address} -> pools_gas_estimates =
result = ContractReader.claim_reward_estimate_gas( Enum.map(pools, fn {_data, pool_staking_address} ->
result =
ContractReader.claim_reward_estimate_gas(
staking_contract_address, staking_contract_address,
[], [],
pool_staking_address, pool_staking_address,
staker, staker,
json_rpc_named_arguments json_rpc_named_arguments
) )
{pool_staking_address, result} {pool_staking_address, result}
end) end)
error = Enum.find_value(pools_gas_estimates, fn {_, result} -> error =
Enum.find_value(pools_gas_estimates, fn {_, result} ->
case result do case result do
{:error, reason} -> error_reason_to_string(reason) {:error, reason} -> error_reason_to_string(reason)
_ -> nil _ -> nil
end end
end) end)
pools = if error == nil do pools =
if error == nil do
pools_gas_estimates = Map.new(pools_gas_estimates) pools_gas_estimates = Map.new(pools_gas_estimates)
Map.new(pools, fn {data, pool_staking_address} -> Map.new(pools, fn {data, pool_staking_address} ->
{:ok, estimate} = pools_gas_estimates[pool_staking_address] {:ok, estimate} = pools_gas_estimates[pool_staking_address]
data = Map.put(data, :gas_estimate, estimate) data = Map.put(data, :gas_estimate, estimate)
@ -458,30 +513,16 @@ defmodule BlockScoutWeb.StakesChannel do
{error, pools} {error, pools}
end end
html = View.render_to_string(
StakesView,
"_stakes_modal_claim_reward_content.html",
coin: %Token{symbol: Explorer.coin(), decimals: Decimal.new(18)},
error: error,
pools: pools,
token: ContractState.get(:token)
)
push(socket, "claim_reward_pools", %{
html: html
})
after
:ets.delete(ContractState, claim_reward_long_op_key(staker))
end
end end
def recalc_claim_reward(socket, staking_contract_address, epochs, pool_staking_address, staker) do def recalc_claim_reward(socket, staking_contract_address, epochs, pool_staking_address, staker) do
:ets.insert(ContractState, {claim_reward_long_op_key(staker), true}) :ets.insert(ContractState, {claim_reward_long_op_key(staker), true})
try do try do
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
amounts_result = ContractReader.call_get_reward_amount( amounts_result =
ContractReader.call_get_reward_amount(
staking_contract_address, staking_contract_address,
epochs, epochs,
pool_staking_address, pool_staking_address,
@ -489,15 +530,19 @@ defmodule BlockScoutWeb.StakesChannel do
json_rpc_named_arguments json_rpc_named_arguments
) )
{error, amounts} = case amounts_result do {error, amounts} =
case amounts_result do
{:ok, amounts} -> {:ok, amounts} ->
{nil, amounts} {nil, amounts}
{:error, reason} -> {:error, reason} ->
{error_reason_to_string(reason), %{token_reward_sum: 0, native_reward_sum: 0}} {error_reason_to_string(reason), %{token_reward_sum: 0, native_reward_sum: 0}}
end end
{error, gas_limit} = if error == nil do {error, gas_limit} =
estimate_gas_result = ContractReader.claim_reward_estimate_gas( if error == nil do
estimate_gas_result =
ContractReader.claim_reward_estimate_gas(
staking_contract_address, staking_contract_address,
epochs, epochs,
pool_staking_address, pool_staking_address,
@ -508,6 +553,7 @@ defmodule BlockScoutWeb.StakesChannel do
case estimate_gas_result do case estimate_gas_result do
{:ok, gas_limit} -> {:ok, gas_limit} ->
{nil, gas_limit} {nil, gas_limit}
{:error, reason} -> {:error, reason} ->
{error_reason_to_string(reason), 0} {error_reason_to_string(reason), 0}
end end
@ -519,8 +565,18 @@ defmodule BlockScoutWeb.StakesChannel do
coin = %Token{symbol: Explorer.coin(), decimals: Decimal.new(18)} coin = %Token{symbol: Explorer.coin(), decimals: Decimal.new(18)}
push(socket, "claim_reward_recalculations", %{ push(socket, "claim_reward_recalculations", %{
token_reward_sum: StakesHelpers.format_token_amount(amounts.token_reward_sum, token, digits: token.decimals, ellipsize: false, symbol: false), token_reward_sum:
native_reward_sum: StakesHelpers.format_token_amount(amounts.native_reward_sum, coin, digits: coin.decimals, ellipsize: false, symbol: false), StakesHelpers.format_token_amount(amounts.token_reward_sum, token,
digits: token.decimals,
ellipsize: false,
symbol: false
),
native_reward_sum:
StakesHelpers.format_token_amount(amounts.native_reward_sum, coin,
digits: coin.decimals,
ellipsize: false,
symbol: false
),
gas_limit: gas_limit, gas_limit: gas_limit,
error: error error: error
}) })
@ -534,6 +590,7 @@ defmodule BlockScoutWeb.StakesChannel do
true true
else else
staker = socket.assigns[:account] staker = socket.assigns[:account]
with [{_, true}] <- :ets.lookup(ContractState, claim_reward_long_op_key(staker)) do with [{_, true}] <- :ets.lookup(ContractState, claim_reward_long_op_key(staker)) do
true true
end end
@ -544,13 +601,20 @@ defmodule BlockScoutWeb.StakesChannel do
defp array_to_ranges(numbers, prev_ranges \\ []) do defp array_to_ranges(numbers, prev_ranges \\ []) do
length = Enum.count(numbers) length = Enum.count(numbers)
if length > 0 do if length > 0 do
{first, last, next_index} = get_range(numbers) {first, last, next_index} = get_range(numbers)
ranges = prev_ranges ++ [{first, last}] prev_ranges_reversed = Enum.reverse(prev_ranges)
ranges =
[{first, last} | prev_ranges_reversed]
|> Enum.reverse()
if next_index == 0 || next_index >= length do if next_index == 0 || next_index >= length do
ranges ranges
else else
Enum.slice(numbers, next_index, length - next_index) numbers
|> Enum.slice(next_index, length - next_index)
|> array_to_ranges(ranges) |> array_to_ranges(ranges)
end end
else else
@ -568,10 +632,12 @@ defmodule BlockScoutWeb.StakesChannel do
defp get_range(numbers) do defp get_range(numbers) do
last_index = last_index =
Enum.with_index(numbers) numbers
|> Enum.with_index()
|> Enum.find_index(fn {n, i} -> |> Enum.find_index(fn {n, i} ->
if i > 0, do: n != Enum.at(numbers, i - 1) + 1, else: false if i > 0, do: n != Enum.at(numbers, i - 1) + 1, else: false
end) end)
next_index = if last_index == nil, do: Enum.count(numbers), else: last_index next_index = if last_index == nil, do: Enum.count(numbers), else: last_index
first = Enum.at(numbers, 0) first = Enum.at(numbers, 0)
last = Enum.at(numbers, next_index - 1) last = Enum.at(numbers, next_index - 1)
@ -599,4 +665,85 @@ defmodule BlockScoutWeb.StakesChannel do
staker = if staker == nil, do: "", else: staker staker = if staker == nil, do: "", else: staker
Atom.to_string(@claim_reward_long_op) <> "_" <> staker Atom.to_string(@claim_reward_long_op) <> "_" <> staker
end end
defp handle_in_render_claim_reward_result(
socket,
data,
staker,
staking_contract_address,
empty_staker,
empty_staking_contract_address
) do
cond do
claim_reward_long_op_active(socket) == true ->
{:reply, {:error, %{reason: gettext("Pools searching is already in progress for this address")}}, socket}
empty_staker ->
{:reply, {:error, %{reason: gettext("Unknown staker address. Please, choose your account in MetaMask")}},
socket}
empty_staking_contract_address ->
{:reply, {:error, %{reason: gettext("Unknown address of Staking contract. Please, contact support")}}, socket}
true ->
result =
if data["preload"] do
%{
html: View.render_to_string(StakesView, "_stakes_modal_claim_reward.html", %{}),
socket: socket
}
else
task = Task.async(__MODULE__, :find_claim_reward_pools, [socket, staker, staking_contract_address])
%{
html: "OK",
socket: assign(socket, @claim_reward_long_op, %{task: task, staker: staker})
}
end
{:reply, {:ok, %{html: result.html}}, result.socket}
end
end
defp handle_in_recalc_claim_reward_result(
socket,
epochs,
staking_contract_address,
pool_staking_address,
staker,
empty_pool_staking_address,
empty_staking_contract_address,
empty_staker
) do
cond do
claim_reward_long_op_active(socket) == true ->
{:reply, {:error, %{reason: gettext("Reward calculating is already in progress for this address")}}, socket}
Enum.empty?(epochs) ->
{:reply, {:error, %{reason: gettext("Staking epochs are not specified or not in the allowed range")}}, socket}
empty_pool_staking_address ->
{:reply, {:error, %{reason: gettext("Unknown pool staking address. Please, contact support")}}, socket}
empty_staker ->
{:reply, {:error, %{reason: gettext("Unknown staker address. Please, choose your account in MetaMask")}},
socket}
empty_staking_contract_address ->
{:reply, {:error, %{reason: gettext("Unknown address of Staking contract. Please, contact support")}}, socket}
true ->
task =
Task.async(__MODULE__, :recalc_claim_reward, [
socket,
staking_contract_address,
epochs,
pool_staking_address,
staker
])
socket = assign(socket, @claim_reward_long_op, %{task: task, staker: staker})
{:reply, {:ok, %{html: "OK"}}, socket}
end
end
end end

@ -49,7 +49,8 @@ defmodule BlockScoutWeb.StakesController do
# this is called when account in MetaMask is changed on client side # this is called when account in MetaMask is changed on client side
# or when UI periodically reloads the pool list (e.g. once per 10 blocks) # or when UI periodically reloads the pool list (e.g. once per 10 blocks)
defp render_template(filter, conn, %{"type" => "JSON"} = params) do defp render_template(filter, conn, %{"type" => "JSON"} = params) do
{items, next_page_path} = if Map.has_key?(params, "filterMy") do {items, next_page_path} =
if Map.has_key?(params, "filterMy") do
[paging_options: options] = paging_options(params) [paging_options: options] = paging_options(params)
last_index = last_index =
@ -110,6 +111,7 @@ defmodule BlockScoutWeb.StakesController do
} }
) )
end) end)
{items, next_page_path} {items, next_page_path}
else else
loading_item = View.render_to_string(StakesView, "_rows_loading.html", %{}) loading_item = View.render_to_string(StakesView, "_rows_loading.html", %{})

@ -4823,17 +4823,21 @@ defmodule Explorer.Chain do
end end
def staking_pool_delegators(staking_address_hash, show_snapshotted_data) do def staking_pool_delegators(staking_address_hash, show_snapshotted_data) do
query =
from( from(
d in StakingPoolsDelegator, d in StakingPoolsDelegator,
where: where:
d.staking_address_hash == ^staking_address_hash and d.staking_address_hash == ^staking_address_hash and
(d.is_active == true or ^show_snapshotted_data and d.snapshotted_stake_amount > 0 and d.is_active != true), (d.is_active == true or (^show_snapshotted_data and d.snapshotted_stake_amount > 0 and d.is_active != true)),
order_by: order_by: [desc: d.stake_amount]
[desc: d.stake_amount] )
) |> Repo.all()
query
|> Repo.all()
end end
def staking_pool_snapshotted_inactive_delegators_count(staking_address_hash) do def staking_pool_snapshotted_inactive_delegators_count(staking_address_hash) do
query =
from( from(
d in StakingPoolsDelegator, d in StakingPoolsDelegator,
where: where:
@ -4841,7 +4845,10 @@ defmodule Explorer.Chain do
d.snapshotted_stake_amount > 0 and d.snapshotted_stake_amount > 0 and
d.is_active != true, d.is_active != true,
select: fragment("count(*)") select: fragment("count(*)")
) |> Repo.one() )
query
|> Repo.one()
end end
def staking_pool_delegator(staking_address_hash, address_hash) do def staking_pool_delegator(staking_address_hash, address_hash) do

@ -260,8 +260,8 @@ defmodule Explorer.Chain.Import do
@local_options ~w(on_conflict params with timeout)a @local_options ~w(on_conflict params with timeout)a
defp validate_runner_options_known(runner_option_key, options, runner_specific_options) do defp validate_runner_options_known(runner_option_key, options, runner_specific_options) do
unknown_option_keys = Map.keys(options) -- @local_options base_unknown_option_keys = Map.keys(options) -- @local_options
unknown_option_keys = unknown_option_keys -- runner_specific_options unknown_option_keys = base_unknown_option_keys -- runner_specific_options
if Enum.empty?(unknown_option_keys) do if Enum.empty?(unknown_option_keys) do
:ok :ok

@ -43,18 +43,20 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do
|> Map.put_new(:timeout, @timeout) |> Map.put_new(:timeout, @timeout)
|> Map.put(:timestamps, timestamps) |> Map.put(:timestamps, timestamps)
clear_snapshotted_values = case Map.fetch(insert_options, :clear_snapshotted_values) do clear_snapshotted_values =
case Map.fetch(insert_options, :clear_snapshotted_values) do
{:ok, v} -> v {:ok, v} -> v
:error -> false :error -> false
end end
multi = if not clear_snapshotted_values do multi =
if clear_snapshotted_values do
multi
else
# Enforce ShareLocks tables order (see docs: sharelocks.md) # Enforce ShareLocks tables order (see docs: sharelocks.md)
Multi.run(multi, :acquire_all_staking_pools, fn repo, _ -> Multi.run(multi, :acquire_all_staking_pools, fn repo, _ ->
acquire_all_staking_pools(repo) acquire_all_staking_pools(repo)
end) end)
else
multi
end end
multi multi
@ -84,7 +86,8 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do
end end
defp mark_as_deleted(repo, changes_list, %{timeout: timeout}, clear_snapshotted_values) when is_list(changes_list) do defp mark_as_deleted(repo, changes_list, %{timeout: timeout}, clear_snapshotted_values) when is_list(changes_list) do
query = if clear_snapshotted_values do query =
if clear_snapshotted_values do
from( from(
pool in StakingPool, pool in StakingPool,
update: [ update: [
@ -97,6 +100,7 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do
) )
else else
addresses = Enum.map(changes_list, & &1.staking_address_hash) addresses = Enum.map(changes_list, & &1.staking_address_hash)
from( from(
pool in StakingPool, pool in StakingPool,
where: pool.staking_address_hash not in ^addresses, where: pool.staking_address_hash not in ^addresses,

@ -57,12 +57,14 @@ defmodule Explorer.Chain.Import.Runner.StakingPoolsDelegators do
def timeout, do: @timeout def timeout, do: @timeout
defp mark_as_deleted(repo, %{timeout: timeout} = options) do defp mark_as_deleted(repo, %{timeout: timeout} = options) do
clear_snapshotted_values = case Map.fetch(options, :clear_snapshotted_values) do clear_snapshotted_values =
case Map.fetch(options, :clear_snapshotted_values) do
{:ok, v} -> v {:ok, v} -> v
:error -> false :error -> false
end end
query = if clear_snapshotted_values do query =
if clear_snapshotted_values do
from( from(
d in StakingPoolsDelegator, d in StakingPoolsDelegator,
update: [set: [snapshotted_reward_ratio: nil, snapshotted_stake_amount: nil]] update: [set: [snapshotted_reward_ratio: nil, snapshotted_stake_amount: nil]]

@ -56,29 +56,44 @@ defmodule Explorer.Staking.ContractReader do
staker = address_pad_to_64(staker) staker = address_pad_to_64(staker)
staking_epochs_length = staking_epochs_length =
Enum.count(staking_epochs) staking_epochs
|> Enum.count()
|> Integer.to_string(16) |> Integer.to_string(16)
|> String.pad_leading(64, ["0"]) |> String.pad_leading(64, ["0"])
data = "0xfb367a9b" # `getRewardAmount` function signature # `getRewardAmount` function signature
data = data <> String.pad_leading("60", 64, ["0"]) # offset to the `_stakingEpochs` array function_signature = "0xfb367a9b"
data = data <> pool_staking_address # `_poolStakingAddress` parameter # offset to the `_stakingEpochs` array
data = data <> staker # `_staker` parameter function_signature_with_offset = function_signature <> String.pad_leading("60", 64, ["0"])
data = data <> staking_epochs_length # the length of `_stakingEpochs` array # `_poolStakingAddress` parameter
data = data <> staking_epochs_joint # encoded `_stakingEpochs` array function_with_param_1 = function_signature_with_offset <> pool_staking_address
# `_staker` parameter
result = EthereumJSONRPC.request(%{ function_with_param1_param2 = function_with_param_1 <> staker
# the length of `_stakingEpochs` array
function_with_param_1_length_param2 = function_with_param1_param2 <> staking_epochs_length
# encoded `_stakingEpochs` array
data = function_with_param_1_length_param2 <> staking_epochs_joint
request = %{
id: 0, id: 0,
method: "eth_call", method: "eth_call",
params: [%{ params: [
%{
to: staking_contract_address, to: staking_contract_address,
data: data data: data
}] }
}) |> EthereumJSONRPC.json_rpc(json_rpc_named_arguments) ]
}
result =
request
|> EthereumJSONRPC.request()
|> EthereumJSONRPC.json_rpc(json_rpc_named_arguments)
case result do case result do
{:ok, response} -> {:ok, response} ->
response = String.replace_leading(response, "0x", "") response = String.replace_leading(response, "0x", "")
if String.length(response) != 64 * 2 do if String.length(response) != 64 * 2 do
{:error, "Invalid getRewardAmount response."} {:error, "Invalid getRewardAmount response."}
else else
@ -87,6 +102,7 @@ defmodule Explorer.Staking.ContractReader do
native_reward_sum = String.to_integer(native_reward_sum, 16) native_reward_sum = String.to_integer(native_reward_sum, 16)
{:ok, %{token_reward_sum: token_reward_sum, native_reward_sum: native_reward_sum}} {:ok, %{token_reward_sum: token_reward_sum, native_reward_sum: native_reward_sum}}
end end
{:error, reason} -> {:error, reason} ->
{:error, reason} {:error, reason}
end end
@ -116,26 +132,40 @@ defmodule Explorer.Staking.ContractReader do
pool_staking_address = address_pad_to_64(pool_staking_address) pool_staking_address = address_pad_to_64(pool_staking_address)
staking_epochs_length = staking_epochs_length =
Enum.count(staking_epochs) staking_epochs
|> Enum.count()
|> Integer.to_string(16) |> Integer.to_string(16)
|> String.pad_leading(64, ["0"]) |> String.pad_leading(64, ["0"])
data = "0x3ea15d62" # `claimReward` function signature # `claimReward` function signature
data = data <> String.pad_leading("40", 64, ["0"]) # offset to the `_stakingEpochs` array function_signature = "0x3ea15d62"
data = data <> pool_staking_address # `_poolStakingAddress` parameter # offset to the `_stakingEpochs` array
data = data <> staking_epochs_length # the length of `_stakingEpochs` array function_signature_with_offset = function_signature <> String.pad_leading("40", 64, ["0"])
data = data <> staking_epochs_joint # encoded `_stakingEpochs` array # `_poolStakingAddress` parameter
function_with_param_1 = function_signature_with_offset <> pool_staking_address
result = EthereumJSONRPC.request(%{ # the length of `_stakingEpochs` array
function_with_param_1_length_param2 = function_with_param_1 <> staking_epochs_length
# encoded `_stakingEpochs` array
data = function_with_param_1_length_param2 <> staking_epochs_joint
request = %{
id: 0, id: 0,
method: "eth_estimateGas", method: "eth_estimateGas",
params: [%{ params: [
%{
from: staker, from: staker,
to: staking_contract_address, to: staking_contract_address,
gasPrice: "0x3B9ACA00", # 1 gwei # 1 gwei
gasPrice: "0x3B9ACA00",
data: data data: data
}] }
}) |> EthereumJSONRPC.json_rpc(json_rpc_named_arguments) ]
}
result =
request
|> EthereumJSONRPC.request()
|> EthereumJSONRPC.json_rpc(json_rpc_named_arguments)
case result do case result do
{:ok, response} -> {:ok, response} ->
@ -143,7 +173,9 @@ defmodule Explorer.Staking.ContractReader do
response response
|> String.replace_leading("0x", "") |> String.replace_leading("0x", "")
|> String.to_integer(16) |> String.to_integer(16)
{:ok, estimate} {:ok, estimate}
{:error, reason} -> {:error, reason} ->
{:error, reason} {:error, reason}
end end

@ -34,7 +34,8 @@ defmodule Explorer.Staking.ContractState do
:validator_set_contract :validator_set_contract
] ]
@token_renew_frequency 10 # frequency in blocks # frequency in blocks
@token_renew_frequency 10
defstruct [ defstruct [
:seen_block, :seen_block,
@ -128,81 +129,151 @@ defmodule Explorer.Staking.ContractState do
# read general info from the contracts (including pool list and validator list) # read general info from the contracts (including pool list and validator list)
global_responses = ContractReader.perform_requests(ContractReader.global_requests(), contracts, abi) global_responses = ContractReader.perform_requests(ContractReader.global_requests(), contracts, abi)
validator_min_reward_percent = ContractReader.perform_requests( validator_min_reward_percent = get_validator_min_reward_percent(global_responses, contracts, abi)
ContractReader.validator_min_reward_percent_request(global_responses.epoch_number), contracts, abi
).value
epoch_very_beginning = (global_responses.epoch_start_block == block_number + 1) epoch_very_beginning = global_responses.epoch_start_block == block_number + 1
is_validator = Enum.into(global_responses.validators, %{}, &{address_bytes_to_string(&1), true}) 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))
# save the general info to ETS (excluding pool list and validator list) start_snapshotting =
settings = global_responses.epoch_number > get(:snapshotted_epoch_number) && global_responses.epoch_number > 0 &&
global_responses not get(:is_snapshotting)
|> Map.take([
:token_contract_address,
:min_candidate_stake,
:min_delegator_stake,
:epoch_number,
:epoch_start_block,
:epoch_end_block,
:staking_allowed,
:validator_set_apply_block
])
|> Map.to_list()
|> Enum.concat(validator_min_reward_percent: validator_min_reward_percent)
update_token = # save the general info to ETS (excluding pool list and validator list)
get(:token) == nil or settings = get_settings(global_responses, validator_min_reward_percent, block_number)
get(:token_contract_address) != global_responses.token_contract_address or
rem(block_number, @token_renew_frequency) == 0
settings = if update_token do
Enum.concat(settings, token: get_token(global_responses.token_contract_address))
else
settings
end
:ets.insert(@table_name, settings) :ets.insert(@table_name, settings)
# form the list of validator pools # form the list of validator pools
validators = if start_snapshotting do validators =
if global_responses.validator_set_apply_block == 0 do get_validators(
start_snapshotting,
global_responses,
contracts,
abi
)
# miningToStakingAddress mapping
mining_to_staking_address = get_mining_to_staking_address(validators, contracts, abi)
# 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
)
%{ %{
"getPendingValidators" => {:ok, [validators_pending]}, pool_staking_responses: pool_staking_responses,
"validatorsToBeFinalized" => {:ok, [validators_to_be_finalized]} pool_mining_responses: pool_mining_responses,
} = Reader.query_contract(contracts.validator_set, abi, %{ staker_responses: staker_responses
"getPendingValidators" => [], } = get_responses(pools, block_number, contracts, abi)
"validatorsToBeFinalized" => []
# 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)
# to keep sort order when using `perform_grouped_requests` (see below)
delegator_keys = Enum.map(staker_responses, fn {key, _} -> key end)
# call `BlockReward.delegatorShare` function for each delegator
# to get their reward share of the pool (needed for the `Delegators` list in UI)
delegator_reward_responses =
get_delegator_reward_responses(
staker_responses,
pool_staking_responses,
global_responses,
delegator_keys,
contracts,
abi
)
# 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
}) })
validators_pending = Enum.uniq(validators_pending ++ validators_to_be_finalized)
%{ # form entries for writing to the `staking_pools_delegators` table in DB
all: Enum.uniq(global_responses.validators ++ validators_pending), delegator_entries = get_delegator_entries(staker_responses, delegator_reward_responses)
for_snapshot: validators_pending
} # perform SQL queries
else {:ok, _} =
%{ Chain.import(%{
all: global_responses.validators, staking_pools: %{params: pool_entries},
for_snapshot: global_responses.validators staking_pools_delegators: %{params: delegator_entries},
} timeout: :infinity
})
if epoch_very_beginning or start_snapshotting do
at_start_snapshotting(block_number)
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
# notify the UI about new block
Publisher.broadcast(:staking_update)
end
defp get_settings(global_responses, validator_min_reward_percent, block_number) do
base_settings = get_base_settings(global_responses, validator_min_reward_percent)
update_token =
get(:token) == nil or
get(:token_contract_address) != global_responses.token_contract_address or
rem(block_number, @token_renew_frequency) == 0
if update_token do
Enum.concat(base_settings, token: get_token(global_responses.token_contract_address))
else else
%{all: global_responses.validators} base_settings
end
end end
# miningToStakingAddress mapping defp get_mining_to_staking_address(validators, contracts, abi) do
mining_to_staking_address =
validators.all validators.all
|> Enum.map(&ContractReader.staking_by_mining_request/1) |> Enum.map(&ContractReader.staking_by_mining_request/1)
|> ContractReader.perform_grouped_requests(validators.all, contracts, abi) |> ContractReader.perform_grouped_requests(validators.all, contracts, abi)
|> Map.new(fn {mining_address, resp} -> {mining_address, address_string_to_bytes(resp.staking_address).bytes} end) |> Map.new(fn {mining_address, resp} -> {mining_address, address_string_to_bytes(resp.staking_address).bytes} end)
end
# the list of all pools (validators + active pools + inactive pools) defp get_responses(pools, block_number, contracts, abi) do
pools = Enum.uniq(
Map.values(mining_to_staking_address) ++
global_responses.active_pools ++
global_responses.inactive_pools
)
# read pool info from the contracts by its staking address # read pool info from the contracts by its staking address
pool_staking_responses = pool_staking_responses =
pools pools
@ -233,12 +304,14 @@ defmodule Explorer.Staking.ContractState do
end) end)
|> ContractReader.perform_grouped_requests(stakers, contracts, abi) |> ContractReader.perform_grouped_requests(stakers, 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) pool_staking_responses: pool_staking_responses,
pool_mining_responses: pool_mining_responses,
staker_responses: staker_responses
}
end
# call `BlockReward.validatorShare` function for each pool defp get_candidate_reward_responses(pool_staking_responses, global_responses, pool_staking_keys, contracts, abi) do
# to get validator's reward share of the pool (needed for the `Delegators` list in UI)
candidate_reward_responses =
pool_staking_responses pool_staking_responses
|> Enum.map(fn {_pool_staking_address, resp} -> |> Enum.map(fn {_pool_staking_address, resp} ->
ContractReader.validator_reward_request([ ContractReader.validator_reward_request([
@ -249,13 +322,16 @@ defmodule Explorer.Staking.ContractState do
]) ])
end) end)
|> ContractReader.perform_grouped_requests(pool_staking_keys, contracts, abi) |> ContractReader.perform_grouped_requests(pool_staking_keys, contracts, abi)
end
# to keep sort order when using `perform_grouped_requests` (see below) defp get_delegator_reward_responses(
delegator_keys = Enum.map(staker_responses, fn {key, _} -> key end) staker_responses,
pool_staking_responses,
# call `BlockReward.delegatorShare` function for each delegator global_responses,
# to get their reward share of the pool (needed for the `Delegators` list in UI) delegator_keys,
delegator_reward_responses = contracts,
abi
) do
staker_responses staker_responses
|> Enum.map(fn {{pool_staking_address, _staker_address, _is_active}, resp} -> |> Enum.map(fn {{pool_staking_address, _staker_address, _is_active}, resp} ->
staking_resp = pool_staking_responses[pool_staking_address] staking_resp = pool_staking_responses[pool_staking_address]
@ -269,21 +345,121 @@ defmodule Explorer.Staking.ContractState do
]) ])
end) end)
|> ContractReader.perform_grouped_requests(delegator_keys, contracts, abi) |> ContractReader.perform_grouped_requests(delegator_keys, contracts, abi)
end
# calculate total amount staked into all active pools defp get_delegator_entries(staker_responses, delegator_reward_responses) do
staked_total = Enum.sum(for {_, pool} <- pool_staking_responses, pool.is_active, do: pool.total_staked_amount) Enum.map(staker_responses, fn {{pool_address, delegator_address, is_active}, response} ->
delegator_reward_response = delegator_reward_responses[{pool_address, delegator_address, is_active}]
# calculate likelihood of becoming a validator on the next epoch Map.merge(response, %{
[likelihood_values, total_likelihood] = global_responses.pools_likelihood address_hash: delegator_address,
likelihood = staking_address_hash: pool_address,
global_responses.pools_to_be_elected # array of pool addresses (staking addresses) is_active: is_active,
|> Enum.zip(likelihood_values) reward_ratio: Float.floor(delegator_reward_response.delegator_share / 10_000, 2)
|> Enum.into(%{}) })
end)
end
snapshotted_epoch_number = get(:snapshotted_epoch_number) defp get_validator_min_reward_percent(global_responses, contracts, abi) do
ContractReader.perform_requests(
ContractReader.validator_min_reward_percent_request(global_responses.epoch_number),
contracts,
abi
).value
end
# form entries for writing to the `staking_pools` table in DB defp get_base_settings(global_responses, validator_min_reward_percent) do
pool_entries = global_responses
|> Map.take([
:token_contract_address,
:min_candidate_stake,
:min_delegator_stake,
:epoch_number,
:epoch_start_block,
:epoch_end_block,
:staking_allowed,
:validator_set_apply_block
])
|> Map.to_list()
|> Enum.concat(validator_min_reward_percent: validator_min_reward_percent)
end
defp get_validators(
start_snapshotting,
global_responses,
contracts,
abi
) do
if start_snapshotting do
if global_responses.validator_set_apply_block == 0 do
%{
"getPendingValidators" => {:ok, [validators_pending]},
"validatorsToBeFinalized" => {:ok, [validators_to_be_finalized]}
} =
Reader.query_contract(contracts.validator_set, abi, %{
"getPendingValidators" => [],
"validatorsToBeFinalized" => []
})
validators_pending = Enum.uniq(validators_pending ++ validators_to_be_finalized)
%{
all: Enum.uniq(global_responses.validators ++ validators_pending),
for_snapshot: validators_pending
}
else
%{
all: global_responses.validators,
for_snapshot: global_responses.validators
}
end
else
%{all: global_responses.validators}
end
end
def show_snapshotted_data(
is_validator,
validator_set_apply_block \\ nil,
snapshotted_epoch_number \\ nil,
epoch_number \\ nil
) do
validator_set_apply_block =
if validator_set_apply_block !== nil do
validator_set_apply_block
else
get(:validator_set_apply_block)
end
snapshotted_epoch_number =
if snapshotted_epoch_number !== nil do
snapshotted_epoch_number
else
get(:snapshotted_epoch_number)
end
epoch_number =
if epoch_number !== nil do
epoch_number
else
get(:epoch_number)
end
is_validator && validator_set_apply_block > 0 && snapshotted_epoch_number === epoch_number
end
defp 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
}) do
Enum.map(pools, fn pool_staking_address -> Enum.map(pools, fn pool_staking_address ->
staking_resp = pool_staking_responses[pool_staking_address] staking_resp = pool_staking_responses[pool_staking_address]
mining_resp = pool_mining_responses[pool_staking_address] mining_resp = pool_mining_responses[pool_staking_address]
@ -292,7 +468,12 @@ defmodule Explorer.Staking.ContractState do
delegators_count = delegators_count =
length(staking_resp.active_delegators) + length(staking_resp.active_delegators) +
if show_snapshotted_data(is_validator, global_responses.validator_set_apply_block, snapshotted_epoch_number, global_responses.epoch_number) do if show_snapshotted_data(
is_validator,
global_responses.validator_set_apply_block,
snapshotted_epoch_number,
global_responses.epoch_number
) do
Chain.staking_pool_snapshotted_inactive_delegators_count(pool_staking_address) Chain.staking_pool_snapshotted_inactive_delegators_count(pool_staking_address)
else else
0 0
@ -334,40 +515,24 @@ defmodule Explorer.Staking.ContractState do
]) ])
) )
end) end)
end
# form entries for writing to the `staking_pools_delegators` table in DB defp at_start_snapshotting(block_number) do
delegator_entries =
Enum.map(staker_responses, fn {{pool_address, delegator_address, is_active}, response} ->
delegator_reward_response = delegator_reward_responses[{pool_address, delegator_address, is_active}]
Map.merge(response, %{
address_hash: delegator_address,
staking_address_hash: pool_address,
is_active: is_active,
reward_ratio: Float.floor(delegator_reward_response.delegator_share / 10_000, 2)
})
end)
# 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
# update ERC balance of the BlockReward contract # update ERC balance of the BlockReward contract
token = get(:token) token = get(:token)
if token != nil do if token != nil do
block_reward_address = address_string_to_bytes(get(:block_reward_contract).address) block_reward_address = address_string_to_bytes(get(:block_reward_contract).address)
token_contract_address_hash = token.contract_address_hash token_contract_address_hash = token.contract_address_hash
block_reward_balance = BalanceReader.get_balances_of([%{ block_reward_balance =
BalanceReader.get_balances_of([
%{
token_contract_address_hash: token_contract_address_hash, token_contract_address_hash: token_contract_address_hash,
address_hash: block_reward_address.bytes, address_hash: block_reward_address.bytes,
block_number: block_number block_number: block_number
}])[:ok] }
])[:ok]
token_params = token_params =
token_contract_address_hash token_contract_address_hash
@ -377,32 +542,51 @@ defmodule Explorer.Staking.ContractState do
type: "ERC-20" type: "ERC-20"
}) })
import_result = Chain.import(%{ import_result =
Chain.import(%{
addresses: %{params: [%{hash: block_reward_address.bytes}], on_conflict: :nothing}, addresses: %{params: [%{hash: block_reward_address.bytes}], on_conflict: :nothing},
address_current_token_balances: %{params: [%{ address_current_token_balances: %{
params: [
%{
address_hash: block_reward_address.bytes, address_hash: block_reward_address.bytes,
token_contract_address_hash: token_contract_address_hash, token_contract_address_hash: token_contract_address_hash,
block_number: block_number, block_number: block_number,
value: block_reward_balance, value: block_reward_balance,
value_fetched_at: DateTime.utc_now() value_fetched_at: DateTime.utc_now()
}]}, }
]
},
tokens: %{params: [token_params]} tokens: %{params: [token_params]}
}) })
with {:ok, _} <- import_result, do: with {:ok, _} <- import_result,
Publisher.broadcast([{ do:
:address_token_balances, [ Publisher.broadcast(
[
{
:address_token_balances,
[
%{address_hash: block_reward_address.struct, block_number: block_number} %{address_hash: block_reward_address.struct, block_number: block_number}
] ]
}], }
],
:on_demand :on_demand
) )
end end
end end
if start_snapshotting do defp do_start_snapshotting(
epoch_very_beginning,
pool_staking_responses,
global_responses,
contracts,
abi,
validators,
mining_to_staking_address
) do
# start snapshotting at the beginning of the staking epoch # start snapshotting at the beginning of the staking epoch
cached_pool_staking_responses = if epoch_very_beginning do cached_pool_staking_responses =
if epoch_very_beginning do
pool_staking_responses pool_staking_responses
else else
%{} %{}
@ -412,79 +596,74 @@ defmodule Explorer.Staking.ContractState do
%{contracts: contracts, abi: abi, ets_table_name: @table_name}, %{contracts: contracts, abi: abi, ets_table_name: @table_name},
global_responses.epoch_number, global_responses.epoch_number,
cached_pool_staking_responses, cached_pool_staking_responses,
validators.for_snapshot, # mining addresses of pending/current validators # mining addresses of pending/current validators
validators.for_snapshot,
mining_to_staking_address, mining_to_staking_address,
global_responses.epoch_start_block - 1 # the last block of the previous staking epoch # the last block of the previous staking epoch
global_responses.epoch_start_block - 1
]) ])
end end
# notify the UI about new block
Publisher.broadcast(:staking_update)
end
def show_snapshotted_data(is_validator, validator_set_apply_block \\ nil, snapshotted_epoch_number \\ nil, epoch_number \\ nil) do
validator_set_apply_block = if validator_set_apply_block !== nil do
validator_set_apply_block
else
get(:validator_set_apply_block)
end
snapshotted_epoch_number = if snapshotted_epoch_number !== nil do
snapshotted_epoch_number
else
get(:snapshotted_epoch_number)
end
epoch_number = if epoch_number !== nil do
epoch_number
else
get(:epoch_number)
end
is_validator && validator_set_apply_block > 0 && snapshotted_epoch_number === epoch_number
end
defp get_token(address) do defp get_token(address) do
if address == "0x0000000000000000000000000000000000000000" do if address == "0x0000000000000000000000000000000000000000" do
nil # the token address is empty, so return nil # the token address is empty, so return nil
nil
else else
with {:ok, address_hash} <- Chain.string_to_address_hash(address) do case Chain.string_to_address_hash(address) do
{:ok, address_hash} ->
# the token address has correct format, so try to read the token # the token address has correct format, so try to read the token
# from DB or from its contract # from DB or from its contract
case Chain.token_from_address_hash(address_hash) do case Chain.token_from_address_hash(address_hash) do
{:ok, token} -> {:ok, token} ->
token # the token is read from DB # the token is read from DB
token
_ -> _ ->
fetch_token(address, address_hash)
end
_ ->
# the token address has incorrect format
nil
end
end
end
defp fetch_token(address, address_hash) do
# the token doesn't exist in DB, so try # the token doesn't exist in DB, so try
# to read it from a contract and then write to DB # to read it from a contract and then write to DB
token_functions = MetadataRetriever.get_functions_of(address) token_functions = MetadataRetriever.get_functions_of(address)
if map_size(token_functions) > 0 do if map_size(token_functions) > 0 do
# the token is successfully read from its contract # the token is successfully read from its contract
token_params = Map.merge(token_functions, %{ token_params =
Map.merge(token_functions, %{
contract_address_hash: address, contract_address_hash: address,
type: "ERC-20" type: "ERC-20"
}) })
# try to write the token info to DB # try to write the token info to DB
import_result = Chain.import(%{ import_result =
Chain.import(%{
addresses: %{params: [%{hash: address}], on_conflict: :nothing}, addresses: %{params: [%{hash: address}], on_conflict: :nothing},
tokens: %{params: [token_params]} tokens: %{params: [token_params]}
}) })
with {:ok, _} <- import_result do case import_result do
{:ok, _} ->
# the token is successfully added to DB, so return it as a result # the token is successfully added to DB, so return it as a result
case Chain.token_from_address_hash(address_hash) do case Chain.token_from_address_hash(address_hash) do
{:ok, token} -> token {:ok, token} -> token
_ -> nil _ -> nil
end end
else
_ -> nil # cannot write the token info to DB _ ->
end # cannot write the token info to DB
else nil
nil # cannot read the token info from its contract
end
end end
else else
_ -> nil # the token address has incorrect format # cannot read the token info from its contract
end nil
end end
end end

@ -39,7 +39,15 @@ defmodule Explorer.Staking.StakeSnapshotting do
|> Enum.map(fn staking_address_hash -> |> Enum.map(fn staking_address_hash ->
case Map.fetch(cached_pool_staking_responses, staking_address_hash) do case Map.fetch(cached_pool_staking_responses, staking_address_hash) do
{:ok, resp} -> {:ok, resp} ->
Map.merge(resp, ContractReader.perform_requests(snapshotted_pool_amounts_requests(staking_address_hash, block_number), contracts, abi)) Map.merge(
resp,
ContractReader.perform_requests(
snapshotted_pool_amounts_requests(staking_address_hash, block_number),
contracts,
abi
)
)
:error -> :error ->
ContractReader.perform_requests( ContractReader.perform_requests(
ContractReader.active_delegators_request(staking_address_hash, block_number) ++ ContractReader.active_delegators_request(staking_address_hash, block_number) ++
@ -157,7 +165,11 @@ defmodule Explorer.Staking.StakeSnapshotting do
# perform SQL queries # perform SQL queries
case Chain.import(%{ case Chain.import(%{
staking_pools: %{params: pool_entries, on_conflict: staking_pools_update(), clear_snapshotted_values: true}, staking_pools: %{params: pool_entries, on_conflict: staking_pools_update(), clear_snapshotted_values: true},
staking_pools_delegators: %{params: delegator_entries, on_conflict: staking_pools_delegators_update(), clear_snapshotted_values: true}, staking_pools_delegators: %{
params: delegator_entries,
on_conflict: staking_pools_delegators_update(),
clear_snapshotted_values: true
},
timeout: :infinity timeout: :infinity
}) do }) do
{:ok, _} -> :ets.insert(ets_table_name, snapshotted_epoch_number: epoch_number) {:ok, _} -> :ets.insert(ets_table_name, snapshotted_epoch_number: epoch_number)
@ -172,7 +184,10 @@ defmodule Explorer.Staking.StakeSnapshotting do
defp snapshotted_pool_amounts_requests(pool_staking_address, block_number) do defp snapshotted_pool_amounts_requests(pool_staking_address, block_number) do
[ [
snapshotted_total_staked_amount: {:staking, "stakeAmountTotal", [pool_staking_address], block_number}, snapshotted_total_staked_amount: {:staking, "stakeAmountTotal", [pool_staking_address], block_number},
snapshotted_self_staked_amount: snapshotted_staker_amount_request(pool_staking_address, pool_staking_address, block_number)[:snapshotted_stake_amount] snapshotted_self_staked_amount:
snapshotted_staker_amount_request(pool_staking_address, pool_staking_address, block_number)[
:snapshotted_stake_amount
]
] ]
end end

@ -7,9 +7,7 @@ defmodule Explorer.Repo.Migrations.RemoveDuplicateIndexes do
) )
drop_if_exists( drop_if_exists(
index(:staking_pools_delegators, [:address_hash], index(:staking_pools_delegators, [:address_hash], name: "staking_pools_delegators_address_hash_index")
name: "staking_pools_delegators_address_hash_index"
)
) )
drop_if_exists(index(:transactions, [:to_address_hash], name: "transactions_to_address_hash_index")) drop_if_exists(index(:transactions, [:to_address_hash], name: "transactions_to_address_hash_index"))

Loading…
Cancel
Save