wallet: tx confirmation height, custom kv storage

This commit is contained in:
ardocrat 2024-06-04 11:02:18 +03:00
parent 9fbb76968c
commit 610c96a8bd
11 changed files with 410 additions and 234 deletions

88
Cargo.lock generated
View file

@ -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"

View file

@ -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"] }
eframe = { version = "0.27.2", features = ["wgpu", "android-game-activity"] }

View file

@ -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"))

View file

@ -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()));

View file

@ -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;

View file

@ -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::<Vec<&WalletTransaction>>();
.collect::<Vec<WalletTransaction>>();
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::<Vec<&WalletTransaction>>();
.collect::<Vec<WalletTransaction>>();
if txs.is_empty() {
modal.close();
return;

View file

@ -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()
}
}

View file

@ -30,4 +30,6 @@ mod list;
pub use list::*;
mod utils;
pub use utils::WalletUtils;
pub use utils::WalletUtils;
pub mod store;

63
src/wallet/store.rs Normal file
View file

@ -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<RwLock<Rkv<LmdbEnvironment>>>,
store: IntegerStore<LmdbDatabase, u32>
}
impl TxHeightStore {
/// Create new transaction height storage at provided directory.
pub fn new(dir: String) -> Self {
let mut manager = Manager::<LmdbEnvironment>::singleton().write().unwrap();
let env_arc = manager.get_or_create(std::path::Path::new(&dir), Rkv::new::<Lmdb>).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<u64> {
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();
}
}

View file

@ -137,7 +137,7 @@ pub struct WalletData {
/// Balance data for current account.
pub info: WalletInfo,
/// Transactions data.
pub txs: Vec<WalletTransaction>
pub txs: Option<Vec<WalletTransaction>>
}
/// 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<u64>,
/// Block height when tx was reposted.
pub repost_height: Option<u64>
}

View file

@ -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<AtomicBool>,
/// Info loading progress in percents.
info_sync_progress: Arc<AtomicU8>,
/// Transactions loading progress in percents.
txs_sync_progress: Arc<AtomicU8>,
/// Wallet accounts.
accounts: Arc<RwLock<Vec<WalletAccount>>>,
@ -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<WalletTransaction> {
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::<Vec<WalletTransaction>>();
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::<Vec<WalletTransaction>>();
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::<Vec<WalletTransaction>>();
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::<Vec<WalletTransaction>>();
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::<StatusMessage>();
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::<Vec<TxLogEntry>>();
// 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<WalletTransaction> = 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::<Vec<TxLogEntry>>();
same_tx_posting || wallet.read_slatepack(&slate).is_some()
} else {
false
};
// Create wallet txs.
let mut new_txs: Vec<WalletTransaction> = 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() {