wallet + config + ui: load wallet info, wallet connections and app config refactoring, check external connections availability, config subdir argument format, item button color, radio buttons stroke, chain type selection from connections, update translations

This commit is contained in:
ardocrat 2023-08-09 02:22:16 +03:00
parent 8ed6bc0578
commit 076a692e1b
27 changed files with 647 additions and 856 deletions

View file

@ -47,6 +47,9 @@ network:
autorun: Autorun
disabled_server: 'Enable integrated node or add another connection method by pressing %{dots} in the top-left corner of the screen.'
no_ips: There are no available IP addresses on your system, server cannot be started, check your network connectivity.
available: Available
not_available: Not available
availability_check: Availability check
sync_status:
node_restarting: Node is restarting
node_down: Node is down

View file

@ -47,6 +47,9 @@ network:
autorun: Автозапуск
disabled_server: 'Включите встроенный узел или добавьте другой способ подключения, нажав %{dots} в левом-верхнем углу экрана.'
no_ips: В вашей системе отсутствуют доступные IP адреса, запуск сервера невозможен, проверьте ваше подключение к сети.
available: Доступно
not_available: Недоступно
availability_check: Проверка доступности
sync_status:
node_restarting: Узел перезапускается
node_down: Узел выключен

127
src/config.rs Normal file
View file

@ -0,0 +1,127 @@
// 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 grin_core::global::ChainTypes;
use serde_derive::{Deserialize, Serialize};
use crate::node::NodeConfig;
use crate::Settings;
use crate::wallet::ConnectionsConfig;
/// Application configuration.
#[derive(Serialize, Deserialize)]
pub struct AppConfig {
/// Run node server on startup.
pub auto_start_node: bool,
/// Chain type for node and wallets.
pub(crate) chain_type: ChainTypes,
/// Flag to show wallet list at dual panel wallets mode.
show_wallets_at_dual_panel: bool,
/// Flag to show all connections at network panel or integrated node info.
show_connections_network_panel: bool,
}
impl Default for AppConfig {
fn default() -> Self {
Self {
auto_start_node: false,
chain_type: ChainTypes::default(),
show_wallets_at_dual_panel: true,
show_connections_network_panel: false,
}
}
}
impl AppConfig {
/// Application configuration file name.
pub const FILE_NAME: &'static str = "app.toml";
/// Save application configuration to the file.
pub fn save(&self) {
Settings::write_to_file(self, Settings::get_config_path(Self::FILE_NAME, None));
}
/// Change global [`ChainTypes`] 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 {
// Save chain type at app config.
{
let mut w_app_config = Settings::app_config_to_update();
w_app_config.chain_type = *chain_type;
w_app_config.save();
}
// Load node configuration for selected chain type.
{
let mut w_node_config = Settings::node_config_to_update();
let node_config = NodeConfig::for_chain_type(chain_type);
w_node_config.node = node_config.node;
w_node_config.peers = node_config.peers;
}
// Load connections configuration
{
let mut w_node_config = Settings::conn_config_to_update();
*w_node_config = ConnectionsConfig::for_chain_type(chain_type);
}
}
}
/// Get current [`ChainTypes`] for node and wallets.
pub fn chain_type() -> ChainTypes {
let r_config = Settings::app_config_to_read();
r_config.chain_type
}
/// Check if integrated node is starting with application.
pub fn autostart_node() -> bool {
let r_config = Settings::app_config_to_read();
r_config.auto_start_node
}
/// Toggle integrated node autostart.
pub fn toggle_node_autostart() {
let autostart = Self::autostart_node();
let mut w_app_config = Settings::app_config_to_update();
w_app_config.auto_start_node = !autostart;
w_app_config.save();
}
/// Check if it's needed to show wallet list at dual panel wallets mode.
pub fn show_wallets_at_dual_panel() -> bool {
let r_config = Settings::app_config_to_read();
r_config.show_wallets_at_dual_panel
}
/// Toggle flag to show wallet list at dual panel wallets mode.
pub fn toggle_show_wallets_at_dual_panel() {
let show = Self::show_wallets_at_dual_panel();
let mut w_app_config = Settings::app_config_to_update();
w_app_config.show_wallets_at_dual_panel = !show;
w_app_config.save();
}
/// Check if it's needed to show all connections or integrated node info at network panel.
pub fn show_connections_network_panel() -> bool {
let r_config = Settings::app_config_to_read();
r_config.show_connections_network_panel
}
/// Toggle flag to show all connections or integrated node info at network panel.
pub fn toggle_show_connections_network_panel() {
let show = Self::show_connections_network_panel();
let mut w_app_config = Settings::app_config_to_update();
w_app_config.show_connections_network_panel = !show;
w_app_config.save();
}
}

View file

@ -17,25 +17,25 @@ use url::Url;
use crate::AppConfig;
use crate::gui::Colors;
use crate::gui::icons::{CARET_RIGHT, CHECK_CIRCLE, COMPUTER_TOWER, DOTS_THREE_CIRCLE, GLOBE_SIMPLE, PENCIL, PLAY, STOP, TRASH, X_CIRCLE};
use crate::gui::icons::{CARET_RIGHT, CHECK_CIRCLE, COMPUTER_TOWER, DOTS_THREE_CIRCLE, PENCIL, POWER, TRASH, X_CIRCLE};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, View};
use crate::gui::views::{Modal, NodeSetup, View};
use crate::gui::views::types::{ModalContainer, ModalPosition};
use crate::node::{Node, NodeConfig};
use crate::wallet::{ConnectionsConfig, ExternalConnection};
/// Network connections content.
pub struct ConnectionsContent {
/// Flag to check if modal was just opened.
/// Flag to check if [`Modal`] was just opened to focus on input field.
first_modal_launch: bool,
/// External connection URL value for [`Modal`].
ext_node_url_edit: String,
/// External connection API secret value for [`Modal`].
ext_node_secret_edit: String,
/// Flag to show URL format error.
/// Flag to show URL format error at [`Modal`].
ext_node_url_error: bool,
/// Flag to check if existing connection is editing.
edit_ext_conn: Option<ExternalConnection>,
/// Editing external connection identifier for [`Modal`].
ext_conn_id_edit: Option<i64>,
/// [`Modal`] identifiers allowed at this ui container.
modal_ids: Vec<&'static str>
@ -43,12 +43,15 @@ pub struct ConnectionsContent {
impl Default for ConnectionsContent {
fn default() -> Self {
if AppConfig::show_connections_network_panel() {
ExternalConnection::start_ext_conn_availability_check();
}
Self {
first_modal_launch: true,
ext_node_url_edit: "".to_string(),
ext_node_secret_edit: "".to_string(),
ext_node_url_error: false,
edit_ext_conn: None,
ext_conn_id_edit: None,
modal_ids: vec![
Self::NETWORK_EXT_CONNECTION_MODAL
]
@ -81,15 +84,24 @@ impl ConnectionsContent {
// Draw modal content for current ui container.
self.current_modal_ui(ui, frame, cb);
// Show network type selection.
let saved_chain_type = AppConfig::chain_type();
NodeSetup::chain_type_ui(ui);
// Start external connections availability check on network type change.
if saved_chain_type != AppConfig::chain_type() {
ExternalConnection::start_ext_conn_availability_check();
}
ui.add_space(5.0);
// Show integrated node info content.
Self::integrated_node_item_ui(ui);
ui.add_space(8.0);
let ext_conn_list = ConnectionsConfig::ext_conn_list();
if !ext_conn_list.is_empty() {
ui.add_space(6.0);
ui.label(RichText::new(t!("wallets.ext_conn")).size(16.0).color(Colors::GRAY));
ui.add_space(6.0);
// Show external connections.
let ext_conn_list = ConnectionsConfig::external_connections();
for (index, conn) in ext_conn_list.iter().enumerate() {
ui.horizontal_wrapped(|ui| {
// Draw connection list item.
@ -97,6 +109,7 @@ impl ConnectionsContent {
});
}
}
}
/// Draw integrated node connection item content.
fn integrated_node_item_ui(ui: &mut egui::Ui) {
@ -115,26 +128,26 @@ impl ConnectionsContent {
ui.style_mut().visuals.widgets.hovered.rounding = Rounding::same(8.0);
ui.style_mut().visuals.widgets.active.rounding = Rounding::same(8.0);
// Draw button to show integrated node.
View::item_button(ui, View::item_rounding(0, 1, true), CARET_RIGHT, || {
// Draw button to show integrated node info.
View::item_button(ui, View::item_rounding(0, 1, true), CARET_RIGHT, None, || {
AppConfig::toggle_show_connections_network_panel();
});
if !Node::is_running() {
// Draw button to start integrated node.
View::item_button(ui, Rounding::none(), PLAY, || {
View::item_button(ui, Rounding::none(), POWER, Some(Colors::GREEN), || {
Node::start();
});
} else if !Node::is_starting() && !Node::is_stopping() && !Node::is_restarting() {
// Show button to open closed wallet.
View::item_button(ui, Rounding::none(), STOP, || {
// Draw button to open closed wallet.
View::item_button(ui, Rounding::none(), POWER, Some(Colors::RED), || {
Node::stop(false);
});
}
let layout_size = ui.available_size();
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
ui.add_space(7.0);
ui.add_space(6.0);
ui.vertical(|ui| {
ui.add_space(3.0);
ui.label(RichText::new(t!("network.node"))
@ -157,7 +170,6 @@ impl ConnectionsContent {
};
let status_text = format!("{} {}", status_icon, Node::get_sync_status_text());
ui.label(RichText::new(status_text).size(15.0).color(Colors::GRAY));
ui.add_space(4.0);
})
});
});
@ -172,28 +184,28 @@ impl ConnectionsContent {
cb: &dyn PlatformCallbacks) {
// Setup layout size.
let mut rect = ui.available_rect_before_wrap();
rect.set_height(42.0);
rect.set_height(53.0);
// Draw round background.
let bg_rect = rect.clone();
let item_rounding = View::item_rounding(index, len, false);
ui.painter().rect(bg_rect, item_rounding, Colors::FILL, View::ITEM_STROKE);
ui.painter().rect(bg_rect, item_rounding, Colors::FILL_DARK, View::ITEM_STROKE);
ui.vertical(|ui| {
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
// Draw buttons for non-default connections.
if conn.url != ExternalConnection::DEFAULT_EXTERNAL_NODE_URL {
if conn.url != ExternalConnection::DEFAULT_MAIN_URL {
let button_rounding = View::item_rounding(index, len, true);
View::item_button(ui, button_rounding, TRASH, || {
ConnectionsConfig::remove_external_connection(conn);
View::item_button(ui, button_rounding, TRASH, None, || {
ConnectionsConfig::remove_ext_conn(conn.id);
});
View::item_button(ui, Rounding::none(), PENCIL, || {
View::item_button(ui, Rounding::none(), PENCIL, None, || {
// Setup values for Modal.
self.first_modal_launch = true;
self.ext_node_url_edit = conn.url.clone();
self.ext_node_secret_edit = conn.secret.clone().unwrap_or("".to_string());
self.ext_node_url_error = false;
self.edit_ext_conn = Some(conn.clone());
self.ext_conn_id_edit = Some(conn.id);
// Show modal.
Modal::new(Self::NETWORK_EXT_CONNECTION_MODAL)
.position(ModalPosition::CenterTop)
@ -206,11 +218,29 @@ impl ConnectionsContent {
let layout_size = ui.available_size();
ui.allocate_ui_with_layout(layout_size, Layout::left_to_right(Align::Center), |ui| {
ui.add_space(6.0);
ui.vertical(|ui| {
// Draw connections URL.
let conn_text = format!("{} {}", GLOBE_SIMPLE, conn.url);
ui.add_space(4.0);
let conn_text = format!("{} {}", COMPUTER_TOWER, conn.url);
ui.label(RichText::new(conn_text)
.color(Colors::TEXT_BUTTON)
.size(16.0));
.color(Colors::TEXT)
.size(15.0));
ui.add_space(1.0);
// Setup connection status text.
let status_text = if let Some(available) = conn.available {
if available {
format!("{} {}", CHECK_CIRCLE, t!("network.available"))
} else {
format!("{} {}", X_CIRCLE, t!("network.not_available"))
}
} else {
format!("{} {}", DOTS_THREE_CIRCLE, t!("network.availability_check"))
};
ui.label(RichText::new(status_text).size(15.0).color(Colors::GRAY));
ui.add_space(3.0);
});
});
});
});
@ -223,7 +253,7 @@ impl ConnectionsContent {
self.ext_node_url_edit = "".to_string();
self.ext_node_secret_edit = "".to_string();
self.ext_node_url_error = false;
self.edit_ext_conn = None;
self.ext_conn_id_edit = None;
// Show modal.
Modal::new(Self::NETWORK_EXT_CONNECTION_MODAL)
.position(ModalPosition::CenterTop)
@ -246,7 +276,7 @@ impl ConnectionsContent {
// Draw node URL text edit.
let url_edit_resp = egui::TextEdit::singleline(&mut self.ext_node_url_edit)
.id(Id::from(modal.id).with("node_url_edit"))
.id(Id::from(modal.id).with(self.ext_conn_id_edit))
.font(TextStyle::Heading)
.desired_width(ui.available_width())
.cursor_at_end(true)
@ -315,13 +345,12 @@ impl ConnectionsContent {
};
// Update or create new connections.
let ext_conn = ExternalConnection::new(url.clone(), secret);
if let Some(edit_conn) = self.edit_ext_conn.clone() {
ConnectionsConfig::update_external_connection(edit_conn, ext_conn);
self.edit_ext_conn = None;
} else {
ConnectionsConfig::add_external_connection(ext_conn);
let mut ext_conn = ExternalConnection::new(url, secret);
if let Some(id) = self.ext_conn_id_edit {
ext_conn.id = id;
}
ConnectionsConfig::add_ext_conn(ext_conn);
self.ext_conn_id_edit = None;
// Close modal.
cb.hide_keyboard();

View file

@ -23,6 +23,7 @@ use crate::gui::views::{ConnectionsContent, NetworkMetrics, NetworkMining, Netwo
use crate::gui::views::network::types::{NetworkTab, NetworkTabType};
use crate::gui::views::types::TitleType;
use crate::node::Node;
use crate::wallet::ExternalConnection;
/// Network content.
pub struct NetworkContent {
@ -178,6 +179,9 @@ impl NetworkContent {
if !show_connections {
View::title_button(ui, DOTS_THREE_OUTLINE_VERTICAL, || {
AppConfig::toggle_show_connections_network_panel();
if AppConfig::show_connections_network_panel() {
ExternalConnection::start_ext_conn_availability_check();
}
});
} else {
View::title_button(ui, PLUS_CIRCLE, || {

View file

@ -108,7 +108,7 @@ impl NodeSetup {
ui.add_space(4.0);
// Show chain type setup.
self.chain_type_ui(ui);
Self::chain_type_ui(ui);
// Show loading indicator or controls to stop/start/restart node.
if Node::is_stopping() || Node::is_restarting() || Node::is_starting() {
@ -220,7 +220,7 @@ impl NodeSetup {
}
/// Draw [`ChainTypes`] setup content.
fn chain_type_ui(&mut self, ui: &mut egui::Ui) {
pub fn chain_type_ui(ui: &mut egui::Ui) {
let saved_chain_type = AppConfig::chain_type();
let mut selected_chain_type = saved_chain_type;

View file

@ -908,7 +908,7 @@ fn peer_item_ui(ui: &mut egui::Ui,
ui.allocate_ui_with_layout(rect.size(), Layout::right_to_left(Align::Center), |ui| {
// Draw delete button for non-default seed peers.
if peer_type != &PeerType::DefaultSeed {
View::item_button(ui, View::item_rounding(index, len, true), TRASH, || {
View::item_button(ui, View::item_rounding(index, len, true), TRASH, None, || {
match peer_type {
PeerType::CustomSeed => {
NodeConfig::remove_custom_seed(peer_addr);

View file

@ -104,6 +104,8 @@ impl View {
/// Title button with transparent background fill color, contains only icon.
pub fn title_button(ui: &mut egui::Ui, icon: &str, action: impl FnOnce()) {
ui.scope(|ui| {
// Disable stroke when inactive.
ui.style_mut().visuals.widgets.inactive.bg_stroke = Stroke::NONE;
// Setup stroke around title buttons on click.
ui.style_mut().visuals.widgets.hovered.bg_stroke = Self::HOVER_STROKE;
ui.style_mut().visuals.widgets.active.bg_stroke = Self::DEFAULT_STROKE;
@ -173,8 +175,12 @@ impl View {
}
}
/// Draw list item [`Button`] with given vertical padding and rounding on left and right sides.
pub fn item_button(ui: &mut egui::Ui, r: Rounding, icon: &'static str, action: impl FnOnce()) {
/// Draw list item [`Button`] with provided rounding.
pub fn item_button(ui: &mut egui::Ui,
rounding: Rounding,
text: &'static str,
color: Option<Color32>,
action: impl FnOnce()) {
// Setup button size.
let mut rect = ui.available_rect_before_wrap();
rect.set_width(32.0);
@ -193,9 +199,12 @@ impl View {
ui.visuals_mut().widgets.hovered.bg_stroke = Self::HOVER_STROKE;
ui.visuals_mut().widgets.active.bg_stroke = Self::ITEM_STROKE;
// Setup button text color.
let text_color = if let Some(c) = color { c } else { Colors::ITEM_BUTTON };
// Show button.
let br = Button::new(RichText::new(icon).size(20.0).color(Colors::ITEM_BUTTON))
.rounding(r)
let br = Button::new(RichText::new(text).size(20.0).color(text_color))
.rounding(rounding)
.min_size(button_size)
.ui(ui);
br.surrender_focus();

View file

@ -24,7 +24,7 @@ use crate::gui::views::{Modal, Root, TitlePanel, View};
use crate::gui::views::types::{ModalContainer, ModalPosition, TitleType};
use crate::gui::views::wallets::creation::WalletCreation;
use crate::gui::views::wallets::WalletContent;
use crate::wallet::{Wallet, Wallets};
use crate::wallet::{ConnectionsConfig, ExternalConnection, Wallet, Wallets};
/// Wallets content.
pub struct WalletsContent {
@ -140,9 +140,7 @@ impl WalletsContent {
}
if create_wallet || !show_wallet {
// Show wallet creation content.
self.creation_content.ui(ui, frame, cb, |mut wallet| {
// Load the wallet.
Wallets::load(&mut wallet);
self.creation_content.ui(ui, frame, cb, |wallet| {
// Add created wallet to list.
self.wallets.add(wallet);
});
@ -365,17 +363,23 @@ impl WalletsContent {
if !wallet.is_open() {
// Show button to open closed wallet.
View::item_button(ui, View::item_rounding(0, 1, true), FOLDER_OPEN, || {
View::item_button(ui, View::item_rounding(0, 1, true), FOLDER_OPEN, None, || {
self.wallets.select(Some(id));
self.show_open_wallet_modal(cb);
});
} else if !is_selected {
} else {
if !is_selected {
// Show button to select opened wallet.
View::item_button(ui, View::item_rounding(0, 1, true), CARET_RIGHT, || {
View::item_button(ui, View::item_rounding(0, 1, true), CARET_RIGHT, None, || {
self.wallets.select(Some(id));
});
}
// Show button to close opened wallet.
View::item_button(ui, Rounding::none(), LOCK_KEY, || {
View::item_button(ui, if !is_selected {
Rounding::none()
} else {
View::item_rounding(0, 1, true)
}, LOCK_KEY, None, || {
let _ = wallet.close();
});
}
@ -390,9 +394,12 @@ impl WalletsContent {
View::ellipsize_text(ui, wallet.config.name.to_owned(), 18.0, name_color);
// Setup wallet connection text.
let external_url = &wallet.config.external_node_url;
let conn_text = if let Some(url) = external_url {
format!("{} {}", GLOBE_SIMPLE, url)
let conn_text = if let Some(id) = wallet.config.ext_conn_id {
let ext_conn_url = match ConnectionsConfig::ext_conn(id) {
None => ExternalConnection::DEFAULT_MAIN_URL.to_string(),
Some(ext_conn) => ext_conn.url
};
format!("{} {}", GLOBE_SIMPLE, ext_conn_url)
} else {
format!("{} {}", COMPUTER_TOWER, t!("network.node"))
};
@ -525,7 +532,7 @@ impl WalletsContent {
if self.pass_edit.is_empty() {
return;
}
match self.wallets.open_selected(self.pass_edit.clone()) {
match self.wallets.launch_selected(self.pass_edit.clone()) {
Ok(_) => {
// Clear values.
self.pass_edit = "".to_string();

View file

@ -271,8 +271,8 @@ impl WalletCreation {
let name = self.name_edit.clone();
let pass = self.pass_edit.clone();
let phrase = self.mnemonic_setup.mnemonic.get_phrase();
let ext_conn = self.network_setup.get_ext_conn_url();
let wallet = Wallet::create(name, pass.clone(), phrase, ext_conn).unwrap();
let conn_method = &self.network_setup.method;
let wallet = Wallet::create(name, pass.clone(), phrase, conn_method).unwrap();
// Open created wallet.
wallet.open(pass).unwrap();
// Pass created wallet to callback.

View file

@ -20,13 +20,13 @@ use crate::gui::icons::{GLOBE, GLOBE_SIMPLE};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::{Modal, View};
use crate::gui::views::types::{ModalContainer, ModalPosition};
use crate::gui::views::wallets::setup::ConnectionMethod;
use crate::wallet::{ConnectionsConfig, ExternalConnection};
use crate::wallet::types::ConnectionMethod;
/// Wallet node connection method setup content.
pub struct ConnectionSetup {
/// Selected connection method.
method: ConnectionMethod,
pub method: ConnectionMethod,
/// Flag to check if modal was just opened.
first_modal_launch: bool,
@ -82,14 +82,6 @@ impl ConnectionSetup {
// Self { method: ConnectionMethod::Integrated }
// }
/// Get external connection URL.
pub fn get_ext_conn_url(&self) -> Option<String> {
match &self.method {
ConnectionMethod::Integrated => None,
ConnectionMethod::External(url) => Some(url.clone())
}
}
pub fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
// Draw modal content for current ui container.
self.current_modal_ui(ui, frame, cb);
@ -122,10 +114,10 @@ impl ConnectionSetup {
ui.add_space(12.0);
// Show external nodes URLs selection.
for conn in ConnectionsConfig::external_connections() {
for conn in ConnectionsConfig::ext_conn_list() {
View::radio_value(ui,
&mut self.method,
ConnectionMethod::External(conn.url.clone()),
ConnectionMethod::External(conn.id),
conn.url);
ui.add_space(12.0);
}
@ -162,7 +154,7 @@ impl ConnectionSetup {
// Draw node URL text edit.
let url_edit_resp = egui::TextEdit::singleline(&mut self.ext_node_url_edit)
.id(Id::from(modal.id).with("node_url_edit"))
.id(Id::from(modal.id))
.font(TextStyle::Heading)
.desired_width(ui.available_width())
.cursor_at_end(true)
@ -230,10 +222,10 @@ impl ConnectionSetup {
Some(self.ext_node_secret_edit.to_owned())
};
let ext_conn = ExternalConnection::new(url.clone(), secret);
ConnectionsConfig::add_external_connection(ext_conn);
ConnectionsConfig::add_ext_conn(ext_conn.clone());
// Set added connection as current.
self.method = ConnectionMethod::External(url);
self.method = ConnectionMethod::External(ext_conn.id);
// Close modal.
cb.hide_keyboard();

View file

@ -14,6 +14,3 @@
mod connection;
pub use connection::ConnectionSetup;
mod types;
pub use types::*;

View file

@ -1,22 +0,0 @@
// 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.
/// Wallet node connection method type.
#[derive(PartialEq)]
pub enum ConnectionMethod {
/// Integrated node connection.
Integrated,
/// External node connection.
External(String)
}

View file

@ -15,13 +15,13 @@
use egui::{Margin, RichText};
use crate::gui::Colors;
use crate::gui::icons::{DOWNLOAD, UPLOAD, WALLET, WRENCH};
use crate::gui::icons::{DOWNLOAD, POWER, UPLOAD, WALLET, WRENCH};
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::View;
use crate::gui::views::{Root, View};
use crate::gui::views::wallets::{WalletInfo, WalletReceive, WalletSend, WalletSettings};
use crate::gui::views::wallets::types::{WalletTab, WalletTabType};
use crate::node::Node;
use crate::wallet::{Wallet, Wallets};
use crate::wallet::Wallet;
/// Selected and opened wallet content.
pub struct WalletContent {
@ -71,11 +71,12 @@ impl WalletContent {
..Default::default()
})
.show_inside(ui, |ui| {
self.current_tab.ui(ui, frame, wallet, cb);
});
// Refresh content after delay for loaded wallet.
if wallet.is_loaded() {
ui.ctx().request_repaint_after(Wallets::INFO_UPDATE_DELAY);
if wallet.get_info().is_some() {
ui.ctx().request_repaint_after(Wallet::INFO_UPDATE_DELAY);
} else {
ui.ctx().request_repaint();
}
@ -117,34 +118,48 @@ impl WalletContent {
}
/// Content to draw when wallet is loading.
fn loading_ui(ui: &mut egui::Ui, wallet: &Wallet) {
if wallet.config.external_node_url.is_none() && !Node::is_running() {
pub fn show_loading_ui(ui: &mut egui::Ui, frame: &mut eframe::Frame, wallet: &Wallet) -> bool {
if wallet.config.ext_conn_id.is_none() && !Node::is_running() {
let dual_panel_root = Root::is_dual_panel_mode(frame);
View::center_content(ui, if !dual_panel_root { 162.0 } else { 96.0 }, |ui| {
let text = t!("wallets.enable_node", "settings" => WRENCH);
ui.label(RichText::new(text).size(16.0).color(Colors::INACTIVE_TEXT));
// Show button to enable integrated node at non-dual root panel mode.
if !dual_panel_root {
ui.add_space(8.0);
let enable_node_text = format!("{} {}", POWER, t!("network.enable_node"));
View::button(ui, enable_node_text, Colors::GOLD, || {
Node::start();
});
}
});
return true;
} else {
if wallet.get_info().is_none() || wallet.loading_progress() < 100 {
if let Some(error) = &wallet.loading_error {
// View::center_content(ui, 162.0, |ui| {
// let text = t!("wallets.enable_node", "settings" => WRENCH);
// View::big_loading_spinner(ui);
// ui.add_space(18.0);
// let text = if wallet.loading_progress == 0 {
// t!("wallet_loading")
// } else {
// format!("{}: {}%", t!("wallet_loading"), wallet.loading_progress)
// };
// ui.label(RichText::new(text).size(16.0).color(Colors::INACTIVE_TEXT));
// });
} else if !wallet.is_loaded() {
println!("e: {}", error);
let text = t!("wallets.wallet_loading_err");
ui.label(RichText::new(text).size(16.0).color(Colors::INACTIVE_TEXT));
//TODO: button to retry.
} else {
View::center_content(ui, 162.0, |ui| {
View::big_loading_spinner(ui);
ui.add_space(18.0);
let text = if wallet.loading_progress == 0 {
// Setup loading progress text.
let progress = wallet.loading_progress();
let text = if progress == 0 {
t!("wallets.wallet_loading")
} else {
format!("{}: {}%", t!("wallets.wallet_loading"), wallet.loading_progress)
format!("{}: {}%", t!("wallets.wallet_loading"), progress)
};
ui.label(RichText::new(text).size(16.0).color(Colors::INACTIVE_TEXT));
});
}
return true;
} else {
println!("12345 info!!!");
}
}
false
}
}

View file

@ -15,6 +15,8 @@
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::wallets::types::WalletTab;
use crate::gui::views::wallets::wallet::types::WalletTabType;
use crate::gui::views::wallets::wallet::WalletContent;
use crate::wallet::Wallet;
/// Wallet info tab content.
#[derive(Default)]
@ -25,6 +27,11 @@ impl WalletTab for WalletInfo {
WalletTabType::Info
}
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
fn ui(&mut self,
ui: &mut egui::Ui,
frame: &mut eframe::Frame,
wallet: &Wallet,
cb: &dyn PlatformCallbacks) {
WalletContent::show_loading_ui(ui, frame, wallet);
}
}

View file

@ -14,6 +14,7 @@
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::wallets::wallet::types::{WalletTab, WalletTabType};
use crate::wallet::Wallet;
/// Receive funds tab content.
#[derive(Default)]
@ -24,6 +25,6 @@ impl WalletTab for WalletReceive {
WalletTabType::Receive
}
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
}
}

View file

@ -14,6 +14,7 @@
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::wallets::wallet::types::{WalletTab, WalletTabType};
use crate::wallet::Wallet;
/// Send funds tab content.
#[derive(Default)]
@ -24,6 +25,6 @@ impl WalletTab for WalletSend {
WalletTabType::Send
}
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
}
}

View file

@ -14,6 +14,7 @@
use crate::gui::platform::PlatformCallbacks;
use crate::gui::views::wallets::wallet::types::{WalletTab, WalletTabType};
use crate::wallet::Wallet;
/// Wallet settings tab content.
#[derive(Default)]
@ -24,6 +25,6 @@ impl WalletTab for WalletSettings {
WalletTabType::Settings
}
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks) {
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, wallet: &Wallet, cb: &dyn PlatformCallbacks) {
}
}

View file

@ -13,11 +13,16 @@
// limitations under the License.
use crate::gui::platform::PlatformCallbacks;
use crate::wallet::Wallet;
/// Wallet tab content interface.
pub trait WalletTab {
fn get_type(&self) -> WalletTabType;
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame, cb: &dyn PlatformCallbacks);
fn ui(&mut self,
ui: &mut egui::Ui,
frame: &mut eframe::Frame,
wallet: &Wallet,
cb: &dyn PlatformCallbacks);
}
/// Type of [`WalletTab`] content.

View file

@ -19,7 +19,6 @@ use egui::{Context, Stroke};
#[cfg(target_os = "android")]
use winit::platform::android::activity::AndroidApp;
pub use settings::{AppConfig, Settings};
use crate::gui::{Colors, PlatformApp};
use crate::gui::platform::PlatformCallbacks;
@ -29,7 +28,13 @@ i18n!("locales");
mod node;
mod wallet;
mod settings;
pub use settings::Settings;
mod config;
pub use config::AppConfig;
use crate::gui::views::View;
pub mod gui;
@ -123,8 +128,10 @@ pub fn setup_visuals(ctx: &Context) {
// 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
// Disable stroke around panels by default.
visuals.widgets.noninteractive.bg_stroke = Stroke::NONE;
// Setup stroke around inactive widgets.
visuals.widgets.inactive.bg_stroke = View::DEFAULT_STROKE;
// Setup visuals
ctx.set_visuals(visuals);
}

View file

@ -45,9 +45,7 @@ impl PeersConfig {
/// Save peers config to the file.
pub fn save(&self) {
let chain_type = AppConfig::chain_type();
let chain_name = chain_type.shortname();
let sub_dir = Some(chain_name.as_str());
let config_path = Settings::get_config_path(Self::FILE_NAME, sub_dir);
let config_path = Settings::get_config_path(Self::FILE_NAME, Some(chain_type.shortname()));
Settings::write_to_file(self, config_path);
}
@ -147,8 +145,7 @@ impl NodeConfig {
// Initialize peers config.
let peers_config = {
let chain_name = chain_type.shortname();
let sub_dir = Some(chain_name.as_str());
let sub_dir = Some(chain_type.shortname());
let path = Settings::get_config_path(PeersConfig::FILE_NAME, sub_dir);
let config = Settings::read_from_file::<PeersConfig>(path.clone());
if !path.exists() || config.is_err() {
@ -160,8 +157,7 @@ impl NodeConfig {
// Initialize node config.
let node_config = {
let chain_name = chain_type.shortname();
let sub_dir = Some(chain_name.as_str());
let sub_dir = Some(chain_type.shortname());
let path = Settings::get_config_path(SERVER_CONFIG_FILE_NAME, sub_dir);
let config = Settings::read_from_file::<ConfigMembers>(path.clone());
if !path.exists() || config.is_err() {
@ -176,9 +172,8 @@ impl NodeConfig {
/// Save default node config for specified [`ChainTypes`].
fn save_default_node_server_config(chain_type: &ChainTypes) -> ConfigMembers {
let chain_name = chain_type.shortname();
let sub_dir = Some(chain_name.as_str());
let path = Settings::get_config_path(SERVER_CONFIG_FILE_NAME, sub_dir);
let sub_dir = Some(chain_type.shortname());
let path = Settings::get_config_path(SERVER_CONFIG_FILE_NAME, sub_dir.clone());
let mut default_config = GlobalConfig::for_chain(chain_type);
default_config.update_paths(&Settings::get_base_path(sub_dir));
let config = default_config.members.unwrap();
@ -188,8 +183,7 @@ impl NodeConfig {
/// Save default peers config for specified [`ChainTypes`].
fn save_default_peers_config(chain_type: &ChainTypes) -> PeersConfig {
let chain_name = chain_type.shortname();
let sub_dir = Some(chain_name.as_str());
let sub_dir = Some(chain_type.shortname());
let path = Settings::get_config_path(PeersConfig::FILE_NAME, sub_dir);
let config = PeersConfig::default();
Settings::write_to_file(&config, path);
@ -198,8 +192,7 @@ impl NodeConfig {
/// Save node config to the file.
pub fn save(&self) {
let chain_name = self.node.server.chain_type.shortname();
let sub_dir = Some(chain_name.as_str());
let sub_dir = Some(self.node.server.chain_type.shortname());
let config_path = Settings::get_config_path(SERVER_CONFIG_FILE_NAME, sub_dir);
Settings::write_to_file(&self.node, config_path);
}
@ -241,8 +234,7 @@ impl NodeConfig {
/// Get path for secret file.
fn get_secret_path(chain_type: &ChainTypes, secret_file_name: &str) -> PathBuf {
let chain_name = chain_type.shortname();
let sub_dir = Some(chain_name.as_str());
let sub_dir = Some(chain_type.shortname());
let grin_path = Settings::get_base_path(sub_dir);
let mut api_secret_path = grin_path;
api_secret_path.push(secret_file_name);

View file

@ -18,144 +18,46 @@ use std::path::PathBuf;
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
use grin_config::ConfigError;
use grin_core::global::ChainTypes;
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use serde::de::DeserializeOwned;
use serde::Serialize;
use crate::config::AppConfig;
use crate::node::NodeConfig;
use crate::wallet::ConnectionsConfig;
lazy_static! {
/// Static settings state to be accessible globally.
static ref SETTINGS_STATE: Arc<Settings> = Arc::new(Settings::init());
}
/// Application configuration file name.
const APP_CONFIG_FILE_NAME: &'static str = "app.toml";
/// Common application settings.
#[derive(Serialize, Deserialize)]
pub struct AppConfig {
/// Run node server on startup.
pub auto_start_node: bool,
/// Chain type for node and wallets.
chain_type: ChainTypes,
/// Flag to initially show wallet list at dual panel wallets mode.
show_wallets_at_dual_panel: bool,
/// Flag to initially show all connections at network panel.
show_connections_network_panel: bool,
}
impl Default for AppConfig {
fn default() -> Self {
Self {
auto_start_node: false,
chain_type: ChainTypes::default(),
show_wallets_at_dual_panel: true,
show_connections_network_panel: false,
}
}
}
impl AppConfig {
/// Save app config to file.
pub fn save(&self) {
Settings::write_to_file(self, Settings::get_config_path(APP_CONFIG_FILE_NAME, None));
}
/// 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 {
// Save chain type at app config.
{
let mut w_app_config = Settings::app_config_to_update();
w_app_config.chain_type = *chain_type;
w_app_config.save();
}
// Load node config for selected chain type.
{
let mut w_node_config = Settings::node_config_to_update();
let node_config = NodeConfig::for_chain_type(chain_type);
w_node_config.node = node_config.node;
w_node_config.peers = node_config.peers;
}
}
}
/// Get current [`ChainTypes`] for node and wallets.
pub fn chain_type() -> ChainTypes {
let r_config = Settings::app_config_to_read();
r_config.chain_type
}
/// Check if integrated node is starting with application.
pub fn autostart_node() -> bool {
let r_config = Settings::app_config_to_read();
r_config.auto_start_node
}
/// Toggle integrated node autostart.
pub fn toggle_node_autostart() {
let autostart = Self::autostart_node();
let mut w_app_config = Settings::app_config_to_update();
w_app_config.auto_start_node = !autostart;
w_app_config.save();
}
/// Toggle flag to show wallet list at dual panel wallets mode.
pub fn show_wallets_at_dual_panel() -> bool {
let r_config = Settings::app_config_to_read();
r_config.show_wallets_at_dual_panel
}
/// Toggle flag to show wallet list at dual panel wallets mode.
pub fn toggle_show_wallets_at_dual_panel() {
let show = Self::show_wallets_at_dual_panel();
let mut w_app_config = Settings::app_config_to_update();
w_app_config.show_wallets_at_dual_panel = !show;
w_app_config.save();
}
/// Toggle flag to show all connections at network panel.
pub fn show_connections_network_panel() -> bool {
let r_config = Settings::app_config_to_read();
r_config.show_connections_network_panel
}
/// Toggle flag to show all connections at network panel.
pub fn toggle_show_connections_network_panel() {
let show = Self::show_connections_network_panel();
let mut w_app_config = Settings::app_config_to_update();
w_app_config.show_connections_network_panel = !show;
w_app_config.save();
}
}
/// Main application directory name.
const MAIN_DIR_NAME: &'static str = ".grim";
/// Provides access to application, integrated node and wallets configs.
/// Contains initialized configurations.
pub struct Settings {
/// Application config instance.
/// Application configuration.
app_config: Arc<RwLock<AppConfig>>,
/// Integrated node config instance.
/// Integrated node configuration.
node_config: Arc<RwLock<NodeConfig>>,
/// Wallet connections configuration.
conn_config: Arc<RwLock<ConnectionsConfig>>,
}
impl Settings {
/// Initialize settings with app and node configs.
fn init() -> Self {
let path = Settings::get_config_path(APP_CONFIG_FILE_NAME, None);
let app_config = Self::init_config::<AppConfig>(path);
let app_config_path = Settings::get_config_path(AppConfig::FILE_NAME, None);
let app_config = Self::init_config::<AppConfig>(app_config_path);
let chain_type = &app_config.chain_type;
Self {
node_config: Arc::new(RwLock::new(NodeConfig::for_chain_type(&app_config.chain_type))),
node_config: Arc::new(RwLock::new(NodeConfig::for_chain_type(chain_type))),
conn_config: Arc::new(RwLock::new(ConnectionsConfig::for_chain_type(chain_type))),
app_config: Arc::new(RwLock::new(app_config)),
}
}
/// Initialize config from provided file path or set [`Default`] if file not exists.
/// Initialize configuration from provided file path or set [`Default`] if file not exists.
pub fn init_config<T: Default + Serialize + DeserializeOwned>(path: PathBuf) -> T {
let parsed = Self::read_from_file::<T>(path.clone());
if !path.exists() || parsed.is_err() {
@ -167,29 +69,38 @@ impl Settings {
}
}
/// Get node config to read values.
/// Get node configuration to read values.
pub fn node_config_to_read() -> RwLockReadGuard<'static, NodeConfig> {
SETTINGS_STATE.node_config.read().unwrap()
}
/// Get node config to update values.
/// Get node configuration to update values.
pub fn node_config_to_update() -> RwLockWriteGuard<'static, NodeConfig> {
SETTINGS_STATE.node_config.write().unwrap()
}
/// Get app config to read values.
/// Get app configuration to read values.
pub fn app_config_to_read() -> RwLockReadGuard<'static, AppConfig> {
SETTINGS_STATE.app_config.read().unwrap()
}
/// Get app config to update values.
/// Get app configuration to update values.
pub fn app_config_to_update() -> RwLockWriteGuard<'static, AppConfig> {
SETTINGS_STATE.app_config.write().unwrap()
}
/// Get base directory path for config.
pub fn get_base_path(sub_dir: Option<&str>) -> PathBuf {
/// Get connections configuration to read values.
pub fn conn_config_to_read() -> RwLockReadGuard<'static, ConnectionsConfig> {
SETTINGS_STATE.conn_config.read().unwrap()
}
/// Get connections configuration to update values.
pub fn conn_config_to_update() -> RwLockWriteGuard<'static, ConnectionsConfig> {
SETTINGS_STATE.conn_config.write().unwrap()
}
/// Get base directory path for configuration.
pub fn get_base_path(sub_dir: Option<String>) -> PathBuf {
// Check if dir exists.
let mut path = match dirs::home_dir() {
Some(p) => p,
@ -206,14 +117,14 @@ impl Settings {
path
}
/// Get config file path from provided name and sub-directory if needed.
pub fn get_config_path(config_name: &str, sub_dir: Option<&str>) -> PathBuf {
/// Get configuration file path from provided name and sub-directory if needed.
pub fn get_config_path(config_name: &str, sub_dir: Option<String>) -> PathBuf {
let mut settings_path = Self::get_base_path(sub_dir);
settings_path.push(config_name);
settings_path
}
/// Read config from the file.
/// Read configuration from the file.
pub fn read_from_file<T: DeserializeOwned>(config_path: PathBuf) -> Result<T, ConfigError> {
let file_content = fs::read_to_string(config_path.clone())?;
let parsed = toml::from_str::<T>(file_content.as_str());
@ -228,7 +139,7 @@ impl Settings {
}
}
/// Write config to the file.
/// Write configuration to the file.
pub fn write_to_file<T: Serialize>(config: &T, path: PathBuf) {
let conf_out = toml::to_string(config).unwrap();
let mut file = File::create(path.to_str().unwrap()).unwrap();

View file

@ -19,33 +19,49 @@ use grin_core::global::ChainTypes;
use serde_derive::{Deserialize, Serialize};
use crate::{AppConfig, Settings};
use crate::wallet::types::ConnectionMethod;
/// Wallet configuration.
#[derive(Serialize, Deserialize, Clone)]
pub struct WalletConfig {
/// Chain type for current wallet.
pub(crate) chain_type: ChainTypes,
pub chain_type: ChainTypes,
/// Identifier for a wallet.
pub(crate) id: i64,
pub id: i64,
/// Human-readable wallet name for ui.
pub(crate) name: String,
/// External node connection URL.
pub(crate) external_node_url: Option<String>,
pub name: String,
/// External connection identifier.
pub ext_conn_id: Option<i64>,
/// Minimal amount of confirmations.
pub min_confirmations: u64
}
/// Wallet configuration file name.
const CONFIG_FILE_NAME: &'static str = "grim-wallet.toml";
/// Base wallets directory name.
pub const BASE_DIR_NAME: &'static str = "wallets";
/// Wallet configuration file name.
const CONFIG_FILE_NAME: &'static str = "grim-wallet.toml";
/// Minimal amount of confirmations default value.
const MIN_CONFIRMATIONS_DEFAULT: u64 = 10;
impl WalletConfig {
/// Create wallet config.
pub fn create(name: String, external_node_url: Option<String>) -> WalletConfig {
pub fn create(name: String, conn_method: &ConnectionMethod) -> WalletConfig {
// Setup configuration path.
let id = chrono::Utc::now().timestamp();
let chain_type = AppConfig::chain_type();
let config_path = Self::get_config_file_path(chain_type, id);
let config = WalletConfig { chain_type, id, name, external_node_url };
// Write configuration to the file.
let config = WalletConfig {
chain_type,
id,
name,
ext_conn_id: match conn_method {
ConnectionMethod::Integrated => None,
ConnectionMethod::External(id) => Some(*id)
},
min_confirmations: MIN_CONFIRMATIONS_DEFAULT,
};
Settings::write_to_file(&config, config_path);
config
}
@ -62,8 +78,7 @@ impl WalletConfig {
/// Get wallets base directory path for provided [`ChainTypes`].
pub fn get_base_path(chain_type: ChainTypes) -> PathBuf {
let chain_name = chain_type.shortname();
let sub_dir = Some(chain_name.as_str());
let sub_dir = Some(chain_type.shortname());
let mut wallets_path = Settings::get_base_path(sub_dir);
wallets_path.push(BASE_DIR_NAME);
// Create wallets base directory if it doesn't exist.
@ -98,10 +113,4 @@ impl WalletConfig {
let config_path = Self::get_config_file_path(self.chain_type, self.id);
Settings::write_to_file(self, config_path);
}
/// Set external node connection URL.
pub fn save_external_node_url(&mut self, url: Option<String>) {
self.external_node_url = url;
self.save();
}
}

View file

@ -12,66 +12,73 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::sync::{Arc, RwLock};
use lazy_static::lazy_static;
use grin_core::global::ChainTypes;
use serde_derive::{Deserialize, Serialize};
use crate::config::AppConfig;
use crate::Settings;
use crate::wallet::ExternalConnection;
lazy_static! {
/// Static connections state to be accessible globally.
static ref CONNECTIONS_STATE: Arc<RwLock<ConnectionsConfig>> = Arc::new(
RwLock::new(
Settings::init_config(Settings::get_config_path(CONFIG_FILE_NAME, None))
)
);
}
/// Wallet connections configuration.
#[derive(Serialize, Deserialize, Clone)]
pub struct ConnectionsConfig {
/// Network type for connections.
chain_type: ChainTypes,
/// URLs of external connections for wallets.
external: Vec<ExternalConnection>
}
impl Default for ConnectionsConfig {
fn default() -> Self {
Self {
external: vec![
ExternalConnection::default()
],
}
}
}
/// Wallet configuration file name.
const CONFIG_FILE_NAME: &'static str = "connections.toml";
impl ConnectionsConfig {
/// Save connections config to file.
pub fn save(&self) {
Settings::write_to_file(self, Settings::get_config_path(CONFIG_FILE_NAME, None));
/// Wallet connections configuration file name.
pub const FILE_NAME: &'static str = "connections.toml";
/// Initialize configuration for provided [`ChainTypes`].
pub fn for_chain_type(chain_type: &ChainTypes) -> Self {
let path = Settings::get_config_path(Self::FILE_NAME, Some(chain_type.shortname()));
let parsed = Settings::read_from_file::<ConnectionsConfig>(path.clone());
if !path.exists() || parsed.is_err() {
let default_config = ConnectionsConfig {
chain_type: *chain_type,
external: if chain_type == &ChainTypes::Mainnet {
vec![
ExternalConnection::default_main()
]
} else {
vec![]
},
};
Settings::write_to_file(&default_config, path);
default_config
} else {
parsed.unwrap()
}
}
/// Get external connections for the wallet.
pub fn external_connections() -> Vec<ExternalConnection> {
let r_config = CONNECTIONS_STATE.read().unwrap();
/// Save connections configuration to the file.
pub fn save(&self) {
let chain_type = AppConfig::chain_type();
let sub_dir = Some(chain_type.shortname());
Settings::write_to_file(self, Settings::get_config_path(Self::FILE_NAME, sub_dir));
}
/// Get [`ExternalConnection`] list.
pub fn ext_conn_list() -> Vec<ExternalConnection> {
let r_config = Settings::conn_config_to_read();
r_config.external.clone()
}
/// Save external connection for the wallet in app config.
pub fn add_external_connection(conn: ExternalConnection) {
/// Save [`ExternalConnection`] in configuration.
pub fn add_ext_conn(conn: ExternalConnection) {
// Do not update default connection.
if conn.url == ExternalConnection::DEFAULT_EXTERNAL_NODE_URL {
if conn.url == ExternalConnection::DEFAULT_MAIN_URL {
return;
}
let mut w_config = CONNECTIONS_STATE.write().unwrap();
let mut w_config = Settings::conn_config_to_update();
let mut exists = false;
for mut c in w_config.external.iter_mut() {
// Update connection if URL exists.
if c.url == conn.url {
// Update connection if config exists.
if c.id == conn.id {
c.url = conn.url.clone();
c.secret = conn.secret.clone();
exists = true;
break;
@ -84,39 +91,33 @@ impl ConnectionsConfig {
w_config.save();
}
/// Save external connection for the wallet in app config.
pub fn update_external_connection(conn: ExternalConnection, updated: ExternalConnection) {
// Do not update default connection.
if conn.url == ExternalConnection::DEFAULT_EXTERNAL_NODE_URL {
return;
}
let mut w_config = CONNECTIONS_STATE.write().unwrap();
for mut c in w_config.external.iter_mut() {
// Update connection if URL exists.
if c.url == conn.url {
c.url = updated.url.clone();
c.secret = updated.secret.clone();
break;
}
}
w_config.save();
}
/// Get external node connection secret from provided URL.
pub fn get_external_connection_secret(url: String) -> Option<String> {
let r_config = CONNECTIONS_STATE.read().unwrap();
/// Get [`ExternalConnection`] by provided identifier.
pub fn ext_conn(id: i64) -> Option<ExternalConnection> {
let r_config = Settings::conn_config_to_read();
for c in &r_config.external {
if c.url == url {
return c.secret.clone();
if c.id == id {
return Some(c.clone());
}
}
None
}
/// Remove external node connection.
pub fn remove_external_connection(conn: &ExternalConnection) {
let mut w_config = CONNECTIONS_STATE.write().unwrap();
let index = w_config.external.iter().position(|c| c.url == conn.url);
/// Set [`ExternalConnection`] availability flag.
pub fn update_ext_conn_availability(id: i64, available: bool) {
let mut w_config = Settings::conn_config_to_update();
for mut c in w_config.external.iter_mut() {
if c.id == id {
c.available = Some(available);
w_config.save();
break;
}
}
}
/// Remove external node connection by provided identifier.
pub fn remove_ext_conn(id: i64) {
let mut w_config = Settings::conn_config_to_update();
let index = w_config.external.iter().position(|c| c.id == id);
if let Some(i) = index {
w_config.external.remove(i);
w_config.save();

View file

@ -12,28 +12,85 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::time::Duration;
use serde_derive::{Deserialize, Serialize};
/// External node connection for the wallet.
use crate::AppConfig;
use crate::wallet::ConnectionsConfig;
/// External connection for the wallet.
#[derive(Serialize, Deserialize, Clone)]
pub struct ExternalConnection {
/// Connection identifier.
pub id: i64,
/// Node URL.
pub url: String,
/// Optional API secret key.
pub secret: Option<String>
pub secret: Option<String>,
/// Flag to check if server is available.
#[serde(skip_serializing)]
pub available: Option<bool>
}
impl ExternalConnection {
/// Default external node URL.
pub const DEFAULT_EXTERNAL_NODE_URL: &'static str = "https://grinnnode.live:3413";
/// Default external node URL for main network.
pub const DEFAULT_MAIN_URL: &'static str = "https://grinnode.live:3413";
/// External connections availability check delay.
const AVAILABILITY_CHECK_DELAY: Duration = Duration::from_millis(10 * 1000);
/// Create default external connection.
pub fn default_main() -> Self {
Self { id: 1, url: Self::DEFAULT_MAIN_URL.to_string(), secret: None, available: None }
}
/// Create new external connection.
pub fn new(url: String, secret: Option<String>) -> Self {
Self { url, secret }
let id = chrono::Utc::now().timestamp();
Self { id, url, secret, available: None }
}
}
impl Default for ExternalConnection {
fn default() -> Self {
Self { url: Self::DEFAULT_EXTERNAL_NODE_URL.to_string(), secret: None }
/// Check connection availability.
fn check_conn_availability(&self) {
// Check every connection at separate thread.
let conn = self.clone();
std::thread::spawn(move || {
let url = url::Url::parse(conn.url.as_str()).unwrap();
let addr = url.socket_addrs(|| None).unwrap();
match std::net::TcpStream::connect_timeout(&addr[0], Self::AVAILABILITY_CHECK_DELAY) {
Ok(_) => {
ConnectionsConfig::update_ext_conn_availability(conn.id, true);
}
Err(_) => {
ConnectionsConfig::update_ext_conn_availability(conn.id, false);
}
}
});
}
/// Start external connections availability check at another thread.
pub fn start_ext_conn_availability_check() {
std::thread::spawn(move || {
let chain_type = AppConfig::chain_type();
loop {
// Stop checking if connections are not showing or network type was changed.
if !AppConfig::show_connections_network_panel()
|| chain_type != AppConfig::chain_type() {
break;
}
// Check external connections URLs availability.
let conn_list = ConnectionsConfig::ext_conn_list();
for conn in conn_list {
// Check every connection at separate thread.
conn.check_conn_availability();
}
// Pause checking for delay value.
std::thread::sleep(Self::AVAILABILITY_CHECK_DELAY);
}
});
}
}

View file

@ -77,3 +77,12 @@ pub type WalletInstance = Arc<
>,
>,
>;
/// Wallet node connection method type.
#[derive(PartialEq)]
pub enum ConnectionMethod {
/// Integrated node.
Integrated,
/// External node, contains connection identifier.
External(i64)
}

View file

@ -12,30 +12,25 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::{cmp, thread};
use std::thread;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, mpsc, RwLock};
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
use std::time::Duration;
use grin_core::global;
use grin_core::global::ChainTypes;
use grin_keychain::{ExtKeychain, Identifier, Keychain};
use grin_keychain::{ExtKeychain, Keychain};
use grin_util::types::ZeroingString;
use grin_wallet_api::{Foreign, ForeignCheckMiddlewareFn, Owner};
use grin_wallet_impls::{DefaultLCProvider, DefaultWalletImpl, HTTPNodeClient};
use grin_wallet_libwallet::{Error, NodeClient, NodeVersionInfo, OutputStatus, scan, Slate, slate_versions, SlatepackArmor, Slatepacker, SlatepackerArgs, TxLogEntry, wallet_lock, WalletBackend, WalletInfo, WalletInst, WalletLCProvider};
use log::debug;
use grin_wallet_libwallet::{Error, NodeClient, StatusMessage, WalletBackend, WalletInfo, WalletInst, WalletLCProvider};
use grin_wallet_libwallet::api_impl::owner::retrieve_summary_info;
use parking_lot::Mutex;
use uuid::Uuid;
use crate::AppConfig;
use crate::node::NodeConfig;
use crate::wallet::{ConnectionsConfig, WalletConfig};
use crate::wallet::selection::lock_tx_context;
use crate::wallet::tx::{add_inputs_to_slate, new_tx_slate};
use crate::wallet::types::WalletInstance;
use crate::wallet::updater::{cancel_tx, refresh_output_state, retrieve_txs};
use crate::wallet::{ConnectionsConfig, ExternalConnection, WalletConfig};
use crate::wallet::types::{ConnectionMethod, WalletInstance};
/// [`Wallet`] list wrapper.
pub struct Wallets {
@ -55,12 +50,6 @@ impl Default for Wallets {
}
impl Wallets {
/// Delay in seconds to update outputs after wallet opening.
pub const OUTPUTS_UPDATE_DELAY: Duration = Duration::from_millis(30 * 1000);
/// Delay in seconds to update wallet info.
pub const INFO_UPDATE_DELAY: Duration = Duration::from_millis(10 * 1000);
/// Initialize wallets from base directory for provided [`ChainType`].
fn init(chain_type: ChainTypes) -> Vec<Wallet> {
let mut wallets = Vec::new();
@ -109,87 +98,15 @@ impl Wallets {
false
}
/// Open selected wallet.
pub fn open_selected(&mut self, password: String) -> Result<(), Error> {
/// Open and load selected wallet.
pub fn launch_selected(&mut self, password: String) -> Result<(), Error> {
for w in self.list.iter_mut() {
if Some(w.config.id) == self.selected_id {
return w.open(password);
return w.open(password.clone());
}
}
Err(Error::GenericError("Wallet is not selected".to_string()))
}
/// Load the wallet by scanning available outputs at separate thread.
pub fn load(w: &mut Wallet) {
if !w.is_open() {
return;
}
let mut wallet = w.clone();
thread::spawn(move || {
// Get pmmr range output indexes.
match wallet.pmmr_range() {
Ok((mut lowest_index, highest_index)) => {
println!("pmmr_range: {} {}", lowest_index, highest_index);
let mut from_index = lowest_index;
loop {
// Stop loop if wallet is not open.
if !wallet.is_open() {
break;
}
// Scan outputs for last retrieved index.
println!("scan_outputs from {} to {}", from_index, highest_index);
match wallet.scan_outputs(from_index, highest_index) {
Ok(last_index) => {
println!("retrieved index: {}", last_index);
if lowest_index == 0 {
lowest_index = last_index;
}
// Finish scanning or start new scan from last retrieved index.
if last_index == highest_index {
println!("scan finished");
wallet.loading_progress = 100;
wallet.is_loaded.store(true, Ordering::Relaxed);
} else {
println!("continue scan");
from_index = last_index;
}
// Update loading progress.
let range = highest_index - lowest_index;
let progress = last_index - lowest_index;
wallet.loading_progress = cmp::min(
(progress / range) as u8 * 100,
99
);
println!("progress: {}", wallet.loading_progress);
}
Err(e) => {
if !wallet.is_loaded() {
wallet.loading_error = Some(e);
break;
}
}
}
// Stop loop if wallet is not open.
if !wallet.is_open() {
break;
}
// Scan outputs at next cycle again.
println!("finished scanning, waiting");
thread::sleep(Self::OUTPUTS_UPDATE_DELAY);
}
}
Err(e) => {
wallet.loading_progress = 0;
wallet.loading_error = Some(e);
}
}
});
}
}
/// Contains wallet instance and config.
@ -199,29 +116,33 @@ pub struct Wallet {
instance: WalletInstance,
/// Wallet configuration.
pub(crate) config: WalletConfig,
pub config: WalletConfig,
/// Flag to check if wallet is open.
is_open: Arc<AtomicBool>,
/// Flag to check if wallet is loaded and ready to use.
is_loaded: Arc<AtomicBool>,
/// Error on wallet loading.
pub loading_error: Option<Error>,
/// Loading progress in percents
pub loading_progress: u8,
pub loading_progress: Arc<AtomicU8>,
/// Wallet balance information.
info: Arc<RwLock<Option<WalletInfo>>>
}
impl Wallet {
/// Delay in seconds to update wallet info.
pub const INFO_UPDATE_DELAY: Duration = Duration::from_millis(20 * 1000);
/// Create wallet from provided instance and config.
fn new(instance: WalletInstance, config: WalletConfig) -> Self {
Self {
instance,
config,
is_loaded: Arc::new(AtomicBool::new(false)),
is_open: Arc::new(AtomicBool::new(false)),
is_open: Arc::from(AtomicBool::new(false)),
loading_error: None,
loading_progress: 0,
loading_progress: Arc::new(AtomicU8::new(0)),
info: Arc::new(RwLock::new(None)),
}
}
@ -230,9 +151,9 @@ impl Wallet {
name: String,
password: String,
mnemonic: String,
external_node_url: Option<String>
conn_method: &ConnectionMethod
) -> Result<Wallet, Error> {
let config = WalletConfig::create(name, external_node_url);
let config = WalletConfig::create(name, conn_method);
let instance = Self::create_wallet_instance(config.clone())?;
let w = Wallet::new(instance, config);
{
@ -278,8 +199,12 @@ impl Wallet {
}
// Setup node client.
let (node_api_url, node_secret) = if let Some(url) = &config.external_node_url {
(url.to_owned(), ConnectionsConfig::get_external_connection_secret(url.to_owned()))
let (node_api_url, node_secret) = if let Some(id) = config.ext_conn_id {
if let Some(conn) = ConnectionsConfig::ext_conn(id) {
(conn.url, conn.secret)
} else {
(ExternalConnection::DEFAULT_MAIN_URL.to_string(), None)
}
} else {
let api_url = format!("http://{}", NodeConfig::get_api_address());
let api_secret = NodeConfig::get_foreign_api_secret();
@ -314,16 +239,85 @@ impl Wallet {
Ok(Arc::new(Mutex::new(wallet)))
}
/// Open wallet instance.
/// Open wallet and start info updating at separate thread.
pub fn open(&self, password: String) -> Result<(), Error> {
let mut wallet_lock = self.instance.lock();
let lc = wallet_lock.lc_provider()?;
lc.close_wallet(None)?;
lc.open_wallet(None, ZeroingString::from(password), false, false)?;
let mut wallet = self.clone();
match lc.open_wallet(None, ZeroingString::from(password), false, false) {
Ok(result) => {
self.is_open.store(true, Ordering::Relaxed);
let keychain_mask = result.clone();
// Launch loop at separate thread to update wallet info.
thread::spawn(move || loop {
// Stop updating if wallet was closed.
if !wallet.is_open() {
break;
}
let (tx, rx) = mpsc::channel::<StatusMessage>();
// Update progress at separate thread.
let wallet_scan = wallet.clone();
thread::spawn(move || {
while let Ok(m) = rx.recv() {
println!("m: {}", serde_json::to_string::<StatusMessage>(&m.clone()).unwrap());
match m {
StatusMessage::UpdatingOutputs(_) => {}
StatusMessage::UpdatingTransactions(_) => {}
StatusMessage::FullScanWarn(_) => {}
StatusMessage::Scanning(_, progress) => {
wallet_scan
.loading_progress
.store(progress, Ordering::Relaxed);
}
StatusMessage::ScanningComplete(_) => {
wallet_scan
.loading_progress
.store(100, Ordering::Relaxed);
}
StatusMessage::UpdateWarning(_) => {}
}
}
});
// Retrieve wallet info.
match retrieve_summary_info(
wallet.instance.clone(),
keychain_mask.as_ref(),
&Some(tx),
true,
wallet.config.min_confirmations
) {
Ok(info) => {
let mut w_info = wallet.info.write().unwrap();
*w_info = Some(info.1);
}
Err(e) => {
println!("Error!: {}", e);
wallet.loading_error = Some(e);
}
}
// Repeat after default delay or after 1 second if update was not complete.
let delay = if wallet.loading_progress() == 100 {
Self::INFO_UPDATE_DELAY
} else {
Duration::from_millis(1000)
};
thread::sleep(delay);
});
}
Err(e) => return Err(e)
}
Ok(())
}
pub fn loading_progress(&self) -> u8 {
self.loading_progress.load(Ordering::Relaxed)
}
/// Check if wallet is open.
pub fn is_open(&self) -> bool {
self.is_open.load(Ordering::Relaxed)
@ -340,377 +334,9 @@ impl Wallet {
Ok(())
}
/// Check if wallet is loaded and ready to use.
pub fn is_loaded(&self) -> bool {
self.is_loaded.load(Ordering::Relaxed)
}
/// Scan wallet outputs to check/repair the wallet.
fn scan_outputs(
&self,
last_retrieved_index: u64,
highest_index: u64
) -> Result<u64, Error> {
let wallet = self.instance.clone();
let info = scan(
wallet.clone(),
None,
false,
last_retrieved_index,
highest_index,
&None,
)?;
let result = info.last_pmmr_index;
let parent_key_id = {
wallet_lock!(wallet.clone(), w);
w.parent_key_id().clone()
};
{
wallet_lock!(wallet, w);
let mut batch = w.batch(None)?;
batch.save_last_confirmed_height(&parent_key_id, info.height)?;
batch.commit()?;
};
Ok(result)
}
/// Get pmmr indices representing the outputs for the wallet.
fn pmmr_range(&self) -> Result<(u64, u64), Error> {
wallet_lock!(self.instance.clone(), w);
let pmmr_range = w.w2n_client().height_range_to_pmmr_indices(0, None)?;
Ok(pmmr_range)
}
/// Create transaction.
pub fn tx_create(
&self,
amount: u64,
minimum_confirmations: u64,
selection_strategy_is_use_all: bool,
) -> Result<(Vec<TxLogEntry>, String), Error> {
let parent_key_id = {
wallet_lock!(self.instance, w);
w.parent_key_id().clone()
};
let slate = {
wallet_lock!(self.instance, w);
let mut slate = new_tx_slate(&mut **w, amount, false, 2, false, None)?;
let height = w.w2n_client().get_chain_tip()?.0;
let context = add_inputs_to_slate(
&mut **w,
None,
&mut slate,
height,
minimum_confirmations,
500,
1,
selection_strategy_is_use_all,
&parent_key_id,
true,
false,
false,
)?;
{
let mut batch = w.batch(None)?;
batch.save_private_context(slate.id.as_bytes(), &context)?;
batch.commit()?;
}
lock_tx_context(&mut **w, None, &slate, height, &context, None)?;
slate.compact()?;
slate
};
let packer = Slatepacker::new(SlatepackerArgs {
sender: None, // sender
recipients: vec![],
dec_key: None,
});
let slatepack = packer.create_slatepack(&slate)?;
let api = Owner::new(self.instance.clone(), None);
let txs = api.retrieve_txs(None, false, None, Some(slate.id), None)?;
let result = (
txs.1,
SlatepackArmor::encode(&slatepack)?,
);
Ok(result)
}
/// Callback to check slate compatibility at current node.
fn check_middleware(
name: ForeignCheckMiddlewareFn,
node_version_info: Option<NodeVersionInfo>,
slate: Option<&Slate>,
) -> Result<(), Error> {
match name {
ForeignCheckMiddlewareFn::BuildCoinbase => Ok(()),
_ => {
let mut bhv = 3;
if let Some(n) = node_version_info {
bhv = n.block_header_version;
}
if let Some(s) = slate {
if bhv > 4
&& s.version_info.block_header_version
< slate_versions::GRIN_BLOCK_HEADER_VERSION
{
Err(Error::Compatibility(
"Incoming Slate is not compatible with this wallet. \
Please upgrade the node or use a different one."
.into(),
))?;
}
}
Ok(())
}
}
}
/// Receive transaction.
pub fn tx_receive(
&self,
account: &str,
slate_armored: &str
) -> Result<(Vec<TxLogEntry>, String), Error> {
let foreign_api =
Foreign::new(self.instance.clone(), None, Some(Self::check_middleware), false);
let owner_api = Owner::new(self.instance.clone(), None);
let mut slate =
owner_api.slate_from_slatepack_message(None, slate_armored.to_owned(), vec![0])?;
let slatepack =
owner_api.decode_slatepack_message(None, slate_armored.to_owned(), vec![0])?;
let _ret_address = slatepack.sender;
slate = foreign_api.receive_tx(&slate, Some(&account), None)?;
let txs = owner_api.retrieve_txs(None, false, None, Some(slate.id), None)?;
let packer = Slatepacker::new(SlatepackerArgs {
sender: None, // sender
recipients: vec![],
dec_key: None,
});
let slatepack = packer.create_slatepack(&slate)?;
let result = (
txs.1,
SlatepackArmor::encode(&slatepack)?,
);
Ok(result)
}
/// Cancel transaction.
pub fn tx_cancel(&self, id: u32) -> Result<String, Error> {
wallet_lock!(self.instance, w);
let parent_key_id = w.parent_key_id();
cancel_tx(&mut **w, None, &parent_key_id, Some(id), None)?;
Ok("".to_owned())
}
/// Get transaction info.
pub fn get_tx(&self, tx_slate_id: &str) -> Result<(bool, Vec<TxLogEntry>), Error> {
let api = Owner::new(self.instance.clone(), None);
let uuid = Uuid::parse_str(tx_slate_id).unwrap();
let txs = api.retrieve_txs(None, true, None, Some(uuid), None)?;
Ok(txs)
}
/// Finalize transaction.
pub fn tx_finalize(&self, slate_armored: &str) -> Result<(bool, Vec<TxLogEntry>), Error> {
let owner_api = Owner::new(self.instance.clone(), None);
let mut slate =
owner_api.slate_from_slatepack_message(None, slate_armored.to_owned(), vec![0])?;
let slatepack =
owner_api.decode_slatepack_message(None, slate_armored.to_owned(), vec![0])?;
let _ret_address = slatepack.sender;
slate = owner_api.finalize_tx(None, &slate)?;
let txs = owner_api.retrieve_txs(None, false, None, Some(slate.id), None)?;
Ok(txs)
}
/// Post transaction to node for broadcasting.
pub fn tx_post(&self, tx_slate_id: &str) -> Result<(), Error> {
let api = Owner::new(self.instance.clone(), None);
let tx_uuid = Uuid::parse_str(tx_slate_id).unwrap();
let (_, txs) = api.retrieve_txs(None, true, None, Some(tx_uuid.clone()), None)?;
if txs[0].confirmed {
return Err(Error::GenericError(format!(
"Transaction with id {} is already confirmed. Not posting.",
tx_slate_id
)));
}
let stored_tx = api.get_stored_tx(None, None, Some(&tx_uuid))?;
match stored_tx {
Some(stored_tx) => {
api.post_tx(None, &stored_tx, true)?;
Ok(())
}
None => Err(Error::GenericError(format!(
"Transaction with id {} does not have transaction data. Not posting.",
tx_slate_id
))),
}
}
/// Get transactions and base wallet info.
pub fn get_txs_info(
&self,
minimum_confirmations: u64
) -> Result<(bool, Vec<TxLogEntry>, WalletInfo), Error> {
let refreshed = Self::update_state(self.instance.clone()).unwrap_or(false);
let wallet_info = {
wallet_lock!(self.instance, w);
let parent_key_id = w.parent_key_id();
Self::get_info(&mut **w, &parent_key_id, minimum_confirmations)?
};
let api = Owner::new(self.instance.clone(), None);
let txs = api.retrieve_txs(None, false, None, None, None)?;
Ok((refreshed, txs.1, wallet_info))
}
/// Update wallet instance state.
fn update_state<'a, L, C, K>(
wallet_inst: Arc<Mutex<Box<dyn WalletInst<'a, L, C, K>>>>,
) -> Result<bool, Error>
where
L: WalletLCProvider<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
{
let parent_key_id = {
wallet_lock!(wallet_inst, w);
w.parent_key_id().clone()
};
let mut client = {
wallet_lock!(wallet_inst, w);
w.w2n_client().clone()
};
let tip = client.get_chain_tip()?;
// Step1: Update outputs and transactions purely based on UTXO state.
{
wallet_lock!(wallet_inst, w);
if !match refresh_output_state(&mut **w, None, tip.0, &parent_key_id, true) {
Ok(_) => true,
Err(_) => false,
} {
// We are unable to contact the node.
return Ok(false);
}
}
let mut txs = {
wallet_lock!(wallet_inst, w);
retrieve_txs(&mut **w, None, None, None, Some(&parent_key_id), true)?
};
for tx in txs.iter_mut() {
// Step 2: Cancel any transactions with an expired TTL.
if let Some(e) = tx.ttl_cutoff_height {
if tip.0 >= e {
wallet_lock!(wallet_inst, w);
let parent_key_id = w.parent_key_id();
cancel_tx(&mut **w, None, &parent_key_id, Some(tx.id), None)?;
continue;
}
}
// Step 3: Update outstanding transactions with no change outputs by kernel.
if tx.confirmed {
continue;
}
if tx.amount_debited != 0 && tx.amount_credited != 0 {
continue;
}
if let Some(e) = tx.kernel_excess {
let res = client.get_kernel(&e, tx.kernel_lookup_min_height, Some(tip.0));
let kernel = match res {
Ok(k) => k,
Err(_) => return Ok(false),
};
if let Some(k) = kernel {
debug!("Kernel Retrieved: {:?}", k);
wallet_lock!(wallet_inst, w);
let mut batch = w.batch(None)?;
tx.confirmed = true;
tx.update_confirmation_ts();
batch.save_tx_log_entry(tx.clone(), &parent_key_id)?;
batch.commit()?;
}
}
}
return Ok(true);
}
/// Get summary info about the wallet.
fn get_info<'a, T: ?Sized, C, K>(
wallet: &mut T,
parent_key_id: &Identifier,
minimum_confirmations: u64,
) -> Result<WalletInfo, Error>
where
T: WalletBackend<'a, C, K>,
C: NodeClient + 'a,
K: Keychain + 'a,
{
let current_height = wallet.last_confirmed_height()?;
let outputs = wallet
.iter()
.filter(|out| out.root_key_id == *parent_key_id);
let mut unspent_total = 0;
let mut immature_total = 0;
let mut awaiting_finalization_total = 0;
let mut unconfirmed_total = 0;
let mut locked_total = 0;
let mut reverted_total = 0;
for out in outputs {
match out.status {
OutputStatus::Unspent => {
if out.is_coinbase && out.lock_height > current_height {
immature_total += out.value;
} else if out.num_confirmations(current_height) < minimum_confirmations {
// Treat anything less than minimum confirmations as "unconfirmed".
unconfirmed_total += out.value;
} else {
unspent_total += out.value;
}
}
OutputStatus::Unconfirmed => {
// We ignore unconfirmed coinbase outputs completely.
if !out.is_coinbase {
if minimum_confirmations == 0 {
unconfirmed_total += out.value;
} else {
awaiting_finalization_total += out.value;
}
}
}
OutputStatus::Locked => {
locked_total += out.value;
}
OutputStatus::Reverted => reverted_total += out.value,
OutputStatus::Spent => {}
}
}
Ok(WalletInfo {
last_confirmed_height: current_height,
minimum_confirmations,
total: unspent_total + unconfirmed_total + immature_total,
amount_awaiting_finalization: awaiting_finalization_total,
amount_awaiting_confirmation: unconfirmed_total,
amount_immature: immature_total,
amount_locked: locked_total,
amount_currently_spendable: unspent_total,
amount_reverted: reverted_total,
})
/// Get wallet info.
pub fn get_info(&self) -> Option<WalletInfo> {
let r_info = self.info.read().unwrap();
r_info.clone()
}
}