diff --git a/Cargo.lock b/Cargo.lock index 596815da..738ec5ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3253,15 +3253,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "openssl-src" -version = "300.1.6+3.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439fac53e092cd7442a3660c85dde4643ab3b5bd39040912388dcdabf6b88085" -dependencies = [ - "cc", -] - [[package]] name = "openssl-sys" version = "0.9.96" @@ -3270,7 +3261,6 @@ checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f" dependencies = [ "cc", "libc", - "openssl-src", "pkg-config", "vcpkg", ] @@ -3834,6 +3824,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "qrcode" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "166f136dfdb199f98186f3649cf7a0536534a61417a1a30221b492b4fb60ce3f" +dependencies = [ + "image", +] + [[package]] name = "quick-xml" version = "0.30.0" @@ -5116,14 +5115,16 @@ dependencies = [ "derive_setters", "log", "log4rs 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "qrcode", "ratatui", - "rust_decimal", "strum 0.24.1", "tact", + "tari_common_types", "tari_launchpad_protocol", "tari_sdm", "tari_sdm_assets", "tari_sdm_launchpad", + "tari_utilities", "thiserror", "tokio", ] @@ -5557,13 +5558,10 @@ dependencies = [ "anyhow", "async-trait", "chrono", - "futures 0.3.29", "log", "minotari_app_grpc", "minotari_node_grpc_client", "minotari_wallet_grpc_client", - "openssl", - "rand 0.8.5", "regex", "serde", "serde_json", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 3ca9a09a..16a1361e 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -4,26 +4,27 @@ version = "1.6.0" edition = "2021" [dependencies] +tari_common_types = { git = "https://github.com/tari-project/tari", tag = "v1.0.0-rc.2" } +tari_launchpad_protocol = { path = "../libs/protocol" } +tari_sdm = { path = "../libs/sdm" } +tari_sdm_assets = { path = "../libs/sdm-assets" } +tari_sdm_launchpad = { path = "../libs/sdm-launchpad" } +tari_utilities = "0.7.0" + anyhow = "1.0.71" async-trait = "0.1.68" +byte-unit = "5.1.3" crossterm = "0.26.1" derive_more = "0.99.17" +derive_setters = "0.1.0" log = "0.4.17" log4rs = "1.2.0" -rust_decimal = "1.29.1" +qrcode = { version = "0.13" } +ratatui = "0.23.0" strum = { version = "0.24.1", features = ["derive"] } tact = { path = "../libs/tact" } thiserror = "1.0.40" tokio = "1.28.1" -ratatui = "0.23.0" -byte-unit = "5.1.3" - -derive_setters = "0.1.0" - -tari_launchpad_protocol = { path = "../libs/protocol" } -tari_sdm_assets = { path = "../libs/sdm-assets" } -tari_sdm = { path = "../libs/sdm" } -tari_sdm_launchpad = { path = "../libs/sdm-launchpad" } [features] default = [] diff --git a/cli/src/component/normal/base_node.rs b/cli/src/component/normal/base_node.rs index bd87767c..3decd7b9 100644 --- a/cli/src/component/normal/base_node.rs +++ b/cli/src/component/normal/base_node.rs @@ -8,6 +8,8 @@ use ratatui::{ prelude::*, widgets::Padding, }; +use tari_common_types::types::PublicKey; +use tari_utilities::ByteArray; use crate::{ component::{ @@ -37,10 +39,9 @@ impl BaseNodeStatus { let top_line = Line::from(vec![sync_state, chain_height, peer_count]); let public_key = node_status .identity - .as_ref() - .cloned() - .map(|id| (id.public_key)) - .unwrap_or_else(|| ("-".into())); + .clone() + .map(|id| PublicKey::from_vec(&id.public_key).map_or("-".to_string(), |k| k.to_string())) + .unwrap_or_else(|| "-".into()); let mid_line = Line::from(format!("Public key: {public_key}")); Text::from(vec![top_line, mid_line]) } diff --git a/cli/src/component/settings/base_node.rs b/cli/src/component/settings/base_node.rs index 38d32ab1..828ea1b3 100644 --- a/cli/src/component/settings/base_node.rs +++ b/cli/src/component/settings/base_node.rs @@ -26,6 +26,7 @@ use ratatui::{ layout::{Constraint, Direction, Layout, Rect}, }; +use crate::component::widgets::qr_code::QrCodePreview; use crate::{ component::{ elements::block_with_title, @@ -45,6 +46,8 @@ static ROOT_FOLDER: Focus = focus_id!(); pub struct BaseNodeSettings { expert_sep: Separator, root_folder: LabeledInput, + qr_sep: Separator, + qr_code: QrCodePreview, } impl BaseNodeSettings { @@ -52,6 +55,8 @@ impl BaseNodeSettings { Self { expert_sep: Separator::new("Expert", []), root_folder: LabeledInput::new("Root folder", ROOT_FOLDER), + qr_sep: Separator::new("QR Code", []), + qr_code: QrCodePreview::new(), } } } @@ -94,7 +99,13 @@ impl Component for BaseNodeSettings { let block = block_with_title(Some("BaseNode Settings"), state.focus_on == BASE_NODE_SETTINGS); let inner_rect = block.inner(rect); f.render_widget(block, rect); - let constraints = [Constraint::Length(1), Constraint::Length(3), Constraint::Min(0)]; + let constraints = [ + Constraint::Length(1), + Constraint::Length(3), + Constraint::Length(1), + Constraint::default(), + Constraint::Min(0), + ]; let chunks = Layout::default() .vertical_margin(1) .horizontal_margin(3) @@ -103,5 +114,7 @@ impl Component for BaseNodeSettings { .split(inner_rect); self.expert_sep.draw(f, chunks[0], state); self.root_folder.draw(f, chunks[1], state); + self.qr_sep.draw(f, chunks[2], state); + self.qr_code.draw(f, chunks[3], state); } } diff --git a/cli/src/component/widgets/mod.rs b/cli/src/component/widgets/mod.rs index 9e4192d2..de9fd625 100644 --- a/cli/src/component/widgets/mod.rs +++ b/cli/src/component/widgets/mod.rs @@ -25,8 +25,10 @@ pub mod docker_detect; mod label; mod labeled_input; pub mod popup; +pub mod qr_code; mod separator; pub mod status_line; + pub use label::Label; pub use labeled_input::LabeledInput; pub use separator::Separator; diff --git a/cli/src/component/widgets/qr_code.rs b/cli/src/component/widgets/qr_code.rs new file mode 100644 index 00000000..06aa4c5d --- /dev/null +++ b/cli/src/component/widgets/qr_code.rs @@ -0,0 +1,84 @@ +// Copyright 2023. The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use qrcode::render::unicode; +use qrcode::QrCode; +use ratatui::widgets::Block; +use ratatui::{backend::Backend, layout::Rect, widgets::Paragraph}; +use tari_common_types::types::PublicKey; +use tari_launchpad_protocol::settings::TariNetwork; +use tari_launchpad_protocol::wallet::InvalidPublicKey; +use tari_utilities::hex::Hex; +use tari_utilities::ByteArray; + +use crate::{ + component::{Component, ComponentEvent, Frame, Input}, + state::AppState, +}; + +pub struct QrCodePreview {} + +impl QrCodePreview { + pub fn new() -> Self { + Self {} + } +} + +impl Input for QrCodePreview { + type Output = (); + + fn on_event(&mut self, _event: ComponentEvent, _state: &mut AppState) -> Option { + None + } +} + +impl Component for QrCodePreview { + type State = AppState; + + fn draw(&self, f: &mut Frame, rect: Rect, state: &Self::State) { + let node_status = &state.state.node; + if let (_, Some(identity)) = ( + state.state.config.session.is_base_node_active(), + node_status.identity.clone(), + ) { + let network = if let Some(settings) = state.state.config.settings.as_ref() { + settings.saved_settings.tari_network + } else { + TariNetwork::default() + }; + + let public_key = + match PublicKey::from_vec(&identity.public_key).map_err(|e| InvalidPublicKey(e.to_string())) { + Ok(public_key) => public_key, + Err(e) => { + log::warn!("Couldn't convert public key: {}", e); + return; + }, + }; + + let peer = format!("{}::{}", public_key, identity.public_addresses[0]); + let qr_link = format!( + "tari://{}/base_nodes/add?name={}&peer={}", + network.lower_case(), + identity.node_id.to_hex(), + peer + ); + if let Ok(code) = QrCode::new(qr_link.clone()) { + let image = code + .render::() + .dark_color(unicode::Dense1x2::Dark) + .light_color(unicode::Dense1x2::Light) + .max_dimensions(40, 20) + .module_dimensions(1, 1) + .build() + .lines() + .skip(1) + .fold("".to_string(), |acc, l| format!("{}{}\n", acc, l)); + + let qr_code = Paragraph::new(image).block(Block::default()); + + f.render_widget(qr_code, rect); + } + } + } +} diff --git a/libs/protocol/Cargo.toml b/libs/protocol/Cargo.toml index 5805c9fd..c9d7bc90 100644 --- a/libs/protocol/Cargo.toml +++ b/libs/protocol/Cargo.toml @@ -13,11 +13,11 @@ tari_common_types = { git = "https://github.com/tari-project/tari", tag = "v1.0. tari_utilities = "0.7.0" byte-unit = { version = "5.1.3", features = ["serde"] } +chrono = { version = "0.4.26", features = ["serde"] } derive_more = "0.99.17" +log = "0.4.20" serde = "1" +serde_repr = "0.1.16" strum = { version = "0.25.0", features = ["derive"] } thiserror = "1.0.44" -chrono = { version = "0.4.26", features = ["serde"] } -serde_repr = "0.1.16" toml = { version = "0.8.8", features = [] } -log = "0.4.20" diff --git a/libs/protocol/src/node.rs b/libs/protocol/src/node.rs index 4720824c..dcc6a943 100644 --- a/libs/protocol/src/node.rs +++ b/libs/protocol/src/node.rs @@ -5,23 +5,37 @@ use minotari_node_grpc_client::grpc::NodeIdentity; use serde::{Deserialize, Serialize}; use tari_common_types::{emoji::EmojiId, types::PublicKey}; use tari_utilities::byte_array::ByteArray; +use tari_utilities::hex::Hex; use crate::wallet::InvalidPublicKey; #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct BaseNodeAddress { - pub public_key: String, +#[serde(rename_all = "camelCase")] +pub struct BaseNodeIdentity { + pub public_key: Vec, + pub public_addresses: Vec, + pub node_id: Vec, pub emoji_id: String, } -impl TryFrom for BaseNodeAddress { +impl BaseNodeIdentity { + /// Provide the base node connection string. It is of the form + /// "0eefb45a4de9484eca74846a4f47d2c8d38e76be1fec63b0112bd00d297c0928::/ip4/13.40.98.39/tcp/18189" + pub fn connection_string(&self) -> String { + format!("{}::/dns4/base_node/tcp/18189", self.public_key.to_hex()) + } +} + +impl TryFrom for BaseNodeIdentity { type Error = InvalidPublicKey; fn try_from(value: NodeIdentity) -> Result { let public_key = PublicKey::from_vec(&value.public_key).map_err(|e| InvalidPublicKey(e.to_string()))?; let emoji_id = EmojiId::from_public_key(&public_key).to_string(); - Ok(BaseNodeAddress { - public_key: public_key.to_string(), + Ok(BaseNodeIdentity { + public_key: value.public_key, + public_addresses: value.public_addresses, + node_id: value.node_id, emoji_id, }) } @@ -30,7 +44,7 @@ impl TryFrom for BaseNodeAddress { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NodeState { // The base node's identity - pub identity: Option, + pub identity: Option, // The sync status of the base node pub sync_status: String, // The number of peers connected to the base node @@ -72,7 +86,7 @@ impl Default for NodeState { #[derive(Debug, Clone, Serialize, Deserialize)] pub enum NodeDelta { - SetIdentity(BaseNodeAddress), + SetIdentity(BaseNodeIdentity), SetSyncStatus(String), SetPeerCount(usize), SetChainLength(u64), diff --git a/libs/sdm-launchpad/Cargo.toml b/libs/sdm-launchpad/Cargo.toml index 3eb8e660..d25b402f 100644 --- a/libs/sdm-launchpad/Cargo.toml +++ b/libs/sdm-launchpad/Cargo.toml @@ -9,26 +9,23 @@ edition = "2021" [dependencies] minotari_app_grpc = { git = "https://github.com/tari-project/tari", tag = "v1.0.0-rc.2" } minotari_node_grpc_client = { git = "https://github.com/tari-project/tari", tag = "v1.0.0-rc.2" } +minotari_wallet_grpc_client = { git = "https://github.com/tari-project/tari", tag = "v1.0.0-rc.2" } tari_common_types = { git = "https://github.com/tari-project/tari", tag = "v1.0.0-rc.2" } tari_launchpad_protocol = { path = "../protocol" } -tari_sdm_assets = { path = "../sdm-assets" } tari_sdm = { path = "../sdm" } -minotari_wallet_grpc_client = { git = "https://github.com/tari-project/tari", tag = "v1.0.0-rc.2" } +tari_sdm_assets = { path = "../sdm-assets" } tari_utilities = "0.7.0" -openssl = { version = "0.10.55", features = ["vendored"] } anyhow = "1.0.72" async-trait = "0.1.72" -futures = "0.3.28" +chrono = "0.4.31" log = "0.4.19" -rand = "0.8.5" regex = "1.9.1" serde = "=1.0.167" serde_json = "1.0.103" tauri = { version = "=1.2.5", features = ["api-all", "cli", "macos-private-api"], optional = true } thiserror = "1.0.44" -tor-hash-passwd = "1.0.1" tokio = "1.29.1" toml = "0.8.8" tonic = { version = "0.8.3", features = ["tls"] } -chrono = "0.4.31" +tor-hash-passwd = "1.0.1" diff --git a/libs/sdm-launchpad/src/node_grpc.rs b/libs/sdm-launchpad/src/node_grpc.rs index 38a36961..d0754eb9 100644 --- a/libs/sdm-launchpad/src/node_grpc.rs +++ b/libs/sdm-launchpad/src/node_grpc.rs @@ -10,7 +10,7 @@ use minotari_app_grpc::tari_rpc::{ use tari_launchpad_protocol::{ errors::ErrorRecord, launchpad::{LaunchpadDelta, LaunchpadDelta::AddError, Reaction}, - node::{BaseNodeAddress, NodeDelta}, + node::{BaseNodeIdentity, NodeDelta}, }; use tokio::{ sync::{mpsc, Mutex}, @@ -104,7 +104,7 @@ impl NodeGrpcWorker { } fn process_identity(&mut self, id: NodeIdentity) { - match BaseNodeAddress::try_from(id) { + match BaseNodeIdentity::try_from(id) { Ok(id) => { let delta = NodeDelta::SetIdentity(id); self.send_update(delta); diff --git a/libs/sdm-launchpad/src/resources/config.rs b/libs/sdm-launchpad/src/resources/config.rs index 5a933a78..e7c4b3d8 100644 --- a/libs/sdm-launchpad/src/resources/config.rs +++ b/libs/sdm-launchpad/src/resources/config.rs @@ -24,17 +24,17 @@ use std::{ops::Deref, path::PathBuf}; use anyhow::{anyhow, Error}; -use minotari_node_grpc_client::grpc::NodeIdentity; use minotari_wallet_grpc_client::grpc::GetIdentityResponse; use serde::Serialize; use tari_common_types::{emoji::EmojiId, types::PublicKey}; +use tari_launchpad_protocol::node::BaseNodeIdentity; use tari_launchpad_protocol::session::LaunchpadSession; pub use tari_launchpad_protocol::{ config::LaunchpadConfig, settings::{LaunchpadSettings, TariNetwork}, }; use tari_sdm::{config::ManagedProtocol, image::Envs}; -use tari_utilities::{hex::Hex, ByteArray}; +use tari_utilities::ByteArray; #[derive(Debug)] pub struct LaunchpadProtocol; @@ -51,39 +51,6 @@ pub enum LaunchpadInnerEvent { WalletIdentityReady(WalletIdentity), } -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct BaseNodeIdentity { - pub public_key: Vec, - pub public_addresses: Vec, - node_id: Vec, - emoji_id: String, -} - -impl BaseNodeIdentity { - /// Provide the base node connection string. It is of the form - /// "0eefb45a4de9484eca74846a4f47d2c8d38e76be1fec63b0112bd00d297c0928::/ip4/13.40.98.39/tcp/18189" - pub fn connection_string(&self) -> String { - format!("{}::/dns4/base_node/tcp/18189", self.public_key.to_hex()) - } -} - -impl TryFrom for BaseNodeIdentity { - type Error = Error; - - fn try_from(value: NodeIdentity) -> Result { - let public_key = PublicKey::from_vec(&value.public_key).map_err(|_| anyhow!("PublicKey failed to parse"))?; - // TODO: Implement `Serialize` for `EmojiId` - let emoji_id = EmojiId::from_public_key(&public_key).to_string(); - Ok(BaseNodeIdentity { - public_key: value.public_key, - public_addresses: value.public_addresses, - node_id: value.node_id, - emoji_id, - }) - } -} - #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct WalletIdentity { diff --git a/libs/sdm/Cargo.toml b/libs/sdm/Cargo.toml index 4007dfc4..33bb5396 100644 --- a/libs/sdm/Cargo.toml +++ b/libs/sdm/Cargo.toml @@ -13,6 +13,7 @@ tari_launchpad_protocol = { path = "../protocol" } anyhow = "1.0.72" async-trait = "0.1.72" bollard = "0.14.0" +chrono = "0.4.26" derive_more = "0.99.17" futures = "0.3.28" log = "0.4.19" @@ -20,4 +21,3 @@ rand = "0.8.4" thiserror = "1.0.44" tokio = { version = "1.29.1", features = ["macros"] } tokio-stream = { version = "0.1.14", features = ["sync"] } -chrono = "0.4.26"