Take into account EIP-1559 in gas price oracle

pull/6190/head
sl1depengwyn 2 years ago committed by Maxim Filonov
parent b86f1408c6
commit a3322e693d
  1. 9
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex
  2. 5
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block/by_tag.ex
  3. 11
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/mox_test.exs
  4. 325
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs
  5. 167
      apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex
  6. 118
      apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs
  7. 65
      apps/indexer/test/indexer/block/catchup/bound_interval_supervisor_test.exs

@ -278,9 +278,9 @@ defmodule EthereumJSONRPC do
@doc """ @doc """
Fetches block by "t:tag/0". Fetches block by "t:tag/0".
""" """
@spec fetch_blocks_by_tag(tag(), json_rpc_named_arguments) :: @spec fetch_block_by_tag(tag(), json_rpc_named_arguments) ::
{:ok, Blocks.t()} | {:error, reason :: :invalid_tag | :not_found | term()} {:ok, Blocks.t()} | {:error, reason :: :invalid_tag | :not_found | term()}
def fetch_blocks_by_tag(tag, json_rpc_named_arguments) when tag in ~w(earliest latest pending) do def fetch_block_by_tag(tag, json_rpc_named_arguments) when tag in ~w(earliest latest pending) do
[%{tag: tag}] [%{tag: tag}]
|> fetch_blocks_by_params(&Block.ByTag.request/1, json_rpc_named_arguments) |> fetch_blocks_by_params(&Block.ByTag.request/1, json_rpc_named_arguments)
end end
@ -320,9 +320,8 @@ defmodule EthereumJSONRPC do
@spec fetch_block_number_by_tag(tag(), json_rpc_named_arguments) :: @spec fetch_block_number_by_tag(tag(), json_rpc_named_arguments) ::
{:ok, non_neg_integer()} | {:error, reason :: :invalid_tag | :not_found | term()} {:ok, non_neg_integer()} | {:error, reason :: :invalid_tag | :not_found | term()}
def fetch_block_number_by_tag(tag, json_rpc_named_arguments) when tag in ~w(earliest latest pending) do def fetch_block_number_by_tag(tag, json_rpc_named_arguments) when tag in ~w(earliest latest pending) do
%{id: 0, tag: tag} tag
|> Block.ByTag.request() |> fetch_block_by_tag(json_rpc_named_arguments)
|> json_rpc(json_rpc_named_arguments)
|> Block.ByTag.number_from_result() |> Block.ByTag.number_from_result()
end end

@ -5,6 +5,7 @@ defmodule EthereumJSONRPC.Block.ByTag do
""" """
import EthereumJSONRPC, only: [quantity_to_integer: 1] import EthereumJSONRPC, only: [quantity_to_integer: 1]
alias EthereumJSONRPC.Blocks
def request(%{id: id, tag: tag}) when is_binary(tag) do def request(%{id: id, tag: tag}) when is_binary(tag) do
EthereumJSONRPC.request(%{id: id, method: "eth_getBlockByNumber", params: [tag, false]}) EthereumJSONRPC.request(%{id: id, method: "eth_getBlockByNumber", params: [tag, false]})
@ -16,6 +17,10 @@ defmodule EthereumJSONRPC.Block.ByTag do
{:ok, quantity_to_integer(quantity)} {:ok, quantity_to_integer(quantity)}
end end
def number_from_result({:ok, %Blocks{blocks_params: []}}), do: {:error, :not_found}
def number_from_result({:ok, %Blocks{blocks_params: [%{number: number}]}}), do: {:ok, number}
def number_from_result({:ok, nil}), do: {:error, :not_found} def number_from_result({:ok, nil}), do: {:error, :not_found}
def number_from_result({:error, %{"code" => -32602}}), do: {:error, :invalid_tag} def number_from_result({:error, %{"code" => -32602}}), do: {:error, :invalid_tag}

