diff --git a/locales/en.yml b/locales/en.yml index 112f54b..4ee0638 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -111,6 +111,24 @@ network_settings: aggregation_period: Aggregation period (in seconds) stem_probability: Stem phase probability stem_txs: Stem transactions + p2p_server: P2P server + p2p_port: P2P port + add_seed: Add DNS Seed + add_peer: Add peer + peer_address_error: 'Enter IP address or DNS name (make sure specified host is available) in correct format, e.g.: 192.168.0.1:1234 or example.com:5678' + default: Default + allow_list: Allow list + allow_list_desc: Connect only to peers in this list. + deny_list: Deny list + deny_list_desc: Never connect to peers in this list. + favourites: Favourites + favourites_desc: A list of preferred peers to connect to. + ban_window: How long a banned peer should stay banned (in seconds) + ban_window_desc: The decision to ban is made by node, based on the correctness of the data received from the peer. + max_inbound_count: Maximum number of inbound peer connections + max_outbound_count: Maximum number of outbound peer connections + min_outbound_count: Minimum number of outbound peer connections + min_outbound_desc: Node server will actively keep trying to add peers until it will get to at least this number. modal: cancel: Cancel save: Save diff --git a/locales/ru.yml b/locales/ru.yml index 2f91287..307f6e8 100644 --- a/locales/ru.yml +++ b/locales/ru.yml @@ -111,6 +111,24 @@ network_settings: aggregation_period: Период агрегации (в секундах) stem_probability: Вероятность фазы Stem stem_txs: Stem транзакций + p2p_server: P2P сервер + p2p_port: P2P порт + add_seed: Добавить DNS Seed + add_peer: Добавить пир + peer_address_error: 'Введите IP адрес или DNS имя (убедитесь, что указанный хост доступен) в правильном формате, например: 192.168.0.1:1234 или example.com:5678' + default: По умолчанию + allow_list: Белый список + allow_list_desc: Подключаться только к пирам в данном списке. + deny_list: Чёрный список + deny_list_desc: Никогда не подключаться к пирам в данном списке. + favourites: Избранное + favourites_desc: Список предпочтительных пиров для подключения. + ban_window: Как долго забаненый пир должен оставаться забаненым (в секундах) + ban_window_desc: Решение о запрете принимается нодой, основываясь на корректности данных полученных от пира. + max_inbound_count: Максимальное количество входящих подключений пиров + max_outbound_count: Максимальное количество исходящих подключений к пирам + min_outbound_count: Минимальное количество исходящих подключений к пирам + min_outbound_desc: Сервер узла будет активно пытаться добавить пиры, пока не дойдёт хотя бы до этого числа. modal: cancel: Отмена save: Сохранить diff --git a/src/gui/views/network/configs/dandelion.rs b/src/gui/views/network/configs/dandelion.rs index fa246bd..852f4da 100644 --- a/src/gui/views/network/configs/dandelion.rs +++ b/src/gui/views/network/configs/dandelion.rs @@ -15,7 +15,7 @@ use egui::{Id, RichText, TextStyle, Ui, Widget}; use crate::gui::{Colors, Navigator}; -use crate::gui::icons::{CLOCK_COUNTDOWN, LINE_SEGMENTS, TIMER, WATCH}; +use crate::gui::icons::{CLOCK_COUNTDOWN, GRAPH, TIMER, WATCH}; use crate::gui::platform::PlatformCallbacks; use crate::gui::views::{Modal, ModalPosition, View}; use crate::gui::views::network::settings::NetworkSettings; @@ -58,7 +58,7 @@ impl DandelionSetup { pub const STEM_PROBABILITY_MODAL: &'static str = "stem_probability"; pub fn ui(&mut self, ui: &mut Ui, cb: &dyn PlatformCallbacks) { - View::sub_title(ui, format!("{} {}", LINE_SEGMENTS, "Dandelion")); + View::sub_title(ui, format!("{} {}", GRAPH, "Dandelion")); View::horizontal_line(ui, Colors::STROKE); ui.add_space(6.0); @@ -135,7 +135,7 @@ impl DandelionSetup { let text_edit_resp = egui::TextEdit::singleline(&mut self.epoch_edit) .id(Id::from(modal.id)) .font(TextStyle::Heading) - .desired_width(46.0) + .desired_width(52.0) .cursor_at_end(true) .ui(ui); text_edit_resp.request_focus(); @@ -220,7 +220,7 @@ impl DandelionSetup { let text_edit_resp = egui::TextEdit::singleline(&mut self.embargo_edit) .id(Id::from(modal.id)) .font(TextStyle::Heading) - .desired_width(46.0) + .desired_width(52.0) .cursor_at_end(true) .ui(ui); text_edit_resp.request_focus(); @@ -305,7 +305,7 @@ impl DandelionSetup { let text_edit_resp = egui::TextEdit::singleline(&mut self.aggregation_edit) .id(Id::from(modal.id)) .font(TextStyle::Heading) - .desired_width(36.0) + .desired_width(42.0) .cursor_at_end(true) .ui(ui); text_edit_resp.request_focus(); @@ -390,7 +390,7 @@ impl DandelionSetup { let text_edit_resp = egui::TextEdit::singleline(&mut self.stem_prob_edit) .id(Id::from(modal.id)) .font(TextStyle::Heading) - .desired_width(36.0) + .desired_width(42.0) .cursor_at_end(true) .ui(ui); text_edit_resp.request_focus(); diff --git a/src/gui/views/network/configs/node.rs b/src/gui/views/network/configs/node.rs index f351b39..d25431d 100644 --- a/src/gui/views/network/configs/node.rs +++ b/src/gui/views/network/configs/node.rs @@ -35,13 +35,10 @@ pub struct NodeSetup { api_port_available_edit: bool, /// Flag to check if API port from saved config value is available. - pub(crate) is_api_port_available: bool, + is_api_port_available: bool, - /// Rest API and v2 Owner API secret value. - api_secret_edit: String, - - /// Foreign API secret value. - foreign_api_secret_edit: String, + /// Secret edit value for modal. + secret_edit: String, /// Future Time Limit value. ftl_edit: String, @@ -56,8 +53,7 @@ impl Default for NodeSetup { api_port_edit: api_port, api_port_available_edit: is_api_port_available, is_api_port_available, - api_secret_edit: "".to_string(), - foreign_api_secret_edit: "".to_string(), + secret_edit: "".to_string(), ftl_edit: NodeConfig::get_ftl(), } } @@ -253,7 +249,7 @@ impl NodeSetup { // Draw API port text edit. let text_edit_resp = egui::TextEdit::singleline(&mut self.api_port_edit) .font(TextStyle::Heading) - .desired_width(58.0) + .desired_width(64.0) .cursor_at_end(true) .ui(ui); text_edit_resp.request_focus(); @@ -335,14 +331,7 @@ impl NodeSetup { View::button(ui, secret_text, Colors::BUTTON, || { // Setup values for modal. - match modal_id { - Self::API_SECRET_MODAL => { - self.api_secret_edit = secret_value.unwrap_or("".to_string()); - }, - _ => { - self.foreign_api_secret_edit = secret_value.unwrap_or("".to_string()); - } - } + self.secret_edit = secret_value.unwrap_or("".to_string()); // Show secret edit modal. let port_modal = Modal::new(modal_id) .position(ModalPosition::CenterTop) @@ -367,11 +356,7 @@ impl NodeSetup { ui.add_space(8.0); // Draw API port text edit. - let edit_text = match modal.id { - Self::API_SECRET_MODAL => &mut self.api_secret_edit, - _ => &mut self.foreign_api_secret_edit - }; - let text_edit_resp = egui::TextEdit::singleline(edit_text) + let text_edit_resp = egui::TextEdit::singleline(&mut self.secret_edit) .id(Id::from(modal.id)) .font(TextStyle::Heading) .cursor_at_end(true) @@ -396,25 +381,15 @@ impl NodeSetup { columns[0].with_layout(Layout::right_to_left(Align::Center), |ui| { let copy_title = format!("{} {}", COPY, t!("network_settings.copy")); View::button(ui, copy_title, Colors::WHITE, || { - match modal.id { - Self::API_SECRET_MODAL => { - cb.copy_string_to_buffer(self.api_secret_edit.clone()); - }, - _ => { - cb.copy_string_to_buffer(self.foreign_api_secret_edit.clone()); - } - }; - + cb.copy_string_to_buffer(self.secret_edit.clone()); }); }); columns[1].with_layout(Layout::left_to_right(Align::Center), |ui| { - let paste_title = format!("{} {}", CLIPBOARD_TEXT, t!("network_settings.paste")); + let paste_title = format!("{} {}", + CLIPBOARD_TEXT, + t!("network_settings.paste")); View::button(ui, paste_title, Colors::WHITE, || { - let text = cb.get_string_from_buffer(); - match modal.id { - Self::API_SECRET_MODAL => self.api_secret_edit = text, - _ => self.foreign_api_secret_edit = text - }; + self.secret_edit = cb.get_string_from_buffer(); }); }); }); @@ -435,14 +410,13 @@ impl NodeSetup { // Save button callback. let on_save = || { + let secret = self.secret_edit.clone(); match modal.id { Self::API_SECRET_MODAL => { - let api_secret = &self.api_secret_edit.clone(); - NodeConfig::save_api_secret(api_secret); + NodeConfig::save_api_secret(&secret); } _ => { - let foreign_api_secret = &self.foreign_api_secret_edit.clone(); - NodeConfig::save_foreign_api_secret(foreign_api_secret); + NodeConfig::save_foreign_api_secret(&secret); } }; cb.hide_keyboard(); @@ -504,7 +478,7 @@ impl NodeSetup { let text_edit_resp = egui::TextEdit::singleline(&mut self.ftl_edit) .id(Id::from(modal.id)) .font(TextStyle::Heading) - .desired_width(46.0) + .desired_width(52.0) .cursor_at_end(true) .ui(ui); text_edit_resp.request_focus(); diff --git a/src/gui/views/network/configs/p2p.rs b/src/gui/views/network/configs/p2p.rs index aa04af4..9760a69 100644 --- a/src/gui/views/network/configs/p2p.rs +++ b/src/gui/views/network/configs/p2p.rs @@ -10,4 +10,892 @@ // 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. \ No newline at end of file +// limitations under the License. + +use std::fmt::format; +use egui::{Direction, Id, Layout, RichText, Rounding, Stroke, TextStyle, Ui, Widget}; +use egui_extras::{Size, StripBuilder}; +use grin_core::global::ChainTypes; + +use crate::AppConfig; +use crate::gui::{Colors, Navigator}; +use crate::gui::icons::{HANDSHAKE, PLUG, TRASH, GLOBE_SIMPLE, PLUS_CIRCLE, ARROW_FAT_LINES_UP, ARROW_FAT_LINES_DOWN, ARROW_FAT_LINE_UP, PROHIBIT_INSET}; +use crate::gui::platform::PlatformCallbacks; +use crate::gui::views::{Modal, ModalPosition, View}; +use crate::gui::views::network::settings::NetworkSettings; +use crate::node::{NodeConfig, PeersConfig}; + +/// Type of peer. +#[derive(Eq, PartialEq)] +enum PeerType { + DefaultSeed, + CustomSeed, + Allowed, + Denied, + Preferred +} + +/// P2P server setup ui section. +pub struct P2PSetup { + /// P2P port value. + port_edit: String, + /// Flag to check if p2p port is available. + port_available_edit: bool, + + /// Flag to check if p2p port from saved config value is available. + is_port_available: bool, + + /// Flag to check if entered peer address is correct and/or available. + is_correct_address_edit: bool, + + /// Peer edit value for modal. + peer_edit: String, + /// Default main network seeds. + default_main_seeds: Vec, + /// Default test network seeds. + default_test_seeds: Vec, + + /// How long banned peer should stay banned. + ban_window_edit: String, + + /// Maximum number of inbound peer connections. + max_inbound_count: String, + + /// Maximum number of outbound peer connections. + max_outbound_count: String, + + /// Preferred minimum number of outbound peers. + min_outbound_count: String +} + +impl Default for P2PSetup { + fn default() -> Self { + let port = NodeConfig::get_p2p_port(); + let is_port_available = NodeConfig::is_p2p_port_available(&port); + let default_main_seeds = grin_servers::MAINNET_DNS_SEEDS + .iter() + .map(|s| s.to_string()) + .collect(); + let default_test_seeds = grin_servers::TESTNET_DNS_SEEDS + .into_iter() + .map(|s| s.to_string()) + .collect(); + Self { + port_edit: port, + port_available_edit: is_port_available, + is_correct_address_edit: true, + is_port_available, + peer_edit: "".to_string(), + default_main_seeds, + default_test_seeds, + ban_window_edit: NodeConfig::get_p2p_ban_window(), + max_inbound_count: NodeConfig::get_max_inbound_peers(), + max_outbound_count: NodeConfig::get_max_outbound_peers(), + min_outbound_count: NodeConfig::get_min_outbound_peers(), + } + } +} + +impl P2PSetup { + /// Identifier for port value [`Modal`]. + pub const PORT_MODAL: &'static str = "p2p_port"; + /// Identifier for custom seed [`Modal`]. + pub const CUSTOM_SEED_MODAL: &'static str = "p2p_custom_seed"; + /// Identifier for allowed peer [`Modal`]. + pub const ALLOW_PEER_MODAL: &'static str = "p2p_allow_peer"; + /// Identifier for denied peer [`Modal`]. + pub const DENY_PEER_MODAL: &'static str = "p2p_deny_peer"; + /// Identifier for preferred peer [`Modal`]. + pub const PREFER_PEER_MODAL: &'static str = "p2p_prefer_peer"; + /// Identifier for ban window [`Modal`]. + pub const BAN_WINDOW_MODAL: &'static str = "p2p_ban_window"; + /// Identifier for maximum number of inbound peers [`Modal`]. + pub const MAX_INBOUND_MODAL: &'static str = "p2p_max_inbound"; + /// Identifier for maximum number of outbound peers [`Modal`]. + pub const MAX_OUTBOUND_MODAL: &'static str = "p2p_max_outbound"; + /// Identifier for minimum number of outbound peers [`Modal`]. + pub const MIN_OUTBOUND_MODAL: &'static str = "p2p_min_outbound"; + + /// Title for custom DNS Seeds setup section. + const DNS_SEEDS_TITLE: &'static str = "DNS Seeds"; + + pub fn ui(&mut self, ui: &mut Ui, cb: &dyn PlatformCallbacks) { + View::sub_title(ui, format!("{} {}", HANDSHAKE, t!("network_settings.p2p_server"))); + View::horizontal_line(ui, Colors::STROKE); + ui.add_space(6.0); + + ui.vertical_centered(|ui| { + // Show p2p port setup. + self.port_ui(ui, cb); + + ui.add_space(6.0); + View::horizontal_line(ui, Colors::ITEM_STROKE); + ui.add_space(6.0); + + // Show seeding type setup. + self.seeding_type_ui(ui, cb); + + ui.add_space(6.0); + View::horizontal_line(ui, Colors::ITEM_STROKE); + ui.add_space(6.0); + + ui.label(RichText::new(t!("network_settings.allow_list")) + .size(16.0) + .color(Colors::GRAY)); + ui.add_space(6.0); + // Show allowed peers setup. + self.peer_list_ui(ui, &PeerType::Allowed, cb); + + ui.add_space(6.0); + View::horizontal_line(ui, Colors::ITEM_STROKE); + ui.add_space(6.0); + + ui.label(RichText::new(t!("network_settings.deny_list")) + .size(16.0) + .color(Colors::GRAY)); + ui.add_space(6.0); + // Show denied peers setup. + self.peer_list_ui(ui, &PeerType::Denied, cb); + + ui.add_space(6.0); + View::horizontal_line(ui, Colors::ITEM_STROKE); + ui.add_space(6.0); + + ui.label(RichText::new(t!("network_settings.favourites")) + .size(16.0) + .color(Colors::GRAY)); + ui.add_space(6.0); + // Show preferred peers setup. + self.peer_list_ui(ui, &PeerType::Preferred, cb); + + + ui.add_space(6.0); + View::horizontal_line(ui, Colors::ITEM_STROKE); + ui.add_space(6.0); + + // Show ban window setup. + self.ban_window_ui(ui, cb); + + ui.add_space(6.0); + View::horizontal_line(ui, Colors::ITEM_STROKE); + ui.add_space(6.0); + + // Show maximum inbound peers value setup. + self.max_inbound_ui(ui, cb); + + ui.add_space(6.0); + View::horizontal_line(ui, Colors::ITEM_STROKE); + ui.add_space(6.0); + + // Show maximum outbound peers value setup. + self.max_outbound_ui(ui, cb); + + ui.add_space(6.0); + View::horizontal_line(ui, Colors::ITEM_STROKE); + ui.add_space(6.0); + + // Show minimum outbound peers value setup. + self.min_outbound_ui(ui, cb); + }); + } + + /// Draw p2p port setup content. + fn port_ui(&mut self, ui: &mut Ui, cb: &dyn PlatformCallbacks) { + ui.label(RichText::new(t!("network_settings.p2p_port")) + .size(16.0) + .color(Colors::GRAY) + ); + ui.add_space(6.0); + + let port = NodeConfig::get_p2p_port(); + View::button(ui, format!("{} {}", PLUG, port.clone()), Colors::BUTTON, || { + // Setup values for modal. + self.port_edit = port; + self.port_available_edit = self.is_port_available; + // Show p2p port modal. + let port_modal = Modal::new(Self::PORT_MODAL) + .position(ModalPosition::CenterTop) + .title(t!("network_settings.change_value")); + Navigator::show_modal(port_modal); + cb.show_keyboard(); + }); + ui.add_space(12.0); + + // Show error when stratum server port is unavailable. + if !self.is_port_available { + ui.add_space(6.0); + ui.label(RichText::new(t!("network_settings.port_unavailable")) + .size(16.0) + .color(Colors::RED)); + ui.add_space(12.0); + } + } + + /// Draw p2p port [`Modal`] content. + pub fn port_modal(&mut self, ui: &mut Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + ui.add_space(6.0); + ui.vertical_centered(|ui| { + ui.label(RichText::new(t!("network_settings.p2p_port")) + .size(18.0) + .color(Colors::GRAY)); + ui.add_space(8.0); + + // Draw p2p port text edit. + let text_edit_resp = egui::TextEdit::singleline(&mut self.port_edit) + .id(Id::from(modal.id)) + .font(TextStyle::Heading) + .desired_width(58.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_settings.port_unavailable")) + .size(18.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(8.0, 0.0); + + // Save button callback. + let on_save = || { + // Check if port is available. + let available = NodeConfig::is_p2p_port_available(&self.port_edit); + self.port_available_edit = available; + + // Save port at config if it's available. + if available { + NodeConfig::save_p2p_port(self.port_edit.parse::().unwrap()); + + self.is_port_available = true; + cb.hide_keyboard(); + modal.close(); + } + }; + + 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, on_save); + }); + }); + ui.add_space(6.0); + }); + }); + } + + /// Draw peer list content based on provided [`PeerType`]. + fn peer_list_ui(&mut self, ui: &mut Ui, peer_type: &PeerType, cb: &dyn PlatformCallbacks) { + let peer_list = match peer_type { + PeerType::DefaultSeed => { + if AppConfig::chain_type() == ChainTypes::Testnet { + self.default_test_seeds.clone() + } else { + self.default_main_seeds.clone() + } + } + PeerType::CustomSeed => NodeConfig::get_custom_seeds(), + PeerType::Allowed => NodeConfig::get_allowed_peers(), + PeerType::Denied => NodeConfig::get_denied_peers(), + PeerType::Preferred => NodeConfig::get_preferred_peers() + }; + for (index, peer) in peer_list.iter().enumerate() { + let rounding = if peer_list.len() == 1 { + [true, true] + } else if index == 0 { + [true, false] + } else if index == peer_list.len() - 1 { + [false, true] + } else { + [false, false] + }; + ui.horizontal_wrapped(|ui| { + // Draw peer list item. + Self::peer_item_ui(ui, peer, peer_type, rounding); + }); + } + + if peer_type != &PeerType::DefaultSeed { + // Draw description. + if peer_type != &PeerType::CustomSeed { + if !peer_list.is_empty() { + ui.add_space(12.0); + } + let desc = match peer_type { + PeerType::Allowed => t!("network_settings.allow_list_desc"), + PeerType::Denied => t!("network_settings.deny_list_desc"), + &_ => t!("network_settings.favourites_desc"), + }; + ui.label(RichText::new(desc) + .size(16.0) + .color(Colors::INACTIVE_TEXT)); + ui.add_space(12.0); + } + + let add_text = if peer_type == &PeerType::CustomSeed { + format!("{} {}", PLUS_CIRCLE, t!("network_settings.add_seed")) + } else { + format!("{} {}", PLUS_CIRCLE, t!("network_settings.add_peer")) + + }; + View::button(ui, add_text, Colors::GOLD, || { + // Setup values for modal. + self.peer_edit = "".to_string(); + // Select modal id. + let modal_id = match peer_type { + PeerType::Allowed => Self::ALLOW_PEER_MODAL, + PeerType::Denied => Self::DENY_PEER_MODAL, + PeerType::Preferred => Self::PREFER_PEER_MODAL, + _ => Self::CUSTOM_SEED_MODAL + }; + // Select modal title. + let modal_title = match peer_type { + PeerType::Allowed => t!("network_settings.allow_list"), + PeerType::Denied => t!("network_settings.deny_list"), + PeerType::Preferred => t!("network_settings.favourites"), + _ => Self::DNS_SEEDS_TITLE.to_string() + }; + // Show modal to add peer. + let peer_modal = Modal::new(modal_id) + .position(ModalPosition::CenterTop) + .title(modal_title); + Navigator::show_modal(peer_modal); + cb.show_keyboard(); + }); + } + ui.add_space(6.0); + } + + /// Draw peer creation [`Modal`] content. + pub fn peer_modal(&mut self, ui: &mut Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + ui.add_space(6.0); + ui.vertical_centered(|ui| { + let label_text = match modal.id { + Self::CUSTOM_SEED_MODAL => t!("network_settings.add_seed"), + &_ => t!("network_settings.add_peer") + }; + ui.label(RichText::new(label_text).size(18.0).color(Colors::GRAY)); + ui.add_space(8.0); + + // Draw peer address text edit. + let text_edit_resp = egui::TextEdit::singleline(&mut self.peer_edit) + .id(Id::from(modal.id)) + .font(TextStyle::Heading) + .desired_width(ui.available_width()) + .cursor_at_end(true) + .ui(ui); + text_edit_resp.request_focus(); + if text_edit_resp.clicked() { + cb.show_keyboard(); + } + + // Show error when specified address is incorrect. + if !self.is_correct_address_edit { + ui.add_space(12.0); + ui.label(RichText::new(t!("network_settings.peer_address_error")) + .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(8.0, 0.0); + + // Save button callback. + let on_save = || { + // Check if peer is correct and/or available. + let peer = self.peer_edit.clone(); + let is_correct_address = PeersConfig::peer_to_addr(peer.clone()).is_some(); + self.is_correct_address_edit = is_correct_address; + + // Save peer at config. + if is_correct_address { + match modal.id { + Self::CUSTOM_SEED_MODAL => NodeConfig::save_custom_seed(peer), + Self::ALLOW_PEER_MODAL => NodeConfig::allow_peer(peer), + Self::DENY_PEER_MODAL => NodeConfig::deny_peer(peer), + Self::PREFER_PEER_MODAL => NodeConfig::prefer_peer(peer), + &_ => {} + } + + self.is_port_available = true; + cb.hide_keyboard(); + modal.close(); + } + }; + + 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, on_save); + }); + }); + ui.add_space(6.0); + }); + }); + } + + /// Draw peer list item. + fn peer_item_ui(ui: &mut Ui, peer_addr: &String, peer_type: &PeerType, rounding: [bool; 2]) { + // Draw round background. + let mut rect = ui.available_rect_before_wrap(); + rect.min += egui::emath::vec2(6.0, 0.0); + rect.set_height(42.0); + ui.painter().rect( + rect, + Rounding { + nw: if rounding[0] { 6.0 } else { 0.0 }, + ne: if rounding[0] { 6.0 } else { 0.0 }, + sw: if rounding[1] { 6.0 } else { 0.0 }, + se: if rounding[1] { 6.0 } else { 0.0 }, + }, + Colors::WHITE, + Stroke { width: 1.0, color: Colors::ITEM_STROKE } + ); + + StripBuilder::new(ui) + .size(Size::exact(42.0)) + .vertical(|mut strip| { + strip.strip(|builder| { + builder + .size(Size::exact(13.0)) + .size(Size::remainder()) + .size(Size::exact(46.0)) + .horizontal(|mut strip| { + strip.empty(); + strip.cell(|ui| { + ui.horizontal_centered(|ui| { + // Draw peer address. + let peer_text = format!("{} {}", GLOBE_SIMPLE, &peer_addr); + ui.label(RichText::new(peer_text) + .color(Colors::TEXT_BUTTON) + .size(17.0)); + }); + }); + if peer_type != &PeerType::DefaultSeed { + strip.cell(|ui| { + // Draw delete button for non-default seed peers. + View::button(ui, TRASH.to_string(), Colors::BUTTON, || { + match peer_type { + PeerType::CustomSeed => { + NodeConfig::remove_custom_seed(peer_addr); + } + PeerType::Allowed => { + NodeConfig::remove_allowed_peer(peer_addr); + } + PeerType::Denied => { + NodeConfig::remove_denied_peer(peer_addr); + } + PeerType::Preferred => { + NodeConfig::remove_preferred_peer(peer_addr); + } + PeerType::DefaultSeed => {} + } + }); + }); + } else { + strip.empty(); + } + }); + }); + }); + } + + /// Draw seeding type setup content. + fn seeding_type_ui(&mut self, ui: &mut Ui, cb: &dyn PlatformCallbacks) { + let title = Self::DNS_SEEDS_TITLE; + ui.label(RichText::new(title).size(16.0).color(Colors::GRAY)); + ui.add_space(2.0); + + let default_seeding = NodeConfig::is_default_seeding_type(); + View::checkbox(ui, default_seeding, t!("network_settings.default"), || { + NodeConfig::toggle_seeding_type(); + }); + ui.add_space(8.0); + + let peers_type = if default_seeding { + PeerType::DefaultSeed + } else { + PeerType::CustomSeed + }; + self.peer_list_ui(ui, &peers_type, cb); + } + + /// Draw ban window setup content. + fn ban_window_ui(&mut self, ui: &mut Ui, cb: &dyn PlatformCallbacks) { + ui.label(RichText::new(t!("network_settings.ban_window")) + .size(16.0) + .color(Colors::GRAY) + ); + ui.add_space(6.0); + + let ban_window = NodeConfig::get_p2p_ban_window(); + View::button(ui, format!("{} {}", PROHIBIT_INSET, ban_window.clone()), Colors::BUTTON, || { + // Setup values for modal. + self.ban_window_edit = ban_window; + // Show ban window period setup modal. + let ban_modal = Modal::new(Self::BAN_WINDOW_MODAL) + .position(ModalPosition::CenterTop) + .title(t!("network_settings.change_value")); + Navigator::show_modal(ban_modal); + cb.show_keyboard(); + }); + ui.add_space(6.0); + ui.label(RichText::new(t!("network_settings.ban_window_desc")) + .size(16.0) + .color(Colors::INACTIVE_TEXT) + ); + ui.add_space(2.0); + } + + /// Draw ban window [`Modal`] content. + pub fn ban_window_modal(&mut self, ui: &mut Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + ui.add_space(6.0); + ui.vertical_centered(|ui| { + ui.label(RichText::new(t!("network_settings.ban_window")) + .size(18.0) + .color(Colors::GRAY)); + ui.add_space(8.0); + + // Draw ban window text edit. + let text_edit_resp = egui::TextEdit::singleline(&mut self.ban_window_edit) + .id(Id::from(modal.id)) + .font(TextStyle::Heading) + .desired_width(84.0) + .cursor_at_end(true) + .ui(ui); + text_edit_resp.request_focus(); + if text_edit_resp.clicked() { + cb.show_keyboard(); + } + + // Show error when specified value is not valid or reminder to restart enabled node. + if self.ban_window_edit.parse::().is_err() { + ui.add_space(12.0); + ui.label(RichText::new(t!("network_settings.not_valid_value")) + .size(18.0) + .color(Colors::RED)); + } else { + NetworkSettings::node_restart_required_ui(ui); + } + ui.add_space(12.0); + }); + + // Show modal buttons. + ui.scope(|ui| { + // Setup spacing between buttons. + ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); + + // Save button callback. + let on_save = || { + if let Ok(ban_window) = self.ban_window_edit.parse::() { + NodeConfig::save_p2p_ban_window(ban_window); + cb.hide_keyboard(); + modal.close(); + } + }; + + 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, on_save); + }); + }); + ui.add_space(6.0); + }); + } + + /// Draw maximum number of inbound peers setup content. + fn max_inbound_ui(&mut self, ui: &mut Ui, cb: &dyn PlatformCallbacks) { + ui.label(RichText::new(t!("network_settings.max_inbound_count")) + .size(16.0) + .color(Colors::GRAY) + ); + ui.add_space(6.0); + + let max_inbound = NodeConfig::get_max_inbound_peers(); + let button_text = format!("{} {}", ARROW_FAT_LINES_DOWN, max_inbound.clone()); + View::button(ui, button_text, Colors::BUTTON, || { + // Setup values for modal. + self.max_inbound_count = max_inbound; + // Show maximum number of inbound peers setup modal. + let max_inbound_modal = Modal::new(Self::MAX_INBOUND_MODAL) + .position(ModalPosition::CenterTop) + .title(t!("network_settings.change_value")); + Navigator::show_modal(max_inbound_modal); + cb.show_keyboard(); + }); + ui.add_space(6.0); + } + + /// Draw maximum number of inbound peers [`Modal`] content. + pub fn max_inbound_modal(&mut self, ui: &mut Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + ui.add_space(6.0); + ui.vertical_centered(|ui| { + ui.label(RichText::new(t!("network_settings.max_inbound_count")) + .size(18.0) + .color(Colors::GRAY)); + ui.add_space(8.0); + + // Draw maximum number of inbound peers text edit. + let text_edit_resp = egui::TextEdit::singleline(&mut self.max_inbound_count) + .id(Id::from(modal.id)) + .font(TextStyle::Heading) + .desired_width(42.0) + .cursor_at_end(true) + .ui(ui); + text_edit_resp.request_focus(); + if text_edit_resp.clicked() { + cb.show_keyboard(); + } + + // Show error when specified value is not valid or reminder to restart enabled node. + if self.max_inbound_count.parse::().is_err() { + ui.add_space(12.0); + ui.label(RichText::new(t!("network_settings.not_valid_value")) + .size(18.0) + .color(Colors::RED)); + } else { + NetworkSettings::node_restart_required_ui(ui); + } + ui.add_space(12.0); + }); + + // Show modal buttons. + ui.scope(|ui| { + // Setup spacing between buttons. + ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); + + // Save button callback. + let on_save = || { + if let Ok(max_inbound) = self.max_inbound_count.parse::() { + NodeConfig::save_max_inbound_peers(max_inbound); + cb.hide_keyboard(); + modal.close(); + } + }; + + 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, on_save); + }); + }); + ui.add_space(6.0); + }); + } + + /// Draw maximum number of outbound peers setup content. + fn max_outbound_ui(&mut self, ui: &mut Ui, cb: &dyn PlatformCallbacks) { + ui.label(RichText::new(t!("network_settings.max_outbound_count")) + .size(16.0) + .color(Colors::GRAY) + ); + ui.add_space(6.0); + + let max_outbound = NodeConfig::get_max_outbound_peers(); + let button_text = format!("{} {}", ARROW_FAT_LINES_UP, max_outbound.clone()); + View::button(ui, button_text, Colors::BUTTON, || { + // Setup values for modal. + self.max_outbound_count = max_outbound; + // Show maximum number of outbound peers setup modal. + let max_outbound = Modal::new(Self::MAX_OUTBOUND_MODAL) + .position(ModalPosition::CenterTop) + .title(t!("network_settings.change_value")); + Navigator::show_modal(max_outbound); + cb.show_keyboard(); + }); + ui.add_space(2.0); + } + + /// Draw maximum number of outbound peers [`Modal`] content. + pub fn max_outbound_modal(&mut self, ui: &mut Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + ui.add_space(6.0); + ui.vertical_centered(|ui| { + ui.label(RichText::new(t!("network_settings.max_outbound_count")) + .size(18.0) + .color(Colors::GRAY)); + ui.add_space(8.0); + + // Draw maximum number of outbound peers text edit. + let text_edit_resp = egui::TextEdit::singleline(&mut self.max_outbound_count) + .id(Id::from(modal.id)) + .font(TextStyle::Heading) + .desired_width(42.0) + .cursor_at_end(true) + .ui(ui); + text_edit_resp.request_focus(); + if text_edit_resp.clicked() { + cb.show_keyboard(); + } + + // Show error when specified value is not valid or reminder to restart enabled node. + if self.max_outbound_count.parse::().is_err() { + ui.add_space(12.0); + ui.label(RichText::new(t!("network_settings.not_valid_value")) + .size(18.0) + .color(Colors::RED)); + } else { + NetworkSettings::node_restart_required_ui(ui); + } + ui.add_space(12.0); + }); + + // Show modal buttons. + ui.scope(|ui| { + // Setup spacing between buttons. + ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); + + // Save button callback. + let on_save = || { + if let Ok(max_outbound) = self.max_outbound_count.parse::() { + NodeConfig::save_max_outbound_peers(max_outbound); + cb.hide_keyboard(); + modal.close(); + } + }; + + 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, on_save); + }); + }); + ui.add_space(6.0); + }); + } + + /// Draw minimum number of outbound peers setup content. + fn min_outbound_ui(&mut self, ui: &mut Ui, cb: &dyn PlatformCallbacks) { + ui.label(RichText::new(t!("network_settings.min_outbound_count")) + .size(16.0) + .color(Colors::GRAY) + ); + ui.add_space(6.0); + + let min_outbound = NodeConfig::get_min_outbound_peers(); + let button_text = format!("{} {}", ARROW_FAT_LINE_UP, min_outbound.clone()); + View::button(ui, button_text, Colors::BUTTON, || { + // Setup values for modal. + self.min_outbound_count = min_outbound; + // Show maximum number of outbound peers setup modal. + let min_outbound = Modal::new(Self::MIN_OUTBOUND_MODAL) + .position(ModalPosition::CenterTop) + .title(t!("network_settings.change_value")); + Navigator::show_modal(min_outbound); + cb.show_keyboard(); + }); + ui.add_space(6.0); + ui.label(RichText::new(t!("network_settings.min_outbound_desc")) + .size(16.0) + .color(Colors::INACTIVE_TEXT) + ); + ui.add_space(2.0); + } + + /// Draw minimum number of outbound peers [`Modal`] content. + pub fn min_outbound_modal(&mut self, ui: &mut Ui, modal: &Modal, cb: &dyn PlatformCallbacks) { + ui.add_space(6.0); + ui.vertical_centered(|ui| { + ui.label(RichText::new(t!("network_settings.min_outbound_count")) + .size(18.0) + .color(Colors::GRAY)); + ui.add_space(8.0); + + // Draw maximum number of outbound peers text edit. + let text_edit_resp = egui::TextEdit::singleline(&mut self.min_outbound_count) + .id(Id::from(modal.id)) + .font(TextStyle::Heading) + .desired_width(42.0) + .cursor_at_end(true) + .ui(ui); + text_edit_resp.request_focus(); + if text_edit_resp.clicked() { + cb.show_keyboard(); + } + + // Show error when specified value is not valid or reminder to restart enabled node. + if self.min_outbound_count.parse::().is_err() { + ui.add_space(12.0); + ui.label(RichText::new(t!("network_settings.not_valid_value")) + .size(18.0) + .color(Colors::RED)); + } else { + NetworkSettings::node_restart_required_ui(ui); + } + ui.add_space(12.0); + }); + + // Show modal buttons. + ui.scope(|ui| { + // Setup spacing between buttons. + ui.spacing_mut().item_spacing = egui::Vec2::new(8.0, 0.0); + + // Save button callback. + let on_save = || { + if let Ok(max_outbound) = self.min_outbound_count.parse::() { + NodeConfig::save_min_outbound_peers(max_outbound); + cb.hide_keyboard(); + modal.close(); + } + }; + + 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, on_save); + }); + }); + ui.add_space(6.0); + }); + } +} \ No newline at end of file diff --git a/src/gui/views/network/configs/pool.rs b/src/gui/views/network/configs/pool.rs index ae4555c..6b6dbad 100644 --- a/src/gui/views/network/configs/pool.rs +++ b/src/gui/views/network/configs/pool.rs @@ -137,7 +137,7 @@ impl PoolSetup { let text_edit_resp = egui::TextEdit::singleline(&mut self.fee_base_edit) .id(Id::from(modal.id)) .font(TextStyle::Heading) - .desired_width(70.0) + .desired_width(76.0) .cursor_at_end(true) .ui(ui); text_edit_resp.request_focus(); @@ -222,7 +222,7 @@ impl PoolSetup { let text_edit_resp = egui::TextEdit::singleline(&mut self.reorg_period_edit) .id(Id::from(modal.id)) .font(TextStyle::Heading) - .desired_width(36.0) + .desired_width(42.0) .cursor_at_end(true) .ui(ui); text_edit_resp.request_focus(); @@ -307,7 +307,7 @@ impl PoolSetup { let text_edit_resp = egui::TextEdit::singleline(&mut self.pool_size_edit) .id(Id::from(modal.id)) .font(TextStyle::Heading) - .desired_width(58.0) + .desired_width(64.0) .cursor_at_end(true) .ui(ui); text_edit_resp.request_focus(); @@ -392,7 +392,7 @@ impl PoolSetup { let text_edit_resp = egui::TextEdit::singleline(&mut self.stempool_size_edit) .id(Id::from(modal.id)) .font(TextStyle::Heading) - .desired_width(58.0) + .desired_width(64.0) .cursor_at_end(true) .ui(ui); text_edit_resp.request_focus(); @@ -477,7 +477,7 @@ impl PoolSetup { let text_edit_resp = egui::TextEdit::singleline(&mut self.max_weight_edit) .id(Id::from(modal.id)) .font(TextStyle::Heading) - .desired_width(58.0) + .desired_width(64.0) .cursor_at_end(true) .ui(ui); text_edit_resp.request_focus(); diff --git a/src/gui/views/network/configs/stratum.rs b/src/gui/views/network/configs/stratum.rs index d551131..5ed8fce 100644 --- a/src/gui/views/network/configs/stratum.rs +++ b/src/gui/views/network/configs/stratum.rs @@ -32,7 +32,7 @@ pub struct StratumSetup { stratum_port_available_edit: bool, /// Flag to check if stratum port from saved config value is available. - pub(crate) is_port_available: bool, + is_port_available: bool, /// Attempt time value in seconds to mine on a particular header. attempt_time_edit: String, @@ -192,7 +192,7 @@ impl StratumSetup { let text_edit_resp = egui::TextEdit::singleline(&mut self.stratum_port_edit) .id(Id::from(modal.id)) .font(TextStyle::Heading) - .desired_width(58.0) + .desired_width(64.0) .cursor_at_end(true) .ui(ui); text_edit_resp.request_focus(); @@ -288,7 +288,7 @@ impl StratumSetup { let text_edit_resp = egui::TextEdit::singleline(&mut self.attempt_time_edit) .id(Id::from(modal.id)) .font(TextStyle::Heading) - .desired_width(36.0) + .desired_width(42.0) .cursor_at_end(true) .ui(ui); text_edit_resp.request_focus(); @@ -374,7 +374,7 @@ impl StratumSetup { let text_edit_resp = egui::TextEdit::singleline(&mut self.min_share_diff_edit) .id(Id::from(modal.id)) .font(TextStyle::Heading) - .desired_width(36.0) + .desired_width(42.0) .cursor_at_end(true) .ui(ui); text_edit_resp.request_focus(); diff --git a/src/gui/views/network/container.rs b/src/gui/views/network/container.rs index 895438b..557bbea 100644 --- a/src/gui/views/network/container.rs +++ b/src/gui/views/network/container.rs @@ -21,11 +21,12 @@ use grin_chain::SyncStatus; use crate::AppConfig; use crate::gui::{Colors, Navigator}; -use crate::gui::icons::{CARDHOLDER, DATABASE, DOTS_THREE_OUTLINE_VERTICAL, FACTORY, FADERS, GAUGE}; +use crate::gui::icons::{CARDHOLDER, DATABASE, DOTS_THREE_OUTLINE_VERTICAL, FACTORY, FADERS, GAUGE, POWER}; use crate::gui::platform::PlatformCallbacks; use crate::gui::views::{Modal, ModalContainer, TitlePanel, View}; use crate::gui::views::network::configs::dandelion::DandelionSetup; use crate::gui::views::network::configs::node::NodeSetup; +use crate::gui::views::network::configs::p2p::P2PSetup; use crate::gui::views::network::configs::pool::PoolSetup; use crate::gui::views::network::configs::stratum::StratumSetup; use crate::gui::views::network::metrics::NetworkMetrics; @@ -77,6 +78,16 @@ impl Default for NetworkContainer { NodeSetup::API_SECRET_MODAL, NodeSetup::FOREIGN_API_SECRET_MODAL, NodeSetup::FTL_MODAL, + // P2P setup modals. + P2PSetup::PORT_MODAL, + P2PSetup::CUSTOM_SEED_MODAL, + P2PSetup::ALLOW_PEER_MODAL, + P2PSetup::DENY_PEER_MODAL, + P2PSetup::PREFER_PEER_MODAL, + P2PSetup::BAN_WINDOW_MODAL, + P2PSetup::MAX_INBOUND_MODAL, + P2PSetup::MAX_OUTBOUND_MODAL, + P2PSetup::MIN_OUTBOUND_MODAL, // Stratum setup modals. StratumSetup::STRATUM_PORT_MODAL, StratumSetup::ATTEMPT_TIME_MODAL, @@ -114,6 +125,7 @@ impl NetworkContainer { } egui::TopBottomPanel::top("network_title") + .exact_height(TitlePanel::DEFAULT_HEIGHT) .resizable(false) .frame(egui::Frame { fill: Colors::YELLOW, @@ -187,34 +199,28 @@ impl NetworkContainer { /// Draw title content. fn title_ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) { StripBuilder::new(ui) - .size(Size::exact(TitlePanel::DEFAULT_HEIGHT)) - .vertical(|mut strip| { + .size(Size::exact(52.0)) + .size(Size::remainder()) + .size(Size::exact(52.0)) + .horizontal(|mut strip| { + strip.cell(|ui| { + ui.centered_and_justified(|ui| { + View::title_button(ui, DOTS_THREE_OUTLINE_VERTICAL, || { + //TODO: Show connections + }); + }); + }); strip.strip(|builder| { - builder - .size(Size::exact(52.0)) - .size(Size::remainder()) - .size(Size::exact(52.0)) - .horizontal(|mut strip| { - strip.cell(|ui| { - ui.centered_and_justified(|ui| { - View::title_button(ui, DOTS_THREE_OUTLINE_VERTICAL, || { - //TODO: Actions for node - }); - }); - }); - strip.strip(|builder| { - self.title_text_ui(builder); - }); - strip.cell(|ui| { - if !View::is_dual_panel_mode(frame) { - ui.centered_and_justified(|ui| { - View::title_button(ui, CARDHOLDER, || { - Navigator::toggle_side_panel(); - }); - }); - } + self.title_text_ui(builder); + }); + strip.cell(|ui| { + if !View::is_dual_panel_mode(frame) { + ui.centered_and_justified(|ui| { + View::title_button(ui, CARDHOLDER, || { + Navigator::toggle_side_panel(); }); }); + } }); }); } @@ -249,14 +255,14 @@ impl NetworkContainer { bright as f32 }; - // Draw sync text + // Draw sync status text. let status_color_rgba = Rgba::from(Colors::TEXT) * color_factor; let status_color = Color32::from(status_color_rgba); View::ellipsize_text(ui, Node::get_sync_status_text(), 15.0, status_color); - // Repaint based on sync status + // Repaint delay based on sync status. if idle { - ui.ctx().request_repaint_after(Duration::from_millis(250)); + ui.ctx().request_repaint_after(Node::STATS_UPDATE_DELAY); } else { ui.ctx().request_repaint(); } @@ -274,7 +280,7 @@ impl NetworkContainer { .color(Colors::INACTIVE_TEXT) ); ui.add_space(10.0); - View::button(ui, t!("network.enable_node"), Colors::GOLD, || { + View::button(ui, format!("{} {}", POWER, t!("network.enable_node")), Colors::GOLD, || { Node::start(); }); ui.add_space(2.0); diff --git a/src/gui/views/network/node.rs b/src/gui/views/network/node.rs index b950586..29a8cf8 100644 --- a/src/gui/views/network/node.rs +++ b/src/gui/views/network/node.rs @@ -164,8 +164,7 @@ impl NetworkTab for NetworkNode { // Show peer stats when available. if stats.peer_count > 0 { View::sub_title(ui, format!("{} {}", HANDSHAKE, t!("network_node.peers"))); - for index in 0..stats.peer_stats.len() { - let ps = stats.peer_stats.get(index).unwrap(); + for (index, ps) in stats.peer_stats.iter().enumerate() { let rounding = if stats.peer_count == 1 { [true, true] } else if index == 0 { @@ -188,9 +187,9 @@ impl NetworkTab for NetworkNode { fn draw_peer_stats(ui: &mut egui::Ui, peer: &PeerStats, rounding: [bool; 2]) { ui.vertical(|ui| { + // Draw round background. let mut rect = ui.available_rect_before_wrap(); rect.set_height(78.3); - ui.painter().rect( rect, Rounding { @@ -204,39 +203,29 @@ fn draw_peer_stats(ui: &mut egui::Ui, peer: &PeerStats, rounding: [bool; 2]) { ); ui.add_space(2.0); + + // Draw peer address ui.horizontal(|ui| { ui.add_space(5.0); - // Draw peer address - ui.heading(RichText::new(format!("{} {}", PLUGS_CONNECTED, &peer.addr)) - .color(Colors::BLACK) - .size(18.0)); + let addr_text = format!("{} {}", PLUGS_CONNECTED, &peer.addr); + ui.label(RichText::new(addr_text).color(Colors::BLACK).size(18.0)); }); + // Draw peer difficulty and height ui.horizontal(|ui| { ui.add_space(6.0); - // Draw peer difficulty and height - ui.heading(RichText::new(format!("{} {}", PACKAGE, peer.total_difficulty)) - .color(Colors::TITLE) - .size(16.0)); + let diff_text = format!("{} {}", PACKAGE, peer.total_difficulty); + ui.label(RichText::new(diff_text).color(Colors::TITLE).size(16.0)); ui.add_space(2.0); - ui.heading(RichText::new(AT).color(Colors::TITLE).size(16.0)); + ui.label(RichText::new(AT).color(Colors::TITLE).size(16.0)); ui.add_space(2.0); - ui.heading(RichText::new(peer.height.to_string()) - .color(Colors::TITLE) - .size(16.0)); + ui.label(RichText::new(peer.height.to_string()).color(Colors::TITLE).size(16.0)); }); - + // Draw peer user-agent ui.horizontal(|ui| { ui.add_space(6.0); - // Draw peer user-agent - ui.heading(RichText::new(format!("{} {}", DEVICES, &peer.user_agent)) - .color(Colors::GRAY) - .size(16.0)); + let agent_text = format!("{} {}", DEVICES, &peer.user_agent); + ui.label(RichText::new(agent_text).color(Colors::GRAY).size(16.0)); }); ui.add_space(4.0); }); - - // Add space after last item - // if rounding[1] { - // ui.add_space(2.0); - // } } \ No newline at end of file diff --git a/src/gui/views/network/settings.rs b/src/gui/views/network/settings.rs index ea59add..4f808f5 100644 --- a/src/gui/views/network/settings.rs +++ b/src/gui/views/network/settings.rs @@ -21,13 +21,16 @@ use crate::gui::views::{Modal, ModalPosition, View}; use crate::gui::views::network::{NetworkTab, NetworkTabType}; use crate::gui::views::network::configs::dandelion::DandelionSetup; use crate::gui::views::network::configs::node::NodeSetup; +use crate::gui::views::network::configs::p2p::P2PSetup; use crate::gui::views::network::configs::pool::PoolSetup; use crate::gui::views::network::configs::stratum::StratumSetup; use crate::node::{Node, NodeConfig}; +/// Integrated node settings tab content. #[derive(Default)] pub struct NetworkSettings { node: NodeSetup, + p2p: P2PSetup, stratum: StratumSetup, pool: PoolSetup, dandelion: DandelionSetup @@ -49,6 +52,12 @@ impl NetworkTab for NetworkSettings { View::horizontal_line(ui, Colors::STROKE); ui.add_space(4.0); + self.p2p.ui(ui, cb); + + ui.add_space(6.0); + View::horizontal_line(ui, Colors::STROKE); + ui.add_space(4.0); + self.stratum.ui(ui, cb); ui.add_space(6.0); @@ -81,6 +90,16 @@ impl NetworkTab for NetworkSettings { NodeSetup::API_SECRET_MODAL => self.node.secret_modal(ui, modal, cb), NodeSetup::FOREIGN_API_SECRET_MODAL => self.node.secret_modal(ui, modal, cb), NodeSetup::FTL_MODAL => self.node.ftl_modal(ui, modal, cb), + // P2P setup modals. + P2PSetup::PORT_MODAL => self.p2p.port_modal(ui, modal, cb), + P2PSetup::CUSTOM_SEED_MODAL => self.p2p.peer_modal(ui, modal, cb), + P2PSetup::ALLOW_PEER_MODAL => self.p2p.peer_modal(ui, modal, cb), + P2PSetup::DENY_PEER_MODAL => self.p2p.peer_modal(ui, modal, cb), + P2PSetup::PREFER_PEER_MODAL => self.p2p.peer_modal(ui, modal, cb), + P2PSetup::BAN_WINDOW_MODAL => self.p2p.ban_window_modal(ui, modal, cb), + P2PSetup::MAX_INBOUND_MODAL => self.p2p.max_inbound_modal(ui, modal, cb), + P2PSetup::MAX_OUTBOUND_MODAL => self.p2p.max_outbound_modal(ui, modal, cb), + P2PSetup::MIN_OUTBOUND_MODAL => self.p2p.min_outbound_modal(ui, modal, cb), // Stratum server setup modals. StratumSetup::STRATUM_PORT_MODAL => self.stratum.port_modal(ui, modal, cb), StratumSetup::ATTEMPT_TIME_MODAL => self.stratum.attempt_modal(ui, modal, cb), diff --git a/src/node/config.rs b/src/node/config.rs index ea9059f..7c73aa3 100644 --- a/src/node/config.rs +++ b/src/node/config.rs @@ -14,9 +14,10 @@ use std::fs::File; use std::io::{BufRead, BufReader, Write}; -use std::net::{IpAddr, Ipv4Addr, SocketAddrV4, TcpListener}; +use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4, TcpListener, ToSocketAddrs}; use std::path::PathBuf; use std::str::FromStr; +use egui::Key::P; use grin_config::{config, ConfigError, ConfigMembers, GlobalConfig}; use grin_config::config::{API_SECRET_FILE_NAME, FOREIGN_API_SECRET_FILE_NAME, SERVER_CONFIG_FILE_NAME}; @@ -29,39 +30,171 @@ use crate::node::Node; use crate::{AppConfig, Settings}; -/// Wrapped node config to be used by [`grin_servers::Server`]. -#[derive(Serialize, Deserialize)] -pub struct NodeConfig { - pub members: ConfigMembers +/// Peers config to save peers DNS names into the file. +#[derive(Serialize, Deserialize, Default)] +pub struct PeersConfig { + seeds: Vec, + allowed: Vec, + denied: Vec, + preferred: Vec } -impl NodeConfig { - /// Initialize integrated node config. - pub fn init(chain_type: &ChainTypes) -> Self { - let config_members = Self::for_chain_type(chain_type); - Self { - members: config_members +impl PeersConfig { + /// File name for peers config. + pub const FILE_NAME: &'static str = "peers.toml"; + + /// Save peers config to the file. + pub fn save(&self) { + let chain_type = AppConfig::chain_type(); + let config_path = Settings::get_config_path(Self::FILE_NAME, Some(&chain_type)); + Settings::write_to_file(self, config_path); + } + + /// Save seed peer. + pub fn save_seed(&mut self, peer: String) { + self.seeds.insert(self.seeds.len(), peer); + self.save(); + } + + /// Save allowed peer. + pub fn save_allowed(&mut self, peer: String) { + self.allowed.insert(self.allowed.len(), peer); + self.save(); + } + + /// Save denied peer. + pub fn save_denied(&mut self, peer: String) { + self.denied.insert(self.denied.len(), peer); + self.save(); + } + + /// Save preferred peer. + pub fn save_preferred(&mut self, peer: String) { + self.preferred.insert(self.preferred.len(), peer); + self.save(); + } + + /// Convert string to [`PeerAddr`] if address is in correct format (`host:port`) and available. + pub fn peer_to_addr(peer: String) -> Option { + match SocketAddr::from_str(peer.as_str()) { + // Try to parse IP address first. + Ok(ip) => Some(PeerAddr(ip)), + // If that fails it's probably a DNS record. + Err(_) => { + if let Ok(mut socket_addr_list) = peer.to_socket_addrs() { + if let Some(addr) = socket_addr_list.next() { + return Some(PeerAddr(addr)); + } + } + None + } } } - /// Initialize config with provided [`ChainTypes`]. - pub fn for_chain_type(chain_type: &ChainTypes) -> ConfigMembers { + /// Load saved peers to node server [`ConfigMembers`] config. + fn load_to_server_config() { + let mut w_node_config = Settings::node_config_to_update(); + // Load seeds. + for seed in w_node_config.peers.seeds.clone() { + if let Some(p) = Self::peer_to_addr(seed.to_string()) { + let mut seeds = w_node_config + .node + .server + .p2p_config + .seeds + .clone() + .unwrap_or(PeerAddrs::default()); + seeds.peers.insert(seeds.peers.len(), p); + w_node_config.node.server.p2p_config.seeds = Some(seeds); + } + } + // Load allowed peers. + for peer in w_node_config.peers.allowed.clone() { + if let Some(p) = Self::peer_to_addr(peer.clone()) { + let mut allowed = w_node_config + .node + .server + .p2p_config + .peers_allow + .clone() + .unwrap_or(PeerAddrs::default()); + allowed.peers.insert(allowed.peers.len(), p); + w_node_config.node.server.p2p_config.peers_allow = Some(allowed); + } + } + // Load denied peers. + for peer in w_node_config.peers.denied.clone() { + if let Some(p) = Self::peer_to_addr(peer.clone()) { + let mut denied = w_node_config + .node + .server + .p2p_config + .peers_deny + .clone() + .unwrap_or(PeerAddrs::default()); + denied.peers.insert(denied.peers.len(), p); + w_node_config.node.server.p2p_config.peers_deny = Some(denied); + } + + } + // Load preferred peers. + for peer in &w_node_config.peers.preferred.clone() { + if let Some(p) = Self::peer_to_addr(peer.clone()) { + let mut preferred = w_node_config + .node + .server + .p2p_config + .peers_preferred + .clone() + .unwrap_or(PeerAddrs::default()); + preferred.peers.insert(preferred.peers.len(), p); + w_node_config.node.server.p2p_config.peers_preferred = Some(preferred); + } + } + } +} + +/// Wrapped node config to be used by [`grin_servers::Server`]. +#[derive(Serialize, Deserialize)] +pub struct NodeConfig { + pub(crate) node: ConfigMembers, + pub(crate) peers: PeersConfig +} + +impl NodeConfig { + /// Initialize config fields from provided [`ChainTypes`]. + pub fn for_chain_type(chain_type: &ChainTypes) -> Self { // Check secret files for current chain type. let _ = Self::check_api_secret_files(chain_type, API_SECRET_FILE_NAME); let _ = Self::check_api_secret_files(chain_type, FOREIGN_API_SECRET_FILE_NAME); - // Create config. - let path = Settings::get_config_path(SERVER_CONFIG_FILE_NAME, Some(chain_type)); - let parsed = Settings::read_from_file::(path.clone()); - if !path.exists() || parsed.is_err() { - Self::save_default_config(chain_type) - } else { - parsed.unwrap() - } + // Initialize peers config. + let peers_config = { + let path = Settings::get_config_path(PeersConfig::FILE_NAME, Some(chain_type)); + let config = Settings::read_from_file::(path.clone()); + if !path.exists() || config.is_err() { + Self::save_default_peers_config(chain_type) + } else { + config.unwrap() + } + }; + + // Initialize node config. + let node_config = { + let path = Settings::get_config_path(SERVER_CONFIG_FILE_NAME, Some(chain_type)); + let config = Settings::read_from_file::(path.clone()); + if !path.exists() || config.is_err() { + Self::save_default_node_server_config(chain_type) + } else { + config.unwrap() + } + }; + + Self { node: node_config, peers: peers_config } } - /// Generate and save default node config for specified [`ChainTypes`]. - fn save_default_config(chain_type: &ChainTypes) -> ConfigMembers { + /// Save default node config for specified [`ChainTypes`]. + fn save_default_node_server_config(chain_type: &ChainTypes) -> ConfigMembers { let path = Settings::get_config_path(SERVER_CONFIG_FILE_NAME, Some(chain_type)); let mut default_config = GlobalConfig::for_chain(chain_type); default_config.update_paths(&Settings::get_working_path(Some(chain_type))); @@ -70,32 +203,42 @@ impl NodeConfig { config } - /// Save node config to disk. - pub fn save(&mut self) { - let config_path = Settings::get_config_path( - SERVER_CONFIG_FILE_NAME, - Some(&self.members.server.chain_type) - ); - Settings::write_to_file(&self.members, config_path); + /// Save default peers config for specified [`ChainTypes`]. + fn save_default_peers_config(chain_type: &ChainTypes) -> PeersConfig { + let path = Settings::get_config_path(PeersConfig::FILE_NAME, Some(chain_type)); + let config = PeersConfig::default(); + Settings::write_to_file(&config, path); + config } - /// Get node config values. - pub fn get_members() -> ConfigMembers { + /// Save node config to the file. + pub fn save(&self) { + let config_path = Settings::get_config_path( + SERVER_CONFIG_FILE_NAME, + Some(&self.node.server.chain_type) + ); + Settings::write_to_file(&self.node, config_path); + } + + /// Get server config to use for node server before start. + pub fn node_server_config() -> ConfigMembers { + PeersConfig::load_to_server_config(); let r_config = Settings::node_config_to_read(); - r_config.members.clone() + r_config.node.clone() } /// Reset node config to default values. pub fn reset_to_default() { let chain_type = { let r_config = Settings::node_config_to_read(); - r_config.members.server.chain_type + r_config.node.server.chain_type }; - let members = Self::save_default_config(&chain_type); + let node_server_config = Self::save_default_node_server_config(&chain_type); + let peers_config = Self::save_default_peers_config(&chain_type); { let mut w_node_config = Settings::node_config_to_update(); - w_node_config.members = members; - w_node_config.save(); + w_node_config.node = node_server_config; + w_node_config.peers = peers_config; } } @@ -134,7 +277,7 @@ impl NodeConfig { } /// Check whether a port is available on the provided host. - fn is_port_available(host: &String, port: &String) -> bool { + fn is_host_port_available(host: &String, port: &String) -> bool { if let Ok(p) = port.parse::() { let ip_addr = Ipv4Addr::from_str(host.as_str()).unwrap(); let ipv4 = SocketAddrV4::new(ip_addr, p); @@ -143,11 +286,27 @@ impl NodeConfig { false } + /// Check whether a port is available across the system at all hosts. + fn is_port_available(port: &String) -> bool { + if let Ok(p) = port.parse::() { + for ip in Self::get_ip_addrs() { + let ip_addr = Ipv4Addr::from_str(ip.as_str()).unwrap(); + let ipv4 = SocketAddrV4::new(ip_addr, p); + if TcpListener::bind(ipv4).is_err() { + return false; + } + } + } else { + return false; + } + true + } + /// Get stratum server IP address and port. pub fn get_stratum_address() -> (String, String) { let r_config = Settings::node_config_to_read(); let saved_stratum_addr = r_config - .members + .node .server .stratum_mining_config .as_ref() @@ -164,7 +323,7 @@ impl NodeConfig { let addr_to_save = format!("{}:{}", addr, port); let mut w_node_config = Settings::node_config_to_update(); w_node_config - .members + .node .server .stratum_mining_config .as_mut() @@ -186,14 +345,14 @@ impl NodeConfig { /// Check if stratum port is available when server is not running. fn is_not_running_stratum_port_available(ip: &String, port: &String) -> bool { - if Self::is_port_available(&ip, &port) { - if &Self::get_p2p_port().to_string() != port { + if Self::is_host_port_available(&ip, &port) { + if &Self::get_p2p_port() != port { let (api_ip, api_port) = Self::get_api_address(); return if &api_ip == ip { &api_port != port } else { true - } + }; } } false @@ -202,13 +361,13 @@ impl NodeConfig { /// Get stratum mining server wallet address to get rewards. pub fn get_stratum_wallet_addr() -> String { let r_config = Settings::node_config_to_read(); - r_config.members.clone().server.stratum_mining_config.unwrap().wallet_listener_url + r_config.node.clone().server.stratum_mining_config.unwrap().wallet_listener_url } /// Get the amount of time in seconds to attempt to mine on a particular header. pub fn get_stratum_attempt_time() -> String { let r_config = Settings::node_config_to_read(); - r_config.members + r_config.node .clone() .server .stratum_mining_config @@ -219,8 +378,8 @@ impl NodeConfig { /// Save stratum attempt time value in seconds. pub fn save_stratum_attempt_time(time: u32) { - let mut w_node_config = Settings::node_config_to_update(); - w_node_config.members + let w_node_config = Settings::node_config_to_update(); + w_node_config.node .clone() .server .stratum_mining_config @@ -232,7 +391,7 @@ impl NodeConfig { /// Get minimum acceptable share difficulty to request from miners. pub fn get_stratum_min_share_diff() -> String { let r_config = Settings::node_config_to_read(); - r_config.members + r_config.node .clone() .server .stratum_mining_config @@ -243,8 +402,8 @@ impl NodeConfig { /// Save minimum acceptable share difficulty. pub fn save_stratum_min_share_diff(diff: u64) { - let mut w_node_config = Settings::node_config_to_update(); - w_node_config.members + let w_node_config = Settings::node_config_to_update(); + w_node_config.node .clone() .server .stratum_mining_config @@ -256,7 +415,7 @@ impl NodeConfig { /// Check if stratum mining server autorun is enabled. pub fn is_stratum_autorun_enabled() -> bool { let stratum_config = Settings::node_config_to_read() - .members + .node .clone() .server .stratum_mining_config @@ -271,7 +430,7 @@ impl NodeConfig { pub fn toggle_stratum_autorun() { let autorun = Self::is_stratum_autorun_enabled(); let mut w_node_config = Settings::node_config_to_update(); - w_node_config.members + w_node_config.node .server .stratum_mining_config .as_mut() @@ -284,7 +443,7 @@ impl NodeConfig { pub fn get_api_address() -> (String, String) { let r_config = Settings::node_config_to_read(); let saved_api_addr = r_config - .members + .node .server .api_http_addr .as_str(); @@ -296,7 +455,7 @@ impl NodeConfig { pub fn save_api_address(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.node.server.api_http_addr = addr_to_save; w_node_config.save(); } @@ -309,12 +468,12 @@ impl NodeConfig { } else { false }; - if same_running || Self::is_port_available(&ip, &port) { - return &Self::get_p2p_port().to_string() != port; + if same_running || Self::is_host_port_available(ip, port) { + return &Self::get_p2p_port() != port; } return false; - } else if Self::is_port_available(&ip, &port) { - return &Self::get_p2p_port().to_string() != port; + } else if Self::is_host_port_available(ip, port) { + return &Self::get_p2p_port() != port; } false } @@ -323,7 +482,7 @@ impl NodeConfig { pub fn get_api_secret() -> Option { let r_config = Settings::node_config_to_read(); let api_secret_path = r_config - .members + .node .server .api_secret_path .clone(); @@ -335,7 +494,7 @@ impl NodeConfig { Some(first_line.unwrap()) } else { None - } + }; } /// Save API secret text. @@ -347,7 +506,7 @@ impl NodeConfig { pub fn get_foreign_api_secret() -> Option { let r_config = Settings::node_config_to_read(); let foreign_secret_path = r_config - .members + .node .server .foreign_api_secret_path .clone(); @@ -359,7 +518,7 @@ impl NodeConfig { Some(first_line.unwrap()) } else { None - } + }; } /// Update Foreign API secret. @@ -373,8 +532,8 @@ impl NodeConfig { if value.is_empty() { let mut w_config = Settings::node_config_to_update(); match file_name { - API_SECRET_FILE_NAME => w_config.members.server.api_secret_path = None, - _ => w_config.members.server.foreign_api_secret_path = None + API_SECRET_FILE_NAME => w_config.node.server.api_secret_path = None, + _ => w_config.node.server.foreign_api_secret_path = None } w_config.save(); return; @@ -385,8 +544,8 @@ impl NodeConfig { let secret_path = { let r_config = Settings::node_config_to_read(); let path = match file_name { - API_SECRET_FILE_NAME => r_config.members.server.api_secret_path.clone(), - _ => r_config.members.server.foreign_api_secret_path.clone() + API_SECRET_FILE_NAME => r_config.node.server.api_secret_path.clone(), + _ => r_config.node.server.foreign_api_secret_path.clone() }; path.unwrap_or_else(|| { secret_enabled = false; @@ -400,10 +559,10 @@ impl NodeConfig { let mut w_config = Settings::node_config_to_update(); match file_name { API_SECRET_FILE_NAME => w_config - .members + .node .server .api_secret_path = Some(secret_path.clone()), - _ => w_config.members.server.foreign_api_secret_path = Some(secret_path.clone()) + _ => w_config.node.server.foreign_api_secret_path = Some(secret_path.clone()) }; w_config.save(); @@ -415,19 +574,19 @@ impl NodeConfig { /// Get Future Time Limit. pub fn get_ftl() -> String { - Settings::node_config_to_read().members.server.future_time_limit.to_string() + Settings::node_config_to_read().node.server.future_time_limit.to_string() } /// 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.node.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; + let mode = Settings::node_config_to_read().node.clone().server.chain_validation_mode; mode == ChainValidationMode::EveryBlock } @@ -440,13 +599,13 @@ impl NodeConfig { 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.node.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; + let archive_mode = Settings::node_config_to_read().node.clone().server.archive_mode; archive_mode.is_some() && archive_mode.unwrap() } @@ -454,159 +613,219 @@ impl NodeConfig { 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.node.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 + pub fn get_p2p_port() -> String { + Settings::node_config_to_read().node.server.p2p_config.port.to_string() } - /// Get P2P server port. + /// Check if P2P server port is available across the system and config. + pub fn is_p2p_port_available(port: &String) -> bool { + if port.parse::().is_err() { + return false; + } + let (_, api_port) = Self::get_api_address(); + if Node::is_running() { + // Check if P2P server with same port is running. + let same_running = if let Some(running_port) = Node::get_p2p_port() { + &running_port.to_string() == port + } else { + false + }; + if same_running || Self::is_port_available(port) { + return &api_port != port; + } + return false; + } else if Self::is_port_available(port) { + return &api_port != port; + } + false + } + + /// Save 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.node.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 + /// Check if default seed list is used. + pub fn is_default_seeding_type() -> bool { + Settings::node_config_to_read().node.server.p2p_config.seeding_type == Seeding::DNSSeed } - /// 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) { + /// Toggle seeding type to use default or custom seed list. + pub fn toggle_seeding_type() { + let seeding_type = if Self::is_default_seeding_type() { + Seeding::List + } else { + Seeding::DNSSeed + }; 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.node.server.p2p_config.seeding_type = seeding_type; w_node_config.save(); } + /// Get custom seed peers. + pub fn get_custom_seeds() -> Vec { + Settings::node_config_to_read().peers.seeds.clone() + } + + /// Save custom seed peer. + pub fn save_custom_seed(peer: String) { + let mut w_node_config = Settings::node_config_to_update(); + let size = w_node_config.peers.seeds.len(); + w_node_config.peers.seeds.insert(size, peer); + w_node_config.peers.save(); + } + + /// Remove custom seed peer. + pub fn remove_custom_seed(peer: &String) { + let mut w_node_config = Settings::node_config_to_update(); + let mut seeds = w_node_config.peers.seeds.clone(); + if let Some(index) = seeds.iter().position(|x| x == peer) { + seeds.remove(index); + } + w_node_config.peers.seeds = seeds; + w_node_config.peers.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()) + pub fn get_denied_peers() -> Vec { + Settings::node_config_to_read().peers.denied.clone() } - /// Add peer at denied list. + /// Save peer to 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(); + let size = w_node_config.peers.denied.len(); + w_node_config.peers.denied.insert(size, peer); + w_node_config.peers.save(); } - /// Save denied peer list. - pub fn save_denied_peers(peers: Option) { + /// Remove denied peer. + pub fn remove_denied_peer(peer: &String) { let mut w_node_config = Settings::node_config_to_update(); - w_node_config.members.server.p2p_config.peers_deny = peers; - w_node_config.save(); + let mut denied = w_node_config.peers.denied.clone(); + if let Some(index) = denied.iter().position(|x| x == peer) { + denied.remove(index); + } + w_node_config.peers.denied = denied; + w_node_config.peers.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()) + pub fn get_allowed_peers() -> Vec { + Settings::node_config_to_read().peers.allowed.clone() } - /// Save allowed peer list. - pub fn save_allowed_peers(peers: Option) { + /// Save peer to allowed list. + pub fn allow_peer(peer: String) { let mut w_node_config = Settings::node_config_to_update(); - w_node_config.members.server.p2p_config.peers_allow = peers; - w_node_config.save(); + let size = w_node_config.peers.allowed.len(); + w_node_config.peers.allowed.insert(size, peer); + w_node_config.peers.save(); + } + + /// Remove allowed peer. + pub fn remove_allowed_peer(peer: &String) { + let mut w_node_config = Settings::node_config_to_update(); + let mut allowed = w_node_config.peers.allowed.clone(); + if let Some(index) = allowed.iter().position(|x| x == peer) { + allowed.remove(index); + } + w_node_config.peers.allowed = allowed; + w_node_config.peers.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()) + pub fn get_preferred_peers() -> Vec { + Settings::node_config_to_read().peers.preferred.clone() } /// 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(); + let size = w_node_config.peers.preferred.len(); + w_node_config.peers.preferred.insert(size, peer); + w_node_config.peers.save(); } - /// Save preferred peer list. - pub fn save_preferred_peers(peers: Option) { + /// Remove preferred peer. + pub fn remove_preferred_peer(peer: &String) { let mut w_node_config = Settings::node_config_to_update(); - w_node_config.members.server.p2p_config.peers_preferred = peers; - w_node_config.save(); + let mut preferred = w_node_config.peers.preferred.clone(); + if let Some(index) = preferred.iter().position(|x| x == peer) { + preferred.remove(index); + } + w_node_config.peers.preferred = preferred; + w_node_config.peers.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() + pub fn get_p2p_ban_window() -> String { + Settings::node_config_to_read().node.server.p2p_config.ban_window().to_string() } /// Save for how long a banned peer should stay banned in ms. - pub fn save_ban_window(time: i64) { + pub fn save_p2p_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.node.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() + pub fn get_max_inbound_peers() -> String { + Settings::node_config_to_read() + .node.server + .p2p_config + .peer_max_inbound_count() + .to_string() } /// Save maximum number of inbound peer connections. - pub fn save_max_inbound_count(count: u32) { + pub fn save_max_inbound_peers(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.node.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() + pub fn get_max_outbound_peers() -> String { + Settings::node_config_to_read() + .node + .server + .p2p_config + .peer_max_outbound_count() + .to_string() } /// Save maximum number of outbound peer connections. - pub fn save_max_outbound_count(count: u32) { + pub fn save_max_outbound_peers(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.node.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 { + pub fn get_min_outbound_peers() -> String { Settings::node_config_to_read() - .members + .node .server .p2p_config .peer_min_preferred_outbound_count() + .to_string() } /// Save minimum number of outbound peer connections. - pub fn save_min_outbound_count(count: u32) { + pub fn save_min_outbound_peers(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.node.server.p2p_config.peer_min_preferred_outbound_count = Some(count); w_node_config.save(); } @@ -614,61 +833,61 @@ impl NodeConfig { /// Base fee that's accepted into the pool. pub fn get_base_fee() -> String { - Settings::node_config_to_read().members.server.pool_config.accept_fee_base.to_string() + Settings::node_config_to_read().node.server.pool_config.accept_fee_base.to_string() } /// Save base fee that's accepted into the pool. pub fn save_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.node.server.pool_config.accept_fee_base = fee; w_node_config.save(); } /// Reorg cache retention period in minutes. pub fn get_reorg_cache_period() -> String { - Settings::node_config_to_read().members.server.pool_config.reorg_cache_period.to_string() + Settings::node_config_to_read().node.server.pool_config.reorg_cache_period.to_string() } /// Save reorg cache retention period in minutes. pub fn save_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.node.server.pool_config.reorg_cache_period = period; w_node_config.save(); } /// Max amount of transactions at pool. pub fn get_max_pool_size() -> String { - Settings::node_config_to_read().members.server.pool_config.max_pool_size.to_string() + Settings::node_config_to_read().node.server.pool_config.max_pool_size.to_string() } /// Save max amount of transactions at pool. pub fn save_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.node.server.pool_config.max_pool_size = amount; w_node_config.save(); } /// Max amount of transactions at stem pool. pub fn get_max_stempool_size() -> String { - Settings::node_config_to_read().members.server.pool_config.max_stempool_size.to_string() + Settings::node_config_to_read().node.server.pool_config.max_stempool_size.to_string() } /// Save max amount of transactions at stem pool. pub fn save_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.node.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() -> String { - Settings::node_config_to_read().members.server.pool_config.mineable_max_weight.to_string() + Settings::node_config_to_read().node.server.pool_config.mineable_max_weight.to_string() } /// Set max total weight of transactions that can get selected to build a block. pub fn save_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.node.server.pool_config.mineable_max_weight = weight; w_node_config.save(); } @@ -676,63 +895,63 @@ impl NodeConfig { /// Dandelion epoch duration in seconds. pub fn get_dandelion_epoch() -> String { - Settings::node_config_to_read().members.server.dandelion_config.epoch_secs.to_string() + Settings::node_config_to_read().node.server.dandelion_config.epoch_secs.to_string() } /// Save Dandelion epoch duration in seconds. pub fn save_dandelion_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.node.server.dandelion_config.epoch_secs = secs; w_node_config.save(); } /// Dandelion embargo timer in seconds. /// Fluff and broadcast after embargo expires if tx not seen on network. pub fn get_dandelion_embargo() -> String { - Settings::node_config_to_read().members.server.dandelion_config.embargo_secs.to_string() + Settings::node_config_to_read().node.server.dandelion_config.embargo_secs.to_string() } /// Save Dandelion embargo timer in seconds. pub fn save_dandelion_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.node.server.dandelion_config.embargo_secs = secs; w_node_config.save(); } /// Dandelion aggregation period in seconds. pub fn get_dandelion_aggregation() -> String { - Settings::node_config_to_read().members.server.dandelion_config.aggregation_secs.to_string() + Settings::node_config_to_read().node.server.dandelion_config.aggregation_secs.to_string() } /// Save Dandelion aggregation period in seconds. pub fn save_dandelion_aggregation(secs: u16) { let mut w_node_config = Settings::node_config_to_update(); - w_node_config.members.server.dandelion_config.aggregation_secs = secs; + w_node_config.node.server.dandelion_config.aggregation_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() -> String { - Settings::node_config_to_read().members.server.dandelion_config.stem_probability.to_string() + Settings::node_config_to_read().node.server.dandelion_config.stem_probability.to_string() } /// Save Dandelion stem probability. pub fn save_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.node.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 + Settings::node_config_to_read().node.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.node.server.dandelion_config.always_stem_our_txs = !stem_txs; w_node_config.save(); } } \ No newline at end of file diff --git a/src/node/mod.rs b/src/node/mod.rs index 3118bae..9074fc2 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -20,4 +20,4 @@ mod config; mod stratum; mod mine_block; -pub use config::NodeConfig; \ No newline at end of file +pub use config::{NodeConfig, PeersConfig}; \ No newline at end of file diff --git a/src/node/node.rs b/src/node/node.rs index ce147ac..47800d3 100644 --- a/src/node/node.rs +++ b/src/node/node.rs @@ -79,6 +79,9 @@ impl Default for Node { } impl Node { + /// Delay for server thread to update the stats. + pub const STATS_UPDATE_DELAY: Duration = Duration::from_millis(250); + /// Stop the [`Server`] and setup exit flag after if needed. pub fn stop(exit_after_stop: bool) { NODE_STATE.stop_needed.store(true, Ordering::Relaxed); @@ -262,7 +265,7 @@ impl Node { NODE_STATE.start_stratum_needed.store(false, Ordering::Relaxed); } - thread::sleep(Duration::from_millis(250)); + thread::sleep(Self::STATS_UPDATE_DELAY); } } Err(e) => { @@ -495,11 +498,11 @@ impl Node { /// Start the node [`Server`]. fn start_node_server() -> Result { - // Get current global config - let config = NodeConfig::get_members(); + // Get saved server config. + let config = NodeConfig::node_server_config(); let server_config = config.server.clone(); - // Remove temporary file dir + // Remove temporary file dir. { let mut tmp_dir = PathBuf::from(&server_config.db_root); tmp_dir = tmp_dir.parent().unwrap().to_path_buf(); @@ -511,6 +514,7 @@ fn start_node_server() -> Result { } } } + // Initialize our global chain_type, feature flags (NRD kernel support currently), // accept_fee_base, and future_time_limit. // These are read via global and not read from config beyond this point. @@ -524,22 +528,18 @@ fn start_node_server() -> Result { if !global::GLOBAL_NRD_FEATURE_ENABLED.is_init() { match global::get_chain_type() { ChainTypes::Mainnet => { - // Set various mainnet specific feature flags. global::init_global_nrd_enabled(false); } _ => { - // Set various non-mainnet feature flags. global::init_global_nrd_enabled(true); } } } else { match global::get_chain_type() { ChainTypes::Mainnet => { - // Set various mainnet specific feature flags. global::set_global_nrd_enabled(false); } _ => { - // Set various non-mainnet feature flags. global::set_global_nrd_enabled(true); } } @@ -562,13 +562,13 @@ fn start_node_server() -> Result { let api_chan: &'static mut (oneshot::Sender<()>, oneshot::Receiver<()>) = Box::leak(Box::new(oneshot::channel::<()>())); - // Write launching API server address. + // Set launching API server address from config to state. { let mut w_api_addr = NODE_STATE.api_addr.write().unwrap(); *w_api_addr = Some(config.server.api_http_addr); } - // Write launching P2P server port. + // Set launching P2P server port from config to state. { let mut w_p2p_port = NODE_STATE.p2p_port.write().unwrap(); *w_p2p_port = Some(config.server.p2p_config.port); @@ -579,7 +579,7 @@ fn start_node_server() -> Result { NODE_STATE.start_stratum_needed.store(true, Ordering::Relaxed); } - let server_result = Server::new(server_config.clone(), None, api_chan); + let server_result = Server::new(server_config, None, api_chan); server_result } diff --git a/src/settings.rs b/src/settings.rs index 8535629..ad9e3ea 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -32,7 +32,7 @@ lazy_static! { const APP_CONFIG_FILE_NAME: &'static str = "app.toml"; -/// Application settings config. +/// Common application settings. #[derive(Serialize, Deserialize)] pub struct AppConfig { /// Run node server on startup. @@ -69,17 +69,19 @@ impl AppConfig { Settings::write_to_file(self, Settings::get_config_path(APP_CONFIG_FILE_NAME, None)); } - /// Change chain type and load new [`NodeConfig`] accordingly. + /// Change chain type and load new [`NodeConfig`]. pub fn change_chain_type(chain_type: &ChainTypes) { let current_chain_type = Self::chain_type(); if current_chain_type != *chain_type { let mut w_app_config = Settings::app_config_to_update(); w_app_config.chain_type = *chain_type; w_app_config.save(); - // Load config for selected chain type. + + // Load node config for selected chain type. let mut w_node_config = Settings::node_config_to_update(); - w_node_config.members = NodeConfig::for_chain_type(chain_type); - w_node_config.save(); + let node_config = NodeConfig::for_chain_type(chain_type); + w_node_config.node = node_config.node; + w_node_config.peers = node_config.peers; } } @@ -117,7 +119,7 @@ impl Settings { fn init() -> Self { let app_config = AppConfig::init(); Self { - node_config: Arc::new(RwLock::new(NodeConfig::init(&app_config.chain_type))), + node_config: Arc::new(RwLock::new(NodeConfig::for_chain_type(&app_config.chain_type))), app_config: Arc::new(RwLock::new(app_config)) } }