Merge pull request #9044 from blockscout/mf-gas-tracker-v1

Expand gas price oracle functionality
pull/8812/head
Victor Baranov 10 months ago committed by GitHub
commit 3cbaa1c423
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      CHANGELOG.md
  2. 4
      apps/block_scout_web/lib/block_scout_web/controllers/api/v1/gas_price_oracle_controller.ex
  3. 34
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex
  4. 10
      apps/block_scout_web/lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex
  5. 5
      apps/block_scout_web/lib/block_scout_web/views/chain_view.ex
  6. 206
      apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex
  7. 2
      apps/explorer/lib/explorer/market/market.ex
  8. 8
      apps/explorer/lib/explorer/market/market_history.ex
  9. 9
      apps/explorer/priv/repo/migrations/20240103094720_constrain_null_date_market_history.exs
  10. 255
      apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs
  11. 1
      config/runtime.exs
  12. 17
      cspell.json
  13. 6
      docker-compose/envs/common-blockscout.env

@ -4,6 +4,8 @@
### Features
- [#9044](https://github.com/blockscout/blockscout/pull/9044) - Expand gas price oracle functionality
### Fixes
- [#9062](https://github.com/blockscout/blockscout/pull/9062) - Fix blockscout-ens integration

@ -30,8 +30,8 @@ defmodule BlockScoutWeb.API.V1.GasPriceOracleController do
|> send_resp(status, result)
end
def result(gas_prices) do
gas_prices
defp result(gas_prices) do
%{slow: gas_prices[:slow][:price], average: gas_prices[:average][:price], fast: gas_prices[:fast][:price]}
|> Jason.encode!()
end

@ -12,6 +12,7 @@ defmodule BlockScoutWeb.API.V2.StatsController do
alias Explorer.Chain.Supply.RSK
alias Explorer.Chain.Transaction.History.TransactionStats
alias Explorer.Counters.AverageBlockTime
alias Plug.Conn
alias Timex.Duration
@api_true [api?: true]
@ -39,6 +40,21 @@ defmodule BlockScoutWeb.API.V2.StatsController do
nil
end
coin_price_change =
case Market.fetch_recent_history() do
[today, yesterday | _] ->
today.closing_price && yesterday.closing_price &&
today.closing_price
|> Decimal.div(yesterday.closing_price)
|> Decimal.sub(1)
|> Decimal.mult(100)
|> Decimal.to_float()
|> Float.ceil(2)
_ ->
nil
end
gas_price = Application.get_env(:block_scout_web, :gas_price)
json(
@ -49,16 +65,20 @@ defmodule BlockScoutWeb.API.V2.StatsController do
"total_transactions" => TransactionCache.estimated_count() |> to_string(),
"average_block_time" => AverageBlockTime.average_block_time() |> Duration.to_milliseconds(),
"coin_price" => exchange_rate_from_db.usd_value,
"coin_price_change_percentage" => coin_price_change,
"total_gas_used" => GasUsage.total() |> to_string(),
"transactions_today" => Enum.at(transaction_stats, 0).number_of_transactions |> to_string(),
"gas_used_today" => Enum.at(transaction_stats, 0).gas_used,
"gas_prices" => gas_prices,
"gas_prices_update_in" => GasPriceOracle.global_ttl(),
"gas_price_updated_at" => GasPriceOracle.get_updated_at(),
"static_gas_price" => gas_price,
"market_cap" => Helper.market_cap(market_cap_type, exchange_rate_from_db),
"tvl" => exchange_rate_from_db.tvl_usd,
"network_utilization_percentage" => network_utilization_percentage()
}
|> add_rootstock_locked_btc()
|> backward_compatibility(conn)
)
end
@ -135,4 +155,18 @@ defmodule BlockScoutWeb.API.V2.StatsController do
_ -> stats
end
end
defp backward_compatibility(response, conn) do
case Conn.get_req_header(conn, "updated-gas-oracle") do
["true"] ->
response
_ ->
response
|> Map.update("gas_prices", nil, fn
gas_prices ->
%{slow: gas_prices[:slow][:price], average: gas_prices[:average][:price], fast: gas_prices[:fast][:price]}
end)
end
end
end

@ -8,7 +8,7 @@
<span class="dashboard-banner-chart-legend-value inline">
<div>
<div class="d-flex flex-row" style="height: 20px; line-height: 20px;">
<div class="flex-column"><%= "#{gas_prices_from_oracle["average"]}" <> " " %><%= gettext "Gwei" %></div>
<div class="flex-column"><%= "#{gas_prices_from_oracle[:average]}" <> " " %><%= gettext "Gwei" %></div>
<span
data-toggle="tooltip"
data-placement="top"
@ -17,9 +17,9 @@
title="
<div class='custom-tooltip header'><%= gettext "Gas tracker" %></div>
<div>
<div class='custom-tooltip description left d-flex'><span><%= gettext "Slow" %></span><span class='custom-tooltip description right'><%= gas_prices_from_oracle["slow"] %> <%= gettext "Gwei" %></span></div>
<div class='custom-tooltip description left d-flex'><span><%= gettext "Average" %></span><span class='custom-tooltip description right'><%= gas_prices_from_oracle["average"] %> <%= gettext "Gwei" %></span></div>
<div class='custom-tooltip description left d-flex'><span><%= gettext "Fast" %></span><span class='custom-tooltip description right'><%= gas_prices_from_oracle["fast"] %> <%= gettext "Gwei" %></span></div>
<div class='custom-tooltip description left d-flex'><span><%= gettext "Slow" %></span><span class='custom-tooltip description right'><%= gas_prices_from_oracle[:slow] %> <%= gettext "Gwei" %></span></div>
<div class='custom-tooltip description left d-flex'><span><%= gettext "Average" %></span><span class='custom-tooltip description right'><%= gas_prices_from_oracle[:average] %> <%= gettext "Gwei" %></span></div>
<div class='custom-tooltip description left d-flex'><span><%= gettext "Fast" %></span><span class='custom-tooltip description right'><%= gas_prices_from_oracle[:fast] %> <%= gettext "Gwei" %></span></div>
</div>
"
>
@ -40,4 +40,4 @@
<% end %>
<% end %>
</div>
</div>
</div>

@ -60,10 +60,7 @@ defmodule BlockScoutWeb.ChainView do
defp gas_prices do
case GasPriceOracle.get_gas_prices() do
{:ok, gas_prices} ->
gas_prices
nil ->
nil
%{slow: gas_prices[:slow][:price], average: gas_prices[:average][:price], fast: gas_prices[:fast][:price]}
_ ->
nil

@ -17,27 +17,61 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do
Wei
}
alias Explorer.Repo
alias Explorer.Counters.AverageBlockTime
alias Explorer.{Market, Repo}
alias Timex.Duration
use Explorer.Chain.MapCache,
name: :gas_price,
key: :gas_prices,
key: :gas_prices_acc,
key: :updated_at,
key: :old_gas_prices,
key: :async_task,
global_ttl: Application.get_env(:explorer, __MODULE__)[:global_ttl],
global_ttl: global_ttl(),
ttl_check_interval: :timer.seconds(1),
callback: &async_task_on_deletion(&1)
@doc """
Get `safelow`, `average` and `fast` percentile of transactions gas prices among the last `num_of_blocks` blocks
Calculates the `slow`, `average`, and `fast` gas price and time percentiles from the last `num_of_blocks` blocks and estimates the fiat price for each percentile.
These percentiles correspond to the likelihood of a transaction being picked up by miners depending on the fee offered.
"""
@spec get_average_gas_price(pos_integer(), pos_integer(), pos_integer(), pos_integer()) ::
{:error, any} | {:ok, %{String.t() => nil | float, String.t() => nil | float, String.t() => nil | float}}
{{:error, any} | {:ok, %{slow: gas_price, average: gas_price, fast: gas_price}},
[
%{
block_number: non_neg_integer(),
slow_gas_price: nil | Decimal.t(),
fast_gas_price: nil | Decimal.t(),
average_gas_price: nil | Decimal.t(),
slow_priority_fee_per_gas: nil | Decimal.t(),
average_priority_fee_per_gas: nil | Decimal.t(),
fast_priority_fee_per_gas: nil | Decimal.t(),
slow_time: nil | Decimal.t(),
average_time: nil | Decimal.t(),
fast_time: nil | Decimal.t()
}
]}
when gas_price: nil | %{price: float(), time: float(), fiat_price: Decimal.t()}
def get_average_gas_price(num_of_blocks, safelow_percentile, average_percentile, fast_percentile) do
safelow_percentile_fraction = safelow_percentile / 100
average_percentile_fraction = average_percentile / 100
fast_percentile_fraction = fast_percentile / 100
acc = get_gas_prices_acc()
from_block =
case acc do
[%{block_number: from_block} | _] -> from_block
_ -> -1
end
average_block_time =
case AverageBlockTime.average_block_time() do
{:error, _} -> nil
average_block_time -> average_block_time |> Duration.to_milliseconds()
end
fee_query =
from(
block in Block,
@ -45,120 +79,186 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do
where: block.consensus == true,
where: transaction.status == ^1,
where: transaction.gas_price > ^0,
group_by: block.number,
order_by: [desc: block.number],
where: transaction.block_number > ^from_block,
group_by: transaction.block_number,
order_by: [desc: transaction.block_number],
select: %{
block_number: transaction.block_number,
slow_gas_price:
fragment(
"percentile_disc(?) within group ( order by ? )",
"percentile_disc(? :: real) within group ( order by ? )",
^safelow_percentile_fraction,
transaction.gas_price
),
average_gas_price:
fragment(
"percentile_disc(?) within group ( order by ? )",
"percentile_disc(? :: real) within group ( order by ? )",
^average_percentile_fraction,
transaction.gas_price
),
fast_gas_price:
fragment(
"percentile_disc(?) within group ( order by ? )",
"percentile_disc(? :: real) within group ( order by ? )",
^fast_percentile_fraction,
transaction.gas_price
),
slow:
slow_priority_fee_per_gas:
fragment(
"percentile_disc(?) within group ( order by ? )",
"percentile_disc(? :: real) within group ( order by ? )",
^safelow_percentile_fraction,
transaction.max_priority_fee_per_gas
),
average:
average_priority_fee_per_gas:
fragment(
"percentile_disc(?) within group ( order by ? )",
"percentile_disc(? :: real) within group ( order by ? )",
^average_percentile_fraction,
transaction.max_priority_fee_per_gas
),
fast:
fast_priority_fee_per_gas:
fragment(
"percentile_disc(?) within group ( order by ? )",
"percentile_disc(? :: real) within group ( order by ? )",
^fast_percentile_fraction,
transaction.max_priority_fee_per_gas
),
slow_time:
fragment(
"percentile_disc(? :: real) within group ( order by coalesce(extract(milliseconds from (?)::interval), ?) desc )",
^safelow_percentile_fraction,
block.timestamp - transaction.earliest_processing_start,
^average_block_time
),
average_time:
fragment(
"percentile_disc(? :: real) within group ( order by coalesce(extract(milliseconds from (?)::interval), ?) desc )",
^average_percentile_fraction,
block.timestamp - transaction.earliest_processing_start,
^average_block_time
),
fast_time:
fragment(
"percentile_disc(? :: real) within group ( order by coalesce(extract(milliseconds from (?)::interval), ?) desc )",
^fast_percentile_fraction,
block.timestamp - transaction.earliest_processing_start,
^average_block_time
)
},
limit: ^num_of_blocks
)
gas_prices = fee_query |> Repo.all(timeout: :infinity) |> process_fee_data_from_db()
new_acc = fee_query |> Repo.all(timeout: :infinity) |> merge_gas_prices(acc, num_of_blocks)
gas_prices = new_acc |> process_fee_data_from_db()
{:ok, gas_prices}
{{:ok, gas_prices}, new_acc}
catch
error ->
{:error, error}
Logger.error("Failed to get gas prices: #{inspect(error)}")
{{:error, error}, get_gas_prices_acc()}
end
defp merge_gas_prices(new, acc, acc_size), do: Enum.take(new ++ acc, acc_size)
defp process_fee_data_from_db([]) do
%{
"slow" => nil,
"average" => nil,
"fast" => nil
slow: nil,
average: nil,
fast: nil
}
end
defp process_fee_data_from_db(fees) do
fees_length = Enum.count(fees)
%{
slow_gas_price: slow_gas_price,
average_gas_price: average_gas_price,
fast_gas_price: fast_gas_price,
slow: slow,
average: average,
fast: fast
} =
fees
|> Enum.reduce(
&Map.merge(&1, &2, fn
_, v1, v2 when nil not in [v1, v2] -> Decimal.add(v1, v2)
_, v1, v2 -> v1 || v2
end)
)
|> Map.new(fn
{key, nil} -> {key, nil}
{key, value} -> {key, Decimal.div(value, fees_length)}
end)
slow_priority_fee_per_gas: slow_priority_fee_per_gas,
average_priority_fee_per_gas: average_priority_fee_per_gas,
fast_priority_fee_per_gas: fast_priority_fee_per_gas,
slow_time: slow_time,
average_time: average_time,
fast_time: fast_time
} = merge_fees(fees)
json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments)
{slow_fee, average_fee, fast_fee} =
case {nil not in [slow, average, fast], EthereumJSONRPC.fetch_block_by_tag("pending", json_rpc_named_arguments)} do
{true, {:ok, %Blocks{blocks_params: [%{base_fee_per_gas: base_fee}]}}} when not is_nil(base_fee) ->
case nil not in [slow_priority_fee_per_gas, average_priority_fee_per_gas, fast_priority_fee_per_gas] &&
EthereumJSONRPC.fetch_block_by_tag("pending", json_rpc_named_arguments) do
{:ok, %Blocks{blocks_params: [%{base_fee_per_gas: base_fee}]}} when not is_nil(base_fee) ->
base_fee_wei = base_fee |> Decimal.new() |> Wei.from(:wei)
{
priority_with_base_fee(slow, base_fee_wei),
priority_with_base_fee(average, base_fee_wei),
priority_with_base_fee(fast, base_fee_wei)
priority_with_base_fee(slow_priority_fee_per_gas, base_fee_wei),
priority_with_base_fee(average_priority_fee_per_gas, base_fee_wei),
priority_with_base_fee(fast_priority_fee_per_gas, base_fee_wei)
}
_ ->
{gas_price(slow_gas_price), gas_price(average_gas_price), gas_price(fast_gas_price)}
end
exchange_rate_from_db = Market.get_coin_exchange_rate()
%{
"slow" => slow_fee,
"average" => average_fee,
"fast" => fast_fee
slow: compose_gas_price(slow_fee, slow_time, exchange_rate_from_db),
average: compose_gas_price(average_fee, average_time, exchange_rate_from_db),
fast: compose_gas_price(fast_fee, fast_time, exchange_rate_from_db)
}
end
defp merge_fees(fees_from_db) do
fees_from_db
|> Stream.map(&Map.delete(&1, :block_number))
|> Enum.reduce(
&Map.merge(&1, &2, fn
_, nil, nil -> nil
_, val, acc when nil not in [val, acc] and is_list(acc) -> [val | acc]
_, val, acc when nil not in [val, acc] -> [val, acc]
_, val, acc -> [val || acc]
end)
)
|> Map.new(fn
{key, nil} ->
{key, nil}
{key, value} ->
value = if is_list(value), do: value, else: [value]
count = Enum.count(value)
{key, value |> Enum.reduce(Decimal.new(0), &Decimal.add/2) |> Decimal.div(count)}
end)
end
defp compose_gas_price(fee, time, exchange_rate_from_db) do
%{
price: fee |> format_wei(),
time: time && time |> Decimal.to_float(),
fiat_price: fiat_fee(fee, exchange_rate_from_db)
}
end
defp fiat_fee(fee, exchange_rate) do
exchange_rate.usd_value &&
fee
|> Wei.to(:ether)
|> Decimal.mult(exchange_rate.usd_value)
|> Decimal.mult(simple_transaction_gas())
|> Decimal.round(2)
end
defp priority_with_base_fee(priority, base_fee) do
priority |> Wei.from(:wei) |> Wei.sum(base_fee) |> Wei.to(:gwei) |> Decimal.to_float() |> Float.ceil(2)
priority |> Wei.from(:wei) |> Wei.sum(base_fee)
end
defp gas_price(value) do
value |> Wei.from(:wei) |> Wei.to(:gwei) |> Decimal.to_float() |> Float.ceil(2)
value |> Wei.from(:wei)
end
defp format_wei(wei), do: wei |> Wei.to(:gwei) |> Decimal.to_float() |> Float.ceil(2)
def global_ttl, do: Application.get_env(:explorer, __MODULE__)[:global_ttl]
defp simple_transaction_gas, do: Application.get_env(:explorer, __MODULE__)[:simple_transaction_gas]
defp num_of_blocks, do: Application.get_env(:explorer, __MODULE__)[:num_of_blocks]
defp safelow, do: Application.get_env(:explorer, __MODULE__)[:safelow_percentile]
@ -181,12 +281,14 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do
{:ok, task} =
Task.start(fn ->
try do
result = get_average_gas_price(num_of_blocks(), safelow(), average(), fast())
{result, acc} = get_average_gas_price(num_of_blocks(), safelow(), average(), fast())
set_gas_prices_acc(acc)
set_gas_prices(result)
set_updated_at(DateTime.utc_now())
rescue
e ->
Logger.debug([
Logger.error([
"Couldn't update gas used gas_prices",
Exception.format(:error, e, __STACKTRACE__)
])
@ -198,6 +300,10 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do
{:update, task}
end
defp handle_fallback(:gas_prices_acc) do
{:return, []}
end
defp handle_fallback(_), do: {:return, nil}
# By setting this as a `callback` an async task will be started each time the

@ -52,7 +52,7 @@ defmodule Explorer.Market do
@doc """
Get most recent exchange rate for the native coin from ETS or from DB.
"""
@spec get_coin_exchange_rate() :: Token.t() | nil
@spec get_coin_exchange_rate() :: Token.t()
def get_coin_exchange_rate do
get_native_coin_exchange_rate_from_cache() || get_native_coin_exchange_rate_from_db() || Token.null()
end

@ -23,10 +23,10 @@ defmodule Explorer.Market.MarketHistory do
* `:market_cap` - TVL in USD.
"""
@type t :: %__MODULE__{
closing_price: Decimal.t(),
closing_price: Decimal.t() | nil,
date: Date.t(),
opening_price: Decimal.t(),
market_cap: Decimal.t(),
tvl: Decimal.t()
opening_price: Decimal.t() | nil,
market_cap: Decimal.t() | nil,
tvl: Decimal.t() | nil
}
end

@ -0,0 +1,9 @@
defmodule Explorer.Repo.Migrations.ConstrainNullDateMarketHistory do
use Ecto.Migration
def change do
alter table(:market_history) do
modify(:date, :date, null: false, from: {:date, null: true})
end
end
end

@ -4,6 +4,7 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do
import Mox
alias Explorer.Chain.Cache.GasPriceOracle
alias Explorer.Counters.AverageBlockTime
@block %{
"difficulty" => "0x0",
@ -48,12 +49,12 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do
test "returns nil percentile values if no blocks in the DB" do
expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end)
assert {:ok,
%{
"slow" => nil,
"average" => nil,
"fast" => nil
}} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
assert {{:ok,
%{
slow: nil,
average: nil,
fast: nil
}}, []} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
end
test "returns nil percentile values if blocks are empty in the DB" do
@ -63,12 +64,12 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do
insert(:block)
insert(:block)
assert {:ok,
%{
"slow" => nil,
"average" => nil,
"fast" => nil
}} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
assert {{:ok,
%{
slow: nil,
average: nil,
fast: nil
}}, []} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
end
test "returns nil percentile values for blocks with failed txs in the DB" do
@ -89,12 +90,12 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do
hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269"
)
assert {:ok,
%{
"slow" => nil,
"average" => nil,
"fast" => nil
}} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
assert {{:ok,
%{
slow: nil,
average: nil,
fast: nil
}}, []} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
end
test "returns nil percentile values for transactions with 0 gas price aka 'whitelisted transactions' in the DB" do
@ -127,12 +128,12 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do
hash: "0x5d5c2776f96704e7845f7d3c1fbba6685ab6efd6f82b6cd11d549f3b3a46bd03"
)
assert {:ok,
%{
"slow" => nil,
"average" => nil,
"fast" => nil
}} = GasPriceOracle.get_average_gas_price(2, 35, 60, 90)
assert {{:ok,
%{
slow: nil,
average: nil,
fast: nil
}}, []} = GasPriceOracle.get_average_gas_price(2, 35, 60, 90)
end
test "returns the same percentile values if gas price is the same over transactions" do
@ -165,12 +166,12 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do
hash: "0x5d5c2776f96704e7845f7d3c1fbba6685ab6efd6f82b6cd11d549f3b3a46bd03"
)
assert {:ok,
%{
"slow" => 1.0,
"average" => 1.0,
"fast" => 1.0
}} = GasPriceOracle.get_average_gas_price(2, 35, 60, 90)
assert {{:ok,
%{
slow: %{price: 1.0},
average: %{price: 1.0},
fast: %{price: 1.0}
}}, _} = GasPriceOracle.get_average_gas_price(2, 35, 60, 90)
end
test "returns correct min gas price from the block" do
@ -215,12 +216,12 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do
hash: "0x906b80861b4a0921acfbb91a7b527227b0d32adabc88bc73e8c52ff714e55016"
)
assert {:ok,
%{
"slow" => 1.0,
"average" => 2.0,
"fast" => 2.0
}} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
assert {{:ok,
%{
slow: %{price: 1.0},
average: %{price: 2.0},
fast: %{price: 2.0}
}}, _} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
end
test "returns correct average percentile" do
@ -266,10 +267,10 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do
hash: "0x7d4bc5569053fc29f471901e967c9e60205ac7a122b0e9a789683652c34cc11a"
)
assert {:ok,
%{
"average" => 3.34
}} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
assert {{:ok,
%{
average: %{price: 3.34}
}}, _} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
end
test "returns correct gas price for EIP-1559 transactions" do
@ -320,13 +321,173 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do
hash: "0x906b80861b4a0921acfbb91a7b527227b0d32adabc88bc73e8c52ff714e55016"
)
assert {:ok,
%{
# including base fee
"slow" => 1.5,
"average" => 2.5,
"fast" => 2.5
}} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
assert {{:ok,
%{
# including base fee
slow: %{price: 1.5},
average: %{price: 2.5}
}}, _} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
end
test "return gas prices with time if available" do
expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end)
block1 =
insert(:block,
number: 100,
hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391",
timestamp: ~U[2023-12-12 12:12:30.000000Z]
)
block2 =
insert(:block,
number: 101,
hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729",
timestamp: ~U[2023-12-12 12:13:00.000000Z]
)
:transaction
|> insert(
status: :ok,
block_hash: block1.hash,
block_number: block1.number,
cumulative_gas_used: 884_322,
gas_used: 106_025,
index: 0,
gas_price: 1_000_000_000,
max_priority_fee_per_gas: 1_000_000_000,
max_fee_per_gas: 1_000_000_000,
hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269",
earliest_processing_start: ~U[2023-12-12 12:12:00.000000Z]
)
:transaction
|> insert(
status: :ok,
block_hash: block2.hash,
block_number: block2.number,
cumulative_gas_used: 884_322,
gas_used: 106_025,
index: 0,
gas_price: 1_000_000_000,
max_priority_fee_per_gas: 1_000_000_000,
max_fee_per_gas: 1_000_000_000,
hash: "0x5d5c2776f96704e7845f7d3c1fbba6685ab6efd6f82b6cd11d549f3b3a46bd03",
earliest_processing_start: ~U[2023-12-12 12:12:00.000000Z]
)
:transaction
|> insert(
status: :ok,
block_hash: block2.hash,
block_number: block2.number,
cumulative_gas_used: 884_322,
gas_used: 106_025,
index: 1,
gas_price: 3_000_000_000,
max_priority_fee_per_gas: 3_000_000_000,
max_fee_per_gas: 3_000_000_000,
hash: "0x906b80861b4a0921acfbb91a7b527227b0d32adabc88bc73e8c52ff714e55016",
earliest_processing_start: ~U[2023-12-12 12:12:55.000000Z]
)
assert {{
:ok,
%{
average: %{price: 2.5, time: 15000.0},
fast: %{price: 2.5, time: 15000.0},
slow: %{price: 1.5, time: 17500.0}
}
}, _} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
end
test "return gas prices with average block time if earliest_processing_start is not available" do
expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end)
old_env = Application.get_env(:explorer, AverageBlockTime)
Application.put_env(:explorer, AverageBlockTime, enabled: true, cache_period: 1_800_000)
start_supervised!(AverageBlockTime)
block_number = 99_999_999
first_timestamp = ~U[2023-12-12 12:12:30.000000Z]
Enum.each(1..100, fn i ->
insert(:block,
number: block_number + 1 + i,
consensus: true,
timestamp: Timex.shift(first_timestamp, seconds: -(101 - i) - 12)
)
end)
block1 =
insert(:block,
number: block_number + 102,
consensus: true,
timestamp: Timex.shift(first_timestamp, seconds: -10)
)
block2 =
insert(:block,
number: block_number + 103,
consensus: true,
timestamp: Timex.shift(first_timestamp, seconds: -7)
)
AverageBlockTime.refresh()
:transaction
|> insert(
status: :ok,
block_hash: block1.hash,
block_number: block1.number,
cumulative_gas_used: 884_322,
gas_used: 106_025,
index: 0,
gas_price: 1_000_000_000,
max_priority_fee_per_gas: 1_000_000_000,
max_fee_per_gas: 1_000_000_000,
hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269"
)
:transaction
|> insert(
status: :ok,
block_hash: block2.hash,
block_number: block2.number,
cumulative_gas_used: 884_322,
gas_used: 106_025,
index: 0,
gas_price: 1_000_000_000,
max_priority_fee_per_gas: 1_000_000_000,
max_fee_per_gas: 1_000_000_000,
hash: "0x5d5c2776f96704e7845f7d3c1fbba6685ab6efd6f82b6cd11d549f3b3a46bd03"
)
:transaction
|> insert(
status: :ok,
block_hash: block2.hash,
block_number: block2.number,
cumulative_gas_used: 884_322,
gas_used: 106_025,
index: 1,
gas_price: 3_000_000_000,
max_priority_fee_per_gas: 3_000_000_000,
max_fee_per_gas: 3_000_000_000,
hash: "0x906b80861b4a0921acfbb91a7b527227b0d32adabc88bc73e8c52ff714e55016"
)
AverageBlockTime.refresh()
assert {{
:ok,
%{
average: %{price: 2.5, time: 1000.0},
fast: %{price: 2.5, time: 1000.0},
slow: %{price: 1.5, time: 1000.0}
}
}, _} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
Application.put_env(:explorer, AverageBlockTime, old_env)
end
end
end

@ -243,6 +243,7 @@ config :explorer, Explorer.Chain.Cache.PendingBlockOperation,
config :explorer, Explorer.Chain.Cache.GasPriceOracle,
global_ttl: ConfigHelper.parse_time_env_var("GAS_PRICE_ORACLE_CACHE_PERIOD", "30s"),
simple_transaction_gas: ConfigHelper.parse_integer_env_var("GAS_PRICE_ORACLE_SIMPLE_TRANSACTION_GAS", 21000),
num_of_blocks: ConfigHelper.parse_integer_env_var("GAS_PRICE_ORACLE_NUM_OF_BLOCKS", 200),
safelow_percentile: ConfigHelper.parse_integer_env_var("GAS_PRICE_ORACLE_SAFELOW_PERCENTILE", 35),
average_percentile: ConfigHelper.parse_integer_env_var("GAS_PRICE_ORACLE_AVERAGE_PERCENTILE", 60),

@ -166,6 +166,7 @@
"Faileddi",
"falala",
"Filesize",
"fkey",
"Floki",
"fontawesome",
"fortawesome",
@ -264,6 +265,7 @@
"mergeable",
"Merkle",
"metatags",
"microsecs",
"millis",
"mintings",
"mistmatches",
@ -408,6 +410,7 @@
"snapshotted",
"snapshotting",
"Sokol",
"SOLIDITYSCAN",
"soljson",
"someout",
"sourcecode",
@ -534,19 +537,7 @@
"zindex",
"zipcode",
"zkbob",
"zkevm",
"erts",
"Asfpp",
"Nerg",
"secp",
"qwertyuioiuytrewertyuioiuytrertyuio",
"urlset",
"lastmod",
"qitmeer",
"meer",
"DefiLlama",
"SOLIDITYSCAN",
"fkey"
"zkevm"
],
"enableFiletypes": [
"dotenv",

@ -182,6 +182,12 @@ COIN_BALANCE_HISTORY_DAYS=90
APPS_MENU=true
EXTERNAL_APPS=[]
# GAS_PRICE=
# GAS_PRICE_ORACLE_CACHE_PERIOD=
# GAS_PRICE_ORACLE_SIMPLE_TRANSACTION_GAS=
# GAS_PRICE_ORACLE_NUM_OF_BLOCKS=
# GAS_PRICE_ORACLE_SAFELOW_PERCENTILE=
# GAS_PRICE_ORACLE_AVERAGE_PERCENTILE=
# GAS_PRICE_ORACLE_FAST_PERCENTILE=
# RESTRICTED_LIST=
# RESTRICTED_LIST_KEY=
SHOW_MAINTENANCE_ALERT=false

Loading…
Cancel
Save