From 17df9b5074f22d91e6367134ce203f9df219a67f Mon Sep 17 00:00:00 2001 From: Apoorv Date: Wed, 21 Sep 2022 17:20:34 +0530 Subject: [PATCH] [feature] Node Shared Secrets chain explorer --- .gitignore | 2 + lib/archethic/bootstrap.ex | 4 +- .../bootstrap/network_constraints.ex | 8 +- lib/archethic/crypto.ex | 4 +- lib/archethic/db.ex | 5 +- lib/archethic/db/embedded_impl.ex | 11 +- lib/archethic/db/embedded_impl/chain_index.ex | 35 +++ .../mining/pending_transaction_validation.ex | 8 +- lib/archethic/oracle_chain.ex | 4 +- lib/archethic/pub_sub.ex | 8 + lib/archethic/reward.ex | 4 +- lib/archethic/shared_secrets.ex | 8 +- lib/archethic/transaction_chain.ex | 27 ++ .../live/chains/node_shared_secrets_live.ex | 269 ++++++++++++++++++ lib/archethic_web/live/chains/reward_live.ex | 173 +++++++++++ lib/archethic_web/router.ex | 3 + .../explorer/beacon_chain_index.html.leex | 2 +- .../node_shared_secrets_chain_index.html.leex | 77 +++++ .../explorer/oracle_chain_index.html.leex | 6 +- .../explorer/reward_chain_index.html.leex | 61 ++++ .../templates/layout/root.html.eex | 6 + src/c/nat/miniupnp | 2 +- test/archethic/db/embedded_impl_test.exs | 62 ++++ .../controllers/faucet_controller_test.exs | 7 +- test/archethic_web/live/rewards_live_test.exs | 93 ++++++ 25 files changed, 860 insertions(+), 29 deletions(-) create mode 100644 lib/archethic_web/live/chains/node_shared_secrets_live.ex create mode 100644 lib/archethic_web/live/chains/reward_live.ex create mode 100644 lib/archethic_web/templates/explorer/node_shared_secrets_chain_index.html.leex create mode 100644 lib/archethic_web/templates/explorer/reward_chain_index.html.leex create mode 100644 test/archethic_web/live/rewards_live_test.exs diff --git a/.gitignore b/.gitignore index 310c4b9f2f..39d6c0ae59 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,5 @@ log_* archethic-node.iml projectFilesBackup tags +.vscode/settings.json +/src/c/nat/ \ No newline at end of file diff --git a/lib/archethic/bootstrap.ex b/lib/archethic/bootstrap.ex index c324bda906..ea258764aa 100644 --- a/lib/archethic/bootstrap.ex +++ b/lib/archethic/bootstrap.ex @@ -271,11 +271,11 @@ defmodule Archethic.Bootstrap do @spec get_genesis_addr(:node_shared_secrets | :oracle) :: binary() | nil defp get_genesis_addr(:oracle) do - Archethic.OracleChain.get_gen_addr().current |> elem(0) + Archethic.OracleChain.genesis_address().current |> elem(0) end defp get_genesis_addr(:node_shared_secrets) do - Archethic.SharedSecrets.get_gen_addr(:node_shared_secrets) + Archethic.SharedSecrets.genesis_address(:node_shared_secrets) end defp first_initialization( diff --git a/lib/archethic/bootstrap/network_constraints.ex b/lib/archethic/bootstrap/network_constraints.ex index 8908662626..57a30b2ac7 100644 --- a/lib/archethic/bootstrap/network_constraints.ex +++ b/lib/archethic/bootstrap/network_constraints.ex @@ -34,7 +34,7 @@ defmodule Archethic.Bootstrap.NetworkConstraints do @spec persist(:oracle | :reward | :origin | :node_shared_secrets) :: :ok | :error def persist(:reward) do - case Reward.get_gen_addr() do + case Reward.genesis_address() do nil -> Reward.persist_gen_addr() @@ -44,7 +44,7 @@ defmodule Archethic.Bootstrap.NetworkConstraints do end def persist(:origin) do - case SharedSecrets.get_gen_addr(:origin) do + case SharedSecrets.genesis_address(:origin) do nil -> SharedSecrets.persist_gen_addr(:origin) @@ -54,7 +54,7 @@ defmodule Archethic.Bootstrap.NetworkConstraints do end def persist(:node_shared_secrets) do - case SharedSecrets.get_gen_addr(:node_shared_secrets) do + case SharedSecrets.genesis_address(:node_shared_secrets) do nil -> SharedSecrets.persist_gen_addr(:node_shared_secrets) @@ -68,7 +68,7 @@ defmodule Archethic.Bootstrap.NetworkConstraints do OracleChain.update_summ_gen_addr() Logger.info("Oracle Gen Addr Table: Loaded") - if gen_addr = OracleChain.get_gen_addr() do + if gen_addr = OracleChain.genesis_address() do Logger.debug("New Oracle Gen Addr") Logger.debug(gen_addr) end diff --git a/lib/archethic/crypto.ex b/lib/archethic/crypto.ex index 7ef007951d..338a79b619 100755 --- a/lib/archethic/crypto.ex +++ b/lib/archethic/crypto.ex @@ -297,13 +297,13 @@ defmodule Archethic.Crypto do @doc """ Return the the node shared secrets public key using the node shared secret transaction seed """ - @spec node_shared_secrets_public_key(index :: number()) :: key() + @spec node_shared_secrets_public_key(index :: non_neg_integer()) :: key() defdelegate node_shared_secrets_public_key(index), to: SharedSecretsKeystore @doc """ Return the the network pool public key using the network pool transaction seed """ - @spec network_pool_public_key(index :: number()) :: key() + @spec network_pool_public_key(index :: non_neg_integer()) :: key() defdelegate network_pool_public_key(index), to: SharedSecretsKeystore @doc """ diff --git a/lib/archethic/db.ex b/lib/archethic/db.ex index 227d554414..f2bcd92eec 100644 --- a/lib/archethic/db.ex +++ b/lib/archethic/db.ex @@ -21,7 +21,7 @@ defmodule Archethic.DB do @callback write_transaction(Transaction.t()) :: :ok @callback write_beacon_summary(Summary.t()) :: :ok @callback write_transaction_chain(Enumerable.t()) :: :ok - @callback write_transaction(Transaction.t()) :: :ok + # @callback write_transaction(Transaction.t()) :: :ok @callback list_transactions(fields :: list()) :: Enumerable.t() @callback add_last_transaction_address(binary(), binary(), DateTime.t()) :: :ok @callback list_last_transaction_addresses() :: Enumerable.t() @@ -34,6 +34,9 @@ defmodule Archethic.DB do @callback list_addresses_by_type(Transaction.transaction_type()) :: Enumerable.t() | list(binary()) + @callback list_chain_addresses(binary()) :: + Enumerable.t() | list({binary(), non_neg_integer()}) + @callback get_last_chain_address(binary()) :: {binary(), DateTime.t()} @callback get_last_chain_address(binary(), DateTime.t()) :: {binary(), DateTime.t()} @callback get_first_chain_address(binary()) :: binary() diff --git a/lib/archethic/db/embedded_impl.ex b/lib/archethic/db/embedded_impl.ex index 0266e11b63..24098ef300 100644 --- a/lib/archethic/db/embedded_impl.ex +++ b/lib/archethic/db/embedded_impl.ex @@ -40,7 +40,7 @@ defmodule Archethic.DB.EmbeddedImpl do @doc """ Write the transaction chain through the a chain writer which will - happens the transactions to the chain's file + append the transactions to the chain's file If a transaction already exists it will be discarded @@ -179,6 +179,15 @@ defmodule Archethic.DB.EmbeddedImpl do ChainIndex.get_addresses_by_type(type, db_path()) end + @doc """ + Stream all the addresses from the Genesis address(following it). + """ + @spec list_chain_addresses(binary()) :: + Enumerable.t() | list({binary(), non_neg_integer()}) + def list_chain_addresses(address) when is_binary(address) do + ChainIndex.list_chain_addresses(address, db_path()) + end + @doc """ Count the number of transactions for a given type """ diff --git a/lib/archethic/db/embedded_impl/chain_index.ex b/lib/archethic/db/embedded_impl/chain_index.ex index 57666bdc26..471ec69e9c 100644 --- a/lib/archethic/db/embedded_impl/chain_index.ex +++ b/lib/archethic/db/embedded_impl/chain_index.ex @@ -294,6 +294,41 @@ defmodule Archethic.DB.EmbeddedImpl.ChainIndex do ) end + @doc """ + Stream all the transaction addresses from genesis_address-address. + """ + @spec list_chain_addresses(binary(), String.t()) :: + Enumerable.t() | list({binary(), non_neg_integer()}) + def list_chain_addresses(address, db_path) when is_binary(address) do + filepath = chain_addresses_path(db_path, address) + + Stream.resource( + fn -> File.open(filepath, [:binary, :read]) end, + fn + {:error, _} -> + {:halt, nil} + + {:ok, fd} -> + with {:ok, <>} <- :file.read(fd, 8), + {:ok, <>} <- :file.read(fd, 2), + hash_size <- Crypto.hash_size(hash_id), + {:ok, hash} <- :file.read(fd, hash_size) do + address = <> + # return tuple of address and timestamp + {[{address, timestamp}], {:ok, fd}} + else + :eof -> + :file.close(fd) + {:halt, {:ok, fd}} + end + end, + fn + nil -> address + {:ok, fd} -> :file.close(fd) + end + ) + end + @doc """ Return the number of transactions for a given type """ diff --git a/lib/archethic/mining/pending_transaction_validation.ex b/lib/archethic/mining/pending_transaction_validation.ex index f105dc28a3..72d286841d 100644 --- a/lib/archethic/mining/pending_transaction_validation.ex +++ b/lib/archethic/mining/pending_transaction_validation.ex @@ -105,7 +105,7 @@ defmodule Archethic.Mining.PendingTransactionValidation do when type in [:oracle, :oracle_summary] do # mulitpe txn chain based on summary date - case OracleChain.get_gen_addr() do + case OracleChain.genesis_address() do nil -> false @@ -127,7 +127,7 @@ defmodule Archethic.Mining.PendingTransactionValidation do def validate_network_chain?(type, tx = %Transaction{}) when type in [:mint_rewards, :node_rewards] do # singleton tx chain in network lifespan - case Reward.get_gen_addr() do + case Reward.genesis_address() do nil -> false @@ -144,7 +144,7 @@ defmodule Archethic.Mining.PendingTransactionValidation do def validate_network_chain?(:node_shared_secrets, tx = %Transaction{}) do # singleton tx chain in network lifespan - case SharedSecrets.get_gen_addr(:node_shared_secrets) do + case SharedSecrets.genesis_address(:node_shared_secrets) do nil -> false @@ -161,7 +161,7 @@ defmodule Archethic.Mining.PendingTransactionValidation do def validate_network_chain?(:origin, tx = %Transaction{}) do # singleton tx chain in network lifespan # not parsing orgin pub key for origin family - case SharedSecrets.get_gen_addr(:origin) do + case SharedSecrets.genesis_address(:origin) do nil -> false diff --git a/lib/archethic/oracle_chain.ex b/lib/archethic/oracle_chain.ex index cf04f8d49e..a65a44234c 100644 --- a/lib/archethic/oracle_chain.ex +++ b/lib/archethic/oracle_chain.ex @@ -209,8 +209,8 @@ defmodule Archethic.OracleChain do @doc """ Returns current and previous summary_time genesis address of oracle chain """ - @spec get_gen_addr() :: map() | nil - defdelegate get_gen_addr(), + @spec genesis_address() :: map() | nil + defdelegate genesis_address(), to: MemTable, as: :get_addr end diff --git a/lib/archethic/pub_sub.ex b/lib/archethic/pub_sub.ex index ef9a0842b9..c0018f3796 100644 --- a/lib/archethic/pub_sub.ex +++ b/lib/archethic/pub_sub.ex @@ -175,6 +175,14 @@ defmodule Archethic.PubSub do Registry.register(PubSubRegistry, :node_update, []) end + @doc """ + UnRegister a process to a node update publication + """ + @spec unregister_to_node_update :: :ok + def unregister_to_node_update() do + Registry.unregister(PubSubRegistry, :node_update) + end + @doc """ Register a process to a code deployment publication """ diff --git a/lib/archethic/reward.ex b/lib/archethic/reward.ex index 015b47e3b1..02292fb42c 100644 --- a/lib/archethic/reward.ex +++ b/lib/archethic/reward.ex @@ -266,8 +266,8 @@ defmodule Archethic.Reward do end end - @spec get_gen_addr() :: binary() | nil - def get_gen_addr() do + @spec genesis_address() :: binary() | nil + def genesis_address() do :persistent_term.get(@key, nil) end end diff --git a/lib/archethic/shared_secrets.ex b/lib/archethic/shared_secrets.ex index ec6977a87b..303b5943b7 100644 --- a/lib/archethic/shared_secrets.ex +++ b/lib/archethic/shared_secrets.ex @@ -199,13 +199,13 @@ defmodule Archethic.SharedSecrets do end end - @spec get_gen_addr(:origin) :: binary() | nil - def get_gen_addr(:origin) do + @spec genesis_address(:origin) :: binary() | nil + def genesis_address(:origin) do :persistent_term.get(@origin_gen_key, nil) end - @spec get_gen_addr(:node_shared_secrets) :: binary() | nil - def get_gen_addr(:node_shared_secrets) do + @spec genesis_address(:node_shared_secrets) :: binary() | nil + def genesis_address(:node_shared_secrets) do :persistent_term.get(@nss_gen_key, nil) end end diff --git a/lib/archethic/transaction_chain.ex b/lib/archethic/transaction_chain.ex index 076b8becde..f08001d83c 100644 --- a/lib/archethic/transaction_chain.ex +++ b/lib/archethic/transaction_chain.ex @@ -75,6 +75,12 @@ defmodule Archethic.TransactionChain do @spec list_addresses_by_type(Transaction.transaction_type()) :: Enumerable.t() | list(binary()) defdelegate list_addresses_by_type(type), to: DB + @doc """ + Stream all the addresses in chronological belonging to a genesis address + """ + @spec list_chain_addresses(binary()) :: Enumerable.t() | list({binary(), non_neg_integer()}) + defdelegate list_chain_addresses(genesis_address), to: DB + @doc """ Get the last transaction address from a transaction chain with the latest time """ @@ -125,6 +131,27 @@ defmodule Archethic.TransactionChain do @doc """ Retrieve an entire chain from the last transaction The returned list is ordered chronologically. + + ## Example + tx0->tx1->tx2->tx3->tx4->tx5->tx6->tx7->tx8->tx9->tx10->tx11->tx12->tx13->tx14->tx15->tx16 + + Query: TransactionChain.get(tx5.address) + tx0->tx1->tx2->tx3->tx4->tx5 + + Query: TransactionChain.get(tx15.address) + tx0->tx1->tx2->tx3->tx4->tx5->tx6->tx7->tx8->tx9->tx10 + more?: true + paging_address: tx10.address + + Query: TransactionChain.get(tx15.address, paging_address: tx10.address) + tx11->tx12->tx13->tx14->tx15->tx16 + more?: false + paging_address: nil + + Query: TransactionChain.get(tx4.address, paging_address: tx4.address) + tx5->tx6->tx7->tx8->tx9->tx10->tx11->tx12->tx13->tx14 + more?: true + paging_address: tx15.address """ @spec get(binary(), list()) :: Enumerable.t() | {list(Transaction.t()), boolean(), binary()} 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 0000000000..460a025274 --- /dev/null +++ b/lib/archethic_web/live/chains/node_shared_secrets_live.ex @@ -0,0 +1,269 @@ +defmodule ArchethicWeb.NodeSharedSecretsChainLive do + @moduledoc false + + use ArchethicWeb, :live_view + + alias Archethic.{ + Crypto, + TransactionChain, + TransactionChain.Transaction, + P2P, + P2P.Node, + PubSub, + SharedSecrets + } + + alias ArchethicWeb.{ExplorerView} + 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 + socket = + case P2P.authorized_node?() do + false -> + # display empty ui + mount_component(socket, false) + + true -> + mount_component(socket, true) + end + + {:ok, socket} + end + + @spec mount_component(socket :: LiveView.Socket.t(), true | false) :: LiveView.Socket.t() + def mount_component(socket, false) do + PubSub.register_to_node_update() + # On page 1, node get authorized start displaying the txs + socket + |> assign(:nb_authorized_nodes, 0) + |> assign(:tx_count, 0) + # avoid frequent calls to P2P.authorized_node? + |> assign(:authorized?, false) + |> assign(:nb_pages, 0) + |> assign(:current_page, 1) + |> assign(:transactions, []) + end + + def mount_component(socket, true) do + if connected?(socket) do + PubSub.register_to_new_transaction_by_type(@txn_type) + end + + tx_count = TransactionChain.count_transactions_by_type(@txn_type) + + nb_authorized_nodes = + @txn_type + |> SharedSecrets.genesis_address() + |> TransactionChain.get_last_transaction() + |> elem(1) + |> nb_of_authorized_keys() + + socket + |> assign(:nb_authorized_nodes, nb_authorized_nodes) + |> assign(:authorized?, true) + |> assign(:tx_count, tx_count) + |> assign(:nb_pages, total_pages(tx_count)) + |> assign(:current_page, 1) + |> assign(:transactions, transactions_from_page(1, tx_count)) + 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_event(_event :: binary(), _params :: map(), socket :: LiveView.Socket.t()) :: + {:noreply, LiveView.Socket.t()} + def handle_event(_event = "next_page", _params = %{"page" => page}, socket) do + # change in url + {:noreply, push_patch(socket, to: Routes.live_path(socket, __MODULE__, %{"page" => page}))} + end + + def handle_event(_event = "prev_page", _params = %{"page" => page}, socket) do + # change in url + {:noreply, push_patch(socket, to: Routes.live_path(socket, __MODULE__, %{"page" => page}))} + 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, authorized?: true}} + ) 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 > 0 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_info(_msg :: any(), socket :: LiveView.Socket.t()) :: + {:noreply, LiveView.Socket.t()} + def handle_info( + _msg = {:new_transaction, address, :node_shared_secrets, timestamp}, + socket = %{ + assigns: %{ + authorized?: true, + current_page: current_page, + transactions: tranasction_list, + tx_count: total_tx_count + } + } + ) do + display_txs = Enum.count(tranasction_list) + + updated_socket = + case current_page do + 1 when display_txs < @display_limit -> + nb_authorized_nodes = + TransactionChain.get_transaction(address, data: [:ownerships]) + |> elem(1) + |> nb_of_authorized_keys() + + update( + socket, + :transactions, + &[ + %{ + address: address, + type: @txn_type, + timestamp: timestamp, + nb_authorized_nodes: nb_authorized_nodes + } + | &1 + ] + ) + |> assign(:tx_count, total_tx_count + 1) + |> assign(:nb_authorized_nodes, nb_authorized_nodes) + |> assign(:nb_pages, total_pages(total_tx_count + 1)) + + 1 when display_txs >= @display_limit -> + nb_authorized_nodes = + TransactionChain.get_transaction(address, data: [:ownerships]) + |> elem(1) + |> nb_of_authorized_keys() + + socket + |> assign(:tx_count, total_tx_count + 1) + |> assign(:current_page, 1) + |> assign(:nb_pages, total_pages(total_tx_count + 1)) + |> assign(:transactions, [ + %{ + address: address, + type: @txn_type, + timestamp: timestamp, + nb_authorized_nodes: nb_authorized_nodes + } + ]) + + _ -> + socket + end + + {:noreply, updated_socket} + end + + def handle_info( + {:node_update, %Node{authorized?: true, first_public_key: first_public_key}}, + socket + ) do + case Crypto.first_node_public_key() == first_public_key do + true -> + PubSub.unregister_to_node_update() + # node is authorized now + {:noreply, mount_component(true, socket)} + + _ -> + {:noreply, socket} + end + end + + def handle_info(_, socket) do + {:noreply, 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 {address, timestamp} -> + nb_authorized_nodes = + TransactionChain.get_transaction(address, data: [:ownerships]) + |> elem(1) + |> nb_of_authorized_keys() + + %{ + address: address, + type: @txn_type, + timestamp: DateTime.from_unix(timestamp, :millisecond) |> elem(1), + nb_authorized_nodes: nb_authorized_nodes + } + end) + |> Enum.reverse() + + _ -> + [] + end + end + + @spec nb_of_authorized_keys(transaction :: Transaction.t()) :: non_neg_integer() + defp nb_of_authorized_keys(transaction) do + Enum.count(Enum.at(transaction.data.ownerships, 0).authorized_keys) + 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) +end diff --git a/lib/archethic_web/live/chains/reward_live.ex b/lib/archethic_web/live/chains/reward_live.ex new file mode 100644 index 0000000000..240c0a5600 --- /dev/null +++ b/lib/archethic_web/live/chains/reward_live.ex @@ -0,0 +1,173 @@ +defmodule ArchethicWeb.RewardChainLive do + @moduledoc false + + alias Archethic.{ + TransactionChain, + PubSub, + Reward + } + + use ArchethicWeb, :live_view + + alias ArchethicWeb.{ExplorerView} + alias Phoenix.{View} + + @display_limit 10 + + @spec mount(map(), map(), Phoenix.LiveView.Socket.t()) :: + {:ok, Phoenix.LiveView.Socket.t()} + def mount(_params, _session, socket) do + if connected?(socket) do + PubSub.register_to_new_transaction_by_type(:node_rewards) + PubSub.register_to_new_transaction_by_type(:mint_rewards) + end + + tx_count = + TransactionChain.count_transactions_by_type(:node_rewards) + + TransactionChain.count_transactions_by_type(:mint_rewards) + + socket = + socket + |> assign(:tx_count, tx_count) + |> assign(:nb_pages, total_pages(tx_count)) + |> assign(:current_page, 1) + |> assign(:transactions, transactions_from_page(1, tx_count)) + + {:ok, socket} + end + + @spec render(Phoenix.LiveView.Socket.assigns()) :: Phoenix.LiveView.Rendered.t() + def render(assigns) do + View.render(ExplorerView, "reward_chain_index.html", assigns) + end + + @spec handle_params(map(), binary(), Phoenix.LiveView.Socket.t()) :: + {:noreply, Phoenix.LiveView.Socket.t()} + def handle_params( + %{"page" => page}, + _uri, + 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 > 0 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(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}))} + end + + def handle_event(_event = "next_page", %{"page" => page}, socket) do + {:noreply, push_redirect(socket, to: Routes.live_path(socket, __MODULE__, %{"page" => page}))} + end + + @spec handle_info( + {:new_transaction, binary(), :mint_rewards | :node_rewards, DateTime.t()}, + socket :: Phoenix.LiveView.Socket.t() + ) :: + {:noreply, Phoenix.LiveView.Socket.t()} + def handle_info( + {:new_transaction, address, type, timestamp}, + socket + ) do + {:noreply, handle_new_transaction({address, type, timestamp}, socket)} + end + + def handle_new_transaction( + {address, type, timestamp}, + socket = %{assigns: %{current_page: current_page, transactions: txs, 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(:nb_pages, total_pages(tx_count + 1)) + |> assign(:current_page, 1) + |> update(:transactions, &[%{address: address, type: type, timestamp: timestamp} | &1]) + + 1 when display_txs >= @display_limit -> + socket + |> 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}]) + + _ -> + socket + end + end + + @spec transactions_from_page(non_neg_integer(), 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 Reward.genesis_address() do + address when is_binary(address) -> + address + |> TransactionChain.list_chain_addresses() + |> 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) + } + end) + |> Enum.reverse() + + _ -> + [] + 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) +end diff --git a/lib/archethic_web/router.ex b/lib/archethic_web/router.ex index 3ac3e7a197..8b29f2d070 100644 --- a/lib/archethic_web/router.ex +++ b/lib/archethic_web/router.ex @@ -49,8 +49,11 @@ defmodule ArchethicWeb.Router do live("/transaction/:address", TransactionDetailsLive) get("/chain", ExplorerController, :chain) + 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/beacon_chain_index.html.leex b/lib/archethic_web/templates/explorer/beacon_chain_index.html.leex index bed5a16f26..97586f532c 100644 --- a/lib/archethic_web/templates/explorer/beacon_chain_index.html.leex +++ b/lib/archethic_web/templates/explorer/beacon_chain_index.html.leex @@ -34,7 +34,7 @@

Transaction chain for <%=format_date(Enum.at(@dates, @current_date_page - 1))%>

- + <%= if @fetching do %>

Loading transaction chain...

<% end %> 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 0000000000..19ec598cae --- /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/explorer/oracle_chain_index.html.leex b/lib/archethic_web/templates/explorer/oracle_chain_index.html.leex index e58c084d6e..2a975e369e 100644 --- a/lib/archethic_web/templates/explorer/oracle_chain_index.html.leex +++ b/lib/archethic_web/templates/explorer/oracle_chain_index.html.leex @@ -2,8 +2,8 @@

Last changes from <%= format_date(@update_time) %>

- - + +
@@ -28,7 +28,7 @@ <%= if @current_date_page + 1 <= Enum.count(@dates) do %> Next page <% end %> - +

Page <%= @current_date_page %> on <%= Enum.count(@dates) %>

diff --git a/lib/archethic_web/templates/explorer/reward_chain_index.html.leex b/lib/archethic_web/templates/explorer/reward_chain_index.html.leex new file mode 100644 index 0000000000..d02517e236 --- /dev/null +++ b/lib/archethic_web/templates/explorer/reward_chain_index.html.leex @@ -0,0 +1,61 @@ +
+
+

Reward Chain

+
+
+ Reward Chains are the basis for remuneration. The Archethic Rewards are distributed to Miners, once per Self-Repair Cycle. +
+
+ +
+
+ +
+
+ +
+
+
+

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 %> +
+
+ <% end %> +
+
+
+
+
+
diff --git a/lib/archethic_web/templates/layout/root.html.eex b/lib/archethic_web/templates/layout/root.html.eex index a108b34768..d6963efcf2 100644 --- a/lib/archethic_web/templates/layout/root.html.eex +++ b/lib/archethic_web/templates/layout/root.html.eex @@ -47,6 +47,12 @@ Beacon + + Reward + + + Node Shared Secrets +