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

Node settings UI #619

Merged
merged 8 commits into from
Oct 12, 2022
7 changes: 1 addition & 6 deletions config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,7 @@ config :archethic, Archethic.BeaconChain.SummaryTimer,
interval: "0 * * * * *"

config :archethic, Archethic.Bootstrap,
reward_address:
System.get_env(
"ARCHETHIC_REWARD_ADDRESS",
Base.encode16(<<0::8, 0::8, :crypto.strong_rand_bytes(32)::binary>>)
)
|> Base.decode16!(case: :mixed)
reward_address: System.get_env("ARCHETHIC_REWARD_ADDRESS", "") |> Base.decode16!(case: :mixed)

config :archethic, Archethic.Bootstrap.NetworkInit,
genesis_pools: [
Expand Down
1 change: 1 addition & 0 deletions lib/archethic/bootstrap/sync.ex
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ defmodule Archethic.Bootstrap.Sync do
|> NetworkInit.self_replication()

P2P.set_node_globally_available(Crypto.first_node_public_key())
P2P.set_node_globally_synced(Crypto.first_node_public_key())
P2P.authorize_node(Crypto.last_node_public_key(), DateTime.utc_now())

NetworkInit.init_software_origin_chain()
Expand Down
5 changes: 4 additions & 1 deletion lib/archethic/bootstrap/transaction_handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,10 @@ defmodule Archethic.Bootstrap.TransactionHandler do
condition inherit: [
# We need to ensure the type stays consistent
type: node,
content: true

# Content and token transfers will be validated during tx's validation
content: true,
token_transfers: true
]
""",
content:
Expand Down
2 changes: 2 additions & 0 deletions lib/archethic/crypto.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1041,6 +1041,8 @@ defmodule Archethic.Crypto do
end
end

def valid_address?(_), do: false

@doc """
Load the transaction for the Keystore indexing
"""
Expand Down
11 changes: 9 additions & 2 deletions lib/archethic/mining/pending_transaction_validation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,12 @@ defmodule Archethic.Mining.PendingTransactionValidation do
%Transaction{
type: :node,
data: %TransactionData{
content: content
content: content,
ledger: %Ledger{
token: %TokenLedger{
transfers: token_transfers
}
}
},
previous_public_key: previous_public_key
},
Expand All @@ -290,7 +295,9 @@ defmodule Archethic.Mining.PendingTransactionValidation do
root_ca_public_key
)},
{:conn, :ok} <-
{:conn, valid_connection(ip, port, previous_public_key)} do
{:conn, valid_connection(ip, port, previous_public_key)},
{:transfers, true} <-
{:transfers, Enum.all?(token_transfers, &Reward.is_reward_token?(&1.token_address))} do
:ok
else
:error ->
Expand Down
41 changes: 27 additions & 14 deletions lib/archethic/p2p/mem_table_loader.ex
Original file line number Diff line number Diff line change
Expand Up @@ -108,25 +108,38 @@ defmodule Archethic.P2P.MemTableLoader do
{:ok, ip, port, http_port, transport, reward_address, origin_public_key, _certificate} =
Node.decode_transaction_content(content)

node = %Node{
ip: ip,
port: port,
http_port: http_port,
first_public_key: first_public_key,
last_public_key: previous_public_key,
geo_patch: GeoPatch.from_ip(ip),
transport: transport,
last_address: address,
reward_address: reward_address,
origin_public_key: origin_public_key
}

if first_node_change?(first_public_key, previous_public_key) do
node = %Node{
ip: ip,
port: port,
http_port: http_port,
first_public_key: first_public_key,
last_public_key: previous_public_key,
geo_patch: GeoPatch.from_ip(ip),
transport: transport,
last_address: address,
reward_address: reward_address,
origin_public_key: origin_public_key
}

node
|> Node.enroll(timestamp)
|> MemTable.add_node()
else
MemTable.add_node(node)
{:ok, node} = MemTable.get_node(first_public_key)

MemTable.add_node(%{
node
| ip: ip,
port: port,
http_port: http_port,
last_public_key: previous_public_key,
geo_patch: GeoPatch.from_ip(ip),
transport: transport,
last_address: address,
reward_address: reward_address,
origin_public_key: origin_public_key
})
end

Logger.info("Node loaded into in memory p2p tables", node: Base.encode16(first_public_key))
Expand Down
15 changes: 5 additions & 10 deletions lib/archethic/replication/transaction_validator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -315,18 +315,13 @@ defmodule Archethic.Replication.TransactionValidator do
end

defp check_inputs(
tx = %Transaction{type: type, address: address},
tx = %Transaction{address: address},
inputs
) do
cond do
address == Bootstrap.genesis_address() ->
:ok

Transaction.network_type?(type) ->
:ok

true ->
do_check_inputs(tx, inputs)
if address == Bootstrap.genesis_address() do
:ok
else
do_check_inputs(tx, inputs)
end
end

Expand Down
212 changes: 212 additions & 0 deletions lib/archethic_web/live/settings_live.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
defmodule ArchethicWeb.SettingsLive do
@moduledoc false

use ArchethicWeb, :live_view

alias Archethic.Crypto

alias Archethic.P2P
alias Archethic.P2P.Node

alias Archethic.Reward

alias Archethic.TransactionChain
alias Archethic.TransactionChain.Transaction
alias Archethic.TransactionChain.TransactionData
alias Archethic.TransactionChain.TransactionData.Ledger
alias Archethic.TransactionChain.TransactionData.TokenLedger
alias Archethic.TransactionChain.TransactionData.TokenLedger.Transfer, as: TokenTransfer

alias ArchethicWeb.TransactionSubscriber

@ip_validate_regex ~r/(^127\.)|(^192\.168\.)/

def mount(_params, %{"remote_ip" => remote_ip}, socket) do
# Only authorized the page in the node's private network
private_ip? =
Regex.match?(
@ip_validate_regex,
:inet.ntoa(remote_ip) |> to_string()
)

new_socket =
socket
|> assign(:allowed, private_ip?)
|> assign(:reward_address, "")
|> assign(:error, nil)
|> assign(:sending, false)
|> assign(:notification, "")
|> assign(:notification_status, "")

{:ok, new_socket}
end

def handle_params(_params, _uri, socket = %{assigns: %{allowed: true}}) do
%Node{reward_address: reward_address} = P2P.get_node_info()
{:noreply, assign(socket, :reward_address, Base.encode16(reward_address))}
end

def handle_params(_params, _uri, socket) do
{:noreply, push_redirect(socket, to: "/", replace: true)}
end

def handle_event(
"save",
%{"reward_address" => reward_address},
socket = %{assigns: %{error: nil, reward_address: previous_reward_address}}
) do
if previous_reward_address != reward_address do
send_new_transaction(Base.decode16!(reward_address, case: :mixed))
{:noreply, assign(socket, :sending, true)}
else
{:noreply, socket}
end
end

def handle_event("save", _params, socket) do
{:noreply, socket}
end

def handle_event("validate", %{"reward_address" => reward_address}, socket) do
with {:ok, reward_address_bin} <- Base.decode16(reward_address, case: :mixed),
true <- Crypto.valid_address?(reward_address_bin) do
{:noreply, assign(socket, :error, nil)}
else
_ ->
{:noreply, assign(socket, :error, "Invalid address")}
end
end

def handle_info({:new_transaction, _tx_address}, socket) do
%Node{reward_address: reward_address} = P2P.get_node_info()

new_socket =
socket
|> assign(:sending, false)
|> assign(:reward_address, Base.encode16(reward_address))
|> assign(:notification, "Change applied!")
|> assign(:notification_status, "success")

{:noreply, new_socket}
end

def handle_info({:transaction_error, _address, _context, reason}, socket) do
new_socket =
socket
|> assign(:sending, false)
|> assign(:notification, "Transaction is invalid - #{reason}")
|> assign(:notification_status, "error")

{:noreply, new_socket}
end

def render(assigns) do
~L"""
<%= if @notification != "" do %>
<div class="notification
<%= if @notification_status == "success" do %>
is-success
<% else %>
is-danger
<% end %>
is-light" x-data="{ open: true }" x-init="() => { setTimeout(() => open = false, 3000)}" x-show="open">
<button class="delete"></button>
<%= @notification %>
</div>
<% end %>
<div class="box">
<div class="columns">
<div class="column">
<h1 class="subtitle is-5">Node's settings</h1>
</div>
</div>

<form class="columns" phx-submit="save" phx-change="validate">
<div class="column is-5-desktop">
<div class="field">
<label class="label">Reward's address</label>
<div class="control">
<input class="input <%= if @error, do: 'is-danger'%>" type="text" placeholder="Enter your new reward address" value="<%= @reward_address %>" name="reward_address" />
</div>
<p class="help is-danger"><%= @error %></p>
</div>

<div class="field">
<div class="control">
<%= if @sending do %>
<button class="button is-link is-loading" disabled>Save</button>
<% else %>
<button class="button is-link">Save</submit>
<% end %>
</div>
</div>
</div>
</div>
</div>
"""
end

defp send_new_transaction(next_reward_address) do
%Node{
ip: ip,
port: port,
http_port: http_port,
transport: transport,
reward_address: previous_reward_address
} = P2P.get_node_info()

genesis_address = Crypto.first_node_public_key() |> Crypto.derive_address()

token_transfers =
case genesis_address do
^previous_reward_address ->
get_token_transfers(previous_reward_address, next_reward_address)

_ ->
[]
end

{:ok, %Transaction{data: %TransactionData{code: code}}} =
TransactionChain.get_last_transaction(genesis_address, data: [:code])

tx =
Transaction.new(:node, %TransactionData{
ledger: %Ledger{
token: %TokenLedger{
transfers: token_transfers
}
},
code: code,
content:
Node.encode_transaction_content(
ip,
port,
http_port,
transport,
next_reward_address,
Crypto.origin_node_public_key(),
Crypto.get_key_certificate(Crypto.origin_node_public_key())
)
})

TransactionSubscriber.register(tx.address, System.monotonic_time())

Archethic.send_new_transaction(tx)
end

defp get_token_transfers(previous_reward_address, next_reward_address) do
{:ok, last_address} = Archethic.get_last_transaction_address(previous_reward_address)
{:ok, %{token: tokens}} = Archethic.get_balance(last_address)

tokens
|> Enum.filter(fn {{address, _}, _} -> Reward.is_reward_token?(address) end)
|> Enum.map(fn {{address, token_id}, amount} ->
%TokenTransfer{
to: next_reward_address,
amount: amount,
token_id: token_id,
token_address: address
}
end)
end
end
5 changes: 5 additions & 0 deletions lib/archethic_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ defmodule ArchethicWeb.Router do
)
end

scope "/settings" do
pipe_through(:browser)
live("/", ArchethicWeb.SettingsLive, session: {ArchethicWeb.WebUtils, :keep_remote_ip, []})
end

scope "/", ArchethicWeb do
get("/*path", RootController, :index)
end
Expand Down
3 changes: 3 additions & 0 deletions lib/archethic_web/transaction_subscriber.ex
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ defmodule ArchethicWeb.TransactionSubscriber do
{:error, tx_address, context, error},
state
) do
%{from: from} = Map.get(state, tx_address, %{from: make_ref()})
send(from, {:transaction_error, tx_address, context, error})

Subscription.publish(
Endpoint,
%{address: tx_address, context: context, reason: error},
Expand Down
4 changes: 4 additions & 0 deletions lib/archethic_web/web_utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,8 @@ defmodule ArchethicWeb.WebUtils do
def total_pages(tx_count), do: count_pages(tx_count) + 1

def count_pages(tx_count), do: div(tx_count, @display_limit)

def keep_remote_ip(conn) do
%{"remote_ip" => conn.remote_ip}
end
end
2 changes: 1 addition & 1 deletion src/c/nat/miniupnp
Submodule miniupnp updated 1 files
+1 −0 README