diff --git a/.dialyzer-ignore b/.dialyzer-ignore
index bf2a526f58..4e3f681c19 100644
--- a/.dialyzer-ignore
+++ b/.dialyzer-ignore
@@ -30,3 +30,4 @@ lib/explorer/smart_contract/verifier.ex:89
lib/block_scout_web/templates/address_contract/index.html.eex:117
lib/explorer/staking/stake_snapshotting.ex:14: Function do_snapshotting/6 has no local return
lib/explorer/staking/stake_snapshotting.ex:179
+lib/explorer/counters/address_gas_usage_counter.ex:69
diff --git a/CHANGELOG.md b/CHANGELOG.md
index eadf45322d..e6b5948b67 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,7 @@
## Current
### Features
+- [#3384](https://github.com/poanetwork/blockscout/pull/3384) - Address total gas usage
- [#3377](https://github.com/poanetwork/blockscout/pull/3377) - Add links to contract libraries
- [#2292](https://github.com/poanetwork/blockscout/pull/2292), [#3356](https://github.com/poanetwork/blockscout/pull/3356), [#3359](https://github.com/poanetwork/blockscout/pull/3359), [#3360](https://github.com/poanetwork/blockscout/pull/3360), [#3365](https://github.com/poanetwork/blockscout/pull/3365) - Add Web UI for POSDAO Staking DApp
- [#3354](https://github.com/poanetwork/blockscout/pull/3354) - Tx hash in EOA coin balance history
diff --git a/apps/block_scout_web/assets/js/pages/address.js b/apps/block_scout_web/assets/js/pages/address.js
index 2ed9ab2090..b8b0291232 100644
--- a/apps/block_scout_web/assets/js/pages/address.js
+++ b/apps/block_scout_web/assets/js/pages/address.js
@@ -21,6 +21,7 @@ export const initialState = {
balanceCard: null,
fetchedCoinBalanceBlockNumber: null,
transactionCount: null,
+ gasUsageCount: null,
validationCount: null,
countersFetched: false
}
@@ -41,6 +42,7 @@ export function reducer (state = initialState, action) {
case 'COUNTERS_FETCHED': {
return Object.assign({}, state, {
transactionCount: action.transactionCount,
+ gasUsageCount: action.gasUsageCount,
validationCount: action.validationCount,
countersFetched: true
})
@@ -111,6 +113,22 @@ const elements = {
}
}
},
+ '[data-selector="gas-usage-count"]': {
+ load ($el) {
+ return { gasUsageCount: numeral($el.text()).value() }
+ },
+ render ($el, state, oldState) {
+ if (state.countersFetched && state.gasUsageCount) {
+ if (oldState.gasUsageCount === state.gasUsageCount) return
+ $el.empty().append(numeral(state.gasUsageCount).format() + ' Gas used')
+ $el.show()
+ $el.parent('.address-detail-item').removeAttr('style')
+ } else {
+ $el.hide()
+ $el.parent('.address-detail-item').css('display', 'none')
+ }
+ }
+ },
'[data-selector="fetched-coin-balance-block-number"]': {
load ($el) {
return { fetchedCoinBalanceBlockNumber: numeral($el.text()).value() }
diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex
index b7ce5c8506..02afa62adb 100644
--- a/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex
+++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_controller.ex
@@ -4,7 +4,7 @@ defmodule BlockScoutWeb.AddressController do
import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1]
alias BlockScoutWeb.{AccessHelpers, AddressView}
- alias Explorer.Counters.AddressTransactionsCounter
+ alias Explorer.Counters.{AddressTransactionsCounter, AddressTransactionsGasUsageCounter}
alias Explorer.{Chain, Market}
alias Explorer.ExchangeRates.Token
alias Phoenix.View
@@ -84,9 +84,13 @@ defmodule BlockScoutWeb.AddressController do
def address_counters(conn, %{"id" => address_hash_string}) do
with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string),
{:ok, address} <- Chain.hash_to_address(address_hash) do
- {transaction_count, validation_count} = transaction_and_validation_count(address)
+ {transaction_count, gas_usage_count, validation_count} = transaction_and_validation_count(address)
- json(conn, %{transaction_count: transaction_count, validation_count: validation_count})
+ json(conn, %{
+ transaction_count: transaction_count,
+ gas_usage_count: gas_usage_count,
+ validation_count: validation_count
+ })
else
_ -> not_found(conn)
end
@@ -98,12 +102,17 @@ defmodule BlockScoutWeb.AddressController do
transaction_count(address)
end)
+ gas_usage_count_task =
+ Task.async(fn ->
+ gas_usage_count(address)
+ end)
+
validation_count_task =
Task.async(fn ->
validation_count(address)
end)
- [transaction_count_task, validation_count_task]
+ [transaction_count_task, gas_usage_count_task, validation_count_task]
|> Task.yield_many(:timer.seconds(60))
|> Enum.map(fn {_task, res} ->
case res do
@@ -124,6 +133,10 @@ defmodule BlockScoutWeb.AddressController do
AddressTransactionsCounter.fetch(address)
end
+ def gas_usage_count(address) do
+ AddressTransactionsGasUsageCounter.fetch(address)
+ end
+
defp validation_count(address) do
Chain.address_to_validation_count(address.hash)
end
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex
index bf49e576f2..c82671ec59 100644
--- a/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex
+++ b/apps/block_scout_web/lib/block_scout_web/templates/address/overview.html.eex
@@ -102,6 +102,10 @@
+
+
+
+
<%= if @address.fetched_coin_balance_block_number do %>
<%= gettext("Last Balance Update: Block #") %><%= @address.fetched_coin_balance_block_number %>
diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot
index f7b1ea11aa..58697dbcfa 100644
--- a/apps/block_scout_web/priv/gettext/default.pot
+++ b/apps/block_scout_web/priv/gettext/default.pot
@@ -463,7 +463,7 @@ msgid "Create2"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address/overview.html.eex:122
+#: lib/block_scout_web/templates/address/overview.html.eex:126
msgid "Created by"
msgstr ""
@@ -625,7 +625,7 @@ msgid "Error: (Awaiting internal transactions for reason)"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address/overview.html.eex:135
+#: lib/block_scout_web/templates/address/overview.html.eex:139
msgid "Error: Could not determine contract creator."
msgstr ""
@@ -960,7 +960,7 @@ msgid "It could still be in the TX Pool of a different node, waiting to be broad
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address/overview.html.eex:107
+#: lib/block_scout_web/templates/address/overview.html.eex:111
msgid "Last Balance Update: Block #"
msgstr ""
@@ -1173,7 +1173,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:59
-#: lib/block_scout_web/templates/address/overview.html.eex:159
+#: lib/block_scout_web/templates/address/overview.html.eex:163
#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:51
#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:101
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:36
@@ -1658,7 +1658,7 @@ msgid "Yes"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address/overview.html.eex:127
+#: lib/block_scout_web/templates/address/overview.html.eex:131
msgid "at"
msgstr ""
@@ -1716,8 +1716,8 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37
-#: lib/block_scout_web/templates/address/overview.html.eex:160
-#: lib/block_scout_web/templates/address/overview.html.eex:168
+#: lib/block_scout_web/templates/address/overview.html.eex:164
+#: lib/block_scout_web/templates/address/overview.html.eex:172
#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:102
#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:110
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:122
diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
index f7b1ea11aa..58697dbcfa 100644
--- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
+++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
@@ -463,7 +463,7 @@ msgid "Create2"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address/overview.html.eex:122
+#: lib/block_scout_web/templates/address/overview.html.eex:126
msgid "Created by"
msgstr ""
@@ -625,7 +625,7 @@ msgid "Error: (Awaiting internal transactions for reason)"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address/overview.html.eex:135
+#: lib/block_scout_web/templates/address/overview.html.eex:139
msgid "Error: Could not determine contract creator."
msgstr ""
@@ -960,7 +960,7 @@ msgid "It could still be in the TX Pool of a different node, waiting to be broad
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address/overview.html.eex:107
+#: lib/block_scout_web/templates/address/overview.html.eex:111
msgid "Last Balance Update: Block #"
msgstr ""
@@ -1173,7 +1173,7 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/overview.html.eex:59
-#: lib/block_scout_web/templates/address/overview.html.eex:159
+#: lib/block_scout_web/templates/address/overview.html.eex:163
#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:51
#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:101
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:36
@@ -1658,7 +1658,7 @@ msgid "Yes"
msgstr ""
#, elixir-format
-#: lib/block_scout_web/templates/address/overview.html.eex:127
+#: lib/block_scout_web/templates/address/overview.html.eex:131
msgid "at"
msgstr ""
@@ -1716,8 +1716,8 @@ msgstr ""
#, elixir-format
#: lib/block_scout_web/templates/address/_validator_metadata_modal.html.eex:37
-#: lib/block_scout_web/templates/address/overview.html.eex:160
-#: lib/block_scout_web/templates/address/overview.html.eex:168
+#: lib/block_scout_web/templates/address/overview.html.eex:164
+#: lib/block_scout_web/templates/address/overview.html.eex:172
#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:102
#: lib/block_scout_web/templates/tokens/instance/overview/_details.html.eex:110
#: lib/block_scout_web/templates/tokens/overview/_details.html.eex:122
diff --git a/apps/block_scout_web/test/block_scout_web/controllers/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/address_controller_test.exs
index cf0126937d..b3c63dc9e8 100644
--- a/apps/block_scout_web/test/block_scout_web/controllers/address_controller_test.exs
+++ b/apps/block_scout_web/test/block_scout_web/controllers/address_controller_test.exs
@@ -6,7 +6,7 @@ defmodule BlockScoutWeb.AddressControllerTest do
import Mox
alias Explorer.Chain.Address
- alias Explorer.Counters.{AddressesCounter, AddressTransactionsCounter}
+ alias Explorer.Counters.{AddressesCounter}
describe "GET index/2" do
setup :set_mox_global
@@ -85,7 +85,7 @@ defmodule BlockScoutWeb.AddressControllerTest do
assert conn.status == 200
{:ok, response} = Jason.decode(conn.resp_body)
- assert %{"transaction_count" => 0, "validation_count" => 0} == response
+ assert %{"transaction_count" => 0, "validation_count" => 0, "gas_usage_count" => 0} == response
end
end
end
diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs
index 96757c8999..2a845fe249 100644
--- a/apps/explorer/config/config.exs
+++ b/apps/explorer/config/config.exs
@@ -94,6 +94,17 @@ address_transactions_counter_cache_period =
_ -> :timer.hours(1)
end
+config :explorer, Explorer.Counters.AddressTransactionsGasUsageCounter,
+ enabled: true,
+ enable_consolidation: true,
+ update_interval_in_seconds: balances_update_interval || 30 * 60
+
+address_transactions_gas_usage_counter_cache_period =
+ case Integer.parse(System.get_env("ADDRESS_TRANSACTIONS_GAS_USAGE_COUNTER_CACHE_PERIOD", "")) do
+ {secs, ""} -> :timer.seconds(secs)
+ _ -> :timer.hours(1)
+ end
+
config :explorer, Explorer.Counters.AddressTransactionsCounter,
enabled: true,
enable_consolidation: true,
diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex
index f6d58af84b..b4b77d2f04 100644
--- a/apps/explorer/lib/explorer/application.ex
+++ b/apps/explorer/lib/explorer/application.ex
@@ -79,6 +79,7 @@ defmodule Explorer.Application do
configure(Explorer.Counters.AddressesWithBalanceCounter),
configure(Explorer.Counters.AddressesCounter),
configure(Explorer.Counters.AddressTransactionsCounter),
+ configure(Explorer.Counters.AddressTransactionsGasUsageCounter),
configure(Explorer.Counters.AverageBlockTime),
configure(Explorer.Counters.Bridge),
configure(Explorer.Validator.MetadataProcessor),
diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex
index 9e0f5aa441..acff41c861 100644
--- a/apps/explorer/lib/explorer/chain.ex
+++ b/apps/explorer/lib/explorer/chain.ex
@@ -724,6 +724,28 @@ defmodule Explorer.Chain do
Repo.aggregate(to_address_query, :count, :hash, timeout: :infinity)
end
+ @spec address_to_incoming_transaction_gas_usage(Hash.Address.t()) :: non_neg_integer()
+ def address_to_incoming_transaction_gas_usage(address_hash) do
+ to_address_query =
+ from(
+ transaction in Transaction,
+ where: transaction.to_address_hash == ^address_hash
+ )
+
+ Repo.aggregate(to_address_query, :sum, :gas_used, timeout: :infinity)
+ end
+
+ @spec address_to_outcoming_transaction_gas_usage(Hash.Address.t()) :: non_neg_integer()
+ def address_to_outcoming_transaction_gas_usage(address_hash) do
+ to_address_query =
+ from(
+ transaction in Transaction,
+ where: transaction.from_address_hash == ^address_hash
+ )
+
+ Repo.aggregate(to_address_query, :sum, :gas_used, timeout: :infinity)
+ end
+
@spec max_incoming_transactions_count() :: non_neg_integer()
def max_incoming_transactions_count, do: @max_incoming_transactions_count
@@ -1963,6 +1985,21 @@ defmodule Explorer.Chain do
end
end
+ @spec address_to_gas_usage_count(Address.t()) :: non_neg_integer()
+ def address_to_gas_usage_count(address) do
+ if contract?(address) do
+ incoming_transaction_gas_usage = address_to_incoming_transaction_gas_usage(address.hash)
+
+ if incoming_transaction_gas_usage == 0 do
+ address_to_outcoming_transaction_gas_usage(address.hash)
+ else
+ incoming_transaction_gas_usage
+ end
+ else
+ address_to_outcoming_transaction_gas_usage(address.hash)
+ end
+ end
+
defp contract?(%{contract_code: nil}), do: false
defp contract?(%{contract_code: _}), do: true
diff --git a/apps/explorer/lib/explorer/counters/address_gas_usage_counter.ex b/apps/explorer/lib/explorer/counters/address_gas_usage_counter.ex
new file mode 100644
index 0000000000..c5384fb9ad
--- /dev/null
+++ b/apps/explorer/lib/explorer/counters/address_gas_usage_counter.ex
@@ -0,0 +1,112 @@
+defmodule Explorer.Counters.AddressTransactionsGasUsageCounter do
+ @moduledoc """
+ Caches Address transactions gas usage counter.
+ """
+ use GenServer
+
+ alias Explorer.Chain
+
+ @cache_name :address_transactions_gas_usage_counter
+ @last_update_key "last_update"
+ @cache_period Application.get_env(:explorer, __MODULE__)[:period]
+
+ @ets_opts [
+ :set,
+ :named_table,
+ :public,
+ read_concurrency: true
+ ]
+
+ config = Application.get_env(:explorer, Explorer.Counters.AddressTransactionsGasUsageCounter)
+ @enable_consolidation Keyword.get(config, :enable_consolidation)
+
+ @spec start_link(term()) :: GenServer.on_start()
+ def start_link(_) do
+ GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
+ end
+
+ @impl true
+ def init(_args) do
+ create_cache_table()
+
+ {:ok, %{consolidate?: enable_consolidation?()}, {:continue, :ok}}
+ end
+
+ @impl true
+ def handle_continue(:ok, %{consolidate?: true} = state) do
+ {:noreply, state}
+ end
+
+ @impl true
+ def handle_continue(:ok, state) do
+ {:noreply, state}
+ end
+
+ @impl true
+ def handle_info(:consolidate, state) do
+ {:noreply, state}
+ end
+
+ def fetch(address) do
+ if cache_expired?(address) do
+ Task.start_link(fn ->
+ update_cache(address)
+ end)
+ end
+
+ address_hash_string = get_address_hash_string(address)
+ fetch_from_cache("hash_#{address_hash_string}")
+ end
+
+ def cache_name, do: @cache_name
+
+ defp cache_expired?(address) do
+ address_hash_string = get_address_hash_string(address)
+ updated_at = fetch_from_cache("hash_#{address_hash_string}_#{@last_update_key}")
+
+ cond do
+ is_nil(updated_at) -> true
+ current_time() - updated_at > @cache_period -> true
+ true -> false
+ end
+ end
+
+ defp update_cache(address) do
+ address_hash_string = get_address_hash_string(address)
+ put_into_cache("hash_#{address_hash_string}_#{@last_update_key}", current_time())
+ new_data = Chain.address_to_gas_usage_count(address)
+ put_into_cache("hash_#{address_hash_string}", new_data)
+ end
+
+ defp fetch_from_cache(key) do
+ case :ets.lookup(@cache_name, key) do
+ [{_, value}] ->
+ value
+
+ [] ->
+ 0
+ end
+ end
+
+ defp put_into_cache(key, value) do
+ :ets.insert(@cache_name, {key, value})
+ end
+
+ defp get_address_hash_string(address) do
+ Base.encode16(address.hash.bytes, case: :lower)
+ end
+
+ defp current_time do
+ utc_now = DateTime.utc_now()
+
+ DateTime.to_unix(utc_now, :millisecond)
+ end
+
+ def create_cache_table do
+ if :ets.whereis(@cache_name) == :undefined do
+ :ets.new(@cache_name, @ets_opts)
+ end
+ end
+
+ def enable_consolidation?, do: @enable_consolidation
+end
diff --git a/apps/explorer/lib/explorer/counters/address_transactions_counter.ex b/apps/explorer/lib/explorer/counters/address_transactions_counter.ex
index 6a2313da45..6fc1c1de90 100644
--- a/apps/explorer/lib/explorer/counters/address_transactions_counter.ex
+++ b/apps/explorer/lib/explorer/counters/address_transactions_counter.ex
@@ -17,7 +17,7 @@ defmodule Explorer.Counters.AddressTransactionsCounter do
read_concurrency: true
]
- config = Application.get_env(:explorer, Explorer.Counters.AddressesCounter)
+ config = Application.get_env(:explorer, Explorer.Counters.AddressTransactionsCounter)
@enable_consolidation Keyword.get(config, :enable_consolidation)
@spec start_link(term()) :: GenServer.on_start()