diff --git a/locales/en.yml b/locales/en.yml index 34e7892..dd5b1a8 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -19,7 +19,7 @@ sync_status: awaiting_peers: Waiting for peers header_sync: Downloading headers header_sync_percent: 'Downloading headers: %{percent}%' - tx_hashset_pibd: 'Downloading state (PIBD)' + tx_hashset_pibd: Downloading state (PIBD) tx_hashset_pibd_percent: 'Downloading state (PIBD): %{percent}%' tx_hashset_download: Downloading chain state tx_hashset_download_percent: 'Downloading chain state: %{percent}%' @@ -57,22 +57,28 @@ network_mining: loading: Mining will be available after the synchronization server_setup: Stratum server setup enable_server: Enable server - server_setting: 'Enable stratum server or change more settings by selecting %{settings} at the bottom of the screen.' + server_setting: 'Enable stratum server or change more settings by selecting %{settings} at the bottom of the screen. App restart is required to change settings of the running server.' info: 'Mining server is enabled, you can change settings by selecting %{settings} at the bottom of the screen. Data is updating when devices are connected.' - no_ip_addresses: There are no available IP addresses on your system, the server cannot be started, check your network connectivity. - choose_ip_address: 'Choose IP Address:' - change_port: 'Change port:' - ip_address: IP Address + no_ip_addresses: There are no available IP addresses on your system, stratum server cannot be started, check your network connectivity. + port_unavailable: Specified port is unavailable rewards_wallet: Wallet for rewards server: Stratum server + address: Address miners: Miners devices: Devices blocks_found: Blocks found hashrate: 'Hashrate (C%{bits})' connected: Connected disconnected: Disconnected +network_settings: + server: Server + port: Port + ip_address: IP Address + change_port: Change port + enter_value: 'Enter value:' modal: cancel: Cancel + save: Save modal_exit: description: Are you sure you want to quit the application? exit: Exit \ No newline at end of file diff --git a/locales/ru.yml b/locales/ru.yml index 1fa4921..071a817 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -19,7 +19,7 @@ sync_status: awaiting_peers: Ожидание пиров header_sync: Загрузка заголовков header_sync_percent: 'Загрузка заголовков: %{percent}%' - tx_hashset_pibd: 'Загрузка состояния (PIBD)' + tx_hashset_pibd: Загрузка состояния (PIBD) tx_hashset_pibd_percent: 'Загрузка состояния (PIBD): %{percent}%' tx_hashset_download: Загрузка состояния цепи tx_hashset_download_percent: 'Загрузка состояния цепи: %{percent}%' @@ -57,22 +57,28 @@ network_mining: loading: Майнинг будет доступен после синхронизации server_setup: Настройка stratum-сервера enable_server: Включить сервер - server_setting: 'Включите stratum-сервер или измените больше настроек, выбрав %{settings} внизу экрана.' + server_setting: 'Включите stratum-сервер или измените больше настроек, выбрав %{settings} внизу экрана. Для изменения настроек запущенного сервера потребуется перезапуск приложения.' info: 'Сервер майнинга запущен, вы можете изменить настройки, выбрав %{settings} внизу экрана. Данные обновляются, когда устройства подключены.' - no_ip_addresses: В вашей системе отсутствуют доступные IP адреса, запуск сервера невозможен, проверьте ваше подключение к сети. - choose_ip_address: 'Выберите IP адрес:' - change_port: 'Измените порт:' - ip_address: IP Адрес + no_ip_addresses: В вашей системе отсутствуют доступные IP адреса, запуск stratum-сервера невозможен, проверьте ваше подключение к сети. + port_unavailable: Указанный порт недоступен rewards_wallet: Кошелёк для наград server: Stratum-сервер + address: Адрес miners: Майнеры devices: Устройства found: Найдено hashrate: 'Хешрэйт (C%{bits})' connected: Подключен disconnected: Отключен +network_settings: + server: Сервер + ip_address: IP Адрес + port: Порт + change_port: Изменить порт + enter_value: 'Введите значение:' modal: cancel: Отмена + save: Сохранить modal_exit: description: Вы уверены, что хотите выйти из приложения? exit: Выход \ No newline at end of file diff --git a/src/gui/app.rs b/src/gui/app.rs index 208fb53..3deb326 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -64,7 +64,6 @@ impl App { /// Setup application styles. pub fn setup_visuals(ctx: &Context) { - // Setup style let mut style = (*ctx.style()).clone(); // Setup spacing for buttons. style.spacing.button_padding = egui::vec2(12.0, 8.0); @@ -76,14 +75,16 @@ impl App { style.spacing.icon_width = 24.0; style.spacing.icon_width_inner = 14.0; style.spacing.icon_spacing = 10.0; - + // Setup style ctx.set_style(style); - // Setup visuals let mut visuals = egui::Visuals::light(); - + // Setup selection color. + visuals.selection.stroke = Stroke { width: 1.0, color: Colors::TEXT }; + visuals.selection.bg_fill = Colors::GOLD; // Disable stroke around panels by default visuals.widgets.noninteractive.bg_stroke = Stroke::NONE; + // Setup visuals ctx.set_visuals(visuals); } diff --git a/src/gui/colors.rs b/src/gui/colors.rs index 7cd187e..118db5b 100644 --- a/src/gui/colors.rs +++ b/src/gui/colors.rs @@ -25,10 +25,12 @@ impl Colors { pub const GOLD: Color32 = Color32::from_rgb(255, 215, 0); pub const GREEN: Color32 = Color32::from_rgb(0, 0x64, 0); pub const RED: Color32 = Color32::from_rgb(0x8B, 0, 0); - pub const FILL: Color32 = Color32::from_gray(240); + pub const FILL: Color32 = Color32::from_gray(244); + pub const FILL_DARK: Color32 = Color32::from_gray(232); pub const TITLE: Color32 = Color32::from_gray(60); pub const TEXT: Color32 = Color32::from_gray(80); - pub const BUTTON: Color32 = Color32::from_gray(70); + pub const TEXT_BUTTON: Color32 = Color32::from_gray(70); + pub const BUTTON: Color32 = Color32::from_gray(249); pub const GRAY: Color32 = Color32::from_gray(120); pub const STROKE: Color32 = Color32::from_gray(190); pub const INACTIVE_TEXT: Color32 = Color32::from_gray(150); diff --git a/src/gui/navigator.rs b/src/gui/navigator.rs index c1baaec..09fc1a2 100644 --- a/src/gui/navigator.rs +++ b/src/gui/navigator.rs @@ -19,7 +19,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use lazy_static::lazy_static; use crate::gui::screens::ScreenId; -use crate::gui::views::{Modal, ModalId, ModalLocation}; +use crate::gui::views::{Modal, ModalLocation}; lazy_static! { /// Static [`Navigator`] state to be accessible from anywhere. @@ -53,6 +53,9 @@ impl Default for Navigator { } impl Navigator { + /// Identifier for exit [`Modal`]. + pub const EXIT_MODAL: &'static str = "exit"; + /// Initialize navigation from provided [`ScreenId`]. pub fn init(from: ScreenId) { let mut w_nav = NAVIGATOR_STATE.write().unwrap(); @@ -118,7 +121,7 @@ impl Navigator { /// Open exit confirmation [`Modal`] with provided [NAVIGATOR_STATE] lock. fn open_exit_modal_nav(mut w_nav: RwLockWriteGuard) { - let m = Modal::new(ModalId::Exit, ModalLocation::Global).title(t!("modal_exit.exit")); + let m = Modal::new(Self::EXIT_MODAL, ModalLocation::Global).title(t!("modal_exit.exit")); w_nav.global_modal = Some(m); } diff --git a/src/gui/screens/accounts.rs b/src/gui/screens/accounts.rs index cfda9f4..1935b87 100644 --- a/src/gui/screens/accounts.rs +++ b/src/gui/screens/accounts.rs @@ -15,7 +15,7 @@ use egui::Frame; use crate::gui::icons::{ARROW_CIRCLE_LEFT, GLOBE, PLUS}; -use crate::gui::Navigator; +use crate::gui::{Colors, Navigator}; use crate::gui::platform::PlatformCallbacks; use crate::gui::screens::{Screen, ScreenId}; use crate::gui::views::{TitlePanel, TitlePanelAction, View}; @@ -43,6 +43,7 @@ impl Screen for Accounts { egui::CentralPanel::default() .frame(Frame { stroke: View::DEFAULT_STROKE, + fill: Colors::FILL_DARK, ..Default::default() }) .show_inside(ui, |ui| { diff --git a/src/gui/screens/root.rs b/src/gui/screens/root.rs index 833233a..8127059 100644 --- a/src/gui/screens/root.rs +++ b/src/gui/screens/root.rs @@ -17,7 +17,7 @@ use std::cmp::min; use crate::gui::{App, Colors, Navigator}; use crate::gui::platform::PlatformCallbacks; use crate::gui::screens::{Account, Accounts, Screen, ScreenId}; -use crate::gui::views::{ModalId, ModalLocation, Network, View}; +use crate::gui::views::{ModalLocation, Network, View}; use crate::node::Node; pub struct Root { @@ -67,10 +67,9 @@ impl Root { ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) { - let location = ModalLocation::Global; - Navigator::modal_ui(ui, location, |ui, modal| { + Navigator::modal_ui(ui, ModalLocation::Global, |ui, modal| { match modal.id { - ModalId::Exit => { + Navigator::EXIT_MODAL => { if self.show_exit_progress { if !Node::is_running() { App::exit(frame, cb); @@ -89,30 +88,36 @@ impl Root { ui.label(t!("modal_exit.description")); }); ui.add_space(10.0); - // Setup spacing between buttons - ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0); - ui.columns(2, |columns| { - columns[0].vertical_centered_justified(|ui| { - View::button(ui, t!("modal_exit.exit"), Colors::WHITE, || { - if !Node::is_running() { - App::exit(frame, cb); + + // Show modal buttons. + ui.scope(|ui| { + // Setup spacing between buttons. + ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0); + + ui.columns(2, |columns| { + columns[0].vertical_centered_justified(|ui| { + View::button(ui, t!("modal_exit.exit"), Colors::WHITE, || { + if !Node::is_running() { + App::exit(frame, cb); + modal.close(); + } else { + Node::stop(true); + modal.disable_closing(); + self.show_exit_progress = true; + } + }); + }); + columns[1].vertical_centered_justified(|ui| { + View::button(ui, t!("modal.cancel"), Colors::WHITE, || { modal.close(); - } else { - Node::stop(true); - modal.disable_closing(); - self.show_exit_progress = true; - } - }); - }); - columns[1].vertical_centered_justified(|ui| { - View::button(ui, t!("modal.cancel"), Colors::WHITE, || { - modal.close(); + }); }); }); + ui.add_space(6.0); }); - ui.add_space(6.0); } } + _ => {} } }); } diff --git a/src/gui/views/mod.rs b/src/gui/views/mod.rs index c99769c..b4cffbd 100644 --- a/src/gui/views/mod.rs +++ b/src/gui/views/mod.rs @@ -27,4 +27,6 @@ pub use network::*; mod network_node; mod network_settings; mod network_metrics; -mod network_mining; \ No newline at end of file +mod network_mining; +mod settings_stratum; +mod settings_node_server; \ No newline at end of file diff --git a/src/gui/views/modal.rs b/src/gui/views/modal.rs index 92755f9..686229a 100644 --- a/src/gui/views/modal.rs +++ b/src/gui/views/modal.rs @@ -21,11 +21,6 @@ use egui::epaint::RectShape; use crate::gui::Colors; use crate::gui::views::View; -/// Identifier for [`Modal`] content to draw at [`Modal::ui`]. -pub enum ModalId { - Exit -} - /// Location for [`Modal`] at application UI. pub enum ModalLocation { /// To draw globally above side panel and screen. @@ -46,8 +41,8 @@ pub enum ModalPosition { /// Stores data to draw dialog box/popup at UI, powered by [`egui::Window`]. pub struct Modal { - /// Identifier for content. - pub(crate) id: ModalId, + /// Identifier for modal. + pub(crate) id: &'static str, /// Location at UI. pub(crate) location: ModalLocation, /// Position on the screen. @@ -65,7 +60,7 @@ impl Modal { const DEFAULT_WIDTH: i64 = 380; /// Create open and closeable Modal with center position. - pub fn new(id: ModalId, location: ModalLocation) -> Self { + pub fn new(id: &'static str, location: ModalLocation) -> Self { Self { id, location, @@ -116,9 +111,7 @@ impl Modal { /// Show Modal with provided content. pub fn ui(&self, ui: &mut egui::Ui, add_content: impl FnOnce(&mut egui::Ui, &Modal)) { - let width = min(ui.available_width() as i64 - 20, Self::DEFAULT_WIDTH) as f32; - - // Show background Window at full available size + // Show background Window at full available size. egui::Window::new(self.window_id(true)) .title_bar(false) .resizable(false) @@ -133,13 +126,17 @@ impl Modal { ui.set_min_size(ui.available_size()); }); - // Show main content Window at given position + // Choose width of modal content. + let width = min(ui.available_width() as i64 - 20, Self::DEFAULT_WIDTH) as f32; + + // Show main content Window at given position. + let (content_align, content_offset) = self.modal_position(); let layer_id = egui::Window::new(self.window_id(false)) .title_bar(false) .resizable(false) .collapsible(false) .default_width(width) - .anchor(self.modal_position(), Vec2::default()) + .anchor(content_align, content_offset) .frame(egui::Frame { rounding: Rounding::same(8.0), fill: Colors::YELLOW, @@ -152,7 +149,7 @@ impl Modal { self.draw_content(ui, add_content); }).unwrap().response.layer_id; - // Always show main content Window above background Window + // Always show main content Window above background Window. ui.ctx().move_to_top(layer_id); } @@ -173,11 +170,16 @@ impl Modal { } /// Get [`egui::Window`] position based on [`ModalPosition`]. - fn modal_position(&self) -> Align2 { - match self.position { + fn modal_position(&self) -> (Align2, Vec2) { + let align = match self.position { ModalPosition::CenterTop => { Align2::CENTER_TOP } ModalPosition::Center => { Align2::CENTER_CENTER } - } + }; + let offset = match self.position { + ModalPosition::CenterTop => { Vec2::new(0.0, 20.0) } + ModalPosition::Center => { Vec2::new(0.0, 0.0) } + }; + (align, offset) } /// Draw provided content. diff --git a/src/gui/views/network.rs b/src/gui/views/network.rs index 79ee999..4ed1044 100644 --- a/src/gui/views/network.rs +++ b/src/gui/views/network.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::net::{IpAddr, Ipv4Addr, SocketAddrV4, TcpListener}; +use std::str::FromStr; use std::time::Duration; use egui::{Color32, lerp, Rgba, RichText, Stroke}; @@ -22,17 +24,18 @@ use grin_chain::SyncStatus; use crate::gui::{Colors, Navigator}; use crate::gui::icons::{CARDHOLDER, DATABASE, DOTS_THREE_OUTLINE_VERTICAL, FACTORY, FADERS, GAUGE}; use crate::gui::platform::PlatformCallbacks; +use crate::gui::views::{Modal, ModalLocation, View}; use crate::gui::views::network_metrics::NetworkMetrics; use crate::gui::views::network_mining::NetworkMining; use crate::gui::views::network_node::NetworkNode; use crate::gui::views::network_settings::NetworkSettings; -use crate::gui::views::View; use crate::node::Node; use crate::Settings; pub trait NetworkTab { fn get_type(&self) -> NetworkTabType; fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks); + fn on_modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks); } #[derive(PartialEq)] @@ -68,13 +71,19 @@ impl Default for Network { impl Network { pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) { + // Show modal if it's opened. + if Navigator::is_modal_open(ModalLocation::SidePanel) { + Navigator::modal_ui(ui, ModalLocation::SidePanel, |ui, modal| { + self.current_tab.as_mut().on_modal_ui(ui, modal, cb); + }); + } + egui::TopBottomPanel::top("network_title") .resizable(false) .frame(egui::Frame { fill: Colors::YELLOW, inner_margin: Margin::same(0.0), outer_margin: Margin::same(0.0), - stroke: Stroke::NONE, ..Default::default() }) .show_inside(ui, |ui| { @@ -217,7 +226,8 @@ impl Network { }); } - pub fn disabled_server_content(ui: &mut egui::Ui) { + /// Content to draw when node is disabled. + pub fn disabled_node_ui(ui: &mut egui::Ui) { View::center_content(ui, 162.0, |ui| { let text = t!("network.disabled_server", "dots" => DOTS_THREE_OUTLINE_VERTICAL); ui.label(RichText::new(text) @@ -241,5 +251,25 @@ impl Network { }); }); } + + /// List of available IP addresses. + pub fn get_ip_list() -> Vec { + let mut addresses = Vec::new(); + for net_if in pnet::datalink::interfaces() { + for ip in net_if.ips { + if ip.is_ipv4() { + addresses.push(ip.ip()); + } + } + } + addresses + } + + /// Check whether a port is available on the provided host. + pub fn is_port_available(host: &str, port: u16) -> bool { + let ip_addr = Ipv4Addr::from_str(host).unwrap(); + let ipv4 = SocketAddrV4::new(ip_addr, port); + TcpListener::bind(ipv4).is_ok() + } } diff --git a/src/gui/views/network_metrics.rs b/src/gui/views/network_metrics.rs index e4ce78f..1ee9a01 100644 --- a/src/gui/views/network_metrics.rs +++ b/src/gui/views/network_metrics.rs @@ -13,13 +13,13 @@ // limitations under the License. use chrono::{DateTime, NaiveDateTime, Utc}; -use egui::{RichText, Rounding, ScrollArea, Stroke}; +use egui::{RichText, Rounding, ScrollArea, Stroke, Ui}; use grin_servers::DiffBlock; use crate::gui::Colors; use crate::gui::icons::{AT, COINS, CUBE_TRANSPARENT, HASH, HOURGLASS_LOW, HOURGLASS_MEDIUM, TIMER}; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{Network, NetworkTab, NetworkTabType, View}; +use crate::gui::views::{Modal, Network, NetworkTab, NetworkTabType, View}; use crate::node::Node; #[derive(Default)] @@ -39,7 +39,7 @@ impl NetworkTab for NetworkMetrics { // Show message when node is not running or loading spinner when metrics are not available. if server_stats.is_none() || server_stats.as_ref().unwrap().diff_stats.height == 0 { if !Node::is_running() { - Network::disabled_server_content(ui); + Network::disabled_node_ui(ui); } else { View::center_content(ui, 162.0, |ui| { View::big_loading_spinner(ui); @@ -113,9 +113,9 @@ impl NetworkTab for NetworkMetrics { // Show difficulty adjustment window blocks let blocks_size = stats.diff_stats.last_blocks.len(); ScrollArea::vertical() + .id_source("difficulty_scroll") .auto_shrink([false; 2]) .stick_to_bottom(true) - .id_source("difficulty_scroll") .show_rows( ui, DIFF_BLOCK_UI_HEIGHT, @@ -137,6 +137,8 @@ impl NetworkTab for NetworkMetrics { }, ); } + + fn on_modal_ui(&mut self, ui: &mut Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {} } const DIFF_BLOCK_UI_HEIGHT: f32 = 76.60; diff --git a/src/gui/views/network_mining.rs b/src/gui/views/network_mining.rs index 2e2c56d..d6a1f50 100644 --- a/src/gui/views/network_mining.rs +++ b/src/gui/views/network_mining.rs @@ -12,24 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::fmt::format; -use std::net::IpAddr; -use std::str::FromStr; use chrono::{DateTime, NaiveDateTime, Utc}; -use egui::{RichText, Rounding, ScrollArea, Stroke}; +use egui::{Response, RichText, Rounding, ScrollArea, Stroke, TextStyle, Widget}; use grin_chain::SyncStatus; use grin_servers::WorkerStats; -use pnet::ipnetwork::IpNetwork; -use crate::gui::Colors; -use crate::gui::icons::{BARBELL, CLOCK_AFTERNOON, COMPUTER_TOWER, CPU, CUBE, FADERS, FOLDER_DASHED, FOLDER_NOTCH_MINUS, FOLDER_NOTCH_PLUS, PLUGS, PLUGS_CONNECTED, POLYGON, WRENCH}; +use crate::gui::{Colors, Navigator}; +use crate::gui::icons::{BARBELL, CLOCK_AFTERNOON, COMPUTER_TOWER, CPU, CUBE, FADERS, FOLDER_DASHED, FOLDER_NOTCH_MINUS, FOLDER_NOTCH_PLUS, PLUGS, PLUGS_CONNECTED, POLYGON}; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{Network, NetworkTab, NetworkTabType, View}; -use crate::node::Node; +use crate::gui::views::{Modal, Network, NetworkTab, NetworkTabType, View}; +use crate::gui::views::settings_stratum::StratumServerSetup; +use crate::node::{Node, NodeConfig}; use crate::Settings; #[derive(Default)] -pub struct NetworkMining; +pub struct NetworkMining { + stratum_server_setup: StratumServerSetup +} impl NetworkTab for NetworkMining { fn get_type(&self) -> NetworkTabType { @@ -42,7 +41,7 @@ impl NetworkTab for NetworkMining { // Show message when node is not running or loading spinner when mining are not available. if !server_stats.is_some() || Node::get_sync_status().unwrap() != SyncStatus::NoSync { if !Node::is_running() { - Network::disabled_server_content(ui); + Network::disabled_node_ui(ui); } else { View::center_content(ui, 162.0, |ui| { View::big_loading_spinner(ui); @@ -58,137 +57,31 @@ impl NetworkTab for NetworkMining { let stratum_stats = &server_stats.as_ref().unwrap().stratum_stats; - // Stratum server address + port from config. - let saved_stratum_addr = Settings::node_config_to_read() - .members.clone() - .server.stratum_mining_config.unwrap() - .stratum_server_addr.unwrap(); - let (stratum_addr, stratum_port) = saved_stratum_addr.split_once(":").unwrap(); - - // List of available ip addresses. - let mut addresses = Vec::new(); - for net_if in pnet::datalink::interfaces() { - for ip in net_if.ips { - if ip.is_ipv4() { - addresses.push(ip.ip()); - } - } - } - - // Show error message when IP addresses are not available on the system. - if addresses.is_empty() { - View::center_content(ui, 52.0, |ui| { - ui.label(RichText::new(t!("network_mining.no_ip_addresses")) - .size(16.0) - .color(Colors::INACTIVE_TEXT) - ); - }); - return; - } - - // Show stratum server setup when mining server is not enabled. + // Show stratum server setup when mining server is not running. if !stratum_stats.is_running && !Node::is_stratum_server_starting() { ScrollArea::vertical() + .id_source("stratum_server_setup") .auto_shrink([false; 2]) .show(ui, |ui| { - ui.add_space(6.0); - View::sub_title(ui, - format!("{} {}", WRENCH, t!("network_mining.server_setup"))); - - ui.add_space(4.0); - View::horizontal_line(ui, Colors::ITEM_STROKE); - ui.add_space(6.0); + self.stratum_server_setup.ui(ui, cb); ui.vertical_centered(|ui| { - ui.label(RichText::new(t!("network_mining.choose_ip_address")) - .size(16.0) - .color(Colors::GRAY) - ); - ui.add_space(10.0); - - if addresses.len() != 0 { - let saved_ip_addr = &IpAddr::from_str(stratum_addr).unwrap(); - let mut selected_addr = saved_ip_addr; - - // Set first IP address as current if saved is not present at system. - if !addresses.contains(selected_addr) { - selected_addr = addresses.get(0).unwrap(); - } - - // Show available IP addresses on the system. - let _ = addresses.chunks(2).map(|x| { - if x.len() == 2 { - ui.columns(2, |columns| { - let addr0 = x.get(0).unwrap(); - columns[0].vertical_centered(|ui| { - View::radio_value(ui, - &mut selected_addr, - addr0, - addr0.to_string()); - }); - let addr1 = x.get(1).unwrap(); - columns[1].vertical_centered(|ui| { - View::radio_value(ui, - &mut selected_addr, - addr1, - addr1.to_string()); - }) - }); - ui.add_space(12.0); - } else { - let addr = x.get(0).unwrap(); - View::radio_value(ui, - &mut selected_addr, - addr, - addr.to_string()); - ui.add_space(4.0); - } - }).collect::>(); - - // Save stratum server address at config if it was changed. - if saved_ip_addr != selected_addr { - let addr_to_save = format!("{}:{}", selected_addr, stratum_port); - let mut w_node_config = Settings::node_config_to_update(); - w_node_config.members - .server.stratum_mining_config.as_mut().unwrap() - .stratum_server_addr = Some(addr_to_save); - w_node_config.save(); - } - } - - ui.label(RichText::new(t!("network_mining.change_port")) - .size(16.0) - .color(Colors::GRAY) - ); - - // Show button to choose server port. - ui.add_space(6.0); - View::button(ui, stratum_port.to_string(), Colors::WHITE, || { - //TODO: Open modal to change value - cb.show_keyboard(); - }); - - ui.add_space(14.0); - View::horizontal_line(ui, Colors::ITEM_STROKE); - ui.add_space(6.0); - // Show message about stratum server config. - let text = t!( - "network_mining.server_setting", - "address" => saved_stratum_addr, - "settings" => FADERS - ); + let text = t!("network_mining.server_setting", "settings" => FADERS); ui.label(RichText::new(text) .size(16.0) .color(Colors::INACTIVE_TEXT) ); - ui.add_space(8.0); + ui.add_space(4.0); - // Show button to enable server. - View::button(ui, t!("network_mining.enable_server"), Colors::GOLD, || { - Node::start_stratum_server(); - }); - ui.add_space(2.0); + // Show button to enable stratum server if port is available. + if self.stratum_server_setup.stratum_port_available { + ui.add_space(6.0); + View::button(ui, t!("network_mining.enable_server"), Colors::GOLD, || { + Node::start_stratum_server(); + }); + ui.add_space(2.0); + } let stratum_enabled = Settings::node_config_to_read() .members.clone() @@ -218,9 +111,10 @@ impl NetworkTab for NetworkMining { View::sub_title(ui, format!("{} {}", COMPUTER_TOWER, t!("network_mining.server"))); ui.columns(2, |columns| { columns[0].vertical_centered(|ui| { + let (stratum_addr, stratum_port) = NodeConfig::get_stratum_address_port(); View::rounded_box(ui, - saved_stratum_addr, - t!("network_mining.ip_address"), + format!("{}:{}", stratum_addr, stratum_port), + t!("network_mining.address"), [true, false, true, false]); }); columns[1].vertical_centered(|ui| { @@ -302,7 +196,6 @@ impl NetworkTab for NetworkMining { ui.add_space(4.0); View::horizontal_line(ui, Colors::ITEM_STROKE); ui.add_space(4.0); - ScrollArea::vertical() .auto_shrink([false; 2]) .id_source("stratum_workers_scroll") @@ -322,7 +215,7 @@ impl NetworkTab for NetworkMining { } else { [false, false] }; - draw_worker_stats(ui, worker, rounding) + draw_workers_stats(ui, worker, rounding) } }, ); @@ -335,11 +228,20 @@ impl NetworkTab for NetworkMining { }); } } + + fn on_modal_ui(&mut self, ui: &mut egui::Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + match modal.id { + StratumServerSetup::STRATUM_PORT_MODAL => { + self.stratum_server_setup.stratum_port_modal_ui(ui, modal, cb); + }, + _ => {} + } + } } const WORKER_UI_HEIGHT: f32 = 77.0; -fn draw_worker_stats(ui: &mut egui::Ui, ws: &WorkerStats, rounding: [bool; 2]) { +fn draw_workers_stats(ui: &mut egui::Ui, ws: &WorkerStats, rounding: [bool; 2]) { // Add space before the first item. if rounding[0] { ui.add_space(4.0); diff --git a/src/gui/views/network_node.rs b/src/gui/views/network_node.rs index 6b42d70..0db2bd6 100644 --- a/src/gui/views/network_node.rs +++ b/src/gui/views/network_node.rs @@ -13,13 +13,13 @@ // limitations under the License. use eframe::epaint::Stroke; -use egui::{RichText, Rounding, ScrollArea}; +use egui::{RichText, Rounding, ScrollArea, Ui}; use grin_servers::PeerStats; use crate::gui::Colors; use crate::gui::icons::{AT, CUBE, DEVICES, FLOW_ARROW, HANDSHAKE, PACKAGE, PLUGS_CONNECTED, SHARE_NETWORK}; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{Network, NetworkTab, NetworkTabType, View}; +use crate::gui::views::{Modal, Network, NetworkTab, NetworkTabType, View}; use crate::node::Node; #[derive(Default)] @@ -35,7 +35,7 @@ impl NetworkTab for NetworkNode { // Show message when node is not running or loading spinner when stats are not available. if !server_stats.is_some() { if !Node::is_running() { - Network::disabled_server_content(ui); + Network::disabled_node_ui(ui); } else { ui.centered_and_justified(|ui| { View::big_loading_spinner(ui); @@ -47,6 +47,7 @@ impl NetworkTab for NetworkNode { let stats = server_stats.as_ref().unwrap(); ScrollArea::vertical() + .id_source("integrated_node") .auto_shrink([false; 2]) .show(ui, |ui| { // Show header info. @@ -180,6 +181,8 @@ impl NetworkTab for NetworkNode { } }); } + + fn on_modal_ui(&mut self, ui: &mut Ui, modal: &Modal, cb: &dyn PlatformCallbacks) {} } fn draw_peer_stats(ui: &mut egui::Ui, peer: &PeerStats, rounding: [bool; 2]) { diff --git a/src/gui/views/network_settings.rs b/src/gui/views/network_settings.rs index 30e1691..0e1bd2b 100644 --- a/src/gui/views/network_settings.rs +++ b/src/gui/views/network_settings.rs @@ -12,9 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +use egui::Ui; use grin_core::global::ChainTypes; +use crate::gui::Colors; +use crate::gui::icons::COMPUTER_TOWER; use crate::gui::platform::PlatformCallbacks; -use crate::gui::views::{NetworkTab, NetworkTabType}; +use crate::gui::views::{Modal, NetworkTab, NetworkTabType, View}; use crate::Settings; #[derive(Default)] @@ -26,5 +29,13 @@ impl NetworkTab for NetworkSettings { } fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { + View::sub_title(ui, format!("{} {}", COMPUTER_TOWER, t!("network_settings.server"))); + View::horizontal_line(ui, Colors::ITEM_STROKE); + ui.add_space(4.0); + + } + + fn on_modal_ui(&mut self, ui: &mut Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + } } \ No newline at end of file diff --git a/src/gui/views/settings_node_server.rs b/src/gui/views/settings_node_server.rs new file mode 100644 index 0000000..aac9adb --- /dev/null +++ b/src/gui/views/settings_node_server.rs @@ -0,0 +1,16 @@ +// Copyright 2023 The Grim Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// /// Integrated node server setup ui section. +// struct \ No newline at end of file diff --git a/src/gui/views/settings_stratum.rs b/src/gui/views/settings_stratum.rs new file mode 100644 index 0000000..1322c3b --- /dev/null +++ b/src/gui/views/settings_stratum.rs @@ -0,0 +1,247 @@ +// Copyright 2023 The Grim Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::net::IpAddr; +use std::str::FromStr; + +use egui::{RichText, TextStyle, Widget}; + +use crate::gui::{Colors, Navigator}; +use crate::gui::icons::WRENCH; +use crate::gui::platform::PlatformCallbacks; +use crate::gui::views::{Modal, ModalLocation, ModalPosition, Network, View}; +use crate::node::NodeConfig; + +/// Stratum server setup ui section. +pub struct StratumServerSetup { + /// Stratum address to be used inside edit modal. + stratum_address_edit: String, + /// Stratum port to be used inside edit modal. + stratum_port_edit: String, + /// Flag to check if stratum port is available inside edit modal. + port_available_edit: bool, + + /// Flag to check if stratum port is available from saved config value. + pub(crate) stratum_port_available: bool +} + +impl Default for StratumServerSetup { + fn default() -> Self { + let (stratum_address, stratum_port) = NodeConfig::get_stratum_address_port(); + let is_port_available = Network::is_port_available(stratum_address.as_str(), stratum_port); + Self { + stratum_address_edit: stratum_address, + stratum_port_edit: stratum_port.to_string(), + port_available_edit: is_port_available, + stratum_port_available: is_port_available + } + } +} + +impl StratumServerSetup { + /// Identifier for stratum port [`Modal`]. + pub const STRATUM_PORT_MODAL: &'static str = "stratum_port"; + + pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { + View::sub_title(ui, format!("{} {}", WRENCH, t!("network_mining.server_setup"))); + View::horizontal_line(ui, Colors::ITEM_STROKE); + ui.add_space(4.0); + + // Show error message when IP addresses are not available on the system. + let mut addresses = Network::get_ip_list(); + if addresses.is_empty() { + ui.vertical_centered(|ui| { + ui.label(RichText::new(t!("network_mining.no_ip_addresses")) + .size(16.0) + .color(Colors::INACTIVE_TEXT) + ); + ui.add_space(6.0); + }); + return; + } + + ui.vertical_centered(|ui| { + ui.label(RichText::new(t!("network_settings.ip_address")) + .size(16.0) + .color(Colors::GRAY) + ); + ui.add_space(6.0); + + // Show stratum IP address setup. + Self::ip_address_setup_ui(ui, addresses); + + View::horizontal_line(ui, Colors::ITEM_STROKE); + ui.add_space(6.0); + + ui.label(RichText::new(t!("network_settings.port")) + .size(16.0) + .color(Colors::GRAY) + ); + + // Show button to choose stratum server port. + ui.add_space(6.0); + let (stratum_address, stratum_port) = NodeConfig::get_stratum_address_port(); + View::button(ui, stratum_port.to_string(), Colors::BUTTON, || { + // Setup values for modal. + self.stratum_address_edit = stratum_address.clone(); + self.stratum_port_edit = stratum_port.to_string(); + self.port_available_edit = Network::is_port_available( + stratum_address.as_str(), + stratum_port + ); + + // Show stratum port modal. + let port_modal = Modal::new(Self::STRATUM_PORT_MODAL, + ModalLocation::SidePanel) + .position(ModalPosition::CenterTop) + .title(t!("network_settings.change_port")); + Navigator::open_modal(port_modal); + cb.show_keyboard(); + }); + ui.add_space(12.0); + + // Show error when stratum server port is unavailable. + if !self.stratum_port_available { + ui.label(RichText::new(t!("network_mining.port_unavailable")) + .size(16.0) + .color(Colors::RED)); + ui.add_space(12.0); + } + + View::horizontal_line(ui, Colors::ITEM_STROKE); + ui.add_space(6.0); + }); + } + + /// Draw stratum port [`Modal`] content. + pub fn stratum_port_modal_ui(&mut self, + ui: &mut egui::Ui, + modal: &Modal, + cb: &dyn PlatformCallbacks) { + ui.add_space(6.0); + ui.vertical_centered(|ui| { + ui.label(RichText::new(t!("network_settings.enter_value")) + .size(16.0) + .color(Colors::GRAY)); + ui.add_space(8.0); + + // Draw stratum port text edit. + let text_edit_resp = egui::TextEdit::singleline(&mut self.stratum_port_edit) + .font(TextStyle::Button) + .desired_width(48.0) + .cursor_at_end(true) + .ui(ui); + text_edit_resp.request_focus(); + if text_edit_resp.clicked() { + cb.show_keyboard(); + } + + // Show error when specified port is unavailable. + if !self.port_available_edit { + ui.add_space(12.0); + ui.label(RichText::new(t!("network_mining.port_unavailable")) + .size(16.0) + .color(Colors::RED)); + } + + ui.add_space(12.0); + + // Show modal buttons. + ui.scope(|ui| { + // Setup spacing between buttons. + ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0); + + ui.columns(2, |columns| { + columns[0].vertical_centered_justified(|ui| { + View::button(ui, t!("modal.cancel"), Colors::WHITE, || { + // Close modal. + cb.hide_keyboard(); + modal.close(); + }); + }); + columns[1].vertical_centered_justified(|ui| { + View::button(ui, t!("modal.save"), Colors::WHITE, || { + // Check if port is available. + let port_parse = self.stratum_port_edit.parse::(); + let is_available = port_parse.is_ok() && Network::is_port_available( + self.stratum_address_edit.as_str(), + port_parse.unwrap() + ); + self.port_available_edit = is_available; + + // Save port at config if it's available. + if self.port_available_edit { + NodeConfig::save_stratum_address_port( + self.stratum_address_edit.clone(), + self.stratum_port_edit.clone() + ); + + self.stratum_port_available = true; + cb.hide_keyboard(); + modal.close(); + } + }); + }); + }); + ui.add_space(6.0); + }); + }); + } + + /// Show stratum IP address setup. + fn ip_address_setup_ui(ui: &mut egui::Ui, addresses: Vec) { + let (addr, port) = NodeConfig::get_stratum_address_port(); + let saved_ip_addr = &IpAddr::from_str(addr.as_str()).unwrap(); + let mut selected_addr = saved_ip_addr; + + // Set first IP address as current if saved is not present at system. + if !addresses.contains(selected_addr) { + selected_addr = addresses.get(0).unwrap(); + } + + // Show available IP addresses on the system. + let _ = addresses.chunks(2).map(|x| { + if x.len() == 2 { + ui.columns(2, |columns| { + let addr0 = x.get(0).unwrap(); + columns[0].vertical_centered(|ui| { + View::radio_value(ui, + &mut selected_addr, + addr0, + addr0.to_string()); + }); + let addr1 = x.get(1).unwrap(); + columns[1].vertical_centered(|ui| { + View::radio_value(ui, + &mut selected_addr, + addr1, + addr1.to_string()); + }) + }); + } else { + let addr = x.get(0).unwrap(); + View::radio_value(ui, + &mut selected_addr, + addr, + addr.to_string()); + } + ui.add_space(10.0); + }).collect::>(); + + // Save stratum server address at config if it was changed. + if saved_ip_addr != selected_addr { + NodeConfig::save_stratum_address_port(selected_addr.to_string(), port.to_string()); + } + } +} \ No newline at end of file diff --git a/src/gui/views/views.rs b/src/gui/views/views.rs index e9f2c3c..6922f5f 100644 --- a/src/gui/views/views.rs +++ b/src/gui/views/views.rs @@ -115,7 +115,7 @@ impl View { /// Draw [`Button`] with specified background fill color. pub fn button(ui: &mut egui::Ui, text: String, fill_color: Color32, action: impl FnOnce()) { - let br = Button::new(RichText::new(text.to_uppercase()).size(18.0).color(Colors::BUTTON)) + let br = Button::new(RichText::new(text.to_uppercase()).size(18.0).color(Colors::TEXT_BUTTON)) .stroke(Self::DEFAULT_STROKE) .fill(fill_color) .ui(ui); @@ -207,7 +207,7 @@ impl View { /// Draw the button that looks like checkbox with callback on check. pub fn checkbox(ui: &mut egui::Ui, checked: bool, text: String, callback: impl FnOnce()) { let (text_value, color) = match checked { - true => { (format!("{} {}", CHECK_SQUARE, text), Colors::BUTTON) } + true => { (format!("{} {}", CHECK_SQUARE, text), Colors::TEXT_BUTTON) } false => { (format!("{} {}", SQUARE, text), Colors::TEXT) } }; let br = Button::new(RichText::new(text_value).size(18.0).color(color)) diff --git a/src/node/config.rs b/src/node/config.rs index 37e90f5..cf03870 100644 --- a/src/node/config.rs +++ b/src/node/config.rs @@ -12,9 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::fs::File; +use std::io::{BufRead, BufReader, Write}; +use std::net::IpAddr; +use std::str::FromStr; + use grin_config::{config, ConfigError, ConfigMembers, GlobalConfig}; use grin_config::config::{API_SECRET_FILE_NAME, FOREIGN_API_SECRET_FILE_NAME, SERVER_CONFIG_FILE_NAME}; use grin_core::global::ChainTypes; +use grin_p2p::msg::PeerAddrs; +use grin_p2p::{PeerAddr, Seeding}; +use grin_servers::common::types::ChainValidationMode; use serde::{Deserialize, Serialize}; use crate::Settings; @@ -73,4 +81,429 @@ impl NodeConfig { config::check_api_secret(&api_secret_path) } } + + /// Get stratum server IP address and port. + pub fn get_stratum_address_port() -> (String, u16) { + let r_config = Settings::node_config_to_read(); + let saved_stratum_addr = r_config + .members + .server + .stratum_mining_config + .as_ref() + .unwrap() + .stratum_server_addr + .as_ref() + .unwrap(); + let (addr, port) = saved_stratum_addr.split_once(":").unwrap(); + (addr.to_string(), port.parse().unwrap()) + } + + /// Save stratum server IP address and port. + pub fn save_stratum_address_port(addr: String, port: String) { + let addr_to_save = format!("{}:{}", addr, port); + let mut w_node_config = Settings::node_config_to_update(); + w_node_config + .members + .server + .stratum_mining_config + .as_mut() + .unwrap() + .stratum_server_addr = Some(addr_to_save); + w_node_config.save(); + } + + /// Get API server IP address and port. + pub fn get_api_address_port() -> (String, u16) { + let r_config = Settings::node_config_to_read(); + let saved_api_addr = r_config + .members + .server + .api_http_addr + .as_str(); + let (addr, port) = saved_api_addr.split_once(":").unwrap(); + (addr.to_string(), port.parse().unwrap()) + } + + /// Save API server IP address and port. + pub fn save_api_server_address_port(addr: String, port: String) { + let addr_to_save = format!("{}:{}", addr, port); + let mut w_node_config = Settings::node_config_to_update(); + w_node_config.members.server.api_http_addr = addr_to_save; + w_node_config.save(); + } + + /// Get API secret text. + pub fn get_api_secret() -> String { + let r_config = Settings::node_config_to_read(); + let api_secret_path = r_config + .members + .server + .api_secret_path + .as_ref() + .unwrap(); + let api_secret_file = File::open(api_secret_path).unwrap(); + let buf_reader = BufReader::new(api_secret_file); + let mut lines_iter = buf_reader.lines(); + let first_line = lines_iter.next().unwrap(); + first_line.unwrap() + } + + /// Save API secret text. + pub fn save_api_secret(api_secret: String) { + if api_secret.is_empty() { + return; + } + let r_config = Settings::node_config_to_read(); + let api_secret_path = r_config + .members + .server + .api_secret_path + .as_ref() + .unwrap(); + let mut api_secret_file = File::create(api_secret_path).unwrap(); + api_secret_file.write_all(api_secret.as_bytes()).unwrap(); + } + + /// Get Foreign API secret text. + pub fn get_foreign_api_secret() -> String { + let r_config = Settings::node_config_to_read(); + let foreign_api_secret_path = r_config + .members + .server + .foreign_api_secret_path + .as_ref() + .unwrap(); + let foreign_api_secret_file = File::open(foreign_api_secret_path).unwrap(); + let buf_reader = BufReader::new(foreign_api_secret_file); + let mut lines_iter = buf_reader.lines(); + let first_line = lines_iter.next().unwrap(); + first_line.unwrap() + } + + /// Save Foreign API secret text. + pub fn save_foreign_api_secret(api_secret: String) { + if api_secret.is_empty() { + return; + } + let r_config = Settings::node_config_to_read(); + let foreign_api_secret_path = r_config + .members + .server + .foreign_api_secret_path + .as_ref() + .unwrap(); + let mut foreign_api_secret_file = File::create(foreign_api_secret_path).unwrap(); + foreign_api_secret_file.write_all(api_secret.as_bytes()).unwrap(); + } + + /// Get Future Time Limit. + pub fn get_ftl() -> u64 { + Settings::node_config_to_read().members.server.future_time_limit + } + + /// Save Future Time Limit. + pub fn save_ftl(ftl: u64) { + let mut w_config = Settings::node_config_to_update(); + w_config.members.server.future_time_limit = ftl; + w_config.save(); + } + + /// Check if full chain validation mode is enabled. + pub fn is_full_chain_validation() -> bool { + let mode = Settings::node_config_to_read().members.clone().server.chain_validation_mode; + mode == ChainValidationMode::EveryBlock + } + + /// Toggle full chain validation. + pub fn toggle_chain_validation() { + let mode = Settings::node_config_to_read().members.clone().server.chain_validation_mode; + let new_mode = if mode == ChainValidationMode::Disabled { + ChainValidationMode::Disabled + } else { + ChainValidationMode::EveryBlock + }; + let mut w_node_config = Settings::node_config_to_update(); + w_node_config.members.server.chain_validation_mode = new_mode; + w_node_config.save(); + } + + /// Check if node is running in archive mode. + pub fn is_archive_mode() -> bool { + let archive_mode = Settings::node_config_to_read().members.clone().server.archive_mode; + archive_mode.is_some() && archive_mode.unwrap() + } + + /// Toggle archive node mode. + pub fn toggle_archive_mode() { + let archive_mode = Self::is_archive_mode(); + let mut w_node_config = Settings::node_config_to_update(); + w_node_config.members.server.archive_mode = Some(!archive_mode); + w_node_config.save(); + } + + // P2P settings + + /// Get P2P server port. + pub fn get_p2p_port() -> u16 { + Settings::node_config_to_read().members.server.p2p_config.port + } + + /// Get P2P server port. + pub fn save_p2p_port(port: u16) { + let mut w_node_config = Settings::node_config_to_update(); + w_node_config.members.server.p2p_config.port = port; + w_node_config.save(); + } + + /// Get peers seeding type. + pub fn get_peers_seeding_type() -> Seeding { + Settings::node_config_to_read().members.server.p2p_config.seeding_type + } + + /// Get seeds for [`Seeding::List`] type. + pub fn get_seeds() -> PeerAddrs { + let r_config = Settings::node_config_to_read(); + r_config.members.server.p2p_config.seeds.clone().unwrap_or(PeerAddrs::default()) + } + + /// Save peers seeding type, with list of peers for [`Seeding::List`] type. + pub fn save_peers_seeding_type(seeding_type: Seeding, peers: Option) { + let mut w_node_config = Settings::node_config_to_update(); + w_node_config.members.server.p2p_config.seeding_type = seeding_type; + if seeding_type == Seeding::List { + w_node_config.members.server.p2p_config.seeds = peers; + } + w_node_config.save(); + } + + /// Get denied peer list. + pub fn get_denied_peers() -> PeerAddrs { + let r_config = Settings::node_config_to_read(); + r_config.members.server.p2p_config.peers_deny.clone().unwrap_or(PeerAddrs::default()) + } + + /// Add peer at denied list. + pub fn deny_peer(peer: String) { + let ip_addr = IpAddr::from_str(peer.as_str()).unwrap(); + let peer_addr = PeerAddr::from_ip(ip_addr); + + let mut deny_peers = Self::get_denied_peers(); + deny_peers.peers.insert(0, peer_addr); + + let mut w_node_config = Settings::node_config_to_update(); + w_node_config.members.server.p2p_config.peers_deny = Some(deny_peers); + w_node_config.save(); + } + + /// Save denied peer list. + pub fn save_denied_peers(peers: Option) { + let mut w_node_config = Settings::node_config_to_update(); + w_node_config.members.server.p2p_config.peers_deny = peers; + w_node_config.save(); + } + + /// Get allowed peer list. + pub fn get_allowed_peers() -> PeerAddrs { + let r_config = Settings::node_config_to_read(); + r_config.members.server.p2p_config.peers_allow.clone().unwrap_or(PeerAddrs::default()) + } + + /// Save allowed peer list. + pub fn save_allowed_peers(peers: Option) { + let mut w_node_config = Settings::node_config_to_update(); + w_node_config.members.server.p2p_config.peers_allow = peers; + w_node_config.save(); + } + + /// Get preferred peer list. + pub fn get_preferred_peers() -> PeerAddrs { + let r_config = Settings::node_config_to_read(); + r_config.members.server.p2p_config.peers_preferred.clone().unwrap_or(PeerAddrs::default()) + } + + /// Add peer at preferred list. + pub fn prefer_peer(peer: String) { + let ip_addr = IpAddr::from_str(peer.as_str()).unwrap(); + let peer_addr = PeerAddr::from_ip(ip_addr); + + let mut prefer_peers = Self::get_preferred_peers(); + prefer_peers.peers.insert(0, peer_addr); + + let mut w_node_config = Settings::node_config_to_update(); + w_node_config.members.server.p2p_config.peers_preferred = Some(prefer_peers); + w_node_config.save(); + } + + /// Save preferred peer list. + pub fn save_preferred_peers(peers: Option) { + let mut w_node_config = Settings::node_config_to_update(); + w_node_config.members.server.p2p_config.peers_preferred = peers; + w_node_config.save(); + } + + /// How long a banned peer should stay banned in ms. + pub fn get_ban_window() -> i64 { + Settings::node_config_to_read().members.server.p2p_config.ban_window() + } + + /// Set how long a banned peer should stay banned in ms. + pub fn set_ban_window(time: i64) { + let mut w_node_config = Settings::node_config_to_update(); + w_node_config.members.server.p2p_config.ban_window = Some(time); + w_node_config.save(); + } + + /// Maximum number of inbound peer connections. + pub fn get_max_inbound_count() -> u32 { + Settings::node_config_to_read().members.server.p2p_config.peer_max_inbound_count() + } + + /// Set maximum number of inbound peer connections. + pub fn set_max_inbound_count(count: u32) { + let mut w_node_config = Settings::node_config_to_update(); + w_node_config.members.server.p2p_config.peer_max_inbound_count = Some(count); + w_node_config.save(); + } + + /// Maximum number of outbound peer connections. + pub fn get_max_outbound_count() -> u32 { + Settings::node_config_to_read().members.server.p2p_config.peer_max_outbound_count() + } + + /// Set maximum number of outbound peer connections. + pub fn set_max_outbound_count(count: u32) { + let mut w_node_config = Settings::node_config_to_update(); + w_node_config.members.server.p2p_config.peer_max_outbound_count = Some(count); + w_node_config.save(); + } + + /// Minimum number of outbound peer connections. + pub fn get_min_outbound_count() -> u32 { + Settings::node_config_to_read() + .members + .server + .p2p_config + .peer_min_preferred_outbound_count() + } + + /// Set minimum number of outbound peer connections. + pub fn set_min_outbound_count(count: u32) { + let mut w_node_config = Settings::node_config_to_update(); + w_node_config.members.server.p2p_config.peer_min_preferred_outbound_count = Some(count); + w_node_config.save(); + } + + // Pool settings + + /// Base fee that's accepted into the pool. + pub fn get_base_fee() -> u64 { + Settings::node_config_to_read().members.server.pool_config.accept_fee_base + } + + /// Set base fee that's accepted into the pool. + pub fn set_base_fee(fee: u64) { + let mut w_node_config = Settings::node_config_to_update(); + w_node_config.members.server.pool_config.accept_fee_base = fee; + w_node_config.save(); + } + + /// Reorg cache retention period in minute. + pub fn get_reorg_cache_period() -> u32 { + Settings::node_config_to_read().members.server.pool_config.reorg_cache_period + } + + /// Set reorg cache retention period in minute. + pub fn set_reorg_cache_period(period: u32) { + let mut w_node_config = Settings::node_config_to_update(); + w_node_config.members.server.pool_config.reorg_cache_period = period; + w_node_config.save(); + } + + /// Max amount of transactions at pool. + pub fn get_max_pool_size() -> usize { + Settings::node_config_to_read().members.server.pool_config.max_pool_size + } + + /// Set max amount of transactions at pool. + pub fn set_max_pool_size(amount: usize) { + let mut w_node_config = Settings::node_config_to_update(); + w_node_config.members.server.pool_config.max_pool_size = amount; + w_node_config.save(); + } + + /// Max amount of transactions at stem pool. + pub fn get_max_stempool_size() -> usize { + Settings::node_config_to_read().members.server.pool_config.max_stempool_size + } + + /// Set max amount of transactions at stem pool. + pub fn set_max_stempool_size(amount: usize) { + let mut w_node_config = Settings::node_config_to_update(); + w_node_config.members.server.pool_config.max_stempool_size = amount; + w_node_config.save(); + } + + /// Max total weight of transactions that can get selected to build a block. + pub fn get_mineable_max_weight() -> u64 { + Settings::node_config_to_read().members.server.pool_config.mineable_max_weight + } + + /// Set max total weight of transactions that can get selected to build a block. + pub fn set_mineable_max_weight(weight: u64) { + let mut w_node_config = Settings::node_config_to_update(); + w_node_config.members.server.pool_config.mineable_max_weight = weight; + w_node_config.save(); + } + + // Dandelion settings + + /// Dandelion epoch duration in secs. + pub fn get_epoch() -> u16 { + Settings::node_config_to_read().members.server.dandelion_config.epoch_secs + } + + /// Set Dandelion epoch duration in secs. + pub fn set_epoch(secs: u16) { + let mut w_node_config = Settings::node_config_to_update(); + w_node_config.members.server.dandelion_config.epoch_secs = secs; + w_node_config.save(); + } + + /// Dandelion embargo timer in secs. + /// Fluff and broadcast after embargo expires if tx not seen on network. + pub fn get_embargo() -> u16 { + Settings::node_config_to_read().members.server.dandelion_config.embargo_secs + } + + /// Set Dandelion embargo timer. + pub fn set_embargo(secs: u16) { + let mut w_node_config = Settings::node_config_to_update(); + w_node_config.members.server.dandelion_config.embargo_secs = secs; + w_node_config.save(); + } + + /// Dandelion stem probability (default: stem 90% of the time, fluff 10% of the time). + pub fn get_stem_probability() -> u8 { + Settings::node_config_to_read().members.server.dandelion_config.stem_probability + } + + /// Set Dandelion stem probability. + pub fn set_stem_probability(percent: u8) { + let mut w_node_config = Settings::node_config_to_update(); + w_node_config.members.server.dandelion_config.stem_probability = percent; + w_node_config.save(); + } + + /// Default to always stem our txs as described in Dandelion++ paper. + pub fn always_stem_our_txs() -> bool { + Settings::node_config_to_read().members.server.dandelion_config.always_stem_our_txs + } + + /// Toggle stem of our txs. + pub fn toggle_always_stem_our_txs() { + let stem_txs = Self::always_stem_our_txs(); + let mut w_node_config = Settings::node_config_to_update(); + w_node_config.members.server.dandelion_config.always_stem_our_txs = stem_txs; + w_node_config.save(); + } } \ No newline at end of file diff --git a/src/node/node.rs b/src/node/node.rs index 0a7361d..ac1773b 100644 --- a/src/node/node.rs +++ b/src/node/node.rs @@ -355,7 +355,6 @@ fn start_server() -> Server { if !global::GLOBAL_CHAIN_TYPE.is_init() { global::init_global_chain_type(config.server.chain_type); } - info!("Chain: {:?}", global::get_chain_type()); if !global::GLOBAL_NRD_FEATURE_ENABLED.is_init() { match global::get_chain_type() { @@ -372,39 +371,33 @@ fn start_server() -> Server { if !global::GLOBAL_ACCEPT_FEE_BASE.is_init() { let afb = config.server.pool_config.accept_fee_base; global::init_global_accept_fee_base(afb); - info!("Accept Fee Base: {:?}", global::get_accept_fee_base()); } if !global::GLOBAL_FUTURE_TIME_LIMIT.is_init() { let future_time_limit = config.server.future_time_limit; global::init_global_future_time_limit(future_time_limit); - info!("Future Time Limit: {:?}", global::get_future_time_limit()); } let api_chan: &'static mut (oneshot::Sender<()>, oneshot::Receiver<()>) = Box::leak(Box::new(oneshot::channel::<()>())); - let server_result = Server::new(server_config, None, api_chan); - //TODO: handle server errors - // - // if server_result.is_err() { - // let mut db_path = PathBuf::from(&server_config.db_root); - // db_path.push("grin.lock"); - // fs::remove_file(db_path).unwrap(); - // - // // Remove chain data on server start error - // let dirs_to_remove: Vec<&str> = vec!["header", "lmdb", "txhashset"]; - // for dir in dirs_to_remove { - // let mut path = PathBuf::from(&server_config.db_root); - // path.push(dir); - // fs::remove_dir_all(path).unwrap(); - // } - // - // // Recreate server - // let config = node_config.clone().unwrap(); - // let server_config = config.members.as_ref().unwrap().server.clone(); - // let api_chan: &'static mut (oneshot::Sender<()>, oneshot::Receiver<()>) = - // Box::leak(Box::new(oneshot::channel::<()>())); - // server_result = Server::new(server_config.clone(), None, api_chan); - // } + let mut server_result = Server::new(server_config.clone(), None, api_chan); + if server_result.is_err() { + let mut db_path = PathBuf::from(&server_config.db_root); + db_path.push("grin.lock"); + fs::remove_file(db_path).unwrap(); + + // Remove chain data on server start error + let dirs_to_remove: Vec<&str> = vec!["header", "lmdb", "txhashset"]; + for dir in dirs_to_remove { + let mut path = PathBuf::from(&server_config.db_root); + path.push(dir); + fs::remove_dir_all(path).unwrap(); + } + + // Recreate server + let api_chan: &'static mut (oneshot::Sender<()>, oneshot::Receiver<()>) = + Box::leak(Box::new(oneshot::channel::<()>())); + server_result = Server::new(server_config.clone(), None, api_chan); + } server_result.unwrap() } diff --git a/src/settings.rs b/src/settings.rs index c2e2a15..61bf3da 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -37,15 +37,15 @@ const APP_CONFIG_FILE_NAME: &'static str = "app.toml"; pub struct AppConfig { /// Run node server on startup. pub auto_start_node: bool, - /// Chain type for node server. - node_chain_type: ChainTypes + /// Chain type for node and wallets. + chain_type: ChainTypes } impl Default for AppConfig { fn default() -> Self { Self { auto_start_node: false, - node_chain_type: ChainTypes::default(), + chain_type: ChainTypes::default(), } } } @@ -66,10 +66,10 @@ impl AppConfig { /// Change chain type and load new [`NodeConfig`] accordingly. pub fn change_chain_type(&mut self, chain_type: ChainTypes) { - if self.node_chain_type == chain_type { + if self.chain_type == chain_type { return; } else { - self.node_chain_type = chain_type; + self.chain_type = chain_type; self.save(); // Load config for selected chain type. @@ -95,7 +95,7 @@ impl Settings { /// Initialize settings with app and node configs. fn init() -> Self { let app_config = AppConfig::init(); - let chain_type = app_config.node_chain_type; + let chain_type = app_config.chain_type; Self { app_config: Arc::new(RwLock::new(app_config)), node_config: Arc::new(RwLock::new(NodeConfig::init(chain_type)))