From a3322e693d29588c68dcccf3e484f49cb464be09 Mon Sep 17 00:00:00 2001 From: sl1depengwyn Date: Thu, 29 Sep 2022 17:47:32 +0300 Subject: [PATCH] Take into account EIP-1559 in gas price oracle --- apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex | 9 +- .../lib/ethereum_jsonrpc/block/by_tag.ex | 5 + .../test/ethereum_jsonrpc/mox_test.exs | 11 +- .../test/ethereum_jsonrpc_test.exs | 325 +++++------------- .../explorer/chain/cache/gas_price_oracle.ex | 167 ++++++--- .../chain/cache/gas_price_oracle_test.exs | 118 ++++++- .../bound_interval_supervisor_test.exs | 65 ++-- 7 files changed, 375 insertions(+), 325 deletions(-) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex index 6a1aada7c6..4a59a80e9c 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex @@ -278,9 +278,9 @@ defmodule EthereumJSONRPC do @doc """ 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()} - 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}] |> fetch_blocks_by_params(&Block.ByTag.request/1, json_rpc_named_arguments) end @@ -320,9 +320,8 @@ defmodule EthereumJSONRPC do @spec fetch_block_number_by_tag(tag(), json_rpc_named_arguments) :: {: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 - %{id: 0, tag: tag} - |> Block.ByTag.request() - |> json_rpc(json_rpc_named_arguments) + tag + |> fetch_block_by_tag(json_rpc_named_arguments) |> Block.ByTag.number_from_result() end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block/by_tag.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block/by_tag.ex index fa4a9b83c9..d257f4e506 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block/by_tag.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block/by_tag.ex @@ -5,6 +5,7 @@ defmodule EthereumJSONRPC.Block.ByTag do """ import EthereumJSONRPC, only: [quantity_to_integer: 1] + alias EthereumJSONRPC.Blocks def request(%{id: id, tag: tag}) when is_binary(tag) do EthereumJSONRPC.request(%{id: id, method: "eth_getBlockByNumber", params: [tag, false]}) @@ -16,6 +17,10 @@ defmodule EthereumJSONRPC.Block.ByTag do {:ok, quantity_to_integer(quantity)} 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({:error, %{"code" => -32602}}), do: {:error, :invalid_tag} diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/mox_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/mox_test.exs index 2ad5b8f52c..f0c4fbaf2d 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/mox_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/mox_test.exs @@ -21,8 +21,15 @@ defmodule EthereumJSONRPC.MoxTest do describe "fetch_block_number_by_tag/2" do test "with pending with null result", %{json_rpc_named_arguments: json_rpc_named_arguments} do - expect(EthereumJSONRPC.Mox, :json_rpc, fn _json, _options -> - {:ok, nil} + expect(EthereumJSONRPC.Mox, :json_rpc, fn [ + %{ + id: id, + method: "eth_getBlockByNumber", + params: ["pending", false] + } + ], + _options -> + {:ok, [%{id: id, result: nil}]} end) assert {:error, :not_found} = EthereumJSONRPC.fetch_block_number_by_tag("pending", json_rpc_named_arguments) diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs index 629e40c414..7299d48478 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc_test.exs @@ -2,6 +2,7 @@ defmodule EthereumJSONRPCTest do use EthereumJSONRPC.Case, async: true import EthereumJSONRPC.Case + import EthereumJSONRPC, only: [quantity_to_integer: 1] import Mox alias EthereumJSONRPC.{Blocks, FetchedBalances, FetchedBeneficiaries, FetchedCodes, Subscription} @@ -543,191 +544,32 @@ defmodule EthereumJSONRPCTest do end end - describe "fetch_blocks_by_tag/2" do - @tag capture_log: false - 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 + describe "fetch_block_by_tag/2" do + @supported_tags ~w(earliest latest pending) @tag capture_log: false - test "with latest", %{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, - [ - %{ - 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("latest", json_rpc_named_arguments) end, - fn result -> - {:ok, %Blocks{blocks_params: [_ | _], transactions_params: [_ | _]}} = result + test "with all supported tags", %{json_rpc_named_arguments: json_rpc_named_arguments} do + for tag <- @supported_tags do + if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do + expect(EthereumJSONRPC.Mox, :json_rpc, fn [ + %{ + id: id, + method: "eth_getBlockByNumber", + params: [^tag, false] + } + ], + _options -> + block_response(id, tag == "pending", "0x1") + 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, - [ - %{ - 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) + log_bad_gateway( + fn -> EthereumJSONRPC.fetch_block_by_tag(tag, json_rpc_named_arguments) end, + fn result -> + {:ok, %Blocks{blocks_params: [_ | _], transactions_params: []}} = result + 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 test "unknown errors are returned", %{json_rpc_named_arguments: json_rpc_named_arguments} do @@ -740,66 +582,54 @@ defmodule EthereumJSONRPCTest do {:error, unknown_error} end) - assert {:error, ^unknown_error} = - EthereumJSONRPC.fetch_block_number_by_tag("latest", moxed_json_rpc_named_arguments) + assert {:error, ^unknown_error} = EthereumJSONRPC.fetch_block_by_tag("latest", moxed_json_rpc_named_arguments) end end describe "fetch_block_number_by_tag" do - @tag capture_log: false - 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 + @supported_tags %{"earliest" => "0x0", "latest" => "0x1", "pending" => nil} @tag capture_log: false - test "with latest", %{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" => "0x1"}} - end) - end - - log_bad_gateway( - fn -> EthereumJSONRPC.fetch_block_number_by_tag("latest", json_rpc_named_arguments) end, - fn result -> - assert {:ok, number} = result - assert number > 0 + test "with all supported tags", %{json_rpc_named_arguments: json_rpc_named_arguments} do + for {tag, expected_result} <- @supported_tags do + if json_rpc_named_arguments[:transport] == EthereumJSONRPC.Mox do + expect(EthereumJSONRPC.Mox, :json_rpc, fn [ + %{ + id: id, + method: "eth_getBlockByNumber", + params: [^tag, false] + } + ], + _options -> + if tag == "pending" do + {:ok, [%{id: id, result: nil}]} + else + block_response(id, false, expected_result) + end + 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 _json, _options -> - {:ok, nil} - end) + log_bad_gateway( + fn -> EthereumJSONRPC.fetch_block_number_by_tag(tag, json_rpc_named_arguments) end, + if tag == "pending" do + 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 + else + fn result -> + integer_result = expected_result && quantity_to_integer(expected_result) + assert {:ok, ^integer_result} = result + 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 test "unknown errors are returned", %{json_rpc_named_arguments: json_rpc_named_arguments} do @@ -1114,12 +944,43 @@ defmodule EthereumJSONRPCTest do :ok 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 defmodule EthereumJSONRPCSyncTest do use EthereumJSONRPC.Case, async: false - import EthereumJSONRPC.Case import Mox alias EthereumJSONRPC.FetchedBalances diff --git a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex index 4315bd7b5d..60111725e1 100644 --- a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex +++ b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex @@ -10,6 +10,8 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do from: 2 ] + alias EthereumJSONRPC.Blocks + alias Explorer.Chain.{ Block, Wei @@ -25,8 +27,17 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do 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 + """ + @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 - 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( block in Block, left_join: transaction in assoc(block, :transactions), @@ -35,27 +46,48 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do where: transaction.gas_price > ^0, group_by: 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 ) - latest_gas_prices = - 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 - } + gas_prices = fee_query |> Repo.all(timeout: :infinity) |> process_fee_data_from_db() {:ok, gas_prices} catch @@ -63,6 +95,69 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do {:error, error} 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 safelow, do: Application.get_env(:explorer, __MODULE__)[:safelow_percentile] @@ -102,40 +197,6 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do {:update, task} 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 # `gas_prices` expires (unless there is one already running) defp async_task_on_deletion({:delete, _, :gas_prices}), do: get_async_task() diff --git a/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs b/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs index 630301488a..d7004d11d0 100644 --- a/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs +++ b/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs @@ -1,10 +1,53 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do use Explorer.DataCase + import Mox + 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 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, @@ -14,6 +57,8 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do end 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) @@ -27,6 +72,8 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do end 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") :transaction @@ -51,6 +98,8 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do end 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") block2 = insert(:block, number: 101, hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729") @@ -87,6 +136,8 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do end 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") block2 = insert(:block, number: 101, hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729") @@ -123,6 +174,8 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do end 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") block2 = insert(:block, number: 101, hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729") @@ -165,12 +218,14 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do assert {:ok, %{ "slow" => 1.0, - "average" => 1.0, - "fast" => 1.0 + "average" => 2.0, + "fast" => 2.0 }} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) end 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") block2 = insert(:block, number: 101, hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729") block3 = insert(:block, number: 102, hash: "0x659b2a1cc4dd1a5729900cf0c81c471d1c7891b2517bf9466f7fba56ead2fca0") @@ -213,7 +268,64 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do 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) end end diff --git a/apps/indexer/test/indexer/block/catchup/bound_interval_supervisor_test.exs b/apps/indexer/test/indexer/block/catchup/bound_interval_supervisor_test.exs index 77f7a63512..774ebfe3a5 100644 --- a/apps/indexer/test/indexer/block/catchup/bound_interval_supervisor_test.exs +++ b/apps/indexer/test/indexer/block/catchup/bound_interval_supervisor_test.exs @@ -50,37 +50,42 @@ defmodule Indexer.Block.Catchup.BoundIntervalSupervisorTest do EthereumJSONRPC.Mox |> stub(:json_rpc, fn # 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, - %{ - "author" => "0xe2ac1c6843a33f81ae4935e5ef1277a392990381", - "difficulty" => "0xfffffffffffffffffffffffffffffffe", - "extraData" => "0xd583010a068650617269747986312e32362e32826c69", - "gasLimit" => "0x7a1200", - "gasUsed" => "0x0", - "hash" => "0x627baabf5a17c0cfc547b6903ac5e19eaa91f30d9141be1034e3768f6adbc94e", - "logsBloom" => - "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "miner" => "0xe2ac1c6843a33f81ae4935e5ef1277a392990381", - "number" => block_quantity, - "parentHash" => "0x006edcaa1e6fde822908783bc4ef1ad3675532d542fce53537557391cfe34c3c", - "receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "sealFields" => [ - "0x841240b30d", - "0xb84158bc4fa5891934bc94c5dca0301867ce4f35925ef46ea187496162668210bba61b4cda09d7e0dca2f1dd041fad498ced6697aeef72656927f52c55b630f2591c01" - ], - "sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "signature" => - "58bc4fa5891934bc94c5dca0301867ce4f35925ef46ea187496162668210bba61b4cda09d7e0dca2f1dd041fad498ced6697aeef72656927f52c55b630f2591c01", - "size" => "0x243", - "stateRoot" => "0x9a8111062667f7b162851a1cbbe8aece5ff12e761b3dcee93b787fcc12548cf7", - "step" => "306230029", - "timestamp" => "0x5b437f41", - "totalDifficulty" => "0x342337ffffffffffffffffffffffffed8d29bb", - "transactions" => [], - "transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "uncles" => [] - }} + [ + %{ + id: id, + result: %{ + "author" => "0xe2ac1c6843a33f81ae4935e5ef1277a392990381", + "difficulty" => "0xfffffffffffffffffffffffffffffffe", + "extraData" => "0xd583010a068650617269747986312e32362e32826c69", + "gasLimit" => "0x7a1200", + "gasUsed" => "0x0", + "hash" => "0x627baabf5a17c0cfc547b6903ac5e19eaa91f30d9141be1034e3768f6adbc94e", + "logsBloom" => + "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "miner" => "0xe2ac1c6843a33f81ae4935e5ef1277a392990381", + "number" => block_quantity, + "parentHash" => "0x006edcaa1e6fde822908783bc4ef1ad3675532d542fce53537557391cfe34c3c", + "receiptsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "sealFields" => [ + "0x841240b30d", + "0xb84158bc4fa5891934bc94c5dca0301867ce4f35925ef46ea187496162668210bba61b4cda09d7e0dca2f1dd041fad498ced6697aeef72656927f52c55b630f2591c01" + ], + "sha3Uncles" => "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "signature" => + "58bc4fa5891934bc94c5dca0301867ce4f35925ef46ea187496162668210bba61b4cda09d7e0dca2f1dd041fad498ced6697aeef72656927f52c55b630f2591c01", + "size" => "0x243", + "stateRoot" => "0x9a8111062667f7b162851a1cbbe8aece5ff12e761b3dcee93b787fcc12548cf7", + "step" => "306230029", + "timestamp" => "0x5b437f41", + "totalDifficulty" => "0x342337ffffffffffffffffffffffffed8d29bb", + "transactions" => [], + "transactionsRoot" => "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "uncles" => [] + } + } + ]} [%{method: "trace_block"} | _] = requests, _options -> {:ok, Enum.map(requests, fn %{id: id} -> %{id: id, result: []} end)}