Merge pull request #2036 from poanetwork/stakes-tables

New tables for staking pools and delegators
pull/2086/head
Victor Baranov 6 years ago committed by GitHub
commit 191e3a67c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 46
      apps/explorer/lib/explorer/chain.ex
  3. 93
      apps/explorer/lib/explorer/chain/import/runner/staking_pools.ex
  4. 91
      apps/explorer/lib/explorer/chain/import/runner/staking_pools_delegators.ex
  5. 3
      apps/explorer/lib/explorer/chain/import/stage/address_referencing.ex
  6. 97
      apps/explorer/lib/explorer/chain/staking_pool.ex
  7. 64
      apps/explorer/lib/explorer/chain/staking_pools_delegator.ex
  8. 51
      apps/explorer/lib/explorer/staking/pools_reader.ex
  9. 27
      apps/explorer/priv/repo/migrations/20190521104412_create_staking_pools.exs
  10. 26
      apps/explorer/priv/repo/migrations/20190523112839_create_staking_pools_delegators.exs
  11. 32
      apps/explorer/test/explorer/chain/import/runner/staking_pools_delegators_test.exs
  12. 19
      apps/explorer/test/explorer/chain/import/runner/staking_pools_test.exs
  13. 18
      apps/explorer/test/explorer/chain/staking_pool_test.exs
  14. 18
      apps/explorer/test/explorer/chain/staking_pools_delegator_test.exs
  15. 34
      apps/explorer/test/explorer/chain_test.exs
  16. 170
      apps/explorer/test/explorer/staking/pools_reader_test.exs
  17. 48
      apps/explorer/test/support/factory.ex
  18. 43
      apps/indexer/lib/indexer/fetcher/staking_pools.ex
  19. 8
      apps/indexer/test/indexer/fetcher/staking_pools_test.exs

@ -12,6 +12,7 @@
- [#1956](https://github.com/poanetwork/blockscout/pull/1956) - add logs tab to address
- [#1952](https://github.com/poanetwork/blockscout/pull/1952) - feat: exclude empty contracts by default
- [#1954](https://github.com/poanetwork/blockscout/pull/1954) - feat: use creation init on self destruct
- [#2036](https://github.com/poanetwork/blockscout/pull/2036) - New tables for staking pools and delegators
- [#1974](https://github.com/poanetwork/blockscout/pull/1974) - feat: previous page button logic
- [#1999](https://github.com/poanetwork/blockscout/pull/1999) - load data async on addresses page
- [#1807](https://github.com/poanetwork/blockscout/pull/1807) - New theming capabilites.

@ -39,6 +39,7 @@ defmodule Explorer.Chain do
InternalTransaction,
Log,
SmartContract,
StakingPool,
Token,
TokenTransfer,
Transaction,
@ -2925,7 +2926,7 @@ defmodule Explorer.Chain do
def staking_pools(filter, %PagingOptions{page_size: page_size, page_number: page_number} \\ @default_paging_options) do
off = page_size * (page_number - 1)
Address.Name
StakingPool
|> staking_pool_filter(filter)
|> limit(^page_size)
|> offset(^off)
@ -2935,55 +2936,36 @@ defmodule Explorer.Chain do
@doc "Get count of staking pools from the DB"
@spec staking_pools_count(filter :: :validator | :active | :inactive) :: integer
def staking_pools_count(filter) do
Address.Name
StakingPool
|> staking_pool_filter(filter)
|> Repo.aggregate(:count, :address_hash)
|> Repo.aggregate(:count, :staking_address_hash)
end
defp staking_pool_filter(query, :validator) do
where(
query,
[address],
fragment(
"""
(?->>'is_active')::boolean = true and
(?->>'deleted')::boolean is not true and
(?->>'is_validator')::boolean = true
""",
address.metadata,
address.metadata,
address.metadata
)
[pool],
pool.is_active == true and
pool.is_deleted == false and
pool.is_validator == true
)
end
defp staking_pool_filter(query, :active) do
where(
query,
[address],
fragment(
"""
(?->>'is_active')::boolean = true and
(?->>'deleted')::boolean is not true
""",
address.metadata,
address.metadata
)
[pool],
pool.is_active == true and
pool.is_deleted == false
)
end
defp staking_pool_filter(query, :inactive) do
where(
query,
[address],
fragment(
"""
(?->>'is_active')::boolean = false and
(?->>'deleted')::boolean is not true
""",
address.metadata,
address.metadata
)
[pool],
pool.is_active == false and
pool.is_deleted == false
)
end

@ -1,12 +1,12 @@
defmodule Explorer.Chain.Import.Runner.StakingPools do
@moduledoc """
Bulk imports staking pools to Address.Name tabe.
Bulk imports staking pools to StakingPool tabe.
"""
require Ecto.Query
alias Ecto.{Changeset, Multi, Repo}
alias Explorer.Chain.{Address, Import}
alias Explorer.Chain.{Import, StakingPool}
import Ecto.Query, only: [from: 2]
@ -15,10 +15,10 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do
# milliseconds
@timeout 60_000
@type imported :: [Address.Name.t()]
@type imported :: [StakingPool.t()]
@impl Import.Runner
def ecto_schema_module, do: Address.Name
def ecto_schema_module, do: StakingPool
@impl Import.Runner
def option_key, do: :staking_pools
@ -47,23 +47,25 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do
|> Multi.run(:insert_staking_pools, fn repo, _ ->
insert(repo, changes_list, insert_options)
end)
|> Multi.run(:calculate_stakes_ratio, fn repo, _ ->
calculate_stakes_ratio(repo, insert_options)
end)
end
@impl Import.Runner
def timeout, do: @timeout
defp mark_as_deleted(repo, changes_list, %{timeout: timeout}) when is_list(changes_list) do
addresses = Enum.map(changes_list, & &1.address_hash)
addresses = Enum.map(changes_list, & &1.staking_address_hash)
query =
from(
address_name in Address.Name,
where:
address_name.address_hash not in ^addresses and
fragment("(?->>'is_pool')::boolean = true", address_name.metadata),
pool in StakingPool,
where: pool.staking_address_hash not in ^addresses,
update: [
set: [
metadata: fragment("? || '{\"deleted\": true}'::jsonb", address_name.metadata)
is_deleted: true,
is_active: false
]
]
)
@ -83,7 +85,7 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do
required(:timeout) => timeout,
required(:timestamps) => Import.timestamps()
}) ::
{:ok, [Address.Name.t()]}
{:ok, [StakingPool.t()]}
| {:error, [Changeset.t()]}
defp insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do
on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0)
@ -91,11 +93,11 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do
{:ok, _} =
Import.insert_changes_list(
repo,
stakes_ratio(changes_list),
conflict_target: {:unsafe_fragment, "(address_hash) where \"primary\" = true"},
changes_list,
conflict_target: :staking_address_hash,
on_conflict: on_conflict,
for: Address.Name,
returning: [:address_hash],
for: StakingPool,
returning: [:staking_address_hash],
timeout: timeout,
timestamps: timestamps
)
@ -103,31 +105,58 @@ defmodule Explorer.Chain.Import.Runner.StakingPools do
defp default_on_conflict do
from(
name in Address.Name,
pool in StakingPool,
update: [
set: [
name: fragment("EXCLUDED.name"),
metadata: fragment("EXCLUDED.metadata"),
inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", name.inserted_at),
updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", name.updated_at)
mining_address_hash: fragment("EXCLUDED.mining_address_hash"),
delegators_count: fragment("EXCLUDED.delegators_count"),
is_active: fragment("EXCLUDED.is_active"),
is_banned: fragment("EXCLUDED.is_banned"),
is_validator: fragment("EXCLUDED.is_validator"),
likelihood: fragment("EXCLUDED.likelihood"),
staked_ratio: fragment("EXCLUDED.staked_ratio"),
self_staked_amount: fragment("EXCLUDED.self_staked_amount"),
staked_amount: fragment("EXCLUDED.staked_amount"),
was_banned_count: fragment("EXCLUDED.was_banned_count"),
was_validator_count: fragment("EXCLUDED.was_validator_count"),
is_deleted: fragment("EXCLUDED.is_deleted"),
inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", pool.inserted_at),
updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", pool.updated_at)
]
]
)
end
# Calculates staked ratio for each pool
defp stakes_ratio(pools) do
active_pools = Enum.filter(pools, & &1.metadata[:is_active])
stakes_total =
Enum.reduce(pools, 0, fn pool, acc ->
acc + pool.metadata[:staked_amount]
end)
defp calculate_stakes_ratio(repo, %{timeout: timeout}) do
total_query =
from(
pool in StakingPool,
where: pool.is_active == true,
select: sum(pool.staked_amount)
)
Enum.map(active_pools, fn pool ->
staked_ratio = if stakes_total > 0, do: pool.metadata[:staked_amount] / stakes_total, else: 0
total = repo.one!(total_query)
if total > Decimal.new(0) do
query =
from(
p in StakingPool,
where: p.is_active == true,
update: [
set: [
staked_ratio: p.staked_amount / ^total * 100,
likelihood: p.staked_amount / ^total * 100
]
]
)
put_in(pool, [:metadata, :staked_ratio], staked_ratio)
end)
{count, _} = repo.update_all(query, [], timeout: timeout)
{:ok, count}
else
{:ok, 1}
end
rescue
postgrex_error in Postgrex.Error ->
{:error, %{exception: postgrex_error}}
end
end

@ -0,0 +1,91 @@
defmodule Explorer.Chain.Import.Runner.StakingPoolsDelegators do
@moduledoc """
Bulk imports delegators to StakingPoolsDelegator tabe.
"""
require Ecto.Query
alias Ecto.{Changeset, Multi, Repo}
alias Explorer.Chain.{Import, StakingPoolsDelegator}
import Ecto.Query, only: [from: 2]
@behaviour Import.Runner
# milliseconds
@timeout 60_000
@type imported :: [StakingPoolsDelegator.t()]
@impl Import.Runner
def ecto_schema_module, do: StakingPoolsDelegator
@impl Import.Runner
def option_key, do: :staking_pools_delegators
@impl Import.Runner
def imported_table_row do
%{
value_type: "[#{ecto_schema_module()}.t()]",
value_description: "List of `t:#{ecto_schema_module()}.t/0`s"
}
end
@impl Import.Runner
def run(multi, changes_list, %{timestamps: timestamps} = options) do
insert_options =
options
|> Map.get(option_key(), %{})
|> Map.take(~w(on_conflict timeout)a)
|> Map.put_new(:timeout, @timeout)
|> Map.put(:timestamps, timestamps)
multi
|> Multi.run(:insert_staking_pools_delegators, fn repo, _ ->
insert(repo, changes_list, insert_options)
end)
end
@impl Import.Runner
def timeout, do: @timeout
@spec insert(Repo.t(), [map()], %{
optional(:on_conflict) => Import.Runner.on_conflict(),
required(:timeout) => timeout,
required(:timestamps) => Import.timestamps()
}) ::
{:ok, [StakingPoolsDelegator.t()]}
| {:error, [Changeset.t()]}
defp insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = options) when is_list(changes_list) do
on_conflict = Map.get_lazy(options, :on_conflict, &default_on_conflict/0)
{:ok, _} =
Import.insert_changes_list(
repo,
changes_list,
conflict_target: [:pool_address_hash, :delegator_address_hash],
on_conflict: on_conflict,
for: StakingPoolsDelegator,
returning: [:pool_address_hash, :delegator_address_hash],
timeout: timeout,
timestamps: timestamps
)
end
defp default_on_conflict do
from(
delegator in StakingPoolsDelegator,
update: [
set: [
stake_amount: fragment("EXCLUDED.stake_amount"),
ordered_withdraw: fragment("EXCLUDED.ordered_withdraw"),
max_withdraw_allowed: fragment("EXCLUDED.max_withdraw_allowed"),
max_ordered_withdraw_allowed: fragment("EXCLUDED.max_ordered_withdraw_allowed"),
ordered_withdraw_epoch: fragment("EXCLUDED.ordered_withdraw_epoch"),
inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", delegator.inserted_at),
updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", delegator.updated_at)
]
]
)
end
end

@ -25,7 +25,8 @@ defmodule Explorer.Chain.Import.Stage.AddressReferencing do
Runner.TokenTransfers,
Runner.Address.CurrentTokenBalances,
Runner.Address.TokenBalances,
Runner.StakingPools
Runner.StakingPools,
Runner.StakingPoolsDelegators
]
@impl Stage

@ -0,0 +1,97 @@
defmodule Explorer.Chain.StakingPool do
@moduledoc """
The representation of staking pool from POSDAO network.
Staking pools might be candidate or validator.
"""
use Ecto.Schema
import Ecto.Changeset
alias Explorer.Chain.{
Address,
Hash,
StakingPoolsDelegator,
Wei
}
@type t :: %__MODULE__{
staking_address_hash: Hash.Address.t(),
mining_address_hash: Hash.Address.t(),
banned_until: boolean,
delegators_count: integer,
is_active: boolean,
is_banned: boolean,
is_validator: boolean,
likelihood: integer,
staked_ratio: Decimal.t(),
self_staked_amount: Wei.t(),
staked_amount: Wei.t(),
was_banned_count: integer,
was_validator_count: integer,
is_deleted: boolean
}
@attrs ~w(
is_active delegators_count staked_amount self_staked_amount is_validator
was_validator_count is_banned was_banned_count banned_until likelihood
staked_ratio staking_address_hash mining_address_hash
)a
@req_attrs ~w(
is_active delegators_count staked_amount self_staked_amount is_validator
was_validator_count is_banned was_banned_count banned_until
staking_address_hash mining_address_hash
)a
schema "staking_pools" do
field(:banned_until, :integer)
field(:delegators_count, :integer)
field(:is_active, :boolean, default: false)
field(:is_banned, :boolean, default: false)
field(:is_validator, :boolean, default: false)
field(:likelihood, :decimal)
field(:staked_ratio, :decimal)
field(:self_staked_amount, Wei)
field(:staked_amount, Wei)
field(:was_banned_count, :integer)
field(:was_validator_count, :integer)
field(:is_deleted, :boolean, default: false)
has_many(:delegators, StakingPoolsDelegator, foreign_key: :pool_address_hash)
belongs_to(
:staking_address,
Address,
foreign_key: :staking_address_hash,
references: :hash,
type: Hash.Address
)
belongs_to(
:mining_address,
Address,
foreign_key: :mining_address_hash,
references: :hash,
type: Hash.Address
)
timestamps(null: false, type: :utc_datetime_usec)
end
@doc false
def changeset(staking_pool, attrs) do
staking_pool
|> cast(attrs, @attrs)
|> cast_assoc(:delegators)
|> validate_required(@req_attrs)
|> validate_staked_amount()
|> unique_constraint(:staking_address_hash)
end
defp validate_staked_amount(%{valid?: false} = c), do: c
defp validate_staked_amount(changeset) do
if get_field(changeset, :staked_amount) < get_field(changeset, :self_staked_amount) do
add_error(changeset, :staked_amount, "must be greater than self_staked_amount")
else
changeset
end
end
end

@ -0,0 +1,64 @@
defmodule Explorer.Chain.StakingPoolsDelegator do
@moduledoc """
The representation of delegators from POSDAO network.
Delegators make stakes on staking pools and withdraw from them.
"""
use Ecto.Schema
import Ecto.Changeset
alias Explorer.Chain.{
Address,
Hash,
StakingPool,
Wei
}
@type t :: %__MODULE__{
pool_address_hash: Hash.Address.t(),
delegator_address_hash: Hash.Address.t(),
max_ordered_withdraw_allowed: Wei.t(),
max_withdraw_allowed: Wei.t(),
ordered_withdraw: Wei.t(),
stake_amount: Wei.t(),
ordered_withdraw_epoch: integer()
}
@attrs ~w(
pool_address_hash delegator_address_hash max_ordered_withdraw_allowed
max_withdraw_allowed ordered_withdraw stake_amount ordered_withdraw_epoch
)a
schema "staking_pools_delegators" do
field(:max_ordered_withdraw_allowed, Wei)
field(:max_withdraw_allowed, Wei)
field(:ordered_withdraw, Wei)
field(:ordered_withdraw_epoch, :integer)
field(:stake_amount, Wei)
belongs_to(
:staking_pool,
StakingPool,
foreign_key: :pool_address_hash,
references: :staking_address_hash,
type: Hash.Address
)
belongs_to(
:delegator_address,
Address,
foreign_key: :delegator_address_hash,
references: :hash,
type: Hash.Address
)
timestamps(null: false, type: :utc_datetime_usec)
end
@doc false
def changeset(staking_pools_delegator, attrs) do
staking_pools_delegator
|> cast(attrs, @attrs)
|> validate_required(@attrs)
|> unique_constraint(:pool_address_hash, name: :pools_delegator_index)
end
end

@ -24,10 +24,11 @@ defmodule Explorer.Staking.PoolsReader do
@spec pool_data(String.t()) :: {:ok, map()} | :error
def pool_data(staking_address) do
with {:ok, [mining_address]} <- call_validators_method("miningByStakingAddress", [staking_address]),
data = fetch_data(staking_address, mining_address),
data = fetch_pool_data(staking_address, mining_address),
{:ok, [is_active]} <- data["isPoolActive"],
{:ok, [delegator_addresses]} <- data["poolDelegators"],
delegators_count = Enum.count(delegator_addresses),
delegators = delegators_data(delegator_addresses, staking_address),
{:ok, [staked_amount]} <- data["stakeAmountTotalMinusOrderedWithdraw"],
{:ok, [self_staked_amount]} <- data["stakeAmountMinusOrderedWithdraw"],
{:ok, [is_validator]} <- data["isValidator"],
@ -38,8 +39,8 @@ defmodule Explorer.Staking.PoolsReader do
{
:ok,
%{
staking_address: staking_address,
mining_address: mining_address,
staking_address_hash: staking_address,
mining_address_hash: mining_address,
is_active: is_active,
delegators_count: delegators_count,
staked_amount: staked_amount,
@ -48,7 +49,8 @@ defmodule Explorer.Staking.PoolsReader do
was_validator_count: was_validator_count,
is_banned: is_banned,
banned_until: banned_until,
was_banned_count: was_banned_count
was_banned_count: was_banned_count,
delegators: delegators
}
}
else
@ -57,6 +59,35 @@ defmodule Explorer.Staking.PoolsReader do
end
end
defp delegators_data(delegators, pool_address) do
Enum.map(delegators, fn address ->
data =
call_methods([
{:staking, "stakeAmount", [pool_address, address]},
{:staking, "orderedWithdrawAmount", [pool_address, address]},
{:staking, "maxWithdrawAllowed", [pool_address, address]},
{:staking, "maxWithdrawOrderAllowed", [pool_address, address]},
{:staking, "orderWithdrawEpoch", [pool_address, address]}
])
{:ok, [stake_amount]} = data["stakeAmount"]
{:ok, [ordered_withdraw]} = data["orderedWithdrawAmount"]
{:ok, [max_withdraw_allowed]} = data["maxWithdrawAllowed"]
{:ok, [max_ordered_withdraw_allowed]} = data["maxWithdrawOrderAllowed"]
{:ok, [ordered_withdraw_epoch]} = data["orderWithdrawEpoch"]
%{
delegator_address_hash: address,
pool_address_hash: pool_address,
stake_amount: stake_amount,
ordered_withdraw: ordered_withdraw,
max_withdraw_allowed: max_withdraw_allowed,
max_ordered_withdraw_allowed: max_ordered_withdraw_allowed,
ordered_withdraw_epoch: ordered_withdraw_epoch
}
end)
end
defp call_staking_method(method, params) do
%{^method => resp} =
Reader.query_contract(config(:staking_contract_address), abi("staking.json"), %{
@ -75,10 +106,8 @@ defmodule Explorer.Staking.PoolsReader do
resp
end
defp fetch_data(staking_address, mining_address) do
contract_abi = abi("staking.json") ++ abi("validators.json")
methods = [
defp fetch_pool_data(staking_address, mining_address) do
call_methods([
{:staking, "isPoolActive", [staking_address]},
{:staking, "poolDelegators", [staking_address]},
{:staking, "stakeAmountTotalMinusOrderedWithdraw", [staking_address]},
@ -88,7 +117,11 @@ defmodule Explorer.Staking.PoolsReader do
{:validators, "isValidatorBanned", [mining_address]},
{:validators, "bannedUntil", [mining_address]},
{:validators, "banCounter", [mining_address]}
]
])
end
defp call_methods(methods) do
contract_abi = abi("staking.json") ++ abi("validators.json")
methods
|> Enum.map(&format_request/1)

@ -0,0 +1,27 @@
defmodule Explorer.Repo.Migrations.CreateStakingPools do
use Ecto.Migration
def change do
create table(:staking_pools) do
add(:is_active, :boolean, default: false, null: false)
add(:is_deleted, :boolean, default: false, null: false)
add(:delegators_count, :integer)
add(:staked_amount, :numeric, precision: 100)
add(:self_staked_amount, :numeric, precision: 100)
add(:is_validator, :boolean, default: false, null: false)
add(:was_validator_count, :integer)
add(:is_banned, :boolean, default: false, null: false)
add(:was_banned_count, :integer)
add(:banned_until, :bigint)
add(:likelihood, :decimal, precision: 5, scale: 2)
add(:staked_ratio, :decimal, precision: 5, scale: 2)
add(:staking_address_hash, :bytea)
add(:mining_address_hash, :bytea)
timestamps(null: false, type: :utc_datetime_usec)
end
create(index(:staking_pools, [:staking_address_hash], unique: true))
create(index(:staking_pools, [:mining_address_hash]))
end
end

@ -0,0 +1,26 @@
defmodule Explorer.Repo.Migrations.CreateStakingPoolsDelegator do
use Ecto.Migration
def change do
create table(:staking_pools_delegators) do
add(:delegator_address_hash, :bytea)
add(:pool_address_hash, :bytea)
add(:stake_amount, :numeric, precision: 100)
add(:ordered_withdraw, :numeric, precision: 100)
add(:max_withdraw_allowed, :numeric, precision: 100)
add(:max_ordered_withdraw_allowed, :numeric, precision: 100)
add(:ordered_withdraw_epoch, :integer)
timestamps(null: false, type: :utc_datetime_usec)
end
create(index(:staking_pools_delegators, [:delegator_address_hash]))
create(
index(:staking_pools_delegators, [:delegator_address_hash, :pool_address_hash],
unique: true,
name: :pools_delegator_index
)
)
end
end

@ -0,0 +1,32 @@
defmodule Explorer.Chain.Import.Runner.StakingPoolsDelegatorsTest do
use Explorer.DataCase
import Explorer.Factory
alias Ecto.Multi
alias Explorer.Chain.Import.Runner.StakingPoolsDelegators
alias Explorer.Chain.StakingPoolsDelegator
describe "run/1" do
test "insert new pools list" do
delegators =
[params_for(:staking_pools_delegator), params_for(:staking_pools_delegator)]
|> Enum.map(fn param ->
changeset = StakingPoolsDelegator.changeset(%StakingPoolsDelegator{}, param)
changeset.changes
end)
assert {:ok, %{insert_staking_pools_delegators: list}} = run_changes(delegators)
assert Enum.count(list) == Enum.count(delegators)
end
end
defp run_changes(changes) do
Multi.new()
|> StakingPoolsDelegators.run(changes, %{
timeout: :infinity,
timestamps: %{inserted_at: DateTime.utc_now(), updated_at: DateTime.utc_now()}
})
|> Repo.transaction()
end
end

@ -5,25 +5,30 @@ defmodule Explorer.Chain.Import.Runner.StakingPoolsTest do
alias Ecto.Multi
alias Explorer.Chain.Import.Runner.StakingPools
alias Explorer.Chain.StakingPool
describe "run/1" do
test "insert new pools list" do
pools = [pool1, pool2, pool3, pool4] = build_list(4, :staking_pool)
pools =
[pool1, pool2] =
[params_for(:staking_pool), params_for(:staking_pool)]
|> Enum.map(fn param ->
changeset = StakingPool.changeset(%StakingPool{}, param)
changeset.changes
end)
assert {:ok, %{insert_staking_pools: list}} = run_changes(pools)
assert Enum.count(list) == Enum.count(pools)
saved_list =
Explorer.Chain.Address.Name
Explorer.Chain.StakingPool
|> Repo.all()
|> Enum.reduce(%{}, fn pool, acc ->
Map.put(acc, pool.address_hash, pool)
Map.put(acc, pool.staking_address_hash, pool)
end)
assert saved_list[pool1.address_hash].metadata["staked_ratio"] == 0.25
assert saved_list[pool2.address_hash].metadata["staked_ratio"] == 0.25
assert saved_list[pool3.address_hash].metadata["staked_ratio"] == 0.25
assert saved_list[pool4.address_hash].metadata["staked_ratio"] == 0.25
assert saved_list[pool1.staking_address_hash].staked_ratio == Decimal.new("50.00")
assert saved_list[pool2.staking_address_hash].staked_ratio == Decimal.new("50.00")
end
end

@ -0,0 +1,18 @@
defmodule Explorer.Chain.StakingPoolTest do
use Explorer.DataCase
alias Explorer.Chain.StakingPool
describe "changeset/2" do
test "with valid attributes" do
params = params_for(:staking_pool)
changeset = StakingPool.changeset(%StakingPool{}, params)
assert changeset.valid?
end
test "with invalid attributes" do
changeset = StakingPool.changeset(%StakingPool{}, %{staking_address_hash: 0})
refute changeset.valid?
end
end
end

@ -0,0 +1,18 @@
defmodule Explorer.Chain.StakingPoolsDelegatorTest do
use Explorer.DataCase
alias Explorer.Chain.StakingPoolsDelegator
describe "changeset/2" do
test "with valid attributes" do
params = params_for(:staking_pools_delegator)
changeset = StakingPoolsDelegator.changeset(%StakingPoolsDelegator{}, params)
assert changeset.valid?
end
test "with invalid attributes" do
changeset = StakingPoolsDelegator.changeset(%StakingPoolsDelegator{}, %{pool_address_hash: 0})
refute changeset.valid?
end
end
end

@ -3957,54 +3957,54 @@ defmodule Explorer.ChainTest do
describe "staking_pools/3" do
test "validators staking pools" do
inserted_validator = insert(:address_name, primary: true, metadata: %{is_active: true, is_validator: true})
insert(:address_name, primary: true, metadata: %{is_active: true, is_validator: false})
inserted_validator = insert(:staking_pool, is_active: true, is_validator: true)
insert(:staking_pool, is_active: true, is_validator: false)
options = %PagingOptions{page_size: 20, page_number: 1}
assert [gotten_validator] = Chain.staking_pools(:validator, options)
assert inserted_validator.address_hash == gotten_validator.address_hash
assert inserted_validator.staking_address_hash == gotten_validator.staking_address_hash
end
test "active staking pools" do
inserted_validator = insert(:address_name, primary: true, metadata: %{is_active: true})
insert(:address_name, primary: true, metadata: %{is_active: false})
inserted_pool = insert(:staking_pool, is_active: true)
insert(:staking_pool, is_active: false)
options = %PagingOptions{page_size: 20, page_number: 1}
assert [gotten_validator] = Chain.staking_pools(:active, options)
assert inserted_validator.address_hash == gotten_validator.address_hash
assert [gotten_pool] = Chain.staking_pools(:active, options)
assert inserted_pool.staking_address_hash == gotten_pool.staking_address_hash
end
test "inactive staking pools" do
insert(:address_name, primary: true, metadata: %{is_active: true})
inserted_validator = insert(:address_name, primary: true, metadata: %{is_active: false})
insert(:staking_pool, is_active: true)
inserted_pool = insert(:staking_pool, is_active: false)
options = %PagingOptions{page_size: 20, page_number: 1}
assert [gotten_validator] = Chain.staking_pools(:inactive, options)
assert inserted_validator.address_hash == gotten_validator.address_hash
assert [gotten_pool] = Chain.staking_pools(:inactive, options)
assert inserted_pool.staking_address_hash == gotten_pool.staking_address_hash
end
end
describe "staking_pools_count/1" do
test "validators staking pools" do
insert(:address_name, primary: true, metadata: %{is_active: true, is_validator: true})
insert(:address_name, primary: true, metadata: %{is_active: true, is_validator: false})
insert(:staking_pool, is_active: true, is_validator: true)
insert(:staking_pool, is_active: true, is_validator: false)
assert Chain.staking_pools_count(:validator) == 1
end
test "active staking pools" do
insert(:address_name, primary: true, metadata: %{is_active: true})
insert(:address_name, primary: true, metadata: %{is_active: false})
insert(:staking_pool, is_active: true)
insert(:staking_pool, is_active: false)
assert Chain.staking_pools_count(:active) == 1
end
test "inactive staking pools" do
insert(:address_name, primary: true, metadata: %{is_active: true})
insert(:address_name, primary: true, metadata: %{is_active: false})
insert(:staking_pool, is_active: true)
insert(:staking_pool, is_active: false)
assert Chain.staking_pools_count(:inactive) == 1
end

@ -30,25 +30,38 @@ defmodule Explorer.Token.PoolsReaderTest do
test "get_pool_data success" do
get_pool_data_from_blockchain()
address = <<11, 47, 94, 47, 60, 189, 134, 78, 170, 44, 100, 46, 55, 105, 193, 88, 35, 97, 202, 246>>
address = <<219, 156, 178, 71, 141, 145, 119, 25, 197, 56, 98, 0, 134, 114, 22, 104, 8, 37, 133, 119>>
response = {
:ok,
%{
banned_until: 0,
delegators_count: 0,
is_active: true,
is_banned: false,
is_validator: true,
mining_address:
<<187, 202, 168, 212, 130, 137, 187, 31, 252, 249, 128, 141, 154, 164, 177, 210, 21, 5, 76, 120>>,
staked_amount: 0,
self_staked_amount: 0,
staking_address: <<11, 47, 94, 47, 60, 189, 134, 78, 170, 44, 100, 46, 55, 105, 193, 88, 35, 97, 202, 246>>,
was_banned_count: 0,
was_validator_count: 2
}
}
response =
{:ok,
%{
banned_until: 0,
is_active: true,
is_banned: false,
is_validator: true,
was_banned_count: 0,
was_validator_count: 2,
delegators: [
%{
delegator_address_hash:
<<243, 231, 124, 74, 245, 235, 47, 51, 175, 255, 118, 25, 216, 209, 231, 81, 215, 24, 164, 145>>,
max_ordered_withdraw_allowed: 1_000_000_000_000_000_000,
max_withdraw_allowed: 1_000_000_000_000_000_000,
ordered_withdraw: 0,
ordered_withdraw_epoch: 0,
pool_address_hash:
<<219, 156, 178, 71, 141, 145, 119, 25, 197, 56, 98, 0, 134, 114, 22, 104, 8, 37, 133, 119>>,
stake_amount: 1_000_000_000_000_000_000
}
],
delegators_count: 1,
mining_address_hash:
<<190, 105, 235, 9, 104, 34, 106, 24, 8, 151, 94, 26, 31, 33, 39, 102, 127, 43, 255, 179>>,
self_staked_amount: 2_000_000_000_000_000_000,
staked_amount: 3_000_000_000_000_000_000,
staking_address_hash:
<<219, 156, 178, 71, 141, 145, 119, 25, 197, 56, 98, 0, 134, 114, 22, 104, 8, 37, 133, 119>>
}}
assert PoolsReader.pool_data(address) == response
end
@ -101,7 +114,7 @@ defmodule Explorer.Token.PoolsReaderTest do
expect(
EthereumJSONRPC.Mox,
:json_rpc,
2,
3,
fn requests, _opts ->
{:ok,
Enum.map(requests, fn
@ -110,13 +123,13 @@ defmodule Explorer.Token.PoolsReaderTest do
id: id,
method: "eth_call",
params: [
%{data: "0x005351750000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _},
%{data: "0x00535175000000000000000000000000db9cb2478d917719c53862008672166808258577", to: _},
"latest"
]
} ->
%{
id: id,
result: "0x000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78"
result: "0x000000000000000000000000be69eb0968226a1808975e1a1f2127667f2bffb3"
}
# isPoolActive
@ -124,7 +137,7 @@ defmodule Explorer.Token.PoolsReaderTest do
id: id,
method: "eth_call",
params: [
%{data: "0xa711e6a10000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _},
%{data: "0xa711e6a1000000000000000000000000db9cb2478d917719c53862008672166808258577", to: _},
"latest"
]
} ->
@ -138,14 +151,14 @@ defmodule Explorer.Token.PoolsReaderTest do
id: id,
method: "eth_call",
params: [
%{data: "0x9ea8082b0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _},
%{data: "0x9ea8082b000000000000000000000000db9cb2478d917719c53862008672166808258577", to: _},
"latest"
]
} ->
%{
id: id,
result:
"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000"
"0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f3e77c4af5eb2f33afff7619d8d1e751d718a491"
}
# stakeAmountTotalMinusOrderedWithdraw
@ -153,13 +166,13 @@ defmodule Explorer.Token.PoolsReaderTest do
id: id,
method: "eth_call",
params: [
%{data: "0x234fbf2b0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6", to: _},
%{data: "0x234fbf2b000000000000000000000000db9cb2478d917719c53862008672166808258577", to: _},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000000000000000000"
result: "0x00000000000000000000000000000000000000000000000029a2241af62c0000"
}
# stakeAmountMinusOrderedWithdraw
@ -170,7 +183,7 @@ defmodule Explorer.Token.PoolsReaderTest do
params: [
%{
data:
"0x58daab6a0000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf60000000000000000000000000b2f5e2f3cbd864eaa2c642e3769c1582361caf6",
"0x58daab6a000000000000000000000000db9cb2478d917719c53862008672166808258577000000000000000000000000db9cb2478d917719c53862008672166808258577",
to: _
},
"latest"
@ -178,7 +191,7 @@ defmodule Explorer.Token.PoolsReaderTest do
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000000000000000000"
result: "0x0000000000000000000000000000000000000000000000001bc16d674ec80000"
}
# isValidator
@ -186,7 +199,7 @@ defmodule Explorer.Token.PoolsReaderTest do
id: id,
method: "eth_call",
params: [
%{data: "0xfacd743b000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _},
%{data: "0xfacd743b000000000000000000000000be69eb0968226a1808975e1a1f2127667f2bffb3", to: _},
"latest"
]
} ->
@ -200,7 +213,7 @@ defmodule Explorer.Token.PoolsReaderTest do
id: id,
method: "eth_call",
params: [
%{data: "0xb41832e4000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _},
%{data: "0xb41832e4000000000000000000000000be69eb0968226a1808975e1a1f2127667f2bffb3", to: _},
"latest"
]
} ->
@ -214,7 +227,7 @@ defmodule Explorer.Token.PoolsReaderTest do
id: id,
method: "eth_call",
params: [
%{data: "0xa92252ae000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _},
%{data: "0xa92252ae000000000000000000000000be69eb0968226a1808975e1a1f2127667f2bffb3", to: _},
"latest"
]
} ->
@ -228,7 +241,7 @@ defmodule Explorer.Token.PoolsReaderTest do
id: id,
method: "eth_call",
params: [
%{data: "0x5836d08a000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _},
%{data: "0x5836d08a000000000000000000000000be69eb0968226a1808975e1a1f2127667f2bffb3", to: _},
"latest"
]
} ->
@ -242,7 +255,98 @@ defmodule Explorer.Token.PoolsReaderTest do
id: id,
method: "eth_call",
params: [
%{data: "0x1d0cd4c6000000000000000000000000bbcaa8d48289bb1ffcf9808d9aa4b1d215054c78", to: _},
%{data: "0x1d0cd4c6000000000000000000000000be69eb0968226a1808975e1a1f2127667f2bffb3", to: _},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000000000000000000"
}
# DELEGATOR
# stakeAmount
%{
id: id,
method: "eth_call",
params: [
%{
data:
"0xa697ecff000000000000000000000000db9cb2478d917719c53862008672166808258577000000000000000000000000f3e77c4af5eb2f33afff7619d8d1e751d718a491",
to: _
},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000"
}
# orderedWithdrawAmount
%{
id: id,
method: "eth_call",
params: [
%{
data:
"0xe9ab0300000000000000000000000000db9cb2478d917719c53862008672166808258577000000000000000000000000f3e77c4af5eb2f33afff7619d8d1e751d718a491",
to: _
},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000000000000000000"
}
# maxWithdrawAllowed
%{
id: id,
method: "eth_call",
params: [
%{
data:
"0x6bda1577000000000000000000000000db9cb2478d917719c53862008672166808258577000000000000000000000000f3e77c4af5eb2f33afff7619d8d1e751d718a491",
to: _
},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000"
}
# maxWithdrawOrderAllowed
%{
id: id,
method: "eth_call",
params: [
%{
data:
"0x950a6513000000000000000000000000db9cb2478d917719c53862008672166808258577000000000000000000000000f3e77c4af5eb2f33afff7619d8d1e751d718a491",
to: _
},
"latest"
]
} ->
%{
id: id,
result: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000"
}
# orderWithdrawEpoch
%{
id: id,
method: "eth_call",
params: [
%{
data:
"0xa4205967000000000000000000000000db9cb2478d917719c53862008672166808258577000000000000000000000000f3e77c4af5eb2f33afff7619d8d1e751d718a491",
to: _
},
"latest"
]
} ->

@ -26,7 +26,9 @@ defmodule Explorer.Factory do
SmartContract,
Token,
TokenTransfer,
Transaction
Transaction,
StakingPool,
StakingPoolsDelegator
}
alias Explorer.Market.MarketHistory
@ -612,22 +614,34 @@ defmodule Explorer.Factory do
end
def staking_pool_factory do
%{
address_hash: address_hash(),
metadata: %{
banned_unitil: 0,
delegators_count: 0,
is_active: true,
is_banned: false,
is_validator: true,
mining_address: address_hash(),
retries_count: 1,
staked_amount: 25,
was_banned_count: 0,
was_validator_count: 1
},
name: "anonymous",
primary: true
wei_per_ether = 1_000_000_000_000_000_000
%StakingPool{
staking_address_hash: address_hash(),
mining_address_hash: address_hash(),
banned_until: 0,
delegators_count: 0,
is_active: true,
is_banned: false,
is_validator: true,
staked_amount: wei_per_ether * 500,
self_staked_amount: wei_per_ether * 300,
was_banned_count: 0,
was_validator_count: 1
}
end
def staking_pools_delegator_factory do
wei_per_ether = 1_000_000_000_000_000_000
%StakingPoolsDelegator{
pool_address_hash: address_hash(),
delegator_address_hash: address_hash(),
max_ordered_withdraw_allowed: wei_per_ether * 100,
max_withdraw_allowed: wei_per_ether * 50,
ordered_withdraw: wei_per_ether * 600,
stake_amount: wei_per_ether * 200,
ordered_withdraw_epoch: 2
}
end
end

@ -9,6 +9,7 @@ defmodule Indexer.Fetcher.StakingPools do
require Logger
alias Explorer.Chain
alias Explorer.Chain.StakingPool
alias Explorer.Staking.PoolsReader
alias Indexer.BufferedTask
alias Indexer.Fetcher.StakingPools.Supervisor, as: StakingPoolsSupervisor
@ -71,7 +72,7 @@ defmodule Indexer.Fetcher.StakingPools do
def entry(pool_address) do
%{
staking_address: pool_address,
staking_address_hash: pool_address,
retries_count: 0
}
end
@ -79,7 +80,7 @@ defmodule Indexer.Fetcher.StakingPools do
defp fetch_from_blockchain(addresses) do
addresses
|> Enum.filter(&(&1.retries_count <= @max_retries))
|> Enum.map(fn %{staking_address: staking_address} = pool ->
|> Enum.map(fn %{staking_address_hash: staking_address} = pool ->
case PoolsReader.pool_data(staking_address) do
{:ok, data} ->
Map.merge(pool, data)
@ -93,15 +94,22 @@ defmodule Indexer.Fetcher.StakingPools do
defp import_pools(pools) do
{failed, success} =
Enum.reduce(pools, {[], []}, fn
%{error: _error, staking_address: address}, {failed, success} ->
{[address | failed], success}
%{error: _error} = pool, {failed, success} ->
{[pool | failed], success}
pool, {failed, success} ->
{failed, [changeset(pool) | success]}
changeset = StakingPool.changeset(%StakingPool{}, pool)
if changeset.valid? do
{failed, [changeset.changes | success]}
else
{[pool | failed], success}
end
end)
import_params = %{
staking_pools: %{params: success},
staking_pools: %{params: remove_assoc(success)},
staking_pools_delegators: %{params: delegators_list(success)},
timeout: :infinity
}
@ -118,20 +126,15 @@ defmodule Indexer.Fetcher.StakingPools do
failed
end
defp changeset(%{staking_address: staking_address} = pool) do
{:ok, mining_address} = Chain.Hash.Address.cast(pool[:mining_address])
data =
pool
|> Map.delete(:staking_address)
|> Map.put(:mining_address, mining_address)
|> Map.put(:is_pool, true)
defp delegators_list(pools) do
Enum.reduce(pools, [], fn pool, acc ->
pool.delegators
|> Enum.map(&Map.get(&1, :changes))
|> Enum.concat(acc)
end)
end
%{
name: "anonymous",
primary: true,
address_hash: staking_address,
metadata: data
}
defp remove_assoc(pools) do
Enum.map(pools, &Map.delete(&1, :delegators))
end
end

@ -6,7 +6,7 @@ defmodule Indexer.Fetcher.StakingPoolsTest do
alias Indexer.Fetcher.StakingPools
alias Explorer.Staking.PoolsReader
alias Explorer.Chain.Address
alias Explorer.Chain.StakingPool
@moduletag :capture_log
@ -33,15 +33,15 @@ defmodule Indexer.Fetcher.StakingPoolsTest do
success_address =
list
|> List.first()
|> Map.get(:staking_address)
|> Map.get(:staking_address_hash)
get_pool_data_from_blockchain()
assert {:retry, retry_list} = StakingPools.run(list, nil)
assert Enum.count(retry_list) == 2
pool = Explorer.Repo.get_by(Address.Name, address_hash: success_address)
assert pool.name == "anonymous"
pool = Explorer.Repo.get_by(StakingPool, staking_address_hash: success_address)
assert pool.is_active == true
end
end

Loading…
Cancel
Save