diff --git a/lib/archethic/db/embedded_impl/chain_index.ex b/lib/archethic/db/embedded_impl/chain_index.ex index 471ec69e9..7dcc2cc35 100644 --- a/lib/archethic/db/embedded_impl/chain_index.ex +++ b/lib/archethic/db/embedded_impl/chain_index.ex @@ -370,9 +370,15 @@ defmodule Archethic.DB.EmbeddedImpl.ChainIndex do filename = chain_addresses_path(db_path, genesis_address) - :ok = File.write!(filename, encoded_data, [:binary, :append]) - true = :ets.insert(:archethic_db_last_index, {genesis_address, new_address, unix_time}) - :ok + case :ets.lookup(:archethic_db_last_index, genesis_address) do + [{_, ^new_address, _}] -> + :ok + + _ -> + :ok = File.write!(filename, encoded_data, [:binary, :append]) + true = :ets.insert(:archethic_db_last_index, {genesis_address, new_address, unix_time}) + :ok + end end @doc """ diff --git a/lib/archethic_web/live/chains/node_shared_secrets_live.ex b/lib/archethic_web/live/chains/node_shared_secrets_live.ex new file mode 100644 index 000000000..be99f34fc --- /dev/null +++ b/lib/archethic_web/live/chains/node_shared_secrets_live.ex @@ -0,0 +1,191 @@ +defmodule ArchethicWeb.NodeSharedSecretsChainLive do + @moduledoc false + + use ArchethicWeb, :live_view + + alias Archethic.{ + TransactionChain, + TransactionChain.Transaction, + TransactionChain.TransactionData, + TransactionChain.TransactionData.Ownership, + PubSub, + SharedSecrets + } + + alias ArchethicWeb.{ExplorerView, WebUtils} + alias Phoenix.{LiveView, View} + + @display_limit 10 + @txn_type :node_shared_secrets + + @spec mount(_parameters :: map(), _session :: map(), socket :: LiveView.Socket.t()) :: + {:ok, LiveView.Socket.t()} + def mount(_parameters, _session, socket) do + if connected?(socket) do + PubSub.register_to_new_transaction_by_type(@txn_type) + end + + tx_count = TransactionChain.count_transactions_by_type(@txn_type) + + socket = + case SharedSecrets.genesis_address(@txn_type) do + nil -> + socket + |> assign(:tx_count, 0) + |> assign(:nb_pages, 0) + |> assign(:nb_authorized_nodes, 0) + |> assign(:current_page, 1) + |> assign(:transactions, []) + + address when is_binary(address) -> + nb_authorized_nodes = + address + |> TransactionChain.get_last_address() + |> elem(0) + |> nb_of_authorized_keys() + + socket + |> assign(:tx_count, tx_count) + |> assign(:nb_pages, WebUtils.total_pages(tx_count)) + |> assign(:nb_authorized_nodes, nb_authorized_nodes) + |> assign(:current_page, 1) + |> assign(:transactions, transactions_from_page(1, tx_count)) + end + + {:ok, socket} + end + + @spec render(assigns :: LiveView.Socket.assigns()) :: LiveView.Rendered.t() + def render(assigns) do + View.render(ExplorerView, "node_shared_secrets_chain_index.html", assigns) + end + + @spec handle_params(_params :: map(), _uri :: binary(), socket :: LiveView.Socket.t()) :: + {:noreply, LiveView.Socket.t()} + def handle_params( + _params = %{"page" => page}, + _uri, + socket = %{assigns: %{nb_pages: nb_pages, tx_count: tx_count}} + ) do + case Integer.parse(page) do + {number, ""} when number < 1 and number > nb_pages -> + {:noreply, push_patch(socket, to: Routes.live_path(socket, __MODULE__, %{"page" => 1}))} + + {number, ""} when number >= 1 and number <= nb_pages -> + socket = + socket + |> assign(:current_page, number) + |> assign(:transactions, transactions_from_page(number, tx_count)) + + {:noreply, socket} + + _ -> + {:noreply, socket} + end + end + + def handle_params(_, _, socket) do + {:noreply, socket} + end + + @spec handle_event(_event :: binary(), _params :: map(), socket :: LiveView.Socket.t()) :: + {:noreply, LiveView.Socket.t()} + def handle_event(_event = "next_page", _params = %{"page" => page}, socket) do + {:noreply, push_patch(socket, to: Routes.live_path(socket, __MODULE__, %{"page" => page}))} + end + + def handle_event(_event = "prev_page", _params = %{"page" => page}, socket) do + {:noreply, push_patch(socket, to: Routes.live_path(socket, __MODULE__, %{"page" => page}))} + end + + @spec handle_info( + _msg :: + {:new_transaction, address :: binary(), :node_shared_secrets, DateTime.t()}, + socket :: LiveView.Socket.t() + ) :: + {:noreply, LiveView.Socket.t()} + def handle_info( + _msg = {:new_transaction, address, :node_shared_secrets, timestamp}, + socket = %{assigns: %{current_page: current_page, tx_count: tx_count}} + ) do + updated_socket = + case current_page do + 1 -> + nb_auth_nodes = nb_of_authorized_keys(address) + + socket + |> update( + :transactions, + fn tx_list -> + [display_data(address, nb_auth_nodes, timestamp) | tx_list] + |> Enum.take(@display_limit) + end + ) + |> assign(:tx_count, tx_count + 1) + |> assign(:nb_authorized_nodes, nb_auth_nodes) + |> assign(:current_page, 1) + |> assign(:nb_pages, WebUtils.total_pages(tx_count + 1)) + + _ -> + socket + end + + {:noreply, updated_socket} + end + + @spec transactions_from_page(current_page :: non_neg_integer(), tx_count :: non_neg_integer()) :: + list(map()) + def transactions_from_page(current_page, tx_count) do + nb_drops = tx_count - current_page * @display_limit + + {nb_drops, display_limit} = + if nb_drops < 0, do: {0, @display_limit + nb_drops}, else: {nb_drops, @display_limit} + + case SharedSecrets.genesis_address(@txn_type) do + address when is_binary(address) -> + address + |> TransactionChain.list_chain_addresses() + |> Stream.drop(nb_drops) + |> Stream.take(display_limit) + |> Stream.map(fn {addr, timestamp} -> + nb_authorized_nodes = nb_of_authorized_keys(addr) + + display_data( + addr, + nb_authorized_nodes, + DateTime.from_unix(timestamp, :millisecond) |> elem(1) + ) + end) + |> Enum.reverse() + + _ -> + [] + end + end + + @spec nb_of_authorized_keys(address :: binary()) :: non_neg_integer() + defp nb_of_authorized_keys(address) do + with {:ok, %Transaction{data: %TransactionData{ownerships: ownerships}}} <- + TransactionChain.get_transaction(address, data: [:ownerships]), + %Ownership{authorized_keys: authorized_keys} <- Enum.at(ownerships, 0) do + Enum.count(authorized_keys) + else + _ -> 1 + end + end + + @spec display_data( + address :: binary(), + nb_authorized_nodes :: non_neg_integer(), + timestamp :: DateTime.t() + ) :: + map() + defp display_data(address, nb_authorized_nodes, timestamp) do + %{ + address: address, + type: @txn_type, + timestamp: timestamp, + nb_authorized_nodes: nb_authorized_nodes + } + end +end diff --git a/lib/archethic_web/live/chains/reward_live.ex b/lib/archethic_web/live/chains/reward_live.ex index 8f5cd39fb..f46942d97 100644 --- a/lib/archethic_web/live/chains/reward_live.ex +++ b/lib/archethic_web/live/chains/reward_live.ex @@ -9,7 +9,7 @@ defmodule ArchethicWeb.RewardChainLive do use ArchethicWeb, :live_view - alias ArchethicWeb.{ExplorerView} + alias ArchethicWeb.{ExplorerView, WebUtils} alias Phoenix.{View} @display_limit 10 @@ -29,7 +29,7 @@ defmodule ArchethicWeb.RewardChainLive do socket = socket |> assign(:tx_count, tx_count) - |> assign(:nb_pages, total_pages(tx_count)) + |> assign(:nb_pages, WebUtils.total_pages(tx_count)) |> assign(:current_page, 1) |> assign(:transactions, transactions_from_page(1, tx_count)) @@ -49,11 +49,10 @@ defmodule ArchethicWeb.RewardChainLive do socket = %{assigns: %{nb_pages: nb_pages, tx_count: tx_count}} ) do case Integer.parse(page) do - {number, ""} when number > 0 and number > nb_pages -> - {:noreply, - push_redirect(socket, to: Routes.live_path(socket, __MODULE__, %{"page" => 1}))} + {number, ""} when number < 1 and number > nb_pages -> + {:noreply, push_patch(socket, to: Routes.live_path(socket, __MODULE__, %{"page" => 1}))} - {number, ""} when number > 0 and number <= nb_pages -> + {number, ""} when number >= 1 and number <= nb_pages -> socket = socket |> assign(:current_page, number) @@ -73,11 +72,11 @@ defmodule ArchethicWeb.RewardChainLive do @spec handle_event(binary(), map(), Phoenix.LiveView.Socket.t()) :: {:noreply, Phoenix.LiveView.Socket.t()} def handle_event(_event = "prev_page", %{"page" => page}, socket) do - {:noreply, push_redirect(socket, to: Routes.live_path(socket, __MODULE__, %{"page" => page}))} + {:noreply, push_patch(socket, to: Routes.live_path(socket, __MODULE__, %{"page" => page}))} end def handle_event(_event = "next_page", %{"page" => page}, socket) do - {:noreply, push_redirect(socket, to: Routes.live_path(socket, __MODULE__, %{"page" => page}))} + {:noreply, push_patch(socket, to: Routes.live_path(socket, __MODULE__, %{"page" => page}))} end @spec handle_info( @@ -92,25 +91,24 @@ defmodule ArchethicWeb.RewardChainLive do {:noreply, handle_new_transaction({address, type, timestamp}, socket)} end + @spec handle_new_transaction( + {address :: binary(), type :: :mint_rewards | :node_rewards, timestamp :: DateTime.t()}, + socket :: Phoenix.LiveView.Socket.t() + ) :: Phoenix.LiveView.Socket.t() def handle_new_transaction( {address, type, timestamp}, - socket = %{assigns: %{current_page: current_page, transactions: txs, tx_count: tx_count}} + socket = %{assigns: %{current_page: current_page, tx_count: tx_count}} ) do - display_txs = Enum.count(txs) - case current_page do - 1 when display_txs < @display_limit -> - socket - |> assign(:tx_count, tx_count + 1) - |> assign(:current_page, 1) - |> update(:transactions, &[%{address: address, type: type, timestamp: timestamp} | &1]) - - 1 when display_txs >= @display_limit -> + 1 -> socket + |> update(:transactions, fn tx_list -> + [display_data(address, type, timestamp) | tx_list] + |> Enum.take(@display_limit) + end) |> assign(:tx_count, tx_count + 1) - |> assign(:nb_pages, total_pages(tx_count + 1)) |> assign(:current_page, 1) - |> assign(:transactions, [%{address: address, type: type, timestamp: timestamp}]) + |> assign(:nb_pages, WebUtils.total_pages(tx_count + 1)) _ -> socket @@ -131,11 +129,11 @@ defmodule ArchethicWeb.RewardChainLive do |> Stream.drop(nb_drops) |> Stream.take(display_limit) |> Stream.map(fn {addr, timestamp} -> - %{ - address: addr, - type: (TransactionChain.get_transaction(addr, [:type]) |> elem(1)).type, - timestamp: DateTime.from_unix(timestamp, :millisecond) |> elem(1) - } + display_data( + addr, + (TransactionChain.get_transaction(addr, [:type]) |> elem(1)).type, + DateTime.from_unix(timestamp, :millisecond) |> elem(1) + ) end) |> Enum.reverse() @@ -144,29 +142,13 @@ defmodule ArchethicWeb.RewardChainLive do end end - @doc """ - Nb of pages required to display all the transactions. - - ## Examples - iex> total_pages(45) - 5 - iex> total_pages(40) - 4 - iex> total_pages(1) - 1 - iex> total_pages(10) - 1 - iex> total_pages(11) - 2 - iex> total_pages(0) - 0 - """ - @spec total_pages(tx_count :: non_neg_integer()) :: - non_neg_integer() - def total_pages(tx_count) when rem(tx_count, @display_limit) == 0, - do: count_pages(tx_count) - - def total_pages(tx_count), do: count_pages(tx_count) + 1 - - def count_pages(tx_count), do: div(tx_count, @display_limit) + @spec display_data( + address :: binary(), + type :: :node_rewards | :mint_rewards, + timestamp :: DateTime.t() + ) :: + map() + defp display_data(address, type, timestamp) do + %{address: address, type: type, timestamp: timestamp} + end end diff --git a/lib/archethic_web/router.ex b/lib/archethic_web/router.ex index 17e1ab004..8b29f2d07 100644 --- a/lib/archethic_web/router.ex +++ b/lib/archethic_web/router.ex @@ -53,6 +53,7 @@ defmodule ArchethicWeb.Router do live("/chain/oracle", OracleChainLive) live("/chain/beacon", BeaconChainLive) live("/chain/rewards", RewardChainLive) + live("/chain/node_shared_secrets", NodeSharedSecretsChainLive) live("/nodes", NodeListLive) live("/nodes/worldmap", WorldMapLive) diff --git a/lib/archethic_web/templates/explorer/node_shared_secrets_chain_index.html.leex b/lib/archethic_web/templates/explorer/node_shared_secrets_chain_index.html.leex new file mode 100644 index 000000000..19ec598ca --- /dev/null +++ b/lib/archethic_web/templates/explorer/node_shared_secrets_chain_index.html.leex @@ -0,0 +1,77 @@ +
+
+

Node Shared Secrets Chain

+
+
+ Archethic Node Shared Secrets Chain secures the Network, by controlling who can participate in Network. +
+
+ +
+
+
+

Authorized Nodes :

+

+ <%= @nb_authorized_nodes %> +

+
+
+ + +
+
+ +
+
+ +
+
+
+

Transaction chain

+
+
+ <%= for tx <- @transactions do %> +
+
+ <%= link to: Routes.live_path(@socket, ArchethicWeb.TransactionDetailsLive, Base.encode16(tx.address)) do%> + <%= Base.encode16(tx.address) %> + <% end %> +
+
+ <%= format_date(tx.timestamp) %> +
+
+ <%= tx.type %> +
+
+ authorized: + <%= tx.nb_authorized_nodes %> + +
+
+ <% end %> +
+
+
+
+
+ diff --git a/lib/archethic_web/templates/layout/root.html.eex b/lib/archethic_web/templates/layout/root.html.eex index ef15222cd..d6963efcf 100644 --- a/lib/archethic_web/templates/layout/root.html.eex +++ b/lib/archethic_web/templates/layout/root.html.eex @@ -50,6 +50,9 @@ Reward + + Node Shared Secrets +