From 610c96a8bd5d3343af1d03f348cc7e1e9e5ebb17 Mon Sep 17 00:00:00 2001 From: ardocrat Date: Tue, 4 Jun 2024 11:02:18 +0300 Subject: [PATCH] wallet: tx confirmation height, custom kv storage --- Cargo.lock | 88 +++++- Cargo.toml | 5 +- src/gui/views/wallets/content.rs | 10 +- src/gui/views/wallets/wallet/content.rs | 7 +- src/gui/views/wallets/wallet/messages.rs | 2 +- src/gui/views/wallets/wallet/txs.rs | 56 ++-- src/wallet/config.rs | 30 +- src/wallet/mod.rs | 4 +- src/wallet/store.rs | 63 ++++ src/wallet/types.rs | 6 +- src/wallet/wallet.rs | 373 ++++++++++++----------- 11 files changed, 410 insertions(+), 234 deletions(-) create mode 100644 src/wallet/store.rs diff --git a/Cargo.lock b/Cargo.lock index b657ae7..aeb68ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1051,6 +1051,15 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9ff0bbfd639f15c74af777d81383cf53efb7c93613f6cab67c6c11e05bbf8b" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bindgen" version = "0.65.1" @@ -1127,6 +1136,9 @@ name = "bitflags" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +dependencies = [ + "serde", +] [[package]] name = "bitstream-io" @@ -3779,6 +3791,7 @@ dependencies = [ "qrcodegen", "rand 0.8.5", "rfd", + "rkv", "rqrr", "rust-i18n", "serde", @@ -4082,7 +4095,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "uuid", + "uuid 0.8.2", ] [[package]] @@ -4133,7 +4146,7 @@ dependencies = [ "thiserror", "tokio 0.2.25", "url", - "uuid", + "uuid 0.8.2", ] [[package]] @@ -4172,7 +4185,7 @@ dependencies = [ "timer", "tokio 0.2.25", "url", - "uuid", + "uuid 0.8.2", "x25519-dalek 0.6.0", ] @@ -4210,7 +4223,7 @@ dependencies = [ "strum 0.18.0", "strum_macros 0.18.0", "thiserror", - "uuid", + "uuid 0.8.2", "x25519-dalek 0.6.0", ] @@ -4755,6 +4768,12 @@ dependencies = [ "objc2 0.4.1", ] +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + [[package]] name = "ident_case" version = "1.0.1" @@ -5262,6 +5281,29 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" +[[package]] +name = "lmdb-rkv" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447a296f7aca299cfbb50f4e4f3d49451549af655fb7215d7f8c0c3d64bad42b" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "libc", + "lmdb-rkv-sys", +] + +[[package]] +name = "lmdb-rkv-sys" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61b9ce6b3be08acefa3003c57b7565377432a89ec24476bbe72e11d101f852fe" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "lmdb-zero" version = "0.4.4" @@ -6415,6 +6457,15 @@ dependencies = [ "num-traits 0.2.19", ] +[[package]] +name = "ordered-float" +version = "3.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1e1c390732d15f1d48471625cd92d154e66db2c56645e29a9cd26f4699f72dc" +dependencies = [ + "num-traits 0.2.19", +] + [[package]] name = "ordered-multimap" version = "0.4.3" @@ -7591,6 +7642,29 @@ dependencies = [ "opaque-debug 0.3.1", ] +[[package]] +name = "rkv" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6d906922d99c677624d2042a93f89b2b7df0f6411032237d5d99a602c2487c" +dependencies = [ + "arrayref", + "bincode", + "bitflags 2.5.0", + "byteorder", + "id-arena", + "lazy_static", + "lmdb-rkv", + "log", + "ordered-float 3.9.2", + "paste", + "serde", + "serde_derive", + "thiserror", + "url", + "uuid 1.8.0", +] + [[package]] name = "roxmltree" version = "0.19.0" @@ -10461,6 +10535,12 @@ dependencies = [ "serde", ] +[[package]] +name = "uuid" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" + [[package]] name = "v4l" version = "0.14.0" diff --git a/Cargo.toml b/Cargo.toml index 68581ae..b5a8215 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,6 +62,7 @@ qrcodegen = "1.8.0" qrcode = "0.14.0" ur = "0.4.1" gif = "0.13.1" +rkv = { version = "0.19.0", features = ["lmdb"] } ## tor arti-client = { version = "0.18.0", features = ["pt-client", "static", "onion-service-service", "onion-service-client"] } @@ -99,7 +100,7 @@ tls-api-openssl = "0.9.0" [target.'cfg(not(target_os = "android"))'.dependencies] env_logger = "0.11.3" winit = { version = "0.29.15" } -eframe = { version = "0.27.2", features = ["wgpu"] } +eframe = { version = "0.27.2", features = ["wgpu", "glow"] } arboard = "3.2.0" rfd = "0.14.1" dark-light = "1.1.1" @@ -112,4 +113,4 @@ jni = "0.21.1" android-activity = { version = "0.6.0", features = ["game-activity"] } wgpu = "0.19.1" winit = { version = "0.29.15", features = ["android-game-activity"] } -eframe = { version = "0.27.2", features = ["wgpu", "glow", "android-game-activity"] } \ No newline at end of file +eframe = { version = "0.27.2", features = ["wgpu", "android-game-activity"] } \ No newline at end of file diff --git a/src/gui/views/wallets/content.rs b/src/gui/views/wallets/content.rs index b868ac9..b4bc221 100644 --- a/src/gui/views/wallets/content.rs +++ b/src/gui/views/wallets/content.rs @@ -466,15 +466,7 @@ impl WalletsContent { info_progress) } } else { - let tx_progress = wallet.txs_sync_progress(); - if tx_progress == 0 { - format!("{} {}", SPINNER, t!("wallets.tx_loading")) - } else { - format!("{} {}: {}%", - SPINNER, - t!("wallets.tx_loading"), - tx_progress) - } + format!("{} {}", SPINNER, t!("wallets.tx_loading")) } } else { format!("{} {}", FOLDER_OPEN, t!("wallets.unlocked")) diff --git a/src/gui/views/wallets/wallet/content.rs b/src/gui/views/wallets/wallet/content.rs index 3b567a2..c0a820a 100644 --- a/src/gui/views/wallets/wallet/content.rs +++ b/src/gui/views/wallets/wallet/content.rs @@ -622,12 +622,7 @@ impl WalletContent { format!("{}: {}%", t!("wallets.wallet_loading"), info_progress) } } else { - let tx_progress = wallet.txs_sync_progress(); - if tx_progress == 0 { - t!("wallets.tx_loading") - } else { - format!("{}: {}%", t!("wallets.tx_loading"), tx_progress) - } + t!("wallets.tx_loading") } }; ui.label(RichText::new(text).size(16.0).color(Colors::inactive_text())); diff --git a/src/gui/views/wallets/wallet/messages.rs b/src/gui/views/wallets/wallet/messages.rs index 3567aef..449d1fc 100644 --- a/src/gui/views/wallets/wallet/messages.rs +++ b/src/gui/views/wallets/wallet/messages.rs @@ -1076,7 +1076,7 @@ impl WalletMessages { if let Ok(mut slate) = wallet.parse_slatepack(&self.message_edit) { // Try to setup empty amount from transaction by id. if slate.amount == 0 { - let _ = wallet.get_data().unwrap().txs.clone().iter().map(|tx| { + let _ = wallet.get_data().unwrap().txs.as_ref().unwrap().iter().map(|tx| { if tx.data.tx_slate_id == Some(slate.id) { if slate.amount == 0 { slate.amount = tx.amount; diff --git a/src/gui/views/wallets/wallet/txs.rs b/src/gui/views/wallets/wallet/txs.rs index 86f5367..1382004 100644 --- a/src/gui/views/wallets/wallet/txs.rs +++ b/src/gui/views/wallets/wallet/txs.rs @@ -149,7 +149,6 @@ impl WalletTransactions { // Show transactions info. View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| { - // Show non-zero awaiting confirmation amount. if amount_conf != 0 { let awaiting_conf = amount_to_hr_string(amount_conf, true); @@ -163,7 +162,6 @@ impl WalletTransactions { t!("wallets.await_conf_amount"), rounding); } - // Show non-zero awaiting finalization amount. if amount_fin != 0 { let awaiting_conf = amount_to_hr_string(amount_fin, true); @@ -177,7 +175,6 @@ impl WalletTransactions { t!("wallets.await_fin_amount"), rounding); } - // Show non-zero locked amount. if amount_locked != 0 { let awaiting_conf = amount_to_hr_string(amount_locked, true); @@ -187,24 +184,35 @@ impl WalletTransactions { [false, false, true, true]); } - // Show message when wallet txs are empty. - if data.txs.is_empty() { - View::center_content(ui, 96.0, |ui| { - let empty_text = t!( + // Show message when txs are empty. + if let Some(txs) = data.txs.as_ref() { + if txs.is_empty() { + View::center_content(ui, 96.0, |ui| { + let empty_text = t!( "wallets.txs_empty", "message" => CHAT_CIRCLE_TEXT, "transport" => BRIDGE, "settings" => GEAR_FINE ); - ui.label(RichText::new(empty_text).size(16.0).color(Colors::inactive_text())); - }); - return; + ui.label(RichText::new(empty_text).size(16.0).color(Colors::inactive_text())); + }); + return; + } } }); + // Show loader when txs are not loaded. + if data.txs.is_none() { + ui.centered_and_justified(|ui| { + View::big_loading_spinner(ui); + }); + return; + } + ui.add_space(4.0); // Show list of transactions. + let txs = data.txs.as_ref().unwrap(); let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis(); let refresh = self.manual_sync.unwrap_or(0) + 1600 > now; let refresh_resp = PullToRefresh::new(refresh) @@ -215,14 +223,14 @@ impl WalletTransactions { .id_source(Id::from("txs_content").with(wallet.get_config().id)) .scroll_bar_visibility(ScrollBarVisibility::AlwaysHidden) .auto_shrink([false; 2]) - .show_rows(ui, TX_ITEM_HEIGHT, data.txs.len(), |ui, row_range| { + .show_rows(ui, TX_ITEM_HEIGHT, txs.len(), |ui, row_range| { ui.add_space(1.0); View::max_width_ui(ui, Root::SIDE_PANEL_WIDTH * 1.3, |ui| { let padding = amount_conf != 0 || amount_fin != 0 || amount_locked != 0; for index in row_range { // Show transaction item. - let tx = data.txs.get(index).unwrap(); - let rounding = View::item_rounding(index, data.txs.len(), false); + let tx = txs.get(index).unwrap(); + let rounding = View::item_rounding(index, txs.len(), false); self.tx_item_ui(ui, tx, rounding, padding, true, &data, wallet); } }); @@ -449,14 +457,15 @@ impl WalletTransactions { } } } else { - let tx_height = tx.data.kernel_lookup_min_height.unwrap_or(0); match tx.data.tx_type { TxLogEntryType::ConfirmedCoinbase => { format!("{} {}", CHECK_CIRCLE, t!("wallets.tx_confirmed")) }, TxLogEntryType::TxSent | TxLogEntryType::TxReceived => { + let height = data.info.last_confirmed_height; let min_conf = data.info.minimum_confirmations; - if data.info.last_confirmed_height - tx_height > min_conf { + if tx.conf_height.is_none() || (tx.conf_height.unwrap() != 0 && + height - tx.conf_height.unwrap() > min_conf - 1) { let (i, t) = if tx.data.tx_type == TxLogEntryType::TxSent { (ARROW_CIRCLE_UP, t!("wallets.tx_sent")) } else { @@ -464,9 +473,10 @@ impl WalletTransactions { }; format!("{} {}", i, t) } else { - let h = data.info.last_confirmed_height; - let left_conf = h - tx_height; - let conf_info = if h >= tx_height && left_conf <= min_conf { + let tx_height = tx.conf_height.unwrap() - 1; + let left_conf = height - tx_height; + let conf_info = if tx_height != 0 && height >= tx_height && + left_conf < min_conf { format!("{}/{}", left_conf, min_conf) } else { "".to_string() @@ -525,9 +535,10 @@ impl WalletTransactions { } let data = wallet_data.unwrap(); let tx_id = self.tx_info_id.unwrap(); - let txs = data.txs.iter() + let data_txs = data.txs.clone().unwrap(); + let txs = data_txs.into_iter() .filter(|tx| tx.data.id == tx_id) - .collect::>(); + .collect::>(); if txs.is_empty() { cb.hide_keyboard(); modal.close(); @@ -920,9 +931,10 @@ impl WalletTransactions { ui.vertical_centered(|ui| { // Setup confirmation text. let data = wallet.get_data().unwrap(); - let txs = data.txs.iter() + let data_txs = data.txs.unwrap(); + let txs = data_txs.into_iter() .filter(|tx| tx.data.id == self.confirm_cancel_tx_id.unwrap()) - .collect::>(); + .collect::>(); if txs.is_empty() { modal.close(); return; diff --git a/src/wallet/config.rs b/src/wallet/config.rs index 6833085..6256c72 100644 --- a/src/wallet/config.rs +++ b/src/wallet/config.rs @@ -92,6 +92,12 @@ impl WalletConfig { None } + /// Save wallet config. + pub fn save(&self) { + let config_path = Self::get_config_file_path(self.chain_type, self.id); + Settings::write_to_file(self, config_path); + } + /// Get wallets base directory path for provided [`ChainTypes`]. pub fn get_base_path(chain_type: ChainTypes) -> PathBuf { let sub_dir = Some(chain_type.shortname()); @@ -134,19 +140,23 @@ impl WalletConfig { /// Get Slatepacks data path for current wallet. pub fn get_slatepack_path(&self, slate: &Slate) -> PathBuf { - let mut slatepack_dir = PathBuf::from(self.get_data_path()); - slatepack_dir.push(SLATEPACKS_DIR_NAME); - if !slatepack_dir.exists() { - let _ = fs::create_dir_all(slatepack_dir.clone()); + let mut path = PathBuf::from(self.get_data_path()); + path.push(SLATEPACKS_DIR_NAME); + if !path.exists() { + let _ = fs::create_dir_all(path.clone()); } let slatepack_file_name = format!("{}.{}.slatepack", slate.id, slate.state); - slatepack_dir.push(slatepack_file_name); - slatepack_dir + path.push(slatepack_file_name); + path } - /// Save wallet config. - pub fn save(&self) { - let config_path = Self::get_config_file_path(self.chain_type, self.id); - Settings::write_to_file(self, config_path); + /// Get path to extra db storage. + pub fn get_extra_db_path(&self) -> String { + let mut path = PathBuf::from(self.get_db_path()); + path.push("extra"); + if !path.exists() { + let _ = fs::create_dir_all(path.clone()); + } + path.to_str().unwrap().to_string() } } \ No newline at end of file diff --git a/src/wallet/mod.rs b/src/wallet/mod.rs index 939fb7b..8b20596 100644 --- a/src/wallet/mod.rs +++ b/src/wallet/mod.rs @@ -30,4 +30,6 @@ mod list; pub use list::*; mod utils; -pub use utils::WalletUtils; \ No newline at end of file +pub use utils::WalletUtils; + +pub mod store; \ No newline at end of file diff --git a/src/wallet/store.rs b/src/wallet/store.rs new file mode 100644 index 0000000..d76f39c --- /dev/null +++ b/src/wallet/store.rs @@ -0,0 +1,63 @@ +// Copyright 2024 The Grim Developers +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::sync::{Arc, RwLock}; +use rkv::backend::{Lmdb, LmdbDatabase, LmdbEnvironment}; +use rkv::{IntegerStore, Manager, Rkv, StoreOptions, Value}; + +/// Transaction confirmation height storage. +pub struct TxHeightStore { + env_arc: Arc>>, + store: IntegerStore +} + +impl TxHeightStore { + /// Create new transaction height storage at provided directory. + pub fn new(dir: String) -> Self { + let mut manager = Manager::::singleton().write().unwrap(); + let env_arc = manager.get_or_create(std::path::Path::new(&dir), Rkv::new::).unwrap(); + + let env_arc_store = env_arc.clone(); + let env = env_arc_store.read().unwrap(); + let store = env.open_integer("tx_height", StoreOptions::create()).unwrap(); + Self { + env_arc, + store + } + } + + /// Read transaction height from database. + pub fn read_tx_height(&self, id: u32) -> Option { + let env = self.env_arc.read().unwrap(); + let reader = env.read().unwrap(); + if let Ok(value) = self.store.get(&reader, id) { + if let Some(height) = value { + return match height { + Value::U64(v) => Some(v), + _ => None + }; + } + return None; + } + None + } + + /// Write transaction height to database. + pub fn write_tx_height(&self, id: u32, height: u64) { + let env = self.env_arc.read().unwrap(); + let mut writer = env.write().unwrap(); + self.store.put(&mut writer, id, &Value::U64(height)).unwrap(); + writer.commit().unwrap(); + } +} diff --git a/src/wallet/types.rs b/src/wallet/types.rs index 7ad3d8c..795862b 100644 --- a/src/wallet/types.rs +++ b/src/wallet/types.rs @@ -137,7 +137,7 @@ pub struct WalletData { /// Balance data for current account. pub info: WalletInfo, /// Transactions data. - pub txs: Vec + pub txs: Option> } /// Wallet transaction data. @@ -153,7 +153,9 @@ pub struct WalletTransaction { pub posting: bool, /// Flag to check if transaction can be finalized based on Slatepack message state. pub can_finalize: bool, - /// Last wallet block height of transaction reposting. + /// Block height when tx was confirmed. + pub conf_height: Option, + /// Block height when tx was reposted. pub repost_height: Option } diff --git a/src/wallet/wallet.rs b/src/wallet/wallet.rs index c67ceaa..0ee5d8c 100644 --- a/src/wallet/wallet.rs +++ b/src/wallet/wallet.rs @@ -47,6 +47,7 @@ use crate::AppConfig; use crate::node::{Node, NodeConfig}; use crate::tor::Tor; use crate::wallet::{ConnectionsConfig, ExternalConnection, WalletConfig}; +use crate::wallet::store::TxHeightStore; use crate::wallet::types::{ConnectionMethod, WalletAccount, WalletData, WalletInstance, WalletTransaction}; /// Contains wallet instance, configuration and state, handles wallet commands. @@ -82,8 +83,6 @@ pub struct Wallet { sync_error: Arc, /// Info loading progress in percents. info_sync_progress: Arc, - /// Transactions loading progress in percents. - txs_sync_progress: Arc, /// Wallet accounts. accounts: Arc>>, @@ -117,7 +116,6 @@ impl Wallet { deleted: Arc::new(AtomicBool::new(false)), sync_error: Arc::from(AtomicBool::new(false)), info_sync_progress: Arc::from(AtomicU8::new(0)), - txs_sync_progress: Arc::from(AtomicU8::new(0)), accounts: Arc::new(RwLock::new(vec![])), data: Arc::new(RwLock::new(None)), sync_attempts: Arc::new(AtomicU8::new(0)), @@ -496,7 +494,6 @@ impl Wallet { // Reset progress values. self.info_sync_progress.store(0, Ordering::Relaxed); - self.txs_sync_progress.store(0, Ordering::Relaxed); // Sync wallet data. self.sync(false); @@ -518,11 +515,6 @@ impl Wallet { self.reopen.load(Ordering::Relaxed) } - /// Get wallet transactions synchronization progress. - pub fn txs_sync_progress(&self) -> u8 { - self.txs_sync_progress.load(Ordering::Relaxed) - } - /// Get wallet info synchronization progress. pub fn info_sync_progress(&self) -> u8 { self.info_sync_progress.load(Ordering::Relaxed) @@ -673,7 +665,8 @@ impl Wallet { /// Get transaction for [`Slate`] id. pub fn tx_by_slate(&self, slate: &Slate) -> Option { if let Some(data) = self.get_data() { - let txs = data.txs.clone().iter().map(|tx| tx.clone()).filter(|tx| { + let data_txs = data.txs.unwrap(); + let txs = data_txs.iter().map(|tx| tx.clone()).filter(|tx| { tx.data.tx_slate_id == Some(slate.id) }).collect::>(); return if let Some(tx) = txs.get(0) { @@ -730,7 +723,7 @@ impl Wallet { { let mut w_data = self.data.write(); let mut data = w_data.clone().unwrap(); - let txs = data.txs.iter_mut().map(|tx| { + let txs = data.txs.clone().unwrap().iter_mut().map(|tx| { if tx.data.tx_slate_id == Some(id) { tx.cancelling = false; tx.posting = false; @@ -743,7 +736,7 @@ impl Wallet { } tx.clone() }).collect::>(); - data.txs = txs; + data.txs = Some(txs); *w_data = Some(data); } // Refresh wallet info to update statuses. @@ -912,13 +905,15 @@ impl Wallet { if let Some(tx) = self.tx_by_slate(&slate) { let mut w_data = self.data.write(); let mut data = w_data.clone().unwrap(); - for t in &mut data.txs { + let mut data_txs = data.txs.unwrap(); + for t in &mut data_txs { if t.data.id == tx.data.id { t.repost_height = Some(data.info.last_confirmed_height); t.posting = true; t.can_finalize = false; } } + data.txs = Some(data_txs); *w_data = Some(data); } // Sync local wallet info. @@ -932,14 +927,14 @@ impl Wallet { { let mut w_data = self.data.write(); let mut data = w_data.clone().unwrap(); - let txs = data.txs.iter_mut().map(|tx| { + let txs = data.txs.clone().unwrap().iter_mut().map(|tx| { if tx.data.id == id { tx.cancelling = true; tx.can_finalize = false; } tx.clone() }).collect::>(); - data.txs = txs; + data.txs = Some(txs); *w_data = Some(data); } @@ -955,7 +950,8 @@ impl Wallet { { let mut w_data = wallet.data.write(); let mut data = w_data.clone().unwrap(); - let txs = data.txs.iter_mut().map(|tx| { + let mut data_txs = data.txs.unwrap(); + let txs = data_txs.iter_mut().map(|tx| { if tx.data.id == id { tx.cancelling = false; tx.posting = false; @@ -968,7 +964,7 @@ impl Wallet { } tx.clone() }).collect::>(); - data.txs = txs; + data.txs = Some(txs); *w_data = Some(data); } // Refresh wallet info to update statuses. @@ -991,7 +987,7 @@ impl Wallet { } /// Check if wallet is repairing. - pub fn is_repairing(&self) -> bool { + pub fn is_repairing(&self) -> bool { self.repair_needed.load(Ordering::Relaxed) } @@ -1071,7 +1067,6 @@ const SYNC_ATTEMPTS: u8 = 10; fn start_sync(wallet: Wallet) -> Thread { // Reset progress values. wallet.info_sync_progress.store(0, Ordering::Relaxed); - wallet.txs_sync_progress.store(0, Ordering::Relaxed); wallet.repair_progress.store(0, Ordering::Relaxed); // To call on sync thread stop. @@ -1109,7 +1104,6 @@ fn start_sync(wallet: Wallet) -> Thread { if not_enabled { // Reset loading progress. wallet.info_sync_progress.store(0, Ordering::Relaxed); - wallet.txs_sync_progress.store(0, Ordering::Relaxed); } // Set an error when required integrated node is not enabled. wallet.set_sync_error(not_enabled); @@ -1123,14 +1117,45 @@ fn start_sync(wallet: Wallet) -> Thread { // Scan outputs if repair is needed or sync data if there is no error. if !wallet.sync_error() { if wallet.is_repairing() { - repair_wallet(&wallet) - } else { - // Retrieve data from database first on empty data. - if wallet.get_data().is_none() { - sync_wallet_data(&wallet, false); + repair_wallet(&wallet); + // Stop sync if wallet was closed. + if !wallet.is_open() { + on_thread_stop(wallet); + return; } - sync_wallet_data(&wallet, true); } + // Retrieve data from local database if current data is empty. + if wallet.get_data().is_none() { + sync_wallet_data(&wallet, false); + } + + // Start Foreign API listener if API server is not running. + let mut api_server_running = { + wallet.foreign_api_server.read().is_some() + }; + if !api_server_running && wallet.is_open() { + match start_api_server(&wallet) { + Ok(api_server) => { + let mut api_server_w = wallet.foreign_api_server.write(); + *api_server_w = Some(api_server); + api_server_running = true; + } + Err(_) => {} + } + } + + // Start Tor service if API server is running and wallet is open. + if wallet.auto_start_tor_listener() && wallet.is_open() && api_server_running && + !Tor::is_service_running(&wallet.identifier()) { + let r_foreign_api = wallet.foreign_api_server.read(); + let api = r_foreign_api.as_ref().unwrap(); + if let Ok(sec_key) = wallet.secret_key() { + Tor::start_service(api.1, sec_key, &wallet.identifier()); + } + } + + // Sync wallet from node. + sync_wallet_data(&wallet, true); } // Stop sync if wallet was closed. @@ -1188,39 +1213,12 @@ fn sync_wallet_data(wallet: &Wallet, from_node: bool) { from_node, config.min_confirmations ) { - // Do not retrieve txs if wallet was closed or its first sync from local database. + // Do not retrieve txs if wallet was closed or its first sync. if !wallet.is_open() || !from_node && info.1.last_confirmed_height == 0 { return; } if wallet.info_sync_progress() == 100 || !from_node { - if from_node { - // Start Foreign API listener if API server is not running. - let mut api_server_running = { - wallet.foreign_api_server.read().is_some() - }; - if !api_server_running && wallet.is_open() { - match start_api_server(&wallet) { - Ok(api_server) => { - let mut api_server_w = wallet.foreign_api_server.write(); - *api_server_w = Some(api_server); - api_server_running = true; - } - Err(_) => {} - } - } - - // Start Tor service if API server is running and wallet is open. - if wallet.auto_start_tor_listener() && wallet.is_open() && api_server_running && - !Tor::is_service_running(&wallet.identifier()) { - let r_foreign_api = wallet.foreign_api_server.read(); - let api = r_foreign_api.as_ref().unwrap(); - if let Ok(sec_key) = wallet.secret_key() { - Tor::start_service(api.1, sec_key, &wallet.identifier()); - } - } - } - // Setup accounts data. let last_height = info.1.last_confirmed_height; let spendable = if wallet.get_data().is_none() { @@ -1236,31 +1234,12 @@ fn sync_wallet_data(wallet: &Wallet, from_node: bool) { let txs = if w_data.is_some() { w_data.clone().unwrap().txs } else { - vec![] + None }; *w_data = Some(WalletData { info: info.1.clone(), txs }); } - // Update txs sync progress at separate thread. - let wallet_txs = wallet.clone(); - let (txs_tx, txs_rx) = mpsc::channel::(); - thread::spawn(move || { - while let Ok(m) = txs_rx.recv() { - match m { - StatusMessage::UpdatingOutputs(_) => {} - StatusMessage::UpdatingTransactions(_) => {} - StatusMessage::FullScanWarn(_) => {} - StatusMessage::Scanning(_, progress) => { - wallet_txs.txs_sync_progress.store(progress, Ordering::Relaxed); - } - StatusMessage::ScanningComplete(_) => { - wallet_txs.txs_sync_progress.store(100, Ordering::Relaxed); - } - StatusMessage::UpdateWarning(_) => {} - } - } - }); - + // Retrieve txs from local database. let txs_args = RetrieveTxQueryArgs { exclude_cancelled: Some(false), sort_field: Some(RetrieveTxQuerySortField::CreationTimestamp), @@ -1269,129 +1248,170 @@ fn sync_wallet_data(wallet: &Wallet, from_node: bool) { }; if let Ok(txs) = retrieve_txs(instance.clone(), None, - &Some(txs_tx), - from_node, + &None, + false, None, None, Some(txs_args)) { - // Do not sync data if wallet was closed. + // Exit if wallet was closed. if !wallet.is_open() { return; } - // Save data if loading was completed. - if wallet.txs_sync_progress() == 100 || !from_node { - // Reset attempts. - wallet.reset_sync_attempts(); + // Reset sync attempts. + wallet.reset_sync_attempts(); - // Filter transactions for current account. - let filter_txs = txs.1.iter().map(|v| v.clone()).filter(|tx| { - match wallet.get_parent_key_id() { - Ok(key) => { - tx.parent_key_id == key - } - Err(_) => { - true + // Filter transactions for current account. + let filter_txs = txs.1.iter().map(|v| v.clone()).filter(|tx| { + match wallet.get_parent_key_id() { + Ok(key) => { + tx.parent_key_id == key + } + Err(_) => { + true + } + } + }).collect::>(); + + // Initialize tx confirmation height storage. + let tx_height_store = TxHeightStore::new(config.get_extra_db_path()); + let data = wallet.get_data().unwrap(); + let data_txs = data.txs.unwrap_or(vec![]); + + // Create wallet txs. + let mut new_txs: Vec = vec![]; + for tx in &filter_txs { + // Setup transaction amount. + let amount = if tx.amount_debited > tx.amount_credited { + tx.amount_debited - tx.amount_credited + } else { + tx.amount_credited - tx.amount_debited + }; + + let unconfirmed_sent_or_received = tx.tx_slate_id.is_some() && + !tx.confirmed && (tx.tx_type == TxLogEntryType::TxSent || + tx.tx_type == TxLogEntryType::TxReceived); + + // Setup transaction posting status based on slate state. + let posting = if unconfirmed_sent_or_received { + // Create slate to check existing file. + let is_invoice = tx.tx_type == TxLogEntryType::TxReceived; + let mut slate = Slate::blank(0, is_invoice); + slate.id = tx.tx_slate_id.unwrap(); + slate.state = match is_invoice { + true => SlateState::Invoice3, + _ => SlateState::Standard3 + }; + + // Setup posting status if we have other tx with same slate id. + let mut same_tx_posting = false; + for t in &mut new_txs { + if t.data.tx_slate_id == tx.tx_slate_id && + tx.tx_type != t.data.tx_type { + same_tx_posting = t.posting || + wallet.read_slatepack(&slate).is_some(); + if same_tx_posting && !t.posting { + t.posting = true; + } + break; } } - }).collect::>(); + same_tx_posting || wallet.read_slatepack(&slate).is_some() + } else { + false + }; - // Create wallet txs. - let mut new_txs: Vec = vec![]; - for tx in &filter_txs { - // Setup transaction amount. - let amount = if tx.amount_debited > tx.amount_credited { - tx.amount_debited - tx.amount_credited - } else { - tx.amount_credited - tx.amount_debited + // Setup flag for ability to finalize transaction. + let can_finalize = if !posting && unconfirmed_sent_or_received { + // Create slate to check existing file. + let mut slate = Slate::blank(1, false); + slate.id = tx.tx_slate_id.unwrap(); + slate.state = match tx.tx_type { + TxLogEntryType::TxReceived => SlateState::Invoice1, + _ => SlateState::Standard1 }; + wallet.read_slatepack(&slate).is_some() + } else { + false + }; - let unconfirmed_sent_or_received = tx.tx_slate_id.is_some() && - !tx.confirmed && (tx.tx_type == TxLogEntryType::TxSent || - tx.tx_type == TxLogEntryType::TxReceived); - - // Setup transaction posting status based on slate state. - let posting = if unconfirmed_sent_or_received { - // Create slate to check existing file. - let is_invoice = tx.tx_type == TxLogEntryType::TxReceived; - let mut slate = Slate::blank(0, is_invoice); - slate.id = tx.tx_slate_id.unwrap(); - slate.state = match is_invoice { - true => SlateState::Invoice3, - _ => SlateState::Standard3 - }; - - // Setup posting status if we have other tx with same slate id. - let mut same_tx_posting = false; - for t in &mut new_txs { - if t.data.tx_slate_id == tx.tx_slate_id && - tx.tx_type != t.data.tx_type { - same_tx_posting = t.posting || - wallet.read_slatepack(&slate).is_some(); - if same_tx_posting && !t.posting { - t.posting = true; + // Setup confirmation, reposting height and cancelling status. + let mut conf_height = None; + let mut setup_conf_height = |t: &TxLogEntry, current_empty: bool| -> bool { + if current_empty && t.kernel_lookup_min_height.is_some() && + t.kernel_excess.is_some() && t.confirmed { + // Get tx height from database or from node. + if let Some(height) = tx_height_store.read_tx_height(t.id) { + conf_height = Some(height); + } else { + let mut w_lock = wallet.instance.as_ref().unwrap().lock(); + let w = w_lock.lc_provider() + .unwrap() + .wallet_inst() + .unwrap(); + if let Ok(res) = w.w2n_client().get_kernel( + t.kernel_excess.as_ref().unwrap(), + t.kernel_lookup_min_height, + None + ) { + if res.is_some() { + let h = res.unwrap().1; + conf_height = Some(h); + tx_height_store.write_tx_height(t.id, h); + } else { + conf_height = Some(0); } - break; + } else { + conf_height = None; } } - same_tx_posting || wallet.read_slatepack(&slate).is_some() - } else { - false - }; + return true; + } + false + }; - // Setup flag for ability to finalize transaction. - let can_finalize = if !posting && unconfirmed_sent_or_received { - // Create slate to check existing file. - let mut slate = Slate::blank(1, false); - slate.id = tx.tx_slate_id.unwrap(); - slate.state = match tx.tx_type { - TxLogEntryType::TxReceived => SlateState::Invoice1, - _ => SlateState::Standard1 - }; - wallet.read_slatepack(&slate).is_some() - } else { - false - }; - - // Setup reposting height and cancelling status. - let mut repost_height = None; - let mut cancelling = false; - if posting { - if let Some(data) = wallet.get_data() { - for t in data.txs { - if t.data.id == tx.id { - repost_height = t.repost_height; - if t.cancelling && - tx.tx_type != TxLogEntryType::TxReceivedCancelled && - tx.tx_type != TxLogEntryType::TxSentCancelled { - cancelling = true; - } - break; - } + let mut repost_height = None; + let mut cancelling = false; + if data_txs.is_empty() { + setup_conf_height(tx, true); + } else { + for t in &data_txs { + if t.data.id == tx.id { + if !setup_conf_height(tx, t.conf_height.is_none() || + t.conf_height.unwrap() == 0) { + conf_height = t.conf_height; } + repost_height = t.repost_height; + if t.cancelling && + tx.tx_type != TxLogEntryType::TxReceivedCancelled && + tx.tx_type != TxLogEntryType::TxSentCancelled { + cancelling = true; + } + break; } } - - // Add transaction to list. - new_txs.push(WalletTransaction { - data: tx.clone(), - amount, - cancelling, - posting, - can_finalize, - repost_height, - }) } - // Update wallet txs. - let mut w_data = wallet.data.write(); - let info = if w_data.is_some() { - w_data.clone().unwrap().info - } else { - info.1 - }; - *w_data = Some(WalletData { info, txs: new_txs }); - return; + // Add transaction to the list. + new_txs.push(WalletTransaction { + data: tx.clone(), + amount, + cancelling, + posting, + can_finalize, + conf_height, + repost_height + }); } + + // Update wallet txs. + let mut w_data = wallet.data.write(); + let info = if w_data.is_some() { + w_data.clone().unwrap().info + } else { + info.1 + }; + *w_data = Some(WalletData { info, txs: Some(new_txs) }); + return; } } } @@ -1399,7 +1419,6 @@ fn sync_wallet_data(wallet: &Wallet, from_node: bool) { // Reset progress. wallet.info_sync_progress.store(0, Ordering::Relaxed); - wallet.txs_sync_progress.store(0, Ordering::Relaxed); // Exit if wallet was closed. if !wallet.is_open() {