@ -21,8 +21,15 @@ defmodule EthereumJSONRPC.MoxTest do
describe "fetch_block_number_by_tag/2" do describe "fetch_block_number_by_tag/2" do
test "with pending with null result", %{json_rpc_named_arguments: json_rpc_named_arguments} do test "with pending with null result", %{json_rpc_named_arguments: json_rpc_named_arguments} do
expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options -> expect(EthereumJSONRPC.Mox, :json_rpc, fn [
{:ok, nil} %{
id: id,
method: "eth_getBlockByNumber",
params: ["pending", false]
}
],
_options ->
{:ok, [%{id: id, result: nil}]}
end) end)
assert {:error, :not_found} = EthereumJSONRPC.fetch_block_number_by_tag("pending", json_rpc_named_arguments) assert {:error, :not_found} = EthereumJSONRPC.fetch_block_number_by_tag("pending", json_rpc_named_arguments)

@ -2,6 +2,7 @@ defmodule EthereumJSONRPCTest do
use EthereumJSONRPC.Case, async: true use EthereumJSONRPC.Case, async: true
import EthereumJSONRPC.Case import EthereumJSONRPC.Case
import EthereumJSONRPC, only: [quantity_to_integer: 1]
import Mox import Mox
alias EthereumJSONRPC.{Blocks, FetchedBalances, FetchedBeneficiaries, FetchedCodes, Subscription} alias EthereumJSONRPC.{Blocks, FetchedBalances, FetchedBeneficiaries, FetchedCodes, Subscription}
@ -543,191 +544,32 @@ defmodule EthereumJSONRPCTest do
end end
end end
describe "fetch_blocks_by_tag/2" do describe "fetch_block_by_tag/2" do
@tag capture_log: false @supported_tags ~w(earliest latest pending)
test "with earliest", %{json_rpc_named_arguments: json_rpc_named_arguments} do
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options ->
block_number = "0x0"
block_hash = "0x29c850324e357f3c0c836d79860c5af55f7b651e5d7ee253c1af1b14908af49c"
transaction_hash = "0xa2e81bb56b55ba3dab2daf76501b50dfaad240cccb905dbf89d65c7a84a4a48e"
{:ok,
[
%{
id: id,
result: %{
"difficulty" => "0x0",
"gasLimit" => "0x0",
"gasUsed" => "0x0",
"hash" => block_hash,
"extraData" => "0x0",
"logsBloom" => "0x0",
"miner" => "0x0",
"number" => block_number,
"parentHash" => "0x0",
"receiptsRoot" => "0x0",
"size" => "0x0",
"sha3Uncles" => "0x0",
"stateRoot" => "0x0",
"timestamp" => "0x0",
"totalDifficulty" => "0x0",
"transactions" => [
%{
"blockHash" => block_hash,
"blockNumber" => block_number,
"from" => "0x0",
"gas" => "0x0",
"gasPrice" => "0x0",
"hash" => transaction_hash,
"input" => "0x",
"nonce" => "0x0",
"r" => "0x0",
"s" => "0x0",
"to" => "0x0",
"transactionIndex" => "0x0",
"v" => "0x0",
"value" => "0x0"
}
],
"transactionsRoot" => "0x0",
"uncles" => []
}
}
]}
end)
end
log_bad_gateway(
fn -> EthereumJSONRPC.fetch_blocks_by_tag("earliest", json_rpc_named_arguments) end,
fn result ->
assert {:ok, %Blocks{blocks_params: [_ | _], transactions_params: [_ | _]}} = result
end
)
end
@tag capture_log: false @tag capture_log: false
test "with latest", %{json_rpc_named_arguments: json_rpc_named_arguments} do test "with all supported tags", %{json_rpc_named_arguments: json_rpc_named_arguments} do
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do for tag <- @supported_tags do
expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
block_number = "0x1" expect(EthereumJSONRPC.Mox, :json_rpc, fn [
block_hash = "0x29c850324e357f3c0c836d79860c5af55f7b651e5d7ee253c1af1b14908af49c" %{
transaction_hash = "0xa2e81bb56b55ba3dab2daf76501b50dfaad240cccb905dbf89d65c7a84a4a48e" id: id,
method: "eth_getBlockByNumber",
{:ok, params: [^tag, false]
[ }
%{ ],
id: id, _options ->
result: %{ block_response(id, tag == "pending", "0x1")
"difficulty" => "0x0", end)
"gasLimit" => "0x0",
"gasUsed" => "0x0",
"hash" => block_hash,
"extraData" => "0x0",
"logsBloom" => "0x0",
"miner" => "0x0",
"number" => block_number,
"parentHash" => "0x0",
"receiptsRoot" => "0x0",
"size" => "0x0",
"sha3Uncles" => "0x0",
"stateRoot" => "0x0",
"timestamp" => "0x0",
"totalDifficulty" => "0x0",
"transactions" => [
%{
"blockHash" => block_hash,
"blockNumber" => block_number,
"from" => "0x0",
"gas" => "0x0",
"gasPrice" => "0x0",
"hash" => transaction_hash,
"input" => "0x",
"nonce" => "0x0",
"r" => "0x0",
"s" => "0x0",
"to" => "0x0",
"transactionIndex" => "0x0",
"v" => "0x0",
"value" => "0x0"
}
],
"transactionsRoot" => "0x0",
"uncles" => []
}
}
]}
end)
end
log_bad_gateway(
fn -> EthereumJSONRPC.fetch_blocks_by_tag("latest", json_rpc_named_arguments) end,
fn result ->
{:ok, %Blocks{blocks_params: [_ | _], transactions_params: [_ | _]}} = result
end end
)
end
@tag capture_log: false
test "with pending", %{json_rpc_named_arguments: json_rpc_named_arguments} do
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options ->
block_number = "0x1"
block_hash = "0x29c850324e357f3c0c836d79860c5af55f7b651e5d7ee253c1af1b14908af49c"
transaction_hash = "0xa2e81bb56b55ba3dab2daf76501b50dfaad240cccb905dbf89d65c7a84a4a48e"
{:ok, log_bad_gateway(
[ fn -> EthereumJSONRPC.fetch_block_by_tag(tag, json_rpc_named_arguments) end,
%{ fn result ->
id: id, {:ok, %Blocks{blocks_params: [_ | _], transactions_params: []}} = result
result: %{ end
"difficulty" => "0x0", )
"gasLimit" => "0x0",
"gasUsed" => "0x0",
"hash" => block_hash,
"extraData" => "0x0",
"logsBloom" => "0x0",
"miner" => "0x0",
"number" => block_number,
"parentHash" => "0x0",
"receiptsRoot" => "0x0",
"size" => "0x0",
"sha3Uncles" => "0x0",
"stateRoot" => "0x0",
"timestamp" => "0x0",
"totalDifficulty" => "0x0",
"transactions" => [
%{
"blockHash" => block_hash,
"blockNumber" => block_number,
"from" => "0x0",
"gas" => "0x0",
"gasPrice" => "0x0",
"hash" => transaction_hash,
"input" => "0x",
"nonce" => "0x0",
"r" => "0x0",
"s" => "0x0",
"to" => "0x0",
"transactionIndex" => "0x0",
"v" => "0x0",
"value" => "0x0"
}
],
"transactionsRoot" => "0x0",
"uncles" => []
}
}
]}
end)
end end
log_bad_gateway(
fn -> EthereumJSONRPC.fetch_blocks_by_tag("pending", json_rpc_named_arguments) end,
fn result ->
{:ok, %Blocks{blocks_params: [_ | _], transactions_params: [_ | _]}} = result
end
)
end end
test "unknown errors are returned", %{json_rpc_named_arguments: json_rpc_named_arguments} do test "unknown errors are returned", %{json_rpc_named_arguments: json_rpc_named_arguments} do
@ -740,66 +582,54 @@ defmodule EthereumJSONRPCTest do
{:error, unknown_error} {:error, unknown_error}
end) end)
assert {:error, ^unknown_error} = assert {:error, ^unknown_error} = EthereumJSONRPC.fetch_block_by_tag("latest", moxed_json_rpc_named_arguments)
EthereumJSONRPC.fetch_block_number_by_tag("latest", moxed_json_rpc_named_arguments)
end end
end end
describe "fetch_block_number_by_tag" do describe "fetch_block_number_by_tag" do
@tag capture_log: false @supported_tags %{"earliest" => "0x0", "latest" => "0x1", "pending" => nil}
test "with earliest", %{json_rpc_named_arguments: json_rpc_named_arguments} do
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options ->
{:ok, %{"number" => "0x0"}}
end)
end
log_bad_gateway(
fn -> EthereumJSONRPC.fetch_block_number_by_tag("earliest", json_rpc_named_arguments) end,
fn result ->
assert {:ok, 0} = result
end
)
end
@tag capture_log: false @tag capture_log: false
test "with latest", %{json_rpc_named_arguments: json_rpc_named_arguments} do test "with all supported tags", %{json_rpc_named_arguments: json_rpc_named_arguments} do
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do for {tag, expected_result} <- @supported_tags do
expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options -> if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do
{:ok, %{"number" => "0x1"}} expect(EthereumJSONRPC.Mox, :json_rpc, fn [
end) %{
end id: id,
method: "eth_getBlockByNumber",
log_bad_gateway( params: [^tag, false]
fn -> EthereumJSONRPC.fetch_block_number_by_tag("latest", json_rpc_named_arguments) end, }
fn result -> ],
assert {:ok, number} = result _options ->
assert number > 0 if tag == "pending" do
{:ok, [%{id: id, result: nil}]}
else
block_response(id, false, expected_result)
end
end)
end end
)
end
@tag capture_log: false log_bad_gateway(
test "with pending", %{json_rpc_named_arguments: json_rpc_named_arguments} do fn -> EthereumJSONRPC.fetch_block_number_by_tag(tag, json_rpc_named_arguments) end,
if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do if tag == "pending" do
expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options -> fn
{:ok, nil} # Parity after https://github.com/paritytech/parity-ethereum/pull/8281 and anything spec-compliant
end) {:error, reason} ->
assert reason == :not_found
# Parity before https://github.com/paritytech/parity-ethereum/pull/8281
{:ok, number} ->
assert is_integer(number)
assert number > 0
end
else
fn result ->
integer_result = expected_result && quantity_to_integer(expected_result)
assert {:ok, ^integer_result} = result
end
end
)
end end
log_bad_gateway(
fn -> EthereumJSONRPC.fetch_block_number_by_tag("pending", json_rpc_named_arguments) end,
fn
# Parity after https://github.com/paritytech/parity-ethereum/pull/8281 and anything spec-compliant
{:error, reason} ->
assert reason == :not_found
# Parity before https://github.com/paritytech/parity-ethereum/pull/8281
{:ok, number} ->
assert is_integer(number)
assert number > 0
end
)
end end
test "unknown errors are returned", %{json_rpc_named_arguments: json_rpc_named_arguments} do test "unknown errors are returned", %{json_rpc_named_arguments: json_rpc_named_arguments} do
@ -1114,12 +944,43 @@ defmodule EthereumJSONRPCTest do
:ok :ok
end end
end end
defp block_response(id, pending, block_number) do
block_hash = "0x29c850324e357f3c0c836d79860c5af55f7b651e5d7ee253c1af1b14908af49c"
transaction_hash = "0xa2e81bb56b55ba3dab2daf76501b50dfaad240cccb905dbf89d65c7a84a4a48e"
{:ok,
[
%{
id: id,
result: %{
"difficulty" => "0x0",
"gasLimit" => "0x0",
"gasUsed" => "0x0",
"hash" => if(pending, do: nil, else: block_hash),
"extraData" => "0x0",
"logsBloom" => "0x0",
"miner" => "0x0",
"number" => block_number,
"parentHash" => "0x0",
"receiptsRoot" => "0x0",
"size" => "0x0",
"sha3Uncles" => "0x0",
"stateRoot" => "0x0",
"timestamp" => "0x0",
"totalDifficulty" => "0x0",
"transactions" => [transaction_hash],
"transactionsRoot" => "0x0",
"uncles" => []
}
}
]}
end
end end
defmodule EthereumJSONRPCSyncTest do defmodule EthereumJSONRPCSyncTest do
use EthereumJSONRPC.Case, async: false use EthereumJSONRPC.Case, async: false
import EthereumJSONRPC.Case
import Mox import Mox
alias EthereumJSONRPC.FetchedBalances alias EthereumJSONRPC.FetchedBalances

@ -10,6 +10,8 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do
from: 2 from: 2
] ]
alias EthereumJSONRPC.Blocks
alias Explorer.Chain.{ alias Explorer.Chain.{
Block, Block,
Wei Wei
@ -25,8 +27,17 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do
ttl_check_interval: :timer.seconds(1), ttl_check_interval: :timer.seconds(1),
callback: &async_task_on_deletion(&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
"""
@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}}
def get_average_gas_price(num_of_blocks, safelow_percentile, average_percentile, fast_percentile) do def get_average_gas_price(num_of_blocks, safelow_percentile, average_percentile, fast_percentile) do
latest_gas_price_query = safelow_percentile_fraction = safelow_percentile / 100
average_percentile_fraction = average_percentile / 100
fast_percentile_fraction = fast_percentile / 100
fee_query =
from( from(
block in Block, block in Block,
left_join: transaction in assoc(block, :transactions), left_join: transaction in assoc(block, :transactions),
@ -35,27 +46,48 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do
where: transaction.gas_price > ^0, where: transaction.gas_price > ^0,
group_by: block.number, group_by: block.number,
order_by: [desc: block.number], order_by: [desc: block.number],
select: min(transaction.gas_price), select: %{
slow_gas_price:
fragment(
"percentile_disc(?) within group ( order by ? )",
^safelow_percentile_fraction,
transaction.gas_price
),
average_gas_price:
fragment(
"percentile_disc(?) within group ( order by ? )",
^average_percentile_fraction,
transaction.gas_price
),
fast_gas_price:
fragment(
"percentile_disc(?) within group ( order by ? )",
^fast_percentile_fraction,
transaction.gas_price
),
slow:
fragment(
"percentile_disc(?) within group ( order by ? )",
^safelow_percentile_fraction,
transaction.max_priority_fee_per_gas
),
average:
fragment(
"percentile_disc(?) within group ( order by ? )",
^average_percentile_fraction,
transaction.max_priority_fee_per_gas
),
fast:
fragment(
"percentile_disc(?) within group ( order by ? )",
^fast_percentile_fraction,
transaction.max_priority_fee_per_gas
)
},
limit: ^num_of_blocks limit: ^num_of_blocks
) )
latest_gas_prices = gas_prices = fee_query |> Repo.all(timeout: :infinity) |> process_fee_data_from_db()
latest_gas_price_query
|> Repo.all(timeout: :infinity)
latest_ordered_gas_prices =
latest_gas_prices
|> Enum.map(fn %Wei{value: gas_price} -> Decimal.to_integer(gas_price) end)
safelow_gas_price = gas_price_percentile_to_gwei(latest_ordered_gas_prices, safelow_percentile)
average_gas_price = gas_price_percentile_to_gwei(latest_ordered_gas_prices, average_percentile)
fast_gas_price = gas_price_percentile_to_gwei(latest_ordered_gas_prices, fast_percentile)
gas_prices = %{
"slow" => safelow_gas_price,
"average" => average_gas_price,
"fast" => fast_gas_price
}
{:ok, gas_prices} {:ok, gas_prices}
catch catch
@ -63,6 +95,69 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do
{:error, error} {:error, error}
end end
defp process_fee_data_from_db([]) do
%{
"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)
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) ->
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)
}
_ ->
{gas_price(slow_gas_price), gas_price(average_gas_price), gas_price(fast_gas_price)}
end
%{
"slow" => slow_fee,
"average" => average_fee,
"fast" => fast_fee
}
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)
end
defp gas_price(value) do
value |> Wei.from(:wei) |> Wei.to(:gwei) |> Decimal.to_float() |> Float.ceil(2)
end
defp num_of_blocks, do: Application.get_env(:explorer, __MODULE__)[:num_of_blocks] defp num_of_blocks, do: Application.get_env(:explorer, __MODULE__)[:num_of_blocks]
defp safelow, do: Application.get_env(:explorer, __MODULE__)[:safelow_percentile] defp safelow, do: Application.get_env(:explorer, __MODULE__)[:safelow_percentile]
@ -102,40 +197,6 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do
{:update, task} {:update, task}
end end
defp gas_price_percentile_to_gwei(gas_prices, percentile) do
gas_price_wei = percentile(gas_prices, percentile)
if gas_price_wei do
gas_price_gwei = Wei.to(%Wei{value: Decimal.from_float(gas_price_wei)}, :gwei)
gas_price_gwei_float = gas_price_gwei |> Decimal.to_float()
if gas_price_gwei_float > 0.01 do
gas_price_gwei_float
|> Float.ceil(2)
else
gas_price_gwei_float
end
else
nil
end
end
@spec percentile(list, number) :: number | nil
defp percentile([], _), do: nil
defp percentile([x], _), do: x
defp percentile(list, 0), do: Enum.min(list)
defp percentile(list, 100), do: Enum.max(list)
defp percentile(list, n) when is_list(list) and is_number(n) do
s = Enum.sort(list)
r = n / 100.0 * (length(list) - 1)
f = :erlang.trunc(r)
lower = Enum.at(s, f)
upper = Enum.at(s, f + 1)
lower + (upper - lower) * (r - f)
end
# By setting this as a `callback` an async task will be started each time the # By setting this as a `callback` an async task will be started each time the
# `gas_prices` expires (unless there is one already running) # `gas_prices` expires (unless there is one already running)
defp async_task_on_deletion({:delete, _, :gas_prices}), do: get_async_task() defp async_task_on_deletion({:delete, _, :gas_prices}), do: get_async_task()

@ -1,10 +1,53 @@
defmodule Explorer.Chain.Cache.GasPriceOracleTest do defmodule Explorer.Chain.Cache.GasPriceOracleTest do
use Explorer.DataCase use Explorer.DataCase
import Mox
alias Explorer.Chain.Cache.GasPriceOracle alias Explorer.Chain.Cache.GasPriceOracle
@block %{
"difficulty" => "0x0",
"gasLimit" => "0x0",
"gasUsed" => "0x0",
"hash" => "0x29c850324e357f3c0c836d79860c5af55f7b651e5d7ee253c1af1b14908af49c",
"extraData" => "0x0",
"logsBloom" => "0x0",
"miner" => "0x0",
"number" => "0x1",
"parentHash" => "0x0",
"receiptsRoot" => "0x0",
"size" => "0x0",
"sha3Uncles" => "0x0",
"stateRoot" => "0x0",
"timestamp" => "0x0",
"baseFeePerGas" => "0x1DCD6500",
"totalDifficulty" => "0x0",
"transactions" => [
%{
"blockHash" => "0x29c850324e357f3c0c836d79860c5af55f7b651e5d7ee253c1af1b14908af49c",
"blockNumber" => "0x1",
"from" => "0x0",
"gas" => "0x0",
"gasPrice" => "0x0",
"hash" => "0xa2e81bb56b55ba3dab2daf76501b50dfaad240cccb905dbf89d65c7a84a4a48e",
"input" => "0x",
"nonce" => "0x0",
"r" => "0x0",
"s" => "0x0",
"to" => "0x0",
"transactionIndex" => "0x0",
"v" => "0x0",
"value" => "0x0"
}
],
"transactionsRoot" => "0x0",
"uncles" => []
}
describe "get_average_gas_price/4" do describe "get_average_gas_price/4" do
test "returns nil percentile values if no blocks in the DB" 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, assert {:ok,
%{ %{
"slow" => nil, "slow" => nil,
@ -14,6 +57,8 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do
end end
test "returns nil percentile values if blocks are empty in the DB" do test "returns nil percentile values if blocks are empty in the DB" do
expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end)
insert(:block) insert(:block)
insert(:block) insert(:block)
insert(:block) insert(:block)
@ -27,6 +72,8 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do
end end
test "returns nil percentile values for blocks with failed txs in the DB" do test "returns nil percentile values for blocks with failed txs in the DB" do
expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end)
block = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391") block = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391")
:transaction :transaction
@ -51,6 +98,8 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do
end end
test "returns nil percentile values for transactions with 0 gas price aka 'whitelisted transactions' in the DB" do test "returns nil percentile values for transactions with 0 gas price aka 'whitelisted transactions' in the DB" do
expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end)
block1 = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391") block1 = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391")
block2 = insert(:block, number: 101, hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729") block2 = insert(:block, number: 101, hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729")
@ -87,6 +136,8 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do
end end
test "returns the same percentile values if gas price is the same over transactions" do test "returns the same percentile values if gas price is the same over transactions" do
expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end)
block1 = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391") block1 = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391")
block2 = insert(:block, number: 101, hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729") block2 = insert(:block, number: 101, hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729")
@ -123,6 +174,8 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do
end end
test "returns correct min gas price from the block" do test "returns correct min gas price from the block" do
expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end)
block1 = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391") block1 = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391")
block2 = insert(:block, number: 101, hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729") block2 = insert(:block, number: 101, hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729")
@ -165,12 +218,14 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do
assert {:ok, assert {:ok,
%{ %{
"slow" => 1.0, "slow" => 1.0,
"average" => 1.0, "average" => 2.0,
"fast" => 1.0 "fast" => 2.0
}} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) }} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
end end
test "returns correct average percentile" do test "returns correct average percentile" do
expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end)
block1 = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391") block1 = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391")
block2 = insert(:block, number: 101, hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729") block2 = insert(:block, number: 101, hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729")
block3 = insert(:block, number: 102, hash: "0x659b2a1cc4dd1a5729900cf0c81c471d1c7891b2517bf9466f7fba56ead2fca0") block3 = insert(:block, number: 102, hash: "0x659b2a1cc4dd1a5729900cf0c81c471d1c7891b2517bf9466f7fba56ead2fca0")
@ -213,7 +268,64 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do
assert {:ok, assert {:ok,
%{ %{
"average" => 4.0 "average" => 3.34
}} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
end
test "returns correct gas price for EIP-1559 transactions" do
expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end)
block1 = insert(:block, number: 100, hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391")
block2 = insert(:block, number: 101, hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729")
: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"
)
assert {:ok,
%{
# including base fee
"slow" => 1.5,
"average" => 2.5,
"fast" => 2.5
}} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) }} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90)
end end
end end

@ -50,37 +50,42 @@ defmodule Indexer.Block.Catchup.BoundIntervalSupervisorTest do
EthereumJSONRPC.Mox EthereumJSONRPC.Mox
|> stub(:json_rpc, fn |> stub(:json_rpc, fn
# latest block number to seed starting block number for genesis and realtime tasks # latest block number to seed starting block number for genesis and realtime tasks
%{method: "eth_getBlockByNumber", params: ["latest", false]}, _options -> [%{id: id, method: "eth_getBlockByNumber", params: ["latest", false]}], _options ->
{:ok, {:ok,
%{ [
"author" => "0xe2ac1c6843a33f81ae4935e5ef1277a392990381", %{
"difficulty" => "0xfffffffffffffffffffffffffffffffe", id: id,
"extraData" => "0xd583010a068650617269747986312e32362e32826c69", result: %{
"gasLimit" => "0x7a1200", "author" => "0xe2ac1c6843a33f81ae4935e5ef1277a392990381",
"gasUsed" => "0x0", "difficulty" => "0xfffffffffffffffffffffffffffffffe",
"hash" => "0x627baabf5a17c0cfc547b6903ac5e19eaa91f30d9141be1034e3768f6adbc94e", "extraData" => "0xd583010a068650617269747986312e32362e32826c69",
"logsBloom" => "gasLimit" => "0x7a1200",
"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "gasUsed" => "0x0",
"miner" => "0xe2ac1c6843a33f81ae4935e5ef1277a392990381", "hash" => "0x627baabf5a17c0cfc547b6903ac5e19eaa91f30d9141be1034e3768f6adbc94e",
"number" => block_quantity, "logsBloom" =>
"parentHash" => "0x006edcaa1e6fde822908783bc4ef1ad3675532d542fce53537557391cfe34c3c", "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "miner" => "0xe2ac1c6843a33f81ae4935e5ef1277a392990381",
"sealFields" => [ "number" => block_quantity,
"0x841240b30d", "parentHash" => "0x006edcaa1e6fde822908783bc4ef1ad3675532d542fce53537557391cfe34c3c",
"0xb84158bc4fa5891934bc94c5dca0301867ce4f35925ef46ea187496162668210bba61b4cda09d7e0dca2f1dd041fad498ced6697aeef72656927f52c55b630f2591c01" "receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
], "sealFields" => [
"sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", "0x841240b30d",
"signature" => "0xb84158bc4fa5891934bc94c5dca0301867ce4f35925ef46ea187496162668210bba61b4cda09d7e0dca2f1dd041fad498ced6697aeef72656927f52c55b630f2591c01"
"58bc4fa5891934bc94c5dca0301867ce4f35925ef46ea187496162668210bba61b4cda09d7e0dca2f1dd041fad498ced6697aeef72656927f52c55b630f2591c01", ],
"size" => "0x243", "sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"stateRoot" => "0x9a8111062667f7b162851a1cbbe8aece5ff12e761b3dcee93b787fcc12548cf7", "signature" =>
"step" => "306230029", "58bc4fa5891934bc94c5dca0301867ce4f35925ef46ea187496162668210bba61b4cda09d7e0dca2f1dd041fad498ced6697aeef72656927f52c55b630f2591c01",
"timestamp" => "0x5b437f41", "size" => "0x243",
"totalDifficulty" => "0x342337ffffffffffffffffffffffffed8d29bb", "stateRoot" => "0x9a8111062667f7b162851a1cbbe8aece5ff12e761b3dcee93b787fcc12548cf7",
"transactions" => [], "step" => "306230029",
"transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "timestamp" => "0x5b437f41",
"uncles" => [] "totalDifficulty" => "0x342337ffffffffffffffffffffffffed8d29bb",
}} "transactions" => [],
"transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"uncles" => []
}
}
]}
[%{method: "trace_block"} | _] = requests, _options -> [%{method: "trace_block"} | _] = requests, _options ->
{:ok, Enum.map(requests, fn %{id: id} -> %{id: id, result: []} end)} {:ok, Enum.map(requests, fn %{id: id} -> %{id: id, result: []} end)}

Loading…
Cancel
Save