feat: Add Blackfort validators (#10744)
* feat: Add Blackfort validators * Finishing touches * Add missing specs, docs, pre-release workflow for Blackfort * Remove blocks_validated --------- Co-authored-by: Victor Baranov <baranov.viktor.27@gmail.com>pull/10739/head
parent
f7a434df17
commit
629620bc52
@ -0,0 +1,97 @@ |
|||||||
|
name: Pre-release for Blackfort |
||||||
|
|
||||||
|
on: |
||||||
|
workflow_dispatch: |
||||||
|
inputs: |
||||||
|
number: |
||||||
|
type: number |
||||||
|
required: true |
||||||
|
|
||||||
|
env: |
||||||
|
OTP_VERSION: ${{ vars.OTP_VERSION }} |
||||||
|
ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} |
||||||
|
|
||||||
|
jobs: |
||||||
|
push_to_registry: |
||||||
|
name: Push Docker image to Docker Hub |
||||||
|
runs-on: ubuntu-latest |
||||||
|
env: |
||||||
|
RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} |
||||||
|
steps: |
||||||
|
- uses: actions/checkout@v4 |
||||||
|
- name: Setup repo |
||||||
|
uses: ./.github/actions/setup-repo |
||||||
|
id: setup |
||||||
|
with: |
||||||
|
docker-username: ${{ secrets.DOCKER_USERNAME }} |
||||||
|
docker-password: ${{ secrets.DOCKER_PASSWORD }} |
||||||
|
docker-remote-multi-platform: true |
||||||
|
docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} |
||||||
|
docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} |
||||||
|
|
||||||
|
- name: Build and push Docker image for Blackfort (indexer + API) |
||||||
|
uses: docker/build-push-action@v5 |
||||||
|
with: |
||||||
|
context: . |
||||||
|
file: ./docker/Dockerfile |
||||||
|
push: true |
||||||
|
tags: blockscout/blockscout-blackfort:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} |
||||||
|
labels: ${{ steps.setup.outputs.docker-labels }} |
||||||
|
platforms: | |
||||||
|
linux/amd64 |
||||||
|
linux/arm64/v8 |
||||||
|
build-args: | |
||||||
|
DISABLE_WEBAPP=false |
||||||
|
API_V1_READ_METHODS_DISABLED=false |
||||||
|
API_V1_WRITE_METHODS_DISABLED=false |
||||||
|
CACHE_EXCHANGE_RATES_PERIOD= |
||||||
|
CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= |
||||||
|
CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= |
||||||
|
ADMIN_PANEL_ENABLED=false |
||||||
|
BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} |
||||||
|
RELEASE_VERSION=${{ env.RELEASE_VERSION }} |
||||||
|
CHAIN_TYPE=blackfort |
||||||
|
|
||||||
|
- name: Build and push Docker image for Blackfort (indexer) |
||||||
|
uses: docker/build-push-action@v5 |
||||||
|
with: |
||||||
|
context: . |
||||||
|
file: ./docker/Dockerfile |
||||||
|
push: true |
||||||
|
tags: blockscout/blockscout-blackfort:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-indexer |
||||||
|
labels: ${{ steps.setup.outputs.docker-labels }} |
||||||
|
platforms: | |
||||||
|
linux/amd64 |
||||||
|
linux/arm64/v8 |
||||||
|
build-args: | |
||||||
|
DISABLE_API=true |
||||||
|
DISABLE_WEBAPP=true |
||||||
|
CACHE_EXCHANGE_RATES_PERIOD= |
||||||
|
CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= |
||||||
|
CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= |
||||||
|
ADMIN_PANEL_ENABLED=false |
||||||
|
BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} |
||||||
|
RELEASE_VERSION=${{ env.RELEASE_VERSION }} |
||||||
|
CHAIN_TYPE=blackfort |
||||||
|
|
||||||
|
- name: Build and push Docker image for Blackfort (API) |
||||||
|
uses: docker/build-push-action@v5 |
||||||
|
with: |
||||||
|
context: . |
||||||
|
file: ./docker/Dockerfile |
||||||
|
push: true |
||||||
|
tags: blockscout/blockscout-blackfort:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-api |
||||||
|
labels: ${{ steps.setup.outputs.docker-labels }} |
||||||
|
platforms: | |
||||||
|
linux/amd64 |
||||||
|
linux/arm64/v8 |
||||||
|
build-args: | |
||||||
|
DISABLE_INDEXER=true |
||||||
|
DISABLE_WEBAPP=true |
||||||
|
CACHE_EXCHANGE_RATES_PERIOD= |
||||||
|
CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= |
||||||
|
CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= |
||||||
|
ADMIN_PANEL_ENABLED=false |
||||||
|
BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} |
||||||
|
RELEASE_VERSION=${{ env.RELEASE_VERSION }} |
||||||
|
CHAIN_TYPE=blackfort |
@ -0,0 +1,51 @@ |
|||||||
|
name: Blackfort Publish Docker image |
||||||
|
|
||||||
|
on: |
||||||
|
workflow_dispatch: |
||||||
|
push: |
||||||
|
branches: |
||||||
|
- production-blackfort |
||||||
|
env: |
||||||
|
OTP_VERSION: ${{ vars.OTP_VERSION }} |
||||||
|
ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} |
||||||
|
jobs: |
||||||
|
push_to_registry: |
||||||
|
name: Push Docker image to Docker Hub |
||||||
|
runs-on: ubuntu-latest |
||||||
|
env: |
||||||
|
RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} |
||||||
|
DOCKER_CHAIN_NAME: blackfort |
||||||
|
steps: |
||||||
|
- uses: actions/checkout@v4 |
||||||
|
- name: Setup repo |
||||||
|
uses: ./.github/actions/setup-repo |
||||||
|
id: setup |
||||||
|
with: |
||||||
|
docker-username: ${{ secrets.DOCKER_USERNAME }} |
||||||
|
docker-password: ${{ secrets.DOCKER_PASSWORD }} |
||||||
|
docker-remote-multi-platform: true |
||||||
|
docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} |
||||||
|
docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} |
||||||
|
|
||||||
|
- name: Build and push Docker image |
||||||
|
uses: docker/build-push-action@v5 |
||||||
|
with: |
||||||
|
context: . |
||||||
|
file: ./docker/Dockerfile |
||||||
|
push: true |
||||||
|
tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }} |
||||||
|
labels: ${{ steps.setup.outputs.docker-labels }} |
||||||
|
platforms: | |
||||||
|
linux/amd64 |
||||||
|
linux/arm64/v8 |
||||||
|
build-args: | |
||||||
|
CACHE_EXCHANGE_RATES_PERIOD= |
||||||
|
API_V1_READ_METHODS_DISABLED=false |
||||||
|
DISABLE_WEBAPP=false |
||||||
|
API_V1_WRITE_METHODS_DISABLED=false |
||||||
|
CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= |
||||||
|
ADMIN_PANEL_ENABLED=false |
||||||
|
CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= |
||||||
|
BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} |
||||||
|
RELEASE_VERSION=${{ env.RELEASE_VERSION }} |
||||||
|
CHAIN_TYPE=blackfort |
@ -0,0 +1,94 @@ |
|||||||
|
name: Release for Blackfort |
||||||
|
|
||||||
|
on: |
||||||
|
release: |
||||||
|
types: [published] |
||||||
|
|
||||||
|
env: |
||||||
|
OTP_VERSION: ${{ vars.OTP_VERSION }} |
||||||
|
ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} |
||||||
|
|
||||||
|
jobs: |
||||||
|
push_to_registry: |
||||||
|
name: Push Docker image to Docker Hub |
||||||
|
runs-on: ubuntu-latest |
||||||
|
env: |
||||||
|
RELEASE_VERSION: ${{ vars.RELEASE_VERSION }} |
||||||
|
steps: |
||||||
|
- uses: actions/checkout@v4 |
||||||
|
- name: Setup repo |
||||||
|
uses: ./.github/actions/setup-repo |
||||||
|
id: setup |
||||||
|
with: |
||||||
|
docker-username: ${{ secrets.DOCKER_USERNAME }} |
||||||
|
docker-password: ${{ secrets.DOCKER_PASSWORD }} |
||||||
|
docker-remote-multi-platform: true |
||||||
|
docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} |
||||||
|
docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} |
||||||
|
|
||||||
|
- name: Build and push Docker image for Blackfort (indexer + API) |
||||||
|
uses: docker/build-push-action@v5 |
||||||
|
with: |
||||||
|
context: . |
||||||
|
file: ./docker/Dockerfile |
||||||
|
push: true |
||||||
|
tags: blockscout/blockscout-blackfort:latest, blockscout/blockscout-blackfort:${{ env.RELEASE_VERSION }} |
||||||
|
labels: ${{ steps.setup.outputs.docker-labels }} |
||||||
|
platforms: | |
||||||
|
linux/amd64 |
||||||
|
linux/arm64/v8 |
||||||
|
build-args: | |
||||||
|
DISABLE_WEBAPP=false |
||||||
|
API_V1_READ_METHODS_DISABLED=false |
||||||
|
API_V1_WRITE_METHODS_DISABLED=false |
||||||
|
CACHE_EXCHANGE_RATES_PERIOD= |
||||||
|
CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= |
||||||
|
CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= |
||||||
|
ADMIN_PANEL_ENABLED=false |
||||||
|
BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta |
||||||
|
RELEASE_VERSION=${{ env.RELEASE_VERSION }} |
||||||
|
CHAIN_TYPE=blackfort |
||||||
|
|
||||||
|
- name: Build and push Docker image for Blackfort (indexer) |
||||||
|
uses: docker/build-push-action@v5 |
||||||
|
with: |
||||||
|
context: . |
||||||
|
file: ./docker/Dockerfile |
||||||
|
push: true |
||||||
|
tags: blockscout/blockscout-blackfort:${{ env.RELEASE_VERSION }}-indexer |
||||||
|
labels: ${{ steps.setup.outputs.docker-labels }} |
||||||
|
platforms: | |
||||||
|
linux/amd64 |
||||||
|
linux/arm64/v8 |
||||||
|
build-args: | |
||||||
|
DISABLE_API=true |
||||||
|
DISABLE_WEBAPP=true |
||||||
|
CACHE_EXCHANGE_RATES_PERIOD= |
||||||
|
CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= |
||||||
|
CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= |
||||||
|
ADMIN_PANEL_ENABLED=false |
||||||
|
BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta |
||||||
|
RELEASE_VERSION=${{ env.RELEASE_VERSION }} |
||||||
|
CHAIN_TYPE=blackfort |
||||||
|
|
||||||
|
- name: Build and push Docker image for Blackfort (API) |
||||||
|
uses: docker/build-push-action@v5 |
||||||
|
with: |
||||||
|
context: . |
||||||
|
file: ./docker/Dockerfile |
||||||
|
push: true |
||||||
|
tags: blockscout/blockscout-blackfort:${{ env.RELEASE_VERSION }}-api |
||||||
|
labels: ${{ steps.setup.outputs.docker-labels }} |
||||||
|
platforms: | |
||||||
|
linux/amd64 |
||||||
|
linux/arm64/v8 |
||||||
|
build-args: | |
||||||
|
DISABLE_INDEXER=true |
||||||
|
DISABLE_WEBAPP=true |
||||||
|
CACHE_EXCHANGE_RATES_PERIOD= |
||||||
|
CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= |
||||||
|
CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= |
||||||
|
ADMIN_PANEL_ENABLED=false |
||||||
|
BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta |
||||||
|
RELEASE_VERSION=${{ env.RELEASE_VERSION }} |
||||||
|
CHAIN_TYPE=blackfort |
@ -0,0 +1,201 @@ |
|||||||
|
defmodule Explorer.Chain.Blackfort.Validator do |
||||||
|
@moduledoc """ |
||||||
|
Blackfort validators |
||||||
|
""" |
||||||
|
|
||||||
|
use Explorer.Schema |
||||||
|
|
||||||
|
alias Explorer.Chain.{Address, Import} |
||||||
|
alias Explorer.Chain.Hash.Address, as: HashAddress |
||||||
|
alias Explorer.{Chain, Repo, SortingHelper} |
||||||
|
|
||||||
|
require Logger |
||||||
|
|
||||||
|
@default_sorting [ |
||||||
|
asc: :address_hash |
||||||
|
] |
||||||
|
|
||||||
|
@primary_key false |
||||||
|
typed_schema "validators_blackfort" do |
||||||
|
field(:address_hash, HashAddress, primary_key: true) |
||||||
|
field(:name, :binary) |
||||||
|
field(:commission, :integer) |
||||||
|
field(:self_bonded_amount, :decimal) |
||||||
|
field(:delegated_amount, :decimal) |
||||||
|
field(:slashing_status_is_slashed, :boolean, default: false) |
||||||
|
field(:slashing_status_by_block, :integer) |
||||||
|
field(:slashing_status_multiplier, :integer) |
||||||
|
|
||||||
|
has_one(:address, Address, foreign_key: :hash, references: :address_hash) |
||||||
|
timestamps() |
||||||
|
end |
||||||
|
|
||||||
|
@required_attrs ~w(address_hash)a |
||||||
|
@optional_attrs ~w(name commission self_bonded_amount delegated_amount)a |
||||||
|
def changeset(%__MODULE__{} = validator, attrs) do |
||||||
|
validator |
||||||
|
|> cast(attrs, @required_attrs ++ @optional_attrs) |
||||||
|
|> validate_required(@required_attrs) |
||||||
|
|> unique_constraint(:address_hash) |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Get validators list. |
||||||
|
Keyword could contain: |
||||||
|
- paging_options |
||||||
|
- necessity_by_association |
||||||
|
- sorting (supported by `Explorer.SortingHelper` module) |
||||||
|
- state (one of `@state_enum`) |
||||||
|
""" |
||||||
|
@spec get_paginated_validators(keyword()) :: [t()] |
||||||
|
def get_paginated_validators(options \\ []) do |
||||||
|
paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) |
||||||
|
necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) |
||||||
|
sorting = Keyword.get(options, :sorting, []) |
||||||
|
|
||||||
|
__MODULE__ |
||||||
|
|> Chain.join_associations(necessity_by_association) |
||||||
|
|> SortingHelper.apply_sorting(sorting, @default_sorting) |
||||||
|
|> SortingHelper.page_with_sorting(paging_options, sorting, @default_sorting) |
||||||
|
|> Chain.select_repo(options).all() |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Get all validators |
||||||
|
""" |
||||||
|
@spec get_all_validators(keyword()) :: [t()] |
||||||
|
def get_all_validators(options \\ []) do |
||||||
|
__MODULE__ |
||||||
|
|> Chain.select_repo(options).all() |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Delete validators by address hashes |
||||||
|
""" |
||||||
|
@spec delete_validators_by_address_hashes([binary() | HashAddress.t()]) :: {non_neg_integer(), nil | []} | :ignore |
||||||
|
def delete_validators_by_address_hashes(list) when is_list(list) and length(list) > 0 do |
||||||
|
__MODULE__ |
||||||
|
|> where([vs], vs.address_hash in ^list) |
||||||
|
|> Repo.delete_all() |
||||||
|
end |
||||||
|
|
||||||
|
def delete_validators_by_address_hashes(_), do: :ignore |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Insert validators |
||||||
|
""" |
||||||
|
@spec insert_validators([map()]) :: {non_neg_integer(), nil | []} |
||||||
|
def insert_validators(validators) do |
||||||
|
Repo.insert_all(__MODULE__, validators, |
||||||
|
on_conflict: {:replace_all_except, [:inserted_at]}, |
||||||
|
conflict_target: [:address_hash] |
||||||
|
) |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Append timestamps (:inserted_at, :updated_at) |
||||||
|
""" |
||||||
|
@spec append_timestamps(map()) :: map() |
||||||
|
def append_timestamps(validator) do |
||||||
|
Map.merge(validator, Import.timestamps()) |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Derive next page params from %Explorer.Chain.Blackfort.Validator{} |
||||||
|
""" |
||||||
|
@spec next_page_params(t()) :: map() |
||||||
|
def next_page_params(%__MODULE__{address_hash: address_hash}) do |
||||||
|
%{"address_hash" => address_hash} |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Returns dynamic query for validated blocks count. Needed for SortingHelper |
||||||
|
""" |
||||||
|
@spec dynamic_validated_blocks() :: Ecto.Query.dynamic_expr() |
||||||
|
def dynamic_validated_blocks do |
||||||
|
dynamic( |
||||||
|
[vs], |
||||||
|
fragment( |
||||||
|
"SELECT count(*) FROM blocks WHERE miner_hash = ?", |
||||||
|
vs.address_hash |
||||||
|
) |
||||||
|
) |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Returns total count of validators. |
||||||
|
""" |
||||||
|
@spec count_validators() :: integer() |
||||||
|
def count_validators do |
||||||
|
Repo.aggregate(__MODULE__, :count, :address_hash) |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Returns count of new validators (inserted withing last 24h). |
||||||
|
""" |
||||||
|
@spec count_new_validators() :: integer() |
||||||
|
def count_new_validators do |
||||||
|
__MODULE__ |
||||||
|
|> where([vs], vs.inserted_at >= ago(1, "day")) |
||||||
|
|> Repo.aggregate(:count, :address_hash) |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Fetch list of Blackfort validators |
||||||
|
""" |
||||||
|
@spec fetch_validators_list() :: {:ok, list()} | :error |
||||||
|
def fetch_validators_list do |
||||||
|
case HTTPoison.get(validator_url(), [], follow_redirect: true) do |
||||||
|
{:ok, %HTTPoison.Response{status_code: 200, body: body}} -> |
||||||
|
body |> Jason.decode() |> parse_validators_info() |
||||||
|
|
||||||
|
error -> |
||||||
|
Logger.error("Failed to fetch blackfort validator info: #{inspect(error)}") |
||||||
|
:error |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
defp parse_validators_info({:ok, validators}) do |
||||||
|
{:ok, |
||||||
|
validators |
||||||
|
|> Enum.map(fn %{ |
||||||
|
"address" => address_hash_string, |
||||||
|
"name" => name, |
||||||
|
"commission" => commission, |
||||||
|
"self_bonded_amount" => self_bonded_amount, |
||||||
|
"delegated_amount" => delegated_amount, |
||||||
|
"slashing_status" => %{ |
||||||
|
"is_slashed" => slashing_status_is_slashed, |
||||||
|
"by_block" => slashing_status_by_block, |
||||||
|
"multiplier" => slashing_status_multiplier |
||||||
|
} |
||||||
|
} -> |
||||||
|
{:ok, address_hash} = HashAddress.cast(address_hash_string) |
||||||
|
|
||||||
|
%{ |
||||||
|
address_hash: address_hash, |
||||||
|
name: name, |
||||||
|
commission: parse_number(commission), |
||||||
|
self_bonded_amount: parse_number(self_bonded_amount), |
||||||
|
delegated_amount: parse_number(delegated_amount), |
||||||
|
slashing_status_is_slashed: slashing_status_is_slashed, |
||||||
|
slashing_status_by_block: slashing_status_by_block, |
||||||
|
slashing_status_multiplier: slashing_status_multiplier |
||||||
|
} |
||||||
|
end)} |
||||||
|
end |
||||||
|
|
||||||
|
defp parse_validators_info({:error, error}) do |
||||||
|
Logger.error("Failed to parse blackfort validator info: #{inspect(error)}") |
||||||
|
:error |
||||||
|
end |
||||||
|
|
||||||
|
defp validator_url do |
||||||
|
Application.get_env(:explorer, __MODULE__)[:api_url] |
||||||
|
end |
||||||
|
|
||||||
|
defp parse_number(string) do |
||||||
|
{number, _} = Integer.parse(string) |
||||||
|
number |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,97 @@ |
|||||||
|
defmodule Explorer.Chain.Cache.BlackfortValidatorsCounters do |
||||||
|
@moduledoc """ |
||||||
|
Counts and store counters of validators blackfort. |
||||||
|
|
||||||
|
It loads the count asynchronously and in a time interval of 30 minutes. |
||||||
|
""" |
||||||
|
|
||||||
|
use GenServer |
||||||
|
|
||||||
|
alias Explorer.Chain |
||||||
|
alias Explorer.Chain.Blackfort.Validator, as: ValidatorBlackfort |
||||||
|
|
||||||
|
@validators_counter_key "blackfort_validators_counter" |
||||||
|
@new_validators_counter_key "new_blackfort_validators_counter" |
||||||
|
|
||||||
|
# It is undesirable to automatically start the consolidation in all environments. |
||||||
|
# Consider the test environment: if the consolidation initiates but does not |
||||||
|
# finish before a test ends, that test will fail. This way, hundreds of |
||||||
|
# tests were failing before disabling the consolidation and the scheduler in |
||||||
|
# the test env. |
||||||
|
config = Application.compile_env(:explorer, __MODULE__) |
||||||
|
@enable_consolidation Keyword.get(config, :enable_consolidation) |
||||||
|
|
||||||
|
@update_interval_in_milliseconds Keyword.get(config, :update_interval_in_milliseconds) |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Starts a process to periodically update validators blackfort counters |
||||||
|
""" |
||||||
|
@spec start_link(term()) :: GenServer.on_start() |
||||||
|
def start_link(_) do |
||||||
|
GenServer.start_link(__MODULE__, [], name: __MODULE__) |
||||||
|
end |
||||||
|
|
||||||
|
@impl true |
||||||
|
def init(_args) do |
||||||
|
{:ok, %{consolidate?: @enable_consolidation}, {:continue, :ok}} |
||||||
|
end |
||||||
|
|
||||||
|
defp schedule_next_consolidation do |
||||||
|
Process.send_after(self(), :consolidate, @update_interval_in_milliseconds) |
||||||
|
end |
||||||
|
|
||||||
|
@impl true |
||||||
|
def handle_continue(:ok, %{consolidate?: true} = state) do |
||||||
|
consolidate() |
||||||
|
schedule_next_consolidation() |
||||||
|
|
||||||
|
{:noreply, state} |
||||||
|
end |
||||||
|
|
||||||
|
@impl true |
||||||
|
def handle_continue(:ok, state) do |
||||||
|
{:noreply, state} |
||||||
|
end |
||||||
|
|
||||||
|
@impl true |
||||||
|
def handle_info(:consolidate, state) do |
||||||
|
consolidate() |
||||||
|
schedule_next_consolidation() |
||||||
|
|
||||||
|
{:noreply, state} |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Fetches values for a blackfort validators counters from the `last_fetched_counters` table. |
||||||
|
""" |
||||||
|
@spec get_counters(Keyword.t()) :: map() |
||||||
|
def get_counters(options) do |
||||||
|
%{ |
||||||
|
validators_counter: Chain.get_last_fetched_counter(@validators_counter_key, options), |
||||||
|
new_validators_counter: Chain.get_last_fetched_counter(@new_validators_counter_key, options) |
||||||
|
} |
||||||
|
end |
||||||
|
|
||||||
|
@doc """ |
||||||
|
Consolidates the info by populating the `last_fetched_counters` table with the current database information. |
||||||
|
""" |
||||||
|
@spec consolidate() :: any() |
||||||
|
def consolidate do |
||||||
|
tasks = [ |
||||||
|
Task.async(fn -> ValidatorBlackfort.count_validators() end), |
||||||
|
Task.async(fn -> ValidatorBlackfort.count_new_validators() end) |
||||||
|
] |
||||||
|
|
||||||
|
[validators_counter, new_validators_counter] = Task.await_many(tasks, :infinity) |
||||||
|
|
||||||
|
Chain.upsert_last_fetched_counter(%{ |
||||||
|
counter_type: @validators_counter_key, |
||||||
|
value: validators_counter |
||||||
|
}) |
||||||
|
|
||||||
|
Chain.upsert_last_fetched_counter(%{ |
||||||
|
counter_type: @new_validators_counter_key, |
||||||
|
value: new_validators_counter |
||||||
|
}) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,20 @@ |
|||||||
|
defmodule Explorer.Repo.Blackfort.Migrations.AddBlackfortValidators do |
||||||
|
use Ecto.Migration |
||||||
|
|
||||||
|
def change do |
||||||
|
create table(:validators_blackfort, primary_key: false) do |
||||||
|
add(:address_hash, :bytea, null: false, primary_key: true) |
||||||
|
add(:name, :string) |
||||||
|
add(:commission, :smallint) |
||||||
|
add(:self_bonded_amount, :numeric, precision: 100) |
||||||
|
add(:delegated_amount, :numeric, precision: 100) |
||||||
|
add(:slashing_status_is_slashed, :boolean, default: false) |
||||||
|
add(:slashing_status_by_block, :bigint) |
||||||
|
add(:slashing_status_multiplier, :integer) |
||||||
|
|
||||||
|
timestamps() |
||||||
|
end |
||||||
|
|
||||||
|
create_if_not_exists(index(:validators_blackfort, ["address_hash ASC"])) |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,49 @@ |
|||||||
|
defmodule Indexer.Fetcher.Blackfort.Validator do |
||||||
|
@moduledoc """ |
||||||
|
GenServer responsible for updating the list of blackfort validators in the database. |
||||||
|
""" |
||||||
|
use GenServer |
||||||
|
|
||||||
|
alias Explorer.Chain.Blackfort.Validator |
||||||
|
|
||||||
|
def start_link(_) do |
||||||
|
GenServer.start_link(__MODULE__, [], name: __MODULE__) |
||||||
|
end |
||||||
|
|
||||||
|
@impl true |
||||||
|
def init(state) do |
||||||
|
GenServer.cast(__MODULE__, :update_validators_list) |
||||||
|
|
||||||
|
{:ok, state} |
||||||
|
end |
||||||
|
|
||||||
|
@impl true |
||||||
|
def handle_cast(:update_validators_list, state) do |
||||||
|
case Validator.fetch_validators_list() do |
||||||
|
{:ok, validators} -> |
||||||
|
validators_from_db = Validator.get_all_validators() |
||||||
|
|
||||||
|
validators_map = |
||||||
|
Enum.reduce(validators, %{}, fn %{address_hash: address_hash}, map -> |
||||||
|
Map.put(map, address_hash.bytes, true) |
||||||
|
end) |
||||||
|
|
||||||
|
address_hashes_to_drop_from_db = |
||||||
|
Enum.flat_map(validators_from_db, fn validator -> |
||||||
|
(is_nil(validators_map[validator.address_hash.bytes]) && |
||||||
|
[validator.address_hash]) || [] |
||||||
|
end) |
||||||
|
|
||||||
|
Validator.delete_validators_by_address_hashes(address_hashes_to_drop_from_db) |
||||||
|
|
||||||
|
validators |
||||||
|
|> Enum.map(&Validator.append_timestamps/1) |
||||||
|
|> Validator.insert_validators() |
||||||
|
|
||||||
|
_ -> |
||||||
|
nil |
||||||
|
end |
||||||
|
|
||||||
|
{:noreply, state} |
||||||
|
end |
||||||
|
end |
Loading…
Reference in new issue