Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add origin chain explorer #412 #587

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions lib/archethic/shared_secrets.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ defmodule Archethic.SharedSecrets do

require Logger

@type origin_family :: :software | :usb | :biometric
@type origin_family :: :software | :hardware | :biometric

@spec list_origin_families() :: list(origin_family())
def list_origin_families, do: [:software, :usb, :biometric]
def list_origin_families, do: [:software, :hardware, :biometric]

@doc """
List the origin public keys
Expand Down Expand Up @@ -111,11 +111,11 @@ defmodule Archethic.SharedSecrets do
@spec get_origin_family_from_origin_id(non_neg_integer()) :: origin_family()
def get_origin_family_from_origin_id(origin_id) do
case Crypto.key_origin(origin_id) do
:software ->
id when id in [:software, :on_chain_wallet] ->
:software

:on_chain_wallet ->
:software
id when id in [:tpm] ->
:hardware

_ ->
:biometric
Expand Down Expand Up @@ -208,4 +208,12 @@ defmodule Archethic.SharedSecrets do
def genesis_address(:node_shared_secrets) do
:persistent_term.get(@nss_gen_key, nil)
end

@doc """
Returs Origin id from Origin Public Key
"""
@spec origin_family_from_public_key(<<_::16, _::_*8>>) :: origin_family()
def origin_family_from_public_key(<<_curve_id::8, origin_id::8, _public_key::binary>>) do
get_origin_family_from_origin_id(origin_id)
end
end
154 changes: 154 additions & 0 deletions lib/archethic_web/live/chains/origin_live.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
defmodule ArchethicWeb.OriginChainLive do
@moduledoc false
use ArchethicWeb, :live_view

alias Archethic.{
TransactionChain,
TransactionChain.Transaction,
TransactionChain.TransactionData,
TransactionChain.Transaction.ValidationStamp,
PubSub,
SharedSecrets,
Utils
}

alias ArchethicWeb.{ExplorerView, WebUtils}
alias Phoenix.{LiveView, View}

@display_limit 10
@txn_type :origin

@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 =
socket
|> assign(:tx_count, tx_count)
|> assign(:nb_pages, WebUtils.total_pages(tx_count))
|> assign(:current_page, 1)
|> assign(:transactions, transactions_from_page(1, tx_count))

{:ok, socket}
end

@spec render(assigns :: LiveView.Socket.assigns()) :: LiveView.Rendered.t()
def render(assigns) do
View.render(ExplorerView, "origin_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 = "prev_page", _params = %{"page" => page}, socket) do
{:noreply, push_patch(socket, to: Routes.live_path(socket, __MODULE__, %{"page" => page}))}
end

def handle_event(_event = "next_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(), _type :: :origin, _timestamp :: DateTime.t()},
socket :: LiveView.Socket.t()
) ::
{:noreply, LiveView.Socket.t()}
def handle_info(
_msg = {:new_transaction, address, :origin, _timestamp},
socket = %{assigns: %{current_page: current_page, tx_count: total_tx_count}}
) do
updated_socket =
case current_page do
1 ->
socket
|> assign(:tx_count, total_tx_count + 1)
|> assign(:nb_pages, WebUtils.total_pages(total_tx_count + 1))
|> assign(:current_page, 1)
|> update(:transactions, fn tx_list ->
[display_data(address) | tx_list] |> Enum.take(@display_limit)
end)

_ ->
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}

TransactionChain.list_addresses_by_type(:origin)
|> Stream.drop(nb_drops)
|> Stream.take(display_limit)
|> Stream.map(fn
nil ->
[]

address ->
display_data(address)
end)
|> Enum.reverse()
end

defp display_data(address) do
with {:ok,
%Transaction{
data: %TransactionData{content: content},
validation_stamp: %ValidationStamp{timestamp: timestamp}
}} <-
TransactionChain.get_transaction(address,
data: [:content],
validation_stamp: [:timestamp]
),
{pb_key, _} <- Utils.deserialize_public_key(content),
family_id <- SharedSecrets.origin_family_from_public_key(pb_key) do
%{
address: address,
type: @txn_type,
timestamp: timestamp,
family_of_origin: family_id
}
else
_ -> []
end
end
end
2 changes: 2 additions & 0 deletions lib/archethic_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ defmodule ArchethicWeb.Router do
live("/chain/rewards", RewardChainLive)
live("/chain/node_shared_secrets", NodeSharedSecretsChainLive)

live("/chain/origin", OriginChainLive)

live("/nodes", NodeListLive)
live("/nodes/worldmap", WorldMapLive)
live("/node/:public_key", NodeDetailsLive)
Expand Down
63 changes: 63 additions & 0 deletions lib/archethic_web/templates/explorer/origin_chain_index.html.leex
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<article class="message is-info">
<div class="message-header ">
<h1 class="subtitle is-size-4 heading has-text-white">Origin Chain</h2>
</div>
<div class="message-body">
<strong>Archethic Origin Chains</strong> is a novel concept to ensure transactions are generated from a valid device.
</a> </div>
</article>

<div class="columns">
<div class="column">
<nav class="pagination is-right" role="navigation" aria-label="pagination">

<%= if @current_page > 1 do %>
<a class="pagination-previous is-outlined has-text-white" phx-value-page="<%= @current_page - 1 %>" phx-click="prev_page">Previous</a>
<% end %>


<%= if @current_page + 1 <= @nb_pages do %>
<a class="pagination-next is-outlined has-text-white" phx-value-page="<%= @current_page + 1 %>"
phx-click="next_page">Next page</a>
<% end %>


<%# page out of total pages %>
<%= if @nb_pages != 0 do %>
<p class="pagination-list has-text-white">
Page <%= @current_page %> on <%= @nb_pages%>
</p>
<% end %>
</nav>
</div>
</div>

<div class="columns">
<div class="column">
<div class="box">
<p class="heading is-size-6">Transaction chain </p>
<div class="columns mt-6">
<div class="column">
<%= for tx <- @transactions do %>
<div class="columns">
<div class="column is-5-desktop">
<%= link to: Routes.live_path(@socket, ArchethicWeb.TransactionDetailsLive, Base.encode16(tx.address)) do%>
<span class="text_wrap"><%= Base.encode16(tx.address) %></span>
<% end %>
</div>
<div class="column is-2-desktop">
<%= format_date(tx.timestamp) %>
</div>
<div class="column is-1-desktop">
<span class="tag is-light is-info">
<%= tx.family_of_origin %>
</span>
</div>
</div>
<% end %>
</div>
</div>
</div>
</div>
</div>
</div>
5 changes: 4 additions & 1 deletion lib/archethic_web/templates/layout/root.html.eex
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,12 @@
<a class="navbar-item" href="<%= Routes.live_path(@conn, ArchethicWeb.RewardChainLive) %>">
Reward
</a>
<a class="navbar-item" href="<%= Routes.live_path(@conn, ArchethicWeb.NodeSharedSecretsChainLive) %>">
<a class="navbar-item" href="<%= Routes.live_path(@conn, ArchethicWeb.NodeSharedSecretsChainLive) %>">
Node Shared Secrets
</a>
<a class="navbar-item" href="<%= Routes.live_path(@conn, ArchethicWeb.OriginChainLive) %>">
Origin
</a>
</div>
</div>
<div class="navbar-item has-dropdown is-hoverable" >
Expand Down