diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 4fd201d657..cdec5e8f23 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -54,7 +54,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_23-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_24-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps- @@ -112,7 +112,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_23-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_24-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -136,7 +136,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_23-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_24-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -159,7 +159,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_23-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_24-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -168,7 +168,7 @@ jobs: id: dialyzer-cache with: path: priv/plts - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-dialyzer-mixlockhash_23-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-dialyzer-mixlockhash_24-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-dialyzer-" @@ -199,7 +199,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_23-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_24-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -225,7 +225,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_23-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_24-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -254,7 +254,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_23-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_24-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -302,7 +302,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_23-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_24-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -348,7 +348,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_23-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_24-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -405,7 +405,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_23-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_24-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -459,7 +459,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_23-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_24-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -524,7 +524,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_23-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_24-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -588,7 +588,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_23-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_24-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" diff --git a/CHANGELOG.md b/CHANGELOG.md index 58f26fe682..ebd0bc0d13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - [#8472](https://github.com/blockscout/blockscout/pull/8472) - Integrate `/api/v2/bytecodes/sources:search-all` of `eth_bytecode_db` - [#8589](https://github.com/blockscout/blockscout/pull/8589) - DefiLlama TVL source - [#8544](https://github.com/blockscout/blockscout/pull/8544) - Fix `nil` `"structLogs"` +- [#8583](https://github.com/blockscout/blockscout/pull/8583) - Add stats widget for rootstock - [#8542](https://github.com/blockscout/blockscout/pull/8542) - Add tracing for rootstock - [#8561](https://github.com/blockscout/blockscout/pull/8561), [#8564](https://github.com/blockscout/blockscout/pull/8564) - Get historical market cap data from CoinGecko - [#8543](https://github.com/blockscout/blockscout/pull/8543) - Fix polygon tracer diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex index 1f1d87302e..825d9f1b25 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex @@ -3,10 +3,11 @@ defmodule BlockScoutWeb.API.V2.StatsController do alias BlockScoutWeb.API.V2.Helper alias BlockScoutWeb.Chain.MarketHistoryChartController + alias EthereumJSONRPC.Variant alias Explorer.{Chain, Market} alias Explorer.Chain.Address.Counters alias Explorer.Chain.Cache.Block, as: BlockCache - alias Explorer.Chain.Cache.{GasPriceOracle, GasUsage} + alias Explorer.Chain.Cache.{GasPriceOracle, GasUsage, RootstockLockedBTC} alias Explorer.Chain.Cache.Transaction, as: TransactionCache alias Explorer.Chain.Supply.RSK alias Explorer.Chain.Transaction.History.TransactionStats @@ -57,6 +58,7 @@ defmodule BlockScoutWeb.API.V2.StatsController do "tvl" => exchange_rate_from_db.tvl_usd, "network_utilization_percentage" => network_utilization_percentage() } + |> add_rootstock_locked_btc() ) end @@ -124,4 +126,13 @@ defmodule BlockScoutWeb.API.V2.StatsController do available_supply: current_total_supply }) end + + defp add_rootstock_locked_btc(stats) do + with "rsk" <- Variant.get(), + rootstock_locked_btc when not is_nil(rootstock_locked_btc) <- RootstockLockedBTC.get_locked_value() do + stats |> Map.put("rootstock_locked_btc", rootstock_locked_btc) + else + _ -> stats + end + end end diff --git a/apps/explorer/config/runtime/test.exs b/apps/explorer/config/runtime/test.exs index bef121b2c1..9449463a5b 100644 --- a/apps/explorer/config/runtime/test.exs +++ b/apps/explorer/config/runtime/test.exs @@ -24,6 +24,11 @@ config :explorer, Explorer.Chain.Cache.VerifiedContractsCounter, enabled: false, config :explorer, Explorer.Chain.Cache.NewVerifiedContractsCounter, enabled: false, enable_consolidation: false config :explorer, Explorer.Chain.Cache.WithdrawalsSum, enabled: false, enable_consolidation: false +config :explorer, Explorer.Chain.Cache.RootstockLockedBTC, + enabled: true, + global_ttl: :timer.minutes(10), + locking_cap: 21_000_000 + config :explorer, Explorer.Market.History.Cataloger, enabled: false config :explorer, Explorer.Tracer, disabled?: false diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index 1799fc2882..21b292fb11 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -124,7 +124,8 @@ defmodule Explorer.Application do configure(Explorer.Chain.Fetcher.CheckBytecodeMatchingOnDemand), configure(Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand), configure(Explorer.TokenInstanceOwnerAddressMigration.Supervisor), - sc_microservice_configure(Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand) + sc_microservice_configure(Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand), + configure(Explorer.Chain.Cache.RootstockLockedBTC) ] |> List.flatten() end diff --git a/apps/explorer/lib/explorer/chain/cache/rootstock_locked_btc.ex b/apps/explorer/lib/explorer/chain/cache/rootstock_locked_btc.ex new file mode 100644 index 0000000000..e4bf151978 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/cache/rootstock_locked_btc.ex @@ -0,0 +1,31 @@ +defmodule Explorer.Chain.Cache.RootstockLockedBTC do + @moduledoc """ + Caches the number of BTC locked in 2WP on Rootstock chain. + """ + + require Logger + alias Explorer.Chain + alias Explorer.Chain.{Address, Wei} + + use Explorer.Chain.MapCache, + name: :locked_rsk, + key: :locked_value, + global_ttl: Application.get_env(:explorer, __MODULE__)[:global_ttl], + ttl_check_interval: :timer.seconds(1) + + defp handle_fallback(:locked_value) do + rootstock_bridge_address_str = Application.get_env(:explorer, Explorer.Chain.Transaction)[:rootstock_bridge_address] + rootstock_locking_cap = Application.get_env(:explorer, __MODULE__)[:locking_cap] |> Decimal.new() + + with {:ok, rootstock_bridge_address_hash} <- Chain.string_to_address_hash(rootstock_bridge_address_str), + {:ok, %Address{fetched_coin_balance: balance}} when not is_nil(balance) <- + Chain.hash_to_address(rootstock_bridge_address_hash) do + {:update, rootstock_locking_cap |> Wei.from(:ether) |> Wei.sub(balance)} + else + _ -> + {:return, nil} + end + end + + defp handle_fallback(_key), do: {:return, nil} +end diff --git a/apps/explorer/test/explorer/chain/cache/rootstock_locked_btc_test.exs b/apps/explorer/test/explorer/chain/cache/rootstock_locked_btc_test.exs new file mode 100644 index 0000000000..46d0d033f3 --- /dev/null +++ b/apps/explorer/test/explorer/chain/cache/rootstock_locked_btc_test.exs @@ -0,0 +1,38 @@ +defmodule Explorer.Chain.Cache.RootstockLockedBTCTest do + use Explorer.DataCase + + alias Explorer.Chain.Cache.RootstockLockedBTC + alias Explorer.Chain.{Transaction, Wei} + + @bridge_address "0x0000000000000000000000000000000001000006" + + setup do + transaction_configuration = Application.get_env(:explorer, Transaction) + Application.put_env(:explorer, Transaction, rootstock_bridge_address: @bridge_address) + + :ok + + Supervisor.terminate_child(Explorer.Supervisor, RootstockLockedBTC.child_id()) + Supervisor.restart_child(Explorer.Supervisor, RootstockLockedBTC.child_id()) + + on_exit(fn -> + Application.put_env(:explorer, Transaction, transaction_configuration) + end) + + :ok + end + + test "returns nil in case if there is no bridged address in the database" do + result = RootstockLockedBTC.get_locked_value() + + assert is_nil(result) + end + + test "updates cache if initial value is zero and returns converted wei" do + insert(:address, hash: @bridge_address, fetched_coin_balance: 42_000_000_000_000_000_000) + + result = RootstockLockedBTC.get_locked_value() + + assert result == Wei.from(Decimal.new(21_000_000), :ether) |> Wei.sub(Wei.from(Decimal.new(42), :ether)) + end +end diff --git a/config/config_helper.exs b/config/config_helper.exs index 9e481014d8..911430cdb1 100644 --- a/config/config_helper.exs +++ b/config/config_helper.exs @@ -36,7 +36,7 @@ defmodule ConfigHelper do |> :timer.seconds() end - @spec parse_integer_env_var(String.t(), String.t()) :: non_neg_integer() + @spec parse_integer_env_var(String.t(), integer()) :: non_neg_integer() def parse_integer_env_var(env_var, default_value) do env_var |> safe_get_env(to_string(default_value)) diff --git a/config/runtime.exs b/config/runtime.exs index 0fe6e7377e..2a339e458e 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -246,6 +246,11 @@ config :explorer, Explorer.Chain.Cache.GasPriceOracle, average_percentile: ConfigHelper.parse_integer_env_var("GAS_PRICE_ORACLE_AVERAGE_PERCENTILE", 60), fast_percentile: ConfigHelper.parse_integer_env_var("GAS_PRICE_ORACLE_FAST_PERCENTILE", 90) +config :explorer, Explorer.Chain.Cache.RootstockLockedBTC, + enabled: System.get_env("ETHEREUM_JSONRPC_VARIANT") == "rsk", + global_ttl: ConfigHelper.parse_time_env_var("ROOTSTOCK_LOCKED_BTC_CACHE_PERIOD", "10m"), + locking_cap: ConfigHelper.parse_integer_env_var("ROOTSTOCK_LOCKING_CAP", 21_000_000) + config :explorer, Explorer.Counters.AddressTransactionsGasUsageCounter, cache_period: ConfigHelper.parse_time_env_var("CACHE_ADDRESS_TRANSACTIONS_GAS_USAGE_COUNTER_PERIOD", "30m") diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index bc1a081199..edd08011ed 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -147,6 +147,8 @@ INDEXER_DISABLE_INTERNAL_TRANSACTIONS_FETCHER=false # WITHDRAWALS_FIRST_BLOCK= # ROOTSTOCK_REMASC_ADDRESS= # ROOTSTOCK_BRIDGE_ADDRESS= +# ROOTSTOCK_LOCKED_BTC_CACHE_PERIOD= +# ROOTSTOCK_LOCKING_CAP= # TOKEN_ID_MIGRATION_FIRST_BLOCK= # TOKEN_ID_MIGRATION_CONCURRENCY= # TOKEN_ID_MIGRATION_BATCH_SIZE=