diff --git a/src/app.rs b/src/app.rs index cecc1a608..c79fbc58e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -8,6 +8,7 @@ use crate::backend_task::{BackendTask, BackendTaskSuccessResult}; use crate::components::core_zmq_listener::{CoreZMQListener, ZMQMessage}; use crate::context::AppContext; use crate::database::Database; +use crate::lock_helper::{MutexExt, RwLockExt}; use crate::logging::initialize_logger; use crate::model::settings::Settings; use crate::ui::contracts_documents::contracts_documents_screen::DocumentQueryScreen; @@ -391,8 +392,7 @@ impl AppState { let mainnet_core_zmq_endpoint = mainnet_app_context .config - .read() - .unwrap() + .read_or_recover() .core_zmq_endpoint .clone() .unwrap_or_else(|| "tcp://127.0.0.1:23708".to_string()); @@ -422,7 +422,7 @@ impl AppState { let testnet_core_zmq_endpoint = testnet_app_context .as_ref() - .and_then(|ctx| ctx.config.read().unwrap().core_zmq_endpoint.clone()) + .and_then(|ctx| ctx.config.read_or_recover().core_zmq_endpoint.clone()) .unwrap_or_else(|| "tcp://127.0.0.1:23709".to_string()); let testnet_disable_zmq = testnet_app_context .as_ref() @@ -449,7 +449,7 @@ impl AppState { let devnet_core_zmq_endpoint = devnet_app_context .as_ref() - .and_then(|ctx| ctx.config.read().unwrap().core_zmq_endpoint.clone()) + .and_then(|ctx| ctx.config.read_or_recover().core_zmq_endpoint.clone()) .unwrap_or_else(|| "tcp://127.0.0.1:23710".to_string()); let devnet_disable_zmq = devnet_app_context .as_ref() @@ -476,7 +476,7 @@ impl AppState { let local_core_zmq_endpoint = local_app_context .as_ref() - .and_then(|ctx| ctx.config.read().unwrap().core_zmq_endpoint.clone()) + .and_then(|ctx| ctx.config.read_or_recover().core_zmq_endpoint.clone()) .unwrap_or_else(|| "tcp://127.0.0.1:20302".to_string()); let local_disable_zmq = local_app_context .as_ref() @@ -964,8 +964,7 @@ impl App for AppState { screen.scheduled_vote_cast_in_progress = true; if let Some((_, s)) = screen .scheduled_votes - .lock() - .unwrap() + .lock_or_recover() .iter_mut() .find(|(v, _)| v == &vote) { diff --git a/src/backend_task/core/create_asset_lock.rs b/src/backend_task/core/create_asset_lock.rs index 94faf5379..12e8cda70 100644 --- a/src/backend_task/core/create_asset_lock.rs +++ b/src/backend_task/core/create_asset_lock.rs @@ -1,5 +1,6 @@ use crate::backend_task::BackendTaskSuccessResult; use crate::context::AppContext; +use crate::lock_helper::{MutexExt, RwLockExt}; use crate::model::wallet::Wallet; use dash_sdk::dashcore_rpc::RpcApi; use dash_sdk::dpp::balances::credits::CREDITS_PER_DUFF; @@ -34,14 +35,13 @@ impl AppContext { // Insert the transaction into waiting for finality { - let mut proofs = self.transactions_waiting_for_finality.lock().unwrap(); + let mut proofs = self.transactions_waiting_for_finality.lock_or_recover(); proofs.insert(tx_id, None); } // Broadcast the transaction self.core_client - .read() - .expect("Core client lock was poisoned") + .read_or_recover() .send_raw_transaction(&asset_lock_transaction) .map_err(|e| format!("Failed to broadcast asset lock transaction: {}", e))?; @@ -96,14 +96,13 @@ impl AppContext { // Insert the transaction into waiting for finality { - let mut proofs = self.transactions_waiting_for_finality.lock().unwrap(); + let mut proofs = self.transactions_waiting_for_finality.lock_or_recover(); proofs.insert(tx_id, None); } // Broadcast the transaction self.core_client - .read() - .expect("Core client lock was poisoned") + .read_or_recover() .send_raw_transaction(&asset_lock_transaction) .map_err(|e| format!("Failed to broadcast asset lock transaction: {}", e))?; diff --git a/src/backend_task/core/mod.rs b/src/backend_task/core/mod.rs index bdad43d1f..a56b25d73 100644 --- a/src/backend_task/core/mod.rs +++ b/src/backend_task/core/mod.rs @@ -9,6 +9,7 @@ use crate::app_dir::core_cookie_path; use crate::backend_task::BackendTaskSuccessResult; use crate::config::{Config, NetworkConfig}; use crate::context::AppContext; +use crate::lock_helper::RwLockExt; use crate::model::wallet::Wallet; use crate::model::wallet::single_key::SingleKeyWallet; use crate::spv::CoreBackendMode; @@ -151,8 +152,7 @@ impl AppContext { match task { CoreTask::GetBestChainLock => self .core_client - .read() - .expect("Core client lock was poisoned") + .read_or_recover() .get_best_chain_lock() .map(|chain_lock| { BackendTaskSuccessResult::CoreItem(CoreItem::ChainLock( @@ -353,8 +353,7 @@ impl AppContext { let txid = self .core_client - .read() - .expect("Core client lock was poisoned") + .read_or_recover() .send_raw_transaction(&tx) .map_err(|e| format!("Failed to broadcast transaction: {e}"))?; diff --git a/src/backend_task/core/recover_asset_locks.rs b/src/backend_task/core/recover_asset_locks.rs index 6b72b357c..3c49629df 100644 --- a/src/backend_task/core/recover_asset_locks.rs +++ b/src/backend_task/core/recover_asset_locks.rs @@ -1,5 +1,6 @@ use crate::backend_task::BackendTaskSuccessResult; use crate::context::AppContext; +use crate::lock_helper::RwLockExt; use crate::model::wallet::Wallet; use dash_sdk::dashcore_rpc::RpcApi; use dash_sdk::dpp::dashcore::hashes::Hash; @@ -42,10 +43,7 @@ impl AppContext { }); } - let client = self - .core_client - .read() - .expect("Core client lock was poisoned"); + let client = self.core_client.read_or_recover(); let mut recovered_count = 0; let mut total_amount = 0u64; diff --git a/src/backend_task/core/refresh_single_key_wallet_info.rs b/src/backend_task/core/refresh_single_key_wallet_info.rs index fb8cc4c54..fca3298ef 100644 --- a/src/backend_task/core/refresh_single_key_wallet_info.rs +++ b/src/backend_task/core/refresh_single_key_wallet_info.rs @@ -1,6 +1,7 @@ //! Refresh Single Key Wallet Info - Reload UTXOs and balances for a single key wallet use crate::context::AppContext; +use crate::lock_helper::RwLockExt; use crate::model::wallet::single_key::SingleKeyWallet; use dash_sdk::dashcore_rpc::RpcApi; use dash_sdk::dpp::dashcore::{OutPoint, TxOut}; @@ -21,10 +22,7 @@ impl AppContext { // Step 2: Import address to Core (needed for UTXO queries) { - let client = self - .core_client - .read() - .expect("Core client lock was poisoned"); + let client = self.core_client.read_or_recover(); if let Err(e) = client.import_address(&address, None, Some(false)) { tracing::debug!(?e, address = %address, "import_address failed during single key refresh"); @@ -33,10 +31,7 @@ impl AppContext { // Step 3: Get UTXOs for this address let utxo_map = { - let client = self - .core_client - .read() - .expect("Core client lock was poisoned"); + let client = self.core_client.read_or_recover(); let utxos = client .list_unspent(Some(0), None, Some(&[&address]), None, None) diff --git a/src/backend_task/core/refresh_wallet_info.rs b/src/backend_task/core/refresh_wallet_info.rs index f1b7eaa4d..134c4c1f7 100644 --- a/src/backend_task/core/refresh_wallet_info.rs +++ b/src/backend_task/core/refresh_wallet_info.rs @@ -1,5 +1,6 @@ use crate::backend_task::BackendTaskSuccessResult; use crate::context::AppContext; +use crate::lock_helper::RwLockExt; use crate::model::wallet::{DerivationPathHelpers, Wallet}; use dash_sdk::dashcore_rpc::RpcApi; use dash_sdk::dpp::dashcore::hashes::Hash; @@ -37,10 +38,7 @@ impl AppContext { // Step 2: Import addresses to Core (no wallet lock needed) { - let client = self - .core_client - .read() - .expect("Core client lock was poisoned"); + let client = self.core_client.read_or_recover(); for address in &addresses { if let Err(e) = client.import_address(address, None, Some(false)) { @@ -51,10 +49,7 @@ impl AppContext { // Step 3: Fetch UTXOs from Core RPC (no wallet lock needed) let utxo_map: HashMap = { - let client = self - .core_client - .read() - .expect("Core client lock was poisoned"); + let client = self.core_client.read_or_recover(); // Get UTXOs for all addresses let utxos = if addresses.is_empty() { @@ -96,10 +91,7 @@ impl AppContext { // Step 5: Fetch total received for each address from Core RPC (no wallet lock) let mut total_received_map: HashMap = HashMap::new(); { - let client = self - .core_client - .read() - .expect("Core client lock was poisoned"); + let client = self.core_client.read_or_recover(); for address in &addresses { match client.get_received_by_address(address, None) { @@ -119,10 +111,7 @@ impl AppContext { // Step 6: Check which asset locks are stale (no wallet lock needed) let stale_txids: Vec<_> = { - let client = self - .core_client - .read() - .expect("Core client lock was poisoned"); + let client = self.core_client.read_or_recover(); asset_lock_txs .iter() diff --git a/src/backend_task/core/send_single_key_wallet_payment.rs b/src/backend_task/core/send_single_key_wallet_payment.rs index ab3b409a0..61bec42ff 100644 --- a/src/backend_task/core/send_single_key_wallet_payment.rs +++ b/src/backend_task/core/send_single_key_wallet_payment.rs @@ -3,6 +3,7 @@ use crate::backend_task::BackendTaskSuccessResult; use crate::backend_task::core::WalletPaymentRequest; use crate::context::AppContext; +use crate::lock_helper::RwLockExt; use crate::model::wallet::single_key::SingleKeyWallet; use dash_sdk::dashcore_rpc::RpcApi; use dash_sdk::dashcore_rpc::dashcore::{Address, OutPoint, ScriptBuf, Transaction, TxIn, TxOut}; @@ -197,8 +198,7 @@ impl AppContext { // Broadcast transaction let txid = self .core_client - .read() - .expect("Core client lock was poisoned") + .read_or_recover() .send_raw_transaction(&tx) .map_err(|e| format!("Failed to broadcast transaction: {}", e))?; diff --git a/src/backend_task/identity/load_identity.rs b/src/backend_task/identity/load_identity.rs index c5b332f37..ac5e38ec2 100644 --- a/src/backend_task/identity/load_identity.rs +++ b/src/backend_task/identity/load_identity.rs @@ -1,6 +1,7 @@ use super::BackendTaskSuccessResult; use crate::backend_task::identity::{IdentityInputToLoad, verify_key_input}; use crate::context::AppContext; +use crate::lock_helper::RwLockExt; use crate::model::qualified_identity::PrivateKeyTarget::{ self, PrivateKeyOnMainIdentity, PrivateKeyOnVoterIdentity, }; @@ -80,7 +81,7 @@ impl AppContext { let mut encrypted_private_keys = BTreeMap::new(); - let wallets = self.wallets.read().unwrap().clone(); + let wallets = self.wallets.read_or_recover().clone(); if identity_type == IdentityType::User && derive_keys_from_wallets @@ -286,7 +287,7 @@ impl AppContext { }; let sdk_guard = { - let guard = self.sdk.read().unwrap(); + let guard = self.sdk.read_or_recover(); guard.clone() }; @@ -339,7 +340,7 @@ impl AppContext { dpns_names: maybe_owned_dpns_names, associated_wallets: wallets .values() - .map(|wallet| (wallet.read().unwrap().seed_hash(), wallet.clone())) + .map(|wallet| (wallet.read_or_recover().seed_hash(), wallet.clone())) .collect(), wallet_index: None, //todo top_ups: Default::default(), @@ -355,7 +356,7 @@ impl AppContext { if let Some((wallet_seed_hash, identity_index)) = wallet_info && let Some(wallet_arc) = wallets.get(&wallet_seed_hash) { - let mut wallet = wallet_arc.write().unwrap(); + let mut wallet = wallet_arc.write_or_recover(); wallet .identities .insert(identity_index, qualified_identity.identity.clone()); @@ -377,7 +378,7 @@ impl AppContext { if wallet_filter.is_some_and(|filter| filter != wallet_seed_hash) { continue; } - let mut wallet = wallet_arc.write().unwrap(); + let mut wallet = wallet_arc.write_or_recover(); if !wallet.is_open() { continue; } diff --git a/src/backend_task/identity/load_identity_by_dpns_name.rs b/src/backend_task/identity/load_identity_by_dpns_name.rs index 3c153f9fd..a4b3f33b0 100644 --- a/src/backend_task/identity/load_identity_by_dpns_name.rs +++ b/src/backend_task/identity/load_identity_by_dpns_name.rs @@ -1,5 +1,6 @@ use super::BackendTaskSuccessResult; use crate::context::AppContext; +use crate::lock_helper::RwLockExt; use crate::model::qualified_identity::{ DPNSNameInfo, IdentityStatus, IdentityType, QualifiedIdentity, }; @@ -138,7 +139,7 @@ impl AppContext { }) .map_err(|e| format!("Error fetching DPNS names: {}", e))?; - let wallets = self.wallets.read().unwrap().clone(); + let wallets = self.wallets.read_or_recover().clone(); // Try to derive keys from wallets if requested let mut encrypted_private_keys = std::collections::BTreeMap::new(); @@ -162,7 +163,7 @@ impl AppContext { dpns_names: owned_dpns_names, associated_wallets: wallets .values() - .map(|wallet| (wallet.read().unwrap().seed_hash(), wallet.clone())) + .map(|wallet| (wallet.read_or_recover().seed_hash(), wallet.clone())) .collect(), wallet_index: None, top_ups: Default::default(), diff --git a/src/backend_task/identity/load_identity_from_wallet.rs b/src/backend_task/identity/load_identity_from_wallet.rs index 8ccc3ba4c..372195df9 100644 --- a/src/backend_task/identity/load_identity_from_wallet.rs +++ b/src/backend_task/identity/load_identity_from_wallet.rs @@ -1,6 +1,7 @@ use super::{BackendTaskSuccessResult, IdentityIndex}; use crate::app::TaskResult; use crate::context::AppContext; +use crate::lock_helper::RwLockExt; use crate::model::qualified_identity::encrypted_key_storage::{ PrivateKeyData, WalletDerivationPath, }; @@ -38,7 +39,7 @@ impl AppContext { for key_index in 0..AUTH_KEY_LOOKUP_WINDOW { let public_key = { - let wallet = wallet_arc_ref.wallet.write().unwrap(); + let wallet = wallet_arc_ref.wallet.write_or_recover(); wallet.identity_authentication_ecdsa_public_key( self.network, identity_index, @@ -116,7 +117,7 @@ impl AppContext { }; let sdk_guard = { - let guard = self.sdk.read().unwrap(); + let guard = self.sdk.read_or_recover(); guard.clone() }; @@ -162,7 +163,7 @@ impl AppContext { let wallet_seed_hash; let (public_key_result_map, public_key_hash_result_map) = { - let mut wallet = wallet_arc_ref.wallet.write().unwrap(); + let mut wallet = wallet_arc_ref.wallet.write_or_recover(); wallet_seed_hash = wallet.seed_hash(); wallet.identity_authentication_ecdsa_public_keys_data_map( self.network, @@ -224,7 +225,7 @@ impl AppContext { let private_keys = private_keys_map.into(); - let wallet_seed_hash = wallet_arc_ref.wallet.read().unwrap().seed_hash(); + let wallet_seed_hash = wallet_arc_ref.wallet.read_or_recover().seed_hash(); let mut qualified_identity = QualifiedIdentity { identity: identity.clone(), @@ -259,7 +260,7 @@ impl AppContext { .map_err(|e| format!("Database error: {}", e))?; { - let mut wallet = wallet_arc_ref.wallet.write().unwrap(); + let mut wallet = wallet_arc_ref.wallet.write_or_recover(); wallet .identities .insert(identity_index, qualified_identity.identity.clone()); diff --git a/src/backend_task/identity/mod.rs b/src/backend_task/identity/mod.rs index c9ceb3e6a..fa29592e5 100644 --- a/src/backend_task/identity/mod.rs +++ b/src/backend_task/identity/mod.rs @@ -14,6 +14,7 @@ mod withdraw_from_identity; use super::{BackendTaskSuccessResult, FeeResult}; use crate::app::TaskResult; use crate::context::AppContext; +use crate::lock_helper::RwLockExt; use crate::model::qualified_identity::encrypted_key_storage::{KeyStorage, WalletDerivationPath}; use crate::model::qualified_identity::qualified_identity_public_key::QualifiedIdentityPublicKey; use crate::model::qualified_identity::{IdentityType, PrivateKeyTarget, QualifiedIdentity}; @@ -572,7 +573,7 @@ impl AppContext { // Get the wallet for signing - clone it to avoid holding guard across await let wallet_clone = { let wallet = { - let wallets = self.wallets.read().unwrap(); + let wallets = self.wallets.read_or_recover(); wallets .get(&wallet_seed_hash) .cloned() @@ -667,7 +668,7 @@ impl AppContext { // Update destination address balances in any wallets that contain them // (using proof-verified data from the SDK response) { - let wallets = self.wallets.read().unwrap(); + let wallets = self.wallets.read_or_recover(); for (seed_hash, wallet_arc) in wallets.iter() { if let Err(e) = self.update_wallet_platform_address_info_from_sdk(*seed_hash, &address_infos) diff --git a/src/backend_task/identity/refresh_loaded_identities_dpns_names.rs b/src/backend_task/identity/refresh_loaded_identities_dpns_names.rs index 2f480d085..2f5542ff1 100644 --- a/src/backend_task/identity/refresh_loaded_identities_dpns_names.rs +++ b/src/backend_task/identity/refresh_loaded_identities_dpns_names.rs @@ -1,6 +1,7 @@ use super::BackendTaskSuccessResult; use crate::app::TaskResult; use crate::context::AppContext; +use crate::lock_helper::RwLockExt; use crate::model::qualified_identity::DPNSNameInfo; use dash_sdk::dpp::document::DocumentV0Getters; use dash_sdk::dpp::identity::accessors::IdentityGettersV0; @@ -35,7 +36,7 @@ impl AppContext { }; let sdk_guard = { - let guard = self.sdk.read().unwrap(); + let guard = self.sdk.read_or_recover(); guard.clone() }; diff --git a/src/backend_task/identity/register_dpns_name.rs b/src/backend_task/identity/register_dpns_name.rs index 1520aef09..799a9308f 100644 --- a/src/backend_task/identity/register_dpns_name.rs +++ b/src/backend_task/identity/register_dpns_name.rs @@ -1,6 +1,7 @@ use std::collections::BTreeMap; use crate::backend_task::FeeResult; +use crate::lock_helper::RwLockExt; use crate::model::fee_estimation::PlatformFeeEstimator; use crate::{context::AppContext, model::qualified_identity::DPNSNameInfo}; use bip39::rand::{Rng, SeedableRng, rngs::StdRng}; @@ -179,7 +180,7 @@ impl AppContext { }; let sdk_guard = { - let guard = self.sdk.read().unwrap(); + let guard = self.sdk.read_or_recover(); guard.clone() }; diff --git a/src/backend_task/identity/register_identity.rs b/src/backend_task/identity/register_identity.rs index 855f08387..b928f311d 100644 --- a/src/backend_task/identity/register_identity.rs +++ b/src/backend_task/identity/register_identity.rs @@ -1,6 +1,7 @@ use crate::backend_task::identity::{IdentityRegistrationInfo, RegisterIdentityFundingMethod}; use crate::backend_task::{BackendTaskSuccessResult, FeeResult}; use crate::context::{AppContext, get_transaction_info_via_dapi}; +use crate::lock_helper::{MutexExt, RwLockExt}; use crate::model::fee_estimation::PlatformFeeEstimator; use crate::model::proof_log_item::{ProofLogItem, RequestType}; use crate::model::qualified_identity::{IdentityStatus, IdentityType, QualifiedIdentity}; @@ -38,7 +39,7 @@ impl AppContext { } = input; let sdk = { - let guard = self.sdk.read().unwrap(); + let guard = self.sdk.read_or_recover(); guard.clone() }; @@ -55,7 +56,7 @@ impl AppContext { // Scope the read guard so it's dropped before the async DAPI call below let private_key = { - let wallet = wallet.read().unwrap(); + let wallet = wallet.read_or_recover(); wallet_id = wallet.seed_hash(); wallet .private_key_for_address(&address, self.network)? @@ -97,7 +98,7 @@ impl AppContext { RegisterIdentityFundingMethod::FundWithWallet(amount, identity_index) => { // Scope the write lock to avoid holding it across an await. let (asset_lock_transaction, asset_lock_proof_private_key, _, used_utxos) = { - let mut wallet = wallet.write().unwrap(); + let mut wallet = wallet.write_or_recover(); wallet_id = wallet.seed_hash(); match wallet.registration_asset_lock_transaction( sdk.network, @@ -110,10 +111,7 @@ impl AppContext { Err(_) => { wallet .reload_utxos( - &self - .core_client - .read() - .expect("Core client lock was poisoned"), + &self.core_client.read_or_recover(), self.network, Some(self), ) @@ -132,13 +130,12 @@ impl AppContext { let tx_id = asset_lock_transaction.txid(); { - let mut proofs = self.transactions_waiting_for_finality.lock().unwrap(); + let mut proofs = self.transactions_waiting_for_finality.lock_or_recover(); proofs.insert(tx_id, None); } self.core_client - .read() - .expect("Core client lock was poisoned") + .read_or_recover() .send_raw_transaction(&asset_lock_transaction) .map_err(|e| e.to_string())?; @@ -161,7 +158,7 @@ impl AppContext { // weren't actually spent. This should be refactored to remove UTXOs only AFTER // successful proof confirmation. See Phase 2.2 in PR review plan. { - let mut wallet = wallet.write().unwrap(); + let mut wallet = wallet.write_or_recover(); wallet.utxos.retain(|_, utxo_map| { utxo_map.retain(|outpoint, _| !used_utxos.contains_key(outpoint)); !utxo_map.is_empty() // Keep addresses that still have UTXOs @@ -191,7 +188,7 @@ impl AppContext { let asset_lock_proof = match tokio::time::timeout(ASSET_LOCK_PROOF_TIMEOUT, async { loop { { - let proofs = self.transactions_waiting_for_finality.lock().unwrap(); + let proofs = self.transactions_waiting_for_finality.lock_or_recover(); if let Some(Some(proof)) = proofs.get(&tx_id) { return proof.clone(); } @@ -204,7 +201,7 @@ impl AppContext { Ok(proof) => proof, Err(_) => { // Clean up on timeout - let mut proofs = self.transactions_waiting_for_finality.lock().unwrap(); + let mut proofs = self.transactions_waiting_for_finality.lock_or_recover(); proofs.remove(&tx_id); return Err(format!( "Timeout waiting for asset lock proof after {} seconds. \ @@ -268,7 +265,7 @@ impl AppContext { ) => { // Scope the write lock to avoid holding it across an await. let (asset_lock_transaction, asset_lock_proof_private_key) = { - let mut wallet = wallet.write().unwrap(); + let mut wallet = wallet.write_or_recover(); wallet_id = wallet.seed_hash(); wallet.registration_asset_lock_transaction_for_utxo( sdk.network, @@ -283,13 +280,12 @@ impl AppContext { let tx_id = asset_lock_transaction.txid(); { - let mut proofs = self.transactions_waiting_for_finality.lock().unwrap(); + let mut proofs = self.transactions_waiting_for_finality.lock_or_recover(); proofs.insert(tx_id, None); } self.core_client - .read() - .expect("Core client lock was poisoned") + .read_or_recover() .send_raw_transaction(&asset_lock_transaction) .map_err(|e| e.to_string())?; @@ -308,7 +304,7 @@ impl AppContext { // TODO: UTXO removal timing issue - see comment above for FundWithWallet case. { - let mut wallet = wallet.write().unwrap(); + let mut wallet = wallet.write_or_recover(); wallet.utxos.retain(|_, utxo_map| { utxo_map.retain(|outpoint, _| outpoint != &utxo); !utxo_map.is_empty() @@ -331,7 +327,7 @@ impl AppContext { let asset_lock_proof = match tokio::time::timeout(ASSET_LOCK_PROOF_TIMEOUT, async { loop { { - let proofs = self.transactions_waiting_for_finality.lock().unwrap(); + let proofs = self.transactions_waiting_for_finality.lock_or_recover(); if let Some(Some(proof)) = proofs.get(&tx_id) { return proof.clone(); } @@ -344,7 +340,7 @@ impl AppContext { Ok(proof) => proof, Err(_) => { // Clean up on timeout - let mut proofs = self.transactions_waiting_for_finality.lock().unwrap(); + let mut proofs = self.transactions_waiting_for_finality.lock_or_recover(); proofs.remove(&tx_id); return Err(format!( "Timeout waiting for asset lock proof after {} seconds. \ @@ -394,7 +390,7 @@ impl AppContext { .expect("expected to make identity") }); - let wallet_seed_hash = { wallet.read().unwrap().seed_hash() }; + let wallet_seed_hash = { wallet.read_or_recover().seed_hash() }; let mut qualified_identity = QualifiedIdentity { identity: identity.clone(), associated_voter_identity: None, @@ -405,7 +401,7 @@ impl AppContext { private_keys: keys.to_key_storage(wallet_seed_hash), dpns_names: vec![], associated_wallets: BTreeMap::from([( - wallet.read().unwrap().seed_hash(), + wallet.read_or_recover().seed_hash(), wallet.clone(), )]), wallet_index: Some(wallet_identity_index), @@ -429,7 +425,7 @@ impl AppContext { .map_err(|e| e.to_string())?; { - let mut wallet = wallet.write().unwrap(); + let mut wallet = wallet.write_or_recover(); wallet .unused_asset_locks .retain(|(tx, _, _, _, _)| tx.txid() != tx_id); @@ -578,7 +574,7 @@ impl AppContext { ) .map_err(|e| e.to_string())?; { - let mut wallet = wallet.write().unwrap(); + let mut wallet = wallet.write_or_recover(); wallet .unused_asset_locks .retain(|(tx, _, _, _, _)| tx.txid() != tx_id); @@ -713,7 +709,7 @@ impl AppContext { use dash_sdk::platform::transition::put_identity::PutIdentity; let sdk = { - let guard = self.sdk.read().unwrap(); + let guard = self.sdk.read_or_recover(); guard.clone() }; @@ -738,7 +734,7 @@ impl AppContext { ) .map_err(|e| format!("Failed to create identity: {}", e))?; - let wallet_seed_hash_actual = { wallet.read().unwrap().seed_hash() }; + let wallet_seed_hash_actual = { wallet.read_or_recover().seed_hash() }; let mut qualified_identity = QualifiedIdentity { identity: identity.clone(), associated_voter_identity: None, @@ -782,7 +778,7 @@ impl AppContext { .map_err(|e| e.to_string())?; { - let mut wallet_guard = wallet.write().unwrap(); + let mut wallet_guard = wallet.write_or_recover(); wallet_guard .identities .insert(wallet_identity_index, qualified_identity.identity.clone()); diff --git a/src/backend_task/identity/top_up_identity.rs b/src/backend_task/identity/top_up_identity.rs index 5a309e89f..f785b9b17 100644 --- a/src/backend_task/identity/top_up_identity.rs +++ b/src/backend_task/identity/top_up_identity.rs @@ -1,6 +1,7 @@ use crate::backend_task::identity::{IdentityTopUpInfo, TopUpIdentityFundingMethod}; use crate::backend_task::{BackendTaskSuccessResult, FeeResult}; use crate::context::{AppContext, get_transaction_info_via_dapi}; +use crate::lock_helper::{MutexExt, RwLockExt}; use crate::model::fee_estimation::PlatformFeeEstimator; use crate::model::proof_log_item::{ProofLogItem, RequestType}; use dash_sdk::Error; @@ -30,7 +31,7 @@ impl AppContext { } = input; let sdk = { - let guard = self.sdk.read().unwrap(); + let guard = self.sdk.read_or_recover(); guard.clone() }; @@ -49,7 +50,7 @@ impl AppContext { // Scope the read guard so it's dropped before the async DAPI call below let private_key = { - let wallet = wallet.read().unwrap(); + let wallet = wallet.read_or_recover(); wallet .private_key_for_address(&address, self.network)? .ok_or("Asset Lock not valid for wallet")? @@ -104,7 +105,7 @@ impl AppContext { used_utxos, wallet_seed_hash, ) = { - let mut wallet = wallet.write().unwrap(); + let mut wallet = wallet.write_or_recover(); let seed_hash = wallet.seed_hash(); let tx_result = match wallet.top_up_asset_lock_transaction( sdk.network, @@ -118,10 +119,7 @@ impl AppContext { Err(_) => { wallet .reload_utxos( - &self - .core_client - .read() - .expect("Core client lock was poisoned"), + &self.core_client.read_or_recover(), self.network, Some(self), ) @@ -154,13 +152,12 @@ impl AppContext { // .map_err(|e| e.to_string())?; { - let mut proofs = self.transactions_waiting_for_finality.lock().unwrap(); + let mut proofs = self.transactions_waiting_for_finality.lock_or_recover(); proofs.insert(tx_id, None); } self.core_client - .read() - .expect("Core client lock was poisoned") + .read_or_recover() .send_raw_transaction(&asset_lock_transaction) .map_err(|e| e.to_string())?; @@ -178,7 +175,7 @@ impl AppContext { .map_err(|e| format!("Failed to store asset lock transaction: {}", e))?; { - let mut wallet = wallet.write().unwrap(); + let mut wallet = wallet.write_or_recover(); wallet.utxos.retain(|_, utxo_map| { utxo_map.retain(|outpoint, _| !used_utxos.contains_key(outpoint)); !utxo_map.is_empty() // Keep addresses that still have UTXOs @@ -210,7 +207,7 @@ impl AppContext { loop { { let proofs = - self.transactions_waiting_for_finality.lock().unwrap(); + self.transactions_waiting_for_finality.lock_or_recover(); if let Some(Some(proof)) = proofs.get(&tx_id) { return proof.clone(); } @@ -224,7 +221,7 @@ impl AppContext { Err(_) => { // Clean up on timeout let mut proofs = - self.transactions_waiting_for_finality.lock().unwrap(); + self.transactions_waiting_for_finality.lock_or_recover(); proofs.remove(&tx_id); return Err(format!( "Timeout waiting for asset lock proof after {} seconds. \ @@ -250,7 +247,7 @@ impl AppContext { ) => { // Scope the write lock to avoid holding it across an await. let (asset_lock_transaction, asset_lock_proof_private_key, wallet_seed_hash) = { - let mut wallet = wallet.write().unwrap(); + let mut wallet = wallet.write_or_recover(); let seed_hash = wallet.seed_hash(); let tx_result = wallet.top_up_asset_lock_transaction_for_utxo( sdk.network, @@ -273,13 +270,12 @@ impl AppContext { // .map_err(|e| e.to_string())?; { - let mut proofs = self.transactions_waiting_for_finality.lock().unwrap(); + let mut proofs = self.transactions_waiting_for_finality.lock_or_recover(); proofs.insert(tx_id, None); } self.core_client - .read() - .expect("Core client lock was poisoned") + .read_or_recover() .send_raw_transaction(&asset_lock_transaction) .map_err(|e| e.to_string())?; @@ -297,7 +293,7 @@ impl AppContext { .map_err(|e| format!("Failed to store asset lock transaction: {}", e))?; { - let mut wallet = wallet.write().unwrap(); + let mut wallet = wallet.write_or_recover(); wallet.utxos.retain(|_, utxo_map| { utxo_map.retain(|outpoint, _| outpoint != &utxo); !utxo_map.is_empty() @@ -322,7 +318,7 @@ impl AppContext { loop { { let proofs = - self.transactions_waiting_for_finality.lock().unwrap(); + self.transactions_waiting_for_finality.lock_or_recover(); if let Some(Some(proof)) = proofs.get(&tx_id) { return proof.clone(); } @@ -336,7 +332,7 @@ impl AppContext { Err(_) => { // Clean up on timeout let mut proofs = - self.transactions_waiting_for_finality.lock().unwrap(); + self.transactions_waiting_for_finality.lock_or_recover(); proofs.remove(&tx_id); return Err(format!( "Timeout waiting for asset lock proof after {} seconds. \ @@ -572,7 +568,7 @@ impl AppContext { .map_err(|e| e.to_string())?; { - let mut wallet = wallet.write().unwrap(); + let mut wallet = wallet.write_or_recover(); wallet .unused_asset_locks .retain(|(tx, _, _, _, _)| tx.txid() != tx_id); diff --git a/src/backend_task/identity/transfer.rs b/src/backend_task/identity/transfer.rs index 84c58af0b..e1c7f639c 100644 --- a/src/backend_task/identity/transfer.rs +++ b/src/backend_task/identity/transfer.rs @@ -1,5 +1,6 @@ use crate::backend_task::FeeResult; use crate::context::AppContext; +use crate::lock_helper::RwLockExt; use crate::model::fee_estimation::PlatformFeeEstimator; use crate::model::qualified_identity::QualifiedIdentity; use dash_sdk::dpp::fee::Credits; @@ -19,7 +20,7 @@ impl AppContext { id: Option, ) -> Result { let sdk_guard = { - let guard = self.sdk.read().unwrap(); + let guard = self.sdk.read_or_recover(); guard.clone() }; diff --git a/src/backend_task/identity/withdraw_from_identity.rs b/src/backend_task/identity/withdraw_from_identity.rs index 1370e3ae5..808a700d0 100644 --- a/src/backend_task/identity/withdraw_from_identity.rs +++ b/src/backend_task/identity/withdraw_from_identity.rs @@ -1,5 +1,6 @@ use crate::backend_task::FeeResult; use crate::context::AppContext; +use crate::lock_helper::RwLockExt; use crate::model::fee_estimation::PlatformFeeEstimator; use crate::model::qualified_identity::QualifiedIdentity; use dash_sdk::dpp::dashcore::Address; @@ -22,7 +23,7 @@ impl AppContext { id: Option, ) -> Result { let sdk_guard = { - let guard = self.sdk.read().unwrap(); + let guard = self.sdk.read_or_recover(); guard.clone() }; diff --git a/src/backend_task/mnlist.rs b/src/backend_task/mnlist.rs index 4dfd2995a..9643d8f78 100644 --- a/src/backend_task/mnlist.rs +++ b/src/backend_task/mnlist.rs @@ -1,6 +1,7 @@ use crate::backend_task::BackendTaskSuccessResult; use crate::components::core_p2p_handler::CoreP2PHandler; use crate::context::AppContext; +use crate::lock_helper::RwLockExt; use dash_sdk::dashcore_rpc::RpcApi; use dash_sdk::dpp::dashcore::bls_sig_utils::BLSSignature; use dash_sdk::dpp::dashcore::hashes::Hash; @@ -78,7 +79,7 @@ pub async fn run_mnlist_task( base_block_height, block_height, } => { - let client = app.core_client.read().unwrap(); + let client = app.core_client.read_or_recover(); // Determine the range (replicate UI logic approximately) let loaded_list_height = match app.network { Network::Dash => 2_227_096, diff --git a/src/backend_task/mod.rs b/src/backend_task/mod.rs index 7d8413fad..be60e8fcb 100644 --- a/src/backend_task/mod.rs +++ b/src/backend_task/mod.rs @@ -1,4 +1,5 @@ use crate::app::TaskResult; +use crate::lock_helper::RwLockExt; use crate::backend_task::contested_names::ContestedResourceTask; use crate::backend_task::contract::ContractTask; use crate::backend_task::core::{CoreItem, CoreTask}; @@ -312,7 +313,7 @@ impl AppContext { sender: SenderAsync, ) -> Result { let sdk = { - let guard = self.sdk.read().unwrap(); + let guard = self.sdk.read_or_recover(); guard.clone() }; match task { diff --git a/src/backend_task/platform_info.rs b/src/backend_task/platform_info.rs index 6cbf246f0..52c155558 100644 --- a/src/backend_task/platform_info.rs +++ b/src/backend_task/platform_info.rs @@ -1,5 +1,6 @@ use crate::backend_task::BackendTaskSuccessResult; use crate::context::AppContext; +use crate::lock_helper::RwLockExt; use dash_sdk::dashcore_rpc::RpcApi; use dash_sdk::dpp::block::extended_epoch_info::{v0::ExtendedEpochInfoV0Getters, ExtendedEpochInfo}; use dash_sdk::dpp::core_types::validator_set::v0::ValidatorSetV0Getters; @@ -334,7 +335,7 @@ impl AppContext { request: PlatformInfoTaskRequestType, ) -> Result { let sdk = { - let sdk_guard = self.sdk.read().unwrap(); + let sdk_guard = self.sdk.read_or_recover(); sdk_guard.clone() }; diff --git a/src/backend_task/wallet/fetch_platform_address_balances.rs b/src/backend_task/wallet/fetch_platform_address_balances.rs index d07f7e030..71332b5fc 100644 --- a/src/backend_task/wallet/fetch_platform_address_balances.rs +++ b/src/backend_task/wallet/fetch_platform_address_balances.rs @@ -1,6 +1,7 @@ use crate::backend_task::BackendTaskSuccessResult; use crate::backend_task::wallet::PlatformSyncMode; use crate::context::AppContext; +use crate::lock_helper::RwLockExt; use crate::model::wallet::{ DerivationPathHelpers, DerivationPathReference, DerivationPathType, Wallet, WalletAddressProvider, WalletSeedHash, @@ -26,7 +27,7 @@ impl AppContext { let start_time = std::time::Instant::now(); let wallet_arc = { - let wallets = self.wallets.read().unwrap(); + let wallets = self.wallets.read_or_recover(); wallets .get(&seed_hash) .cloned() diff --git a/src/backend_task/wallet/fund_platform_address_from_asset_lock.rs b/src/backend_task/wallet/fund_platform_address_from_asset_lock.rs index 5da5ff228..2db3cedf1 100644 --- a/src/backend_task/wallet/fund_platform_address_from_asset_lock.rs +++ b/src/backend_task/wallet/fund_platform_address_from_asset_lock.rs @@ -1,6 +1,7 @@ use crate::backend_task::BackendTaskSuccessResult; use crate::backend_task::wallet::PlatformSyncMode; use crate::context::AppContext; +use crate::lock_helper::RwLockExt; use crate::model::wallet::WalletSeedHash; use dash_sdk::dpp::address_funds::PlatformAddress; use dash_sdk::dpp::balances::credits::Credits; @@ -27,7 +28,7 @@ impl AppContext { // Clone wallet and SDK before the async operation to avoid holding guards across await let (wallet, sdk, asset_lock_private_key) = { let wallet_arc = { - let wallets = self.wallets.read().unwrap(); + let wallets = self.wallets.read_or_recover(); wallets .get(&seed_hash) .cloned() @@ -118,7 +119,7 @@ impl AppContext { // Remove the used asset lock from the wallet and database { let wallet_arc = { - let wallets = self.wallets.read().unwrap(); + let wallets = self.wallets.read_or_recover(); wallets.get(&seed_hash).cloned() }; if let Some(wallet_arc) = wallet_arc { diff --git a/src/backend_task/wallet/fund_platform_address_from_wallet_utxos.rs b/src/backend_task/wallet/fund_platform_address_from_wallet_utxos.rs index ac132b754..5bea0fa93 100644 --- a/src/backend_task/wallet/fund_platform_address_from_wallet_utxos.rs +++ b/src/backend_task/wallet/fund_platform_address_from_wallet_utxos.rs @@ -1,6 +1,7 @@ use crate::backend_task::BackendTaskSuccessResult; use crate::backend_task::wallet::PlatformSyncMode; use crate::context::AppContext; +use crate::lock_helper::{MutexExt, RwLockExt}; use crate::model::wallet::WalletSeedHash; use crate::spv::CoreBackendMode; use dash_sdk::dpp::address_funds::PlatformAddress; @@ -45,7 +46,7 @@ impl AppContext { // Step 1: Create the asset lock transaction let (asset_lock_transaction, asset_lock_private_key, _asset_lock_address, used_utxos) = { let wallet_arc = { - let wallets = self.wallets.read().unwrap(); + let wallets = self.wallets.read_or_recover(); wallets .get(&seed_hash) .cloned() @@ -66,10 +67,7 @@ impl AppContext { // Reload UTXOs and try again wallet .reload_utxos( - &self - .core_client - .read() - .expect("Core client lock was poisoned"), + &self.core_client.read_or_recover(), self.network, Some(self), ) @@ -91,21 +89,20 @@ impl AppContext { // Step 2: Register this transaction as waiting for finality { - let mut proofs = self.transactions_waiting_for_finality.lock().unwrap(); + let mut proofs = self.transactions_waiting_for_finality.lock_or_recover(); proofs.insert(tx_id, None); } // Step 3: Broadcast the transaction self.core_client - .read() - .expect("Core client lock was poisoned") + .read_or_recover() .send_raw_transaction(&asset_lock_transaction) .map_err(|e| format!("Failed to broadcast asset lock transaction: {}", e))?; // Step 4: Remove used UTXOs from wallet { let wallet_arc = { - let wallets = self.wallets.read().unwrap(); + let wallets = self.wallets.read_or_recover(); wallets .get(&seed_hash) .cloned() @@ -156,8 +153,8 @@ impl AppContext { // spent inputs are reconciled (the tx was already broadcast and // may confirm later). SPV handles its own reconciliation. if self.core_backend_mode() == CoreBackendMode::Rpc - && let Some(wallet_arc) = self.wallets.read().ok() - .and_then(|w| w.get(&seed_hash).cloned()) + && let Some(wallet_arc) = self.wallets.read_or_recover() + .get(&seed_hash).cloned() { let ctx = Arc::clone(self); // Fire-and-forget — don't block the error return on refresh @@ -173,7 +170,7 @@ impl AppContext { _ = tokio::time::sleep(Duration::from_millis(200)) => { // Brief lock to check for proof — acquired and released quickly // so contention is minimal. - let proofs = self.transactions_waiting_for_finality.lock().unwrap(); + let proofs = self.transactions_waiting_for_finality.lock_or_recover(); if let Some(Some(proof)) = proofs.get(&tx_id) { asset_lock_proof = proof.clone(); break; @@ -184,14 +181,14 @@ impl AppContext { // Step 6: Clean up the finality tracking { - let mut proofs = self.transactions_waiting_for_finality.lock().unwrap(); + let mut proofs = self.transactions_waiting_for_finality.lock_or_recover(); proofs.remove(&tx_id); } // Step 7: Get wallet, SDK, and derive a fresh change address if needed let (wallet, sdk, change_platform_address) = { let wallet_arc = { - let wallets = self.wallets.read().unwrap(); + let wallets = self.wallets.read_or_recover(); wallets .get(&seed_hash) .cloned() diff --git a/src/backend_task/wallet/generate_receive_address.rs b/src/backend_task/wallet/generate_receive_address.rs index f627a035b..27218bbd8 100644 --- a/src/backend_task/wallet/generate_receive_address.rs +++ b/src/backend_task/wallet/generate_receive_address.rs @@ -1,5 +1,6 @@ use crate::backend_task::BackendTaskSuccessResult; use crate::context::AppContext; +use crate::lock_helper::RwLockExt; use crate::model::wallet::{DerivationPathReference, DerivationPathType, WalletSeedHash}; use crate::spv::CoreBackendMode; use std::sync::Arc; @@ -10,7 +11,7 @@ impl AppContext { seed_hash: WalletSeedHash, ) -> Result { let wallet_arc = { - let wallets = self.wallets.read().unwrap(); + let wallets = self.wallets.read_or_recover(); wallets .get(&seed_hash) .cloned() diff --git a/src/backend_task/wallet/transfer_platform_credits.rs b/src/backend_task/wallet/transfer_platform_credits.rs index 3549af4be..953e8555d 100644 --- a/src/backend_task/wallet/transfer_platform_credits.rs +++ b/src/backend_task/wallet/transfer_platform_credits.rs @@ -1,5 +1,6 @@ use crate::backend_task::BackendTaskSuccessResult; use crate::context::AppContext; +use crate::lock_helper::RwLockExt; use crate::model::wallet::WalletSeedHash; use dash_sdk::dpp::address_funds::PlatformAddress; use dash_sdk::dpp::balances::credits::Credits; @@ -21,7 +22,7 @@ impl AppContext { // Clone wallet and SDK before the async operation to avoid holding guards across await let (wallet, sdk) = { let wallet_arc = { - let wallets = self.wallets.read().unwrap(); + let wallets = self.wallets.read_or_recover(); wallets .get(&seed_hash) .cloned() diff --git a/src/backend_task/wallet/withdraw_from_platform_address.rs b/src/backend_task/wallet/withdraw_from_platform_address.rs index a12b8948f..0597aea5b 100644 --- a/src/backend_task/wallet/withdraw_from_platform_address.rs +++ b/src/backend_task/wallet/withdraw_from_platform_address.rs @@ -1,6 +1,7 @@ use crate::backend_task::BackendTaskSuccessResult; use crate::backend_task::wallet::PlatformSyncMode; use crate::context::AppContext; +use crate::lock_helper::RwLockExt; use crate::model::wallet::WalletSeedHash; use dash_sdk::dpp::address_funds::PlatformAddress; use dash_sdk::dpp::balances::credits::Credits; @@ -25,7 +26,7 @@ impl AppContext { // Clone wallet and SDK before the async operation to avoid holding guards across await let (wallet, sdk) = { let wallet_arc = { - let wallets = self.wallets.read().unwrap(); + let wallets = self.wallets.read_or_recover(); wallets .get(&seed_hash) .cloned() diff --git a/src/context.rs b/src/context.rs index 980053129..6a57e53c6 100644 --- a/src/context.rs +++ b/src/context.rs @@ -5,6 +5,7 @@ use crate::config::{Config, NetworkConfig}; use crate::context_provider::Provider as RpcProvider; use crate::context_provider_spv::SpvProvider; use crate::database::Database; +use crate::lock_helper::{MutexExt, RwLockExt}; use crate::model::contested_name::ContestedName; use crate::model::fee_estimation::PlatformFeeEstimator; use crate::model::password_info::PasswordInfo; @@ -537,7 +538,7 @@ impl AppContext { pub fn bootstrap_loaded_wallets(self: &Arc) { let wallets: Vec<_> = { - let guard = self.wallets.read().unwrap(); + let guard = self.wallets.read_or_recover(); guard.values().cloned().collect() }; @@ -565,7 +566,7 @@ impl AppContext { } let single_key_wallets: Vec<_> = { - let guard = self.single_key_wallets.read().unwrap(); + let guard = self.single_key_wallets.read_or_recover(); guard.values().cloned().collect() }; for wallet in single_key_wallets { @@ -596,7 +597,7 @@ impl AppContext { address_infos: &dash_sdk::query_types::AddressInfos, ) -> Result<(), String> { let wallet_arc = { - let wallets = self.wallets.read().unwrap(); + let wallets = self.wallets.read_or_recover(); wallets .get(&seed_hash) .cloned() @@ -815,7 +816,7 @@ impl AppContext { let mapping = self.spv_manager.det_wallets_snapshot(); // Take a snapshot of known addresses per wallet so we can scope DB updates - let wallets_guard = self.wallets.read().unwrap(); + let wallets_guard = self.wallets.read_or_recover(); for (seed_hash, wallet_id) in mapping.iter() { // Log total balance for visibility @@ -849,7 +850,7 @@ impl AppContext { // Get the wallet's known addresses (only update those to avoid cross-wallet churn) let mut known_addresses: std::collections::BTreeSet = { - let w = wallet_arc.read().unwrap(); + let w = wallet_arc.read_or_recover(); w.known_addresses.keys().cloned().collect() }; @@ -1150,14 +1151,14 @@ impl AppContext { /// Fetches all local qualified identities from the database pub fn load_local_qualified_identities(&self) -> Result> { - let wallets = self.wallets.read().unwrap(); + let wallets = self.wallets.read_or_recover(); self.db.get_local_qualified_identities(self, &wallets) } /// Fetches all local qualified identities from the database #[allow(dead_code)] // May be used for loading identities in wallets pub fn load_local_qualified_identities_in_wallets(&self) -> Result> { - let wallets = self.wallets.read().unwrap(); + let wallets = self.wallets.read_or_recover(); self.db .get_local_qualified_identities_in_wallets(self, &wallets) } @@ -1166,7 +1167,7 @@ impl AppContext { &self, identity_id: &Identifier, ) -> Result> { - let wallets = self.wallets.read().unwrap(); + let wallets = self.wallets.read_or_recover(); // Get the identity from the database let result = self.db.get_identity_by_id(identity_id, self, &wallets)?; @@ -1214,7 +1215,7 @@ impl AppContext { identity: &mut QualifiedIdentity, wallet_hashes: &[WalletSeedHash], ) -> Result<()> { - let wallets = self.wallets.read().unwrap(); + let wallets = self.wallets.read_or_recover(); for wallet_hash in wallet_hashes { if let Some(wallet) = wallets.get(wallet_hash) { identity @@ -1277,7 +1278,7 @@ impl AppContext { /// Fetches the local identities from the database and then maps them to their DPNS names. pub fn local_dpns_names(&self) -> Result> { - let wallets = self.wallets.read().unwrap(); + let wallets = self.wallets.read_or_recover(); let qualified_identities = self.db.get_local_qualified_identities(self, &wallets)?; // Map each identity's DPNS names to (Identifier, DPNSNameInfo) tuples @@ -1343,7 +1344,7 @@ impl AppContext { /// until the database operation is complete. This ensures atomicity and prevents /// race conditions regardless of whether the database operation succeeds or fails. pub fn invalidate_settings_cache(&'_ self) -> SettingsCacheGuard<'_> { - let mut guard = self.cached_settings.write().unwrap(); + let mut guard = self.cached_settings.write_or_recover(); *guard = None; guard } @@ -1359,7 +1360,7 @@ impl AppContext { pub fn get_settings(&self) -> Result> { // First, try to read from cache { - let cache = self.cached_settings.read().unwrap(); + let cache = self.cached_settings.read_or_recover(); if let Some(ref settings) = *cache { return Ok(Some(settings.clone())); } @@ -1370,7 +1371,7 @@ impl AppContext { // Update cache with the fresh data { - let mut cache = self.cached_settings.write().unwrap(); + let mut cache = self.cached_settings.write_or_recover(); *cache = settings.clone(); } @@ -1473,9 +1474,9 @@ impl AppContext { let mut wallet_outpoints = Vec::new(); // Identify the wallets associated with the transaction - let wallets = self.wallets.read().unwrap(); + let wallets = self.wallets.read_or_recover(); for wallet_arc in wallets.values() { - let mut wallet = wallet_arc.write().unwrap(); + let mut wallet = wallet_arc.write_or_recover(); for (vout, tx_out) in tx.output.iter().enumerate() { let address = if let Ok(output_addr) = Address::from_script(&tx_out.script_pubkey, self.network) @@ -1591,7 +1592,7 @@ impl AppContext { }; { - let mut transactions = self.transactions_waiting_for_finality.lock().unwrap(); + let mut transactions = self.transactions_waiting_for_finality.lock_or_recover(); if let Some(asset_lock_proof) = transactions.get_mut(&tx.txid()) { *asset_lock_proof = proof.clone(); @@ -1599,9 +1600,9 @@ impl AppContext { } // Identify the wallet associated with the transaction - let wallets = self.wallets.read().unwrap(); + let wallets = self.wallets.read_or_recover(); for wallet_arc in wallets.values() { - let mut wallet = wallet_arc.write().unwrap(); + let mut wallet = wallet_arc.write_or_recover(); // Check if any of the addresses in the transaction outputs match the wallet's known addresses let matches_wallet = payload.credit_outputs.iter().any(|tx_out| { diff --git a/src/database/asset_lock_transaction.rs b/src/database/asset_lock_transaction.rs index f3d6f44c8..8bf83ccf0 100644 --- a/src/database/asset_lock_transaction.rs +++ b/src/database/asset_lock_transaction.rs @@ -1,5 +1,6 @@ use crate::context::AppContext; use crate::database::Database; +use crate::lock_helper::MutexExt; use dash_sdk::dpp::dashcore::hashes::Hash; use dash_sdk::dpp::dashcore::{ InstantLock, Network, Transaction, @@ -22,7 +23,7 @@ impl Database { let islock_bytes = islock.map(serialize); - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let sql = " INSERT INTO asset_lock_transaction (tx_id, transaction_data, amount, instant_lock_data, wallet, network) @@ -56,7 +57,7 @@ impl Database { &self, txid: &[u8; 32], ) -> rusqlite::Result, [u8; 32], String)>> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare( "SELECT transaction_data, amount, instant_lock_data, wallet, network FROM asset_lock_transaction WHERE tx_id = ?1", @@ -96,7 +97,7 @@ impl Database { txid: &[u8; 32], chain_locked_height: Option, ) -> rusqlite::Result<()> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); conn.execute( "UPDATE asset_lock_transaction SET chain_locked_height = ?1 WHERE tx_id = ?2", @@ -112,7 +113,7 @@ impl Database { tx_id: &[u8; 32], identity_id: &[u8; 32], ) -> rusqlite::Result<()> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let rows_updated = conn.execute( "UPDATE asset_lock_transaction @@ -155,7 +156,7 @@ impl Database { } let network = app_context.network.to_string(); - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); conn.execute( "UPDATE asset_lock_transaction @@ -174,7 +175,7 @@ impl Database { txid: &[u8; 32], identity_id: &[u8; 32], ) -> rusqlite::Result<()> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); conn.execute( "UPDATE asset_lock_transaction SET identity_id_potentially_in_creation = ?1 WHERE tx_id = ?2", @@ -186,7 +187,7 @@ impl Database { /// Deletes an asset lock transaction by its transaction ID (as bytes). pub fn delete_asset_lock_transaction(&self, txid: &[u8; 32]) -> rusqlite::Result<()> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); conn.execute( "DELETE FROM asset_lock_transaction WHERE tx_id = ?1", @@ -212,7 +213,7 @@ impl Database { [u8; 32], )>, > { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare( "SELECT transaction_data, amount, instant_lock_data, chain_locked_height, identity_id, wallet, network FROM asset_lock_transaction where network = ?", @@ -271,7 +272,7 @@ impl Database { String, )>, > { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare( "SELECT transaction_data, amount, instant_lock_data, chain_locked_height, wallet, network FROM asset_lock_transaction WHERE identity_id = ?1", diff --git a/src/database/contacts.rs b/src/database/contacts.rs index 8787b9081..3b88d6573 100644 --- a/src/database/contacts.rs +++ b/src/database/contacts.rs @@ -1,3 +1,4 @@ +use crate::lock_helper::MutexExt; use dash_sdk::platform::Identifier; use rusqlite::{Connection, params}; @@ -60,7 +61,7 @@ impl crate::database::Database { owner_identity_id: &Identifier, contact_identity_id: &Identifier, ) -> rusqlite::Result<(String, String, bool)> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare( "SELECT nickname, notes, is_hidden FROM contact_private_info WHERE owner_identity_id = ?1 AND contact_identity_id = ?2", @@ -91,7 +92,7 @@ impl crate::database::Database { &self, owner_identity_id: &Identifier, ) -> rusqlite::Result> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare( "SELECT owner_identity_id, contact_identity_id, nickname, notes, is_hidden FROM contact_private_info diff --git a/src/database/contested_names.rs b/src/database/contested_names.rs index ee09b3bc9..c7583fa31 100644 --- a/src/database/contested_names.rs +++ b/src/database/contested_names.rs @@ -1,5 +1,6 @@ use crate::context::AppContext; use crate::database::Database; +use crate::lock_helper::MutexExt; use crate::model::contested_name::{ContestState, Contestant, ContestedName}; use dash_sdk::dpp::dashcore::Network; use dash_sdk::dpp::data_contract::document_type::DocumentTypeRef; @@ -23,7 +24,7 @@ impl Database { } else { Duration::from_secs(60 * 90) }; - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare( "SELECT cn.normalized_contested_name, @@ -155,8 +156,11 @@ impl Database { } else { Duration::from_secs(60 * 90) }; - let current_timestamp = std::time::UNIX_EPOCH.elapsed().unwrap().as_millis() as u64; - let conn = self.conn.lock().unwrap(); + let current_timestamp = std::time::UNIX_EPOCH + .elapsed() + .unwrap_or_default() + .as_millis() as u64; + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare( "SELECT cn.normalized_contested_name, @@ -288,7 +292,7 @@ impl Database { let network = app_context.network.to_string(); // Check if the contested name already exists and get the current values if it does - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare( "SELECT locked_votes, abstain_votes, awarded_to, ending_time FROM contested_name @@ -378,7 +382,7 @@ impl Database { match winner { ContestedDocumentVotePollWinnerInfo::NoWinner => {} ContestedDocumentVotePollWinnerInfo::WonByIdentity(won_by) => { - let mut conn = self.conn.lock().unwrap(); + let mut conn = self.conn.lock_or_recover(); // Start a transaction let tx = conn.transaction()?; tx.execute( @@ -396,7 +400,7 @@ impl Database { tx.commit()?; } ContestedDocumentVotePollWinnerInfo::Locked => { - let mut conn = self.conn.lock().unwrap(); + let mut conn = self.conn.lock_or_recover(); // Start a transaction let tx = conn.transaction()?; tx.execute( @@ -415,7 +419,7 @@ impl Database { } return Ok(()); } - let mut conn = self.conn.lock().unwrap(); + let mut conn = self.conn.lock_or_recover(); let locked_votes = contenders.lock_vote_tally.unwrap_or(0) as i64; let abstain_votes = contenders.abstain_vote_tally.unwrap_or(0) as i64; @@ -532,7 +536,7 @@ impl Database { ) -> Result<()> { let network = app_context.network.to_string(); // Check if the contestant already exists and get the current values if it does - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare( "SELECT name, info, votes FROM contestant @@ -599,7 +603,7 @@ impl Database { app_context: &AppContext, ) -> Result> { let network = app_context.network.to_string(); - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut names_to_be_updated: Vec<(String, Option)> = Vec::new(); let mut new_names: Vec = Vec::new(); @@ -684,7 +688,7 @@ impl Database { I: IntoIterator, { let network = app_context.network.to_string(); - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); // Prepare statement for selecting existing entries let select_query = "SELECT end_time @@ -729,7 +733,7 @@ impl Database { vote_strength: u64, vote_choice: ResourceVoteChoice, ) -> Result<()> { - let mut conn = self.conn.lock().unwrap(); + let mut conn = self.conn.lock_or_recover(); let tx = conn.transaction()?; match vote_choice { diff --git a/src/database/contracts.rs b/src/database/contracts.rs index 9144f5915..118e98d99 100644 --- a/src/database/contracts.rs +++ b/src/database/contracts.rs @@ -1,5 +1,6 @@ use crate::context::AppContext; use crate::database::Database; +use crate::lock_helper::MutexExt; use crate::model::qualified_contract::QualifiedContract; use bincode::config; use dash_sdk::dpp::dashcore::Network; @@ -94,7 +95,7 @@ impl Database { let network = app_context.network.to_string(); // Query the contract by ID - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare( "SELECT contract, alias FROM contract WHERE contract_id = ? AND network = ?", )?; @@ -139,7 +140,7 @@ impl Database { let network = app_context.network.to_string(); // Query the contract by ID - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare("SELECT contract FROM contract WHERE contract_id = ? AND network = ?")?; @@ -182,7 +183,7 @@ impl Database { // Get the existing contract alias (if any) let existing_alias = { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); conn.query_row( "SELECT alias FROM contract WHERE contract_id = ? AND network = ?", params![contract_id.to_vec(), network.clone()], @@ -217,7 +218,7 @@ impl Database { let network = app_context.network.to_string(); // Query the contract by alias and network - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare("SELECT contract, alias FROM contract WHERE alias = ? AND network = ?")?; @@ -269,7 +270,7 @@ impl Database { query.push_str(" OFFSET ?"); } - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare(&query)?; // Store the limit and offset in variables to extend their lifetimes @@ -352,7 +353,7 @@ impl Database { } let network = app_context.network.to_string(); - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); // Delete tokens and cascade deletions in related tables due to foreign keys conn.execute("DELETE FROM contract WHERE network = ?", params![network])?; @@ -367,7 +368,7 @@ impl Database { new_alias: Option<&str>, ) -> rusqlite::Result<()> { let id = identifier.to_vec(); - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let rows_updated = conn.execute( "UPDATE contract SET alias = ? WHERE contract_id = ?", @@ -384,7 +385,7 @@ impl Database { #[allow(dead_code)] // May be used for retrieving user-friendly contract names pub fn get_contract_alias(&self, identifier: &Identifier) -> rusqlite::Result> { let id = identifier.to_vec(); - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare("SELECT alias FROM contract WHERE contract_id = ?")?; let alias: Option = stmt.query_row(params![id], |row| row.get(0)).ok(); diff --git a/src/database/dashpay.rs b/src/database/dashpay.rs index 84625e23f..43c302d95 100644 --- a/src/database/dashpay.rs +++ b/src/database/dashpay.rs @@ -1,3 +1,4 @@ +use crate::lock_helper::MutexExt; use dash_sdk::platform::Identifier; use rusqlite::params; use serde::{Deserialize, Serialize}; @@ -283,7 +284,7 @@ impl crate::database::Database { identity_id: &Identifier, network: &str, ) -> rusqlite::Result> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare( "SELECT identity_id, display_name, bio, avatar_url, avatar_hash, @@ -356,7 +357,7 @@ impl crate::database::Database { owner_identity_id: &Identifier, network: &str, ) -> rusqlite::Result> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare( "SELECT owner_identity_id, contact_identity_id, username, display_name, avatar_url, public_message, contact_status, created_at, updated_at, last_seen @@ -443,7 +444,7 @@ impl crate::database::Database { VALUES (?1, ?2, ?3, ?4, ?5, ?6) "; - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); conn.execute( sql, params![ @@ -480,7 +481,7 @@ impl crate::database::Database { network: &str, request_type: &str, ) -> rusqlite::Result> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let sql = if request_type == "sent" { "SELECT id, from_identity_id, to_identity_id, to_username, account_label, request_type, status, created_at, responded_at, expires_at @@ -533,7 +534,7 @@ impl crate::database::Database { VALUES (?1, ?2, ?3, ?4, ?5, ?6) "; - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); conn.execute( sql, params![ @@ -569,7 +570,7 @@ impl crate::database::Database { identity_id: &Identifier, limit: u32, ) -> rusqlite::Result> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare( "SELECT id, tx_id, from_identity_id, to_identity_id, amount, memo, payment_type, status, created_at, confirmed_at @@ -651,7 +652,7 @@ impl crate::database::Database { owner_identity_id: &Identifier, contact_identity_id: &Identifier, ) -> rusqlite::Result { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); // Try to get existing entry let mut stmt = conn.prepare( @@ -701,7 +702,7 @@ impl crate::database::Database { owner_identity_id: &Identifier, contact_identity_id: &Identifier, ) -> rusqlite::Result { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); // First, ensure the row exists with default values if it doesn't let init_sql = " @@ -798,7 +799,7 @@ impl crate::database::Database { &self, owner_identity_id: &Identifier, ) -> rusqlite::Result> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare( "SELECT owner_identity_id, contact_identity_id, next_send_index, highest_receive_index, bloom_registered_count @@ -856,7 +857,7 @@ impl crate::database::Database { &self, address: &dash_sdk::dpp::dashcore::Address, ) -> rusqlite::Result> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare( "SELECT owner_identity_id, contact_identity_id, address_index FROM dashpay_address_mappings @@ -888,7 +889,7 @@ impl crate::database::Database { &self, owner_identity_id: &Identifier, ) -> rusqlite::Result> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare( "SELECT address, contact_identity_id, address_index FROM dashpay_address_mappings diff --git a/src/database/identities.rs b/src/database/identities.rs index 524075f4f..81f44bba6 100644 --- a/src/database/identities.rs +++ b/src/database/identities.rs @@ -1,5 +1,6 @@ use crate::context::AppContext; use crate::database::Database; +use crate::lock_helper::MutexExt; use crate::model::qualified_identity::{IdentityStatus, QualifiedIdentity}; use crate::model::wallet::{Wallet, WalletSeedHash}; use dash_sdk::dpp::dashcore::Network; @@ -17,7 +18,7 @@ impl Database { new_alias: Option<&str>, ) -> rusqlite::Result<()> { let id = identifier.to_vec(); - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let rows_updated = conn.execute( "UPDATE identity SET alias = ? WHERE id = ?", @@ -33,7 +34,7 @@ impl Database { pub fn get_identity_alias(&self, identifier: &Identifier) -> rusqlite::Result> { let id = identifier.to_vec(); - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare("SELECT alias FROM identity WHERE id = ?")?; let alias: Option = stmt.query_row(params![id], |row| row.get(0)).ok(); @@ -130,7 +131,7 @@ impl Database { let network = app_context.network.to_string(); // Check if the identity already exists - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare("SELECT COUNT(*) FROM identity WHERE id = ? AND network = ?")?; let count: i64 = stmt.query_row(params![id, network], |row| row.get(0))?; @@ -154,7 +155,7 @@ impl Database { ) -> rusqlite::Result> { let network = app_context.network.to_string(); - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); // Prepare the main statement to select identities, including wallet_index let mut stmt = conn.prepare( @@ -214,7 +215,7 @@ impl Database { ) -> rusqlite::Result> { let network = app_context.network.to_string(); - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); // Prepare the main statement to select identities, including wallet_index let mut stmt = conn.prepare( @@ -270,7 +271,7 @@ impl Database { ) -> rusqlite::Result> { let network = app_context.network.to_string(); - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); // Prepare the main statement to select identities, including wallet_index let mut stmt = conn.prepare( @@ -324,7 +325,7 @@ impl Database { ) -> rusqlite::Result> { let network = app_context.network.to_string(); - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare( "SELECT data FROM identity WHERE is_local = 1 AND network = ? AND identity_type != 'User' AND data IS NOT NULL", )?; @@ -350,7 +351,7 @@ impl Database { ) -> rusqlite::Result)>> { let network = app_context.network.to_string(); - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare( "SELECT data,wallet FROM identity WHERE is_local = 1 AND network = ? AND identity_type = 'User' AND data IS NOT NULL", )?; @@ -377,7 +378,7 @@ impl Database { let id = identifier.to_vec(); let network = app_context.network.to_string(); - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); // Perform the deletion only if the identity is marked as local conn.execute( @@ -411,7 +412,7 @@ impl Database { } let network = app_context.network.to_string(); - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); // Perform the deletion only if the identity is marked as local conn.execute( @@ -449,7 +450,7 @@ impl Database { /// Saves the user’s custom identity order (the entire list). /// This method overwrites whatever was there before. pub fn save_identity_order(&self, all_ids: Vec) -> rusqlite::Result<()> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let tx = conn.unchecked_transaction()?; // Clear existing rows @@ -472,7 +473,7 @@ impl Database { /// Loads the user’s custom identity order (the entire list). /// If an identity in the order doesn't exist in the identity table, it is removed. pub fn load_identity_order(&self) -> rusqlite::Result> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); // Read all rows sorted by pos let mut stmt = conn.prepare("SELECT identity_id FROM identity_order ORDER BY pos ASC")?; diff --git a/src/database/initialization.rs b/src/database/initialization.rs index b09c7b071..a1f1dc10c 100644 --- a/src/database/initialization.rs +++ b/src/database/initialization.rs @@ -1,4 +1,5 @@ use crate::database::Database; +use crate::lock_helper::MutexExt; use chrono::Utc; use rusqlite::{Connection, params}; use std::fs; @@ -14,7 +15,7 @@ impl Database { // created with an older schema. This must happen before any queries that // depend on these columns (like db_schema_version which needs database_version). { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); // Check if settings table exists before trying to ensure columns let settings_exists: bool = conn.query_row( "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='settings'", @@ -171,10 +172,7 @@ impl Database { original_version, to_version )), std::cmp::Ordering::Less => { - let mut conn = self - .conn - .lock() - .expect("Failed to lock database connection"); + let mut conn = self.conn.lock_or_recover(); for version in (original_version + 1)..=to_version { let tx = conn.transaction().map_err(|e| e.to_string())?; @@ -191,7 +189,7 @@ impl Database { /// Checks if the `settings` table is empty or missing, indicating a first-time setup. fn is_first_time_setup(&self) -> rusqlite::Result { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); // Check if the `settings` table exists by querying `sqlite_master` let table_exists: bool = conn.query_row( @@ -219,7 +217,7 @@ impl Database { /// This is to allow the app to detect when database version is too high and to prevent /// the app from running with an unsupported database version. fn db_schema_version(&self) -> rusqlite::Result { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let result: rusqlite::Result = conn.query_row( "SELECT database_version FROM settings WHERE id = 1", [], @@ -265,7 +263,7 @@ impl Database { /// Creates all required tables with indexes if they don't already exist. fn create_tables(&self) -> rusqlite::Result<()> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); // Create the settings table conn.execute( "CREATE TABLE IF NOT EXISTS settings ( diff --git a/src/database/mod.rs b/src/database/mod.rs index c719d0bb7..deecdea53 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -16,6 +16,7 @@ mod top_ups; mod utxo; mod wallet; +use crate::lock_helper::MutexExt; use dash_sdk::dpp::dashcore::Network; use rusqlite::{Connection, Params}; use std::sync::Mutex; @@ -34,14 +35,14 @@ impl Database { } pub fn execute(&self, sql: &str, params: P) -> rusqlite::Result { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); conn.execute(sql, params) } /// Removes all application data tied to a specific Dash network. pub fn clear_network_data(&self, network: Network) -> rusqlite::Result<()> { let network_str = network.to_string(); - let mut conn = self.conn.lock().unwrap(); + let mut conn = self.conn.lock_or_recover(); let tx = conn.transaction()?; // Remove DashPay/contact data referencing identities from this network. diff --git a/src/database/proof_log.rs b/src/database/proof_log.rs index 672523aa3..57ad1189d 100644 --- a/src/database/proof_log.rs +++ b/src/database/proof_log.rs @@ -1,4 +1,5 @@ use crate::database::Database; +use crate::lock_helper::MutexExt; use crate::model::proof_log_item::{ProofLogItem, RequestType}; use rusqlite::params; use std::ops::Range; @@ -55,7 +56,7 @@ impl Database { /// Inserts a new ProofLogItem into the proof_log table pub fn insert_proof_log_item(&self, item: ProofLogItem) -> rusqlite::Result<()> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); // Convert RequestType to u8 let request_type_int: u8 = item.request_type.into(); @@ -83,7 +84,7 @@ impl Database { only_get_errored: bool, range: Range, ) -> rusqlite::Result> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); // Build the query based on the only_get_errored flag let mut query = String::from( diff --git a/src/database/scheduled_votes.rs b/src/database/scheduled_votes.rs index 51c841d18..b4e5e878d 100644 --- a/src/database/scheduled_votes.rs +++ b/src/database/scheduled_votes.rs @@ -1,3 +1,4 @@ +use crate::lock_helper::MutexExt; use crate::{ backend_task::contested_names::ScheduledDPNSVote, context::AppContext, database::Database, }; @@ -97,7 +98,7 @@ impl Database { votes: &Vec, ) -> rusqlite::Result<()> { let network = app_context.network.to_string(); - let mut conn = self.conn.lock().unwrap(); + let mut conn = self.conn.lock_or_recover(); let tx = conn.transaction()?; for vote in votes { let vote_choice = vote.choice.to_string(); @@ -117,7 +118,7 @@ impl Database { contested_name: &str, ) -> rusqlite::Result<()> { let network = app_context.network.to_string(); - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); conn.execute( "DELETE FROM scheduled_votes WHERE identity_id = ? AND contested_name = ? AND network = ?", params![identity_id, contested_name, network], @@ -145,7 +146,7 @@ impl Database { ) -> rusqlite::Result> { let network = app_context.network.to_string(); - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare("SELECT * FROM scheduled_votes WHERE network = ?")?; let votes_iter = stmt.query_map(params![network], |row| { let voter_id_bytes: Vec = row.get(0)?; @@ -209,7 +210,7 @@ impl Database { /// Clear all scheduled votes from the db pub fn clear_all_scheduled_votes(&self, app_context: &AppContext) -> rusqlite::Result<()> { let network = app_context.network.to_string(); - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); conn.execute( "DELETE FROM scheduled_votes WHERE network = ?", @@ -221,7 +222,7 @@ impl Database { pub fn clear_executed_scheduled_votes(&self, app_context: &AppContext) -> rusqlite::Result<()> { let network = app_context.network.to_string(); - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); conn.execute( "DELETE FROM scheduled_votes WHERE executed = 1 AND network = ?", diff --git a/src/database/settings.rs b/src/database/settings.rs index 1853eeb4f..7866f3a9d 100644 --- a/src/database/settings.rs +++ b/src/database/settings.rs @@ -1,5 +1,6 @@ use crate::database::Database; use crate::database::initialization::DEFAULT_DB_VERSION; +use crate::lock_helper::MutexExt; use crate::model::password_info::PasswordInfo; use crate::model::settings::UserMode; use crate::ui::RootScreenType; @@ -337,7 +338,7 @@ impl Database { /// Gets the use_local_spv_node flag from the settings table. pub fn get_use_local_spv_node(&self) -> Result { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let result: Option = conn.query_row( "SELECT use_local_spv_node FROM settings WHERE id = 1", [], @@ -357,7 +358,7 @@ impl Database { /// Gets the auto_start_spv flag from the settings table. pub fn get_auto_start_spv(&self) -> Result { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let result: Option = conn.query_row( "SELECT auto_start_spv FROM settings WHERE id = 1", [], @@ -396,7 +397,7 @@ impl Database { /// Gets the close_dash_qt_on_exit flag from the settings table. pub fn get_close_dash_qt_on_exit(&self) -> Result { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let result: Option = conn.query_row( "SELECT close_dash_qt_on_exit FROM settings WHERE id = 1", [], @@ -469,7 +470,7 @@ impl Database { /// Gets the selected wallet hashes from the settings table. /// Returns (selected_wallet_hash, selected_single_key_hash). pub fn get_selected_wallet_hashes(&self) -> Result { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let result = conn.query_row( "SELECT selected_wallet_hash, selected_single_key_hash FROM settings WHERE id = 1", [], @@ -550,7 +551,7 @@ impl Database { )>, > { // Query the settings row - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare( "SELECT network, start_root_screen, password_check, main_password_salt, main_password_nonce, custom_dash_qt_path, overwrite_dash_conf, disable_zmq, theme_preference, core_backend_mode, onboarding_completed, show_evonode_tools, user_mode, close_dash_qt_on_exit FROM settings WHERE id = 1", )?; diff --git a/src/database/single_key_wallet.rs b/src/database/single_key_wallet.rs index 372bcfdb9..63ca95a41 100644 --- a/src/database/single_key_wallet.rs +++ b/src/database/single_key_wallet.rs @@ -1,6 +1,7 @@ //! Database operations for single key wallets use crate::database::Database; +use crate::lock_helper::MutexExt; use crate::model::wallet::single_key::{ ClosedSingleKey, SingleKeyData, SingleKeyHash, SingleKeyWallet, }; @@ -44,7 +45,7 @@ impl Database { wallet: &SingleKeyWallet, network: Network, ) -> rusqlite::Result<()> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); conn.execute( "INSERT OR REPLACE INTO single_key_wallet ( key_hash, @@ -84,7 +85,7 @@ impl Database { network: Network, ) -> rusqlite::Result> { let mut wallets = { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare( "SELECT key_hash, @@ -216,7 +217,7 @@ impl Database { key_hash: &SingleKeyHash, network: Network, ) -> rusqlite::Result<()> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); conn.execute( "DELETE FROM single_key_wallet WHERE key_hash = ?1 AND network = ?2", params![key_hash.as_slice(), network.to_string()], @@ -232,7 +233,7 @@ impl Database { unconfirmed_balance: u64, total_balance: u64, ) -> rusqlite::Result<()> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); conn.execute( "UPDATE single_key_wallet SET confirmed_balance = ?1, @@ -255,7 +256,7 @@ impl Database { key_hash: &SingleKeyHash, alias: Option<&str>, ) -> rusqlite::Result<()> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); conn.execute( "UPDATE single_key_wallet SET alias = ?1 WHERE key_hash = ?2", params![alias, key_hash.as_slice()], diff --git a/src/database/tokens.rs b/src/database/tokens.rs index 279147d44..177cd5d40 100644 --- a/src/database/tokens.rs +++ b/src/database/tokens.rs @@ -10,6 +10,7 @@ use rusqlite::OptionalExtension; use rusqlite::params; use super::Database; +use crate::lock_helper::{MutexExt, RwLockExt}; use crate::ui::tokens::tokens_screen::{ IdentityTokenIdentifier, TokenInfo, TokenInfoWithDataContract, }; @@ -43,7 +44,7 @@ impl Database { let network = app_context.network.to_string(); let token_id_bytes = token_id.to_vec(); - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare( "SELECT token_config FROM token @@ -73,7 +74,7 @@ impl Database { let network = app_context.network.to_string(); let token_id_bytes = token_id.to_vec(); - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare( "SELECT data_contract_id FROM token @@ -128,7 +129,7 @@ impl Database { )?; // Insert an identity token balance of 0 for each identity for this token - let wallets = app_context.wallets.read().unwrap(); + let wallets = app_context.wallets.read_or_recover(); for identity in self.get_local_qualified_identities(app_context, &wallets)? { self.insert_identity_token_balance(token_id, &identity.identity.id(), 0, app_context)?; } @@ -197,7 +198,7 @@ impl Database { app_context: &AppContext, ) -> rusqlite::Result> { let network = app_context.network.to_string(); - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare( "SELECT @@ -276,7 +277,7 @@ impl Database { app_context: &AppContext, ) -> rusqlite::Result> { let network = app_context.network.to_string(); - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); // -- 1. query id / alias / config / contract / position ──────────────── let mut stmt = conn.prepare( @@ -338,7 +339,7 @@ impl Database { let network = app_context.network.to_string(); let rows_data = { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let mut stmt = conn.prepare( "SELECT b.token_id, t.token_alias, t.token_config, b.identity_id, b.balance, t.data_contract_id, t.token_position FROM identity_token_balances AS b @@ -480,7 +481,7 @@ impl Database { /// Saves the user’s custom identity order (the entire list). /// This method overwrites whatever was there before. pub fn save_token_order(&self, all_ids: Vec<(Identifier, Identifier)>) -> rusqlite::Result<()> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let tx = conn.unchecked_transaction()?; // Clear existing rows @@ -504,7 +505,7 @@ impl Database { /// Loads the custom identity order from the DB, returning a list of Identifiers in the stored order. /// If there's no data, returns an empty Vec. pub fn load_token_order(&self) -> rusqlite::Result> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); // Read all rows sorted by pos let mut stmt = conn.prepare( @@ -556,7 +557,7 @@ impl Database { } let network = app_context.network.to_string(); - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); // Delete tokens and cascade deletions in related tables due to foreign keys conn.execute("DELETE FROM token WHERE network = ?", params![network])?; diff --git a/src/database/top_ups.rs b/src/database/top_ups.rs index a1ab9654c..f97bb4b2b 100644 --- a/src/database/top_ups.rs +++ b/src/database/top_ups.rs @@ -1,4 +1,5 @@ use crate::database::Database; +use crate::lock_helper::MutexExt; use rusqlite::{OptionalExtension, params}; impl Database { @@ -19,7 +20,7 @@ impl Database { #[allow(dead_code)] // May be used for generating sequential top-up indices pub fn get_next_top_up_index(&self, identity_id: &[u8]) -> rusqlite::Result { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let max_index: Option = conn .query_row( "SELECT MAX(top_up_index) FROM top_up WHERE identity_id = ?", diff --git a/src/database/wallet.rs b/src/database/wallet.rs index 92226981d..db0b9e09b 100644 --- a/src/database/wallet.rs +++ b/src/database/wallet.rs @@ -1,4 +1,5 @@ use crate::database::Database; +use crate::lock_helper::MutexExt; use crate::model::qualified_identity::QualifiedIdentity; use crate::model::wallet::{ AddressInfo, ClosedKeyItem, DerivationPathReference, DerivationPathType, OpenWalletSeed, @@ -61,7 +62,7 @@ impl Database { seed_hash: &[u8; 32], new_alias: Option, ) -> rusqlite::Result<()> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); conn.execute( "UPDATE wallet SET alias = ? WHERE seed_hash = ?", @@ -77,7 +78,7 @@ impl Database { /// to keep the database consistent before deleting the wallet itself. pub fn remove_wallet(&self, seed_hash: &[u8; 32], network: &Network) -> rusqlite::Result<()> { let network_str = network.to_string(); - let mut conn = self.conn.lock().unwrap(); + let mut conn = self.conn.lock_or_recover(); let tx = conn.transaction()?; let mut address_stmt = @@ -138,7 +139,7 @@ impl Database { path_type: DerivationPathType, balance: Option, ) -> rusqlite::Result<()> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let address = check_address_for_network(address.as_unchecked().clone(), network) .expect("Expected address to be valid for network"); @@ -351,7 +352,7 @@ impl Database { network: &Network, transactions: &[WalletTransaction], ) -> rusqlite::Result<()> { - let mut conn = self.conn.lock().unwrap(); + let mut conn = self.conn.lock_or_recover(); let tx = conn.transaction()?; let network_str = network.to_string(); @@ -411,7 +412,7 @@ impl Database { /// Retrieve all wallets for a specific network, including their addresses, balances, and known addresses. pub fn get_wallets(&self, network: &Network) -> rusqlite::Result> { let network_str = network.to_string(); - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); tracing::trace!("step 1: retrieve all wallets for the given network"); let mut stmt = conn.prepare( @@ -964,7 +965,7 @@ impl Database { address: &Address, network: &Network, ) -> rusqlite::Result> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let network_str = network.to_string(); let canonical_address = Wallet::canonical_address(address, *network); let address_str = canonical_address.to_string(); @@ -993,7 +994,7 @@ impl Database { seed_hash: &[u8; 32], network: &Network, ) -> rusqlite::Result> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); let network_str = network.to_string(); let mut stmt = conn.prepare( @@ -1054,7 +1055,7 @@ impl Database { /// This removes both the addresses from wallet_addresses and their balances from platform_address_balances pub fn clear_all_platform_addresses(&self, network: &Network) -> rusqlite::Result { let network_str = network.to_string(); - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); // Delete from platform_address_balances conn.execute( @@ -1080,7 +1081,7 @@ impl Database { &self, seed_hash: &[u8; 32], ) -> rusqlite::Result<(u64, u64, u64)> { - let conn = self.conn.lock().unwrap(); + let conn = self.conn.lock_or_recover(); conn.query_row( "SELECT last_platform_full_sync, last_platform_sync_checkpoint, COALESCE(last_terminal_block, 0) FROM wallet WHERE seed_hash = ?", params![seed_hash], diff --git a/src/lib.rs b/src/lib.rs index 3ff4f42c8..b9705e4aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ pub mod context_provider; pub mod context_provider_spv; pub mod cpu_compatibility; pub mod database; +pub mod lock_helper; pub mod logging; pub mod model; pub mod sdk_wrapper; diff --git a/src/lock_helper.rs b/src/lock_helper.rs new file mode 100644 index 000000000..fd60bc73a --- /dev/null +++ b/src/lock_helper.rs @@ -0,0 +1,69 @@ +//! Lock poisoning recovery helpers. +//! +//! Provides extension traits for `Mutex` and `RwLock` that recover from +//! poison errors instead of panicking. When a lock is poisoned (because +//! another thread panicked while holding it), these helpers log a warning +//! and return the inner data, which is still valid for use. +//! +//! # Usage +//! +//! ```ignore +//! use crate::lock_helper::{MutexExt, RwLockExt}; +//! +//! let mutex = std::sync::Mutex::new(42); +//! let guard = mutex.lock_or_recover(); // never panics +//! +//! let rwlock = std::sync::RwLock::new(42); +//! let read = rwlock.read_or_recover(); // never panics +//! let write = rwlock.write_or_recover(); // never panics +//! ``` + +use std::sync::{Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard}; + +/// Extension trait for `Mutex` that recovers from poisoning. +pub trait MutexExt { + /// Lock the mutex, recovering from poisoning if necessary. + /// + /// If the lock is poisoned, logs a warning and returns the inner data. + fn lock_or_recover(&self) -> MutexGuard<'_, T>; +} + +impl MutexExt for Mutex { + fn lock_or_recover(&self) -> MutexGuard<'_, T> { + self.lock().unwrap_or_else(|poisoned| { + tracing::warn!("Mutex poisoned, recovering: {}", std::any::type_name::()); + poisoned.into_inner() + }) + } +} + +/// Extension trait for `RwLock` that recovers from poisoning. +pub trait RwLockExt { + /// Acquire a read lock, recovering from poisoning if necessary. + fn read_or_recover(&self) -> RwLockReadGuard<'_, T>; + + /// Acquire a write lock, recovering from poisoning if necessary. + fn write_or_recover(&self) -> RwLockWriteGuard<'_, T>; +} + +impl RwLockExt for RwLock { + fn read_or_recover(&self) -> RwLockReadGuard<'_, T> { + self.read().unwrap_or_else(|poisoned| { + tracing::warn!( + "RwLock poisoned (read), recovering: {}", + std::any::type_name::() + ); + poisoned.into_inner() + }) + } + + fn write_or_recover(&self) -> RwLockWriteGuard<'_, T> { + self.write().unwrap_or_else(|poisoned| { + tracing::warn!( + "RwLock poisoned (write), recovering: {}", + std::any::type_name::() + ); + poisoned.into_inner() + }) + } +} diff --git a/src/model/qualified_identity/qualified_identity_public_key.rs b/src/model/qualified_identity/qualified_identity_public_key.rs index c6d6193d3..06764db84 100644 --- a/src/model/qualified_identity/qualified_identity_public_key.rs +++ b/src/model/qualified_identity/qualified_identity_public_key.rs @@ -1,3 +1,4 @@ +use crate::lock_helper::RwLockExt; use crate::model::qualified_identity::encrypted_key_storage::WalletDerivationPath; use crate::model::wallet::Wallet; use bincode::{Decode, Encode}; @@ -67,7 +68,7 @@ impl QualifiedIdentityPublicKey { // Iterate over each wallet to check for matching derivation paths for locked_wallet in wallets { - let wallet = locked_wallet.read().unwrap(); + let wallet = locked_wallet.read_or_recover(); if let Some(derivation_path) = wallet.known_addresses.get(&address) { in_wallet_at_derivation_path = Some(WalletDerivationPath { wallet_seed_hash: wallet.seed_hash(), @@ -108,7 +109,7 @@ impl QualifiedIdentityPublicKey { // Iterate over each wallet to check for matching derivation paths for locked_wallet in wallets { - let wallet = locked_wallet.read().unwrap(); + let wallet = locked_wallet.read_or_recover(); if let Some(derivation_path) = wallet.known_addresses.get(&address) { in_wallet_at_derivation_path = Some(WalletDerivationPath { wallet_seed_hash: wallet.seed_hash(), @@ -158,7 +159,7 @@ impl QualifiedIdentityPublicKey { // Iterate over each wallet to check for matching derivation paths for locked_wallet in wallets { - let wallet = locked_wallet.read().unwrap(); + let wallet = locked_wallet.read_or_recover(); if let Some(derivation_path) = wallet.known_addresses.get(&address) { in_wallet_at_derivation_path = Some(WalletDerivationPath { wallet_seed_hash: wallet.seed_hash(), diff --git a/src/model/wallet/mod.rs b/src/model/wallet/mod.rs index f2c3209a9..47582b3e2 100644 --- a/src/model/wallet/mod.rs +++ b/src/model/wallet/mod.rs @@ -3,6 +3,7 @@ pub mod encryption; pub mod single_key; mod utxos; +use crate::lock_helper::RwLockExt; use dash_sdk::dpp::ProtocolError; use dash_sdk::dpp::address_funds::{AddressWitness, PlatformAddress}; use dash_sdk::dpp::identity::signer::Signer; @@ -607,7 +608,7 @@ impl Wallet { ) -> Option>> { for wallet in slice { // Attempt to read the wallet from the RwLock - let wallet_ref = wallet.read().unwrap(); + let wallet_ref = wallet.read_or_recover(); // Check if the wallet's seed hash matches the provided wallet_seed_hash if wallet_ref.seed_hash() == wallet_seed_hash { // Return a clone of the Arc> that matches @@ -626,7 +627,7 @@ impl Wallet { ) -> Result, String> { for wallet in slice { // Attempt to read the wallet from the RwLock - let wallet_ref = wallet.read().unwrap(); + let wallet_ref = wallet.read_or_recover(); // Check if this wallet's seed hash matches the target hash if wallet_ref.seed_hash() == wallet_seed_hash { // Attempt to derive the private key using the provided derivation path @@ -732,8 +733,7 @@ impl Wallet { let address = Address::p2pkh(&public_key, network); app_context .core_client - .read() - .expect("Core client lock was poisoned") + .read_or_recover() .import_address( &address, Some( diff --git a/src/ui/components/wallet_unlock.rs b/src/ui/components/wallet_unlock.rs index d1d999a26..0974e3f7b 100644 --- a/src/ui/components/wallet_unlock.rs +++ b/src/ui/components/wallet_unlock.rs @@ -1,4 +1,5 @@ use crate::context::AppContext; +use crate::lock_helper::RwLockExt; use crate::model::wallet::Wallet; use crate::ui::components::styled::StyledCheckbox; use eframe::epaint::Color32; @@ -23,7 +24,7 @@ pub trait ScreenWithWalletUnlock { fn should_ask_for_password(&mut self) -> bool { if let Some(wallet_guard) = self.selected_wallet_ref().clone() { - let mut wallet = wallet_guard.write().unwrap(); + let mut wallet = wallet_guard.write_or_recover(); if !wallet.uses_password { if let Err(e) = wallet.wallet_seed.open_no_password() { self.set_error_message(Some(e)); @@ -49,7 +50,7 @@ pub trait ScreenWithWalletUnlock { let mut unlocked_wallet: Option>> = None; if let Some(wallet_guard) = self.selected_wallet_ref().clone() { - let mut wallet = wallet_guard.write().unwrap(); + let mut wallet = wallet_guard.write_or_recover(); // Only render the unlock prompt if the wallet requires a password and is locked if wallet.uses_password && !wallet.is_open() { diff --git a/src/ui/components/wallet_unlock_popup.rs b/src/ui/components/wallet_unlock_popup.rs index 0f8d65a48..598e108b3 100644 --- a/src/ui/components/wallet_unlock_popup.rs +++ b/src/ui/components/wallet_unlock_popup.rs @@ -1,4 +1,5 @@ use crate::context::AppContext; +use crate::lock_helper::RwLockExt; use crate::model::wallet::Wallet; use crate::ui::components::styled::StyledCheckbox; use crate::ui::theme::{ComponentStyles, DashColors, Shape}; @@ -214,7 +215,7 @@ impl WalletUnlockPopup { // Attempt unlock if requested if attempt_unlock { - let mut wallet_guard = wallet.write().unwrap(); + let mut wallet_guard = wallet.write_or_recover(); match wallet_guard.wallet_seed.open(&self.password) { Ok(_) => { // Notify app context that wallet was unlocked @@ -255,13 +256,13 @@ impl WalletUnlockPopup { /// Helper function to check if a wallet needs unlocking pub fn wallet_needs_unlock(wallet: &Arc>) -> bool { - let wallet_guard = wallet.read().unwrap(); + let wallet_guard = wallet.read_or_recover(); wallet_guard.uses_password && !wallet_guard.is_open() } /// Helper function to try opening a wallet without password (for wallets that don't use passwords) pub fn try_open_wallet_no_password(wallet: &Arc>) -> Result<(), String> { - let mut wallet_guard = wallet.write().unwrap(); + let mut wallet_guard = wallet.write_or_recover(); if !wallet_guard.uses_password { wallet_guard.wallet_seed.open_no_password() } else { diff --git a/src/ui/dpns/dpns_contested_names_screen.rs b/src/ui/dpns/dpns_contested_names_screen.rs index 1b52d8f9a..6fa4cedc9 100644 --- a/src/ui/dpns/dpns_contested_names_screen.rs +++ b/src/ui/dpns/dpns_contested_names_screen.rs @@ -1,5 +1,6 @@ use std::sync::{Arc, Mutex}; +use crate::lock_helper::MutexExt; use chrono::{DateTime, LocalResult, TimeZone, Utc}; use chrono_humanize::HumanTime; use dash_sdk::dpp::identity::accessors::IdentityGettersV0; @@ -355,7 +356,7 @@ impl DPNSScreen { }); let contested_names = { - let guard = self.contested_names.lock().unwrap(); + let guard = self.contested_names.lock_or_recover(); let mut cn = guard.clone(); if !self.active_filter_term.is_empty() { let mut filter_lc = self.active_filter_term.to_lowercase(); @@ -679,7 +680,7 @@ impl DPNSScreen { }); let contested_names = { - let guard = self.contested_names.lock().unwrap(); + let guard = self.contested_names.lock_or_recover(); let mut cn = guard.clone(); cn.retain(|c| c.awarded_to.is_some() || c.state == ContestState::Locked); // 1) Filter by `past_filter_term` @@ -856,7 +857,7 @@ impl DPNSScreen { }); let mut filtered_names = { - let guard = self.local_dpns_names.lock().unwrap(); + let guard = self.local_dpns_names.lock_or_recover(); let mut name_infos = guard.clone(); if !self.owned_filter_term.is_empty() { let filter_lc = self.owned_filter_term.to_lowercase(); @@ -1002,7 +1003,7 @@ impl DPNSScreen { fn render_table_scheduled_votes(&mut self, ui: &mut Ui) -> AppAction { let mut action = AppAction::None; let mut sorted_votes = { - let guard = self.scheduled_votes.lock().unwrap(); + let guard = self.scheduled_votes.lock_or_recover(); guard.clone() }; // Sort by contested_name or time @@ -1778,9 +1779,9 @@ impl DPNSScreen { impl ScreenLike for DPNSScreen { fn refresh(&mut self) { self.scheduled_vote_cast_in_progress = false; - let mut contested_names = self.contested_names.lock().unwrap(); - let mut dpns_names = self.local_dpns_names.lock().unwrap(); - let mut scheduled_votes = self.scheduled_votes.lock().unwrap(); + let mut contested_names = self.contested_names.lock_or_recover(); + let mut dpns_names = self.local_dpns_names.lock_or_recover(); + let mut scheduled_votes = self.scheduled_votes.lock_or_recover(); match self.dpns_subscreen { DPNSSubscreen::Active => { @@ -1926,7 +1927,7 @@ impl ScreenLike for DPNSScreen { self.check_error_expiration(); let has_identity_that_can_register = !self.user_identities.is_empty(); let has_active_contests = { - let guard = self.contested_names.lock().unwrap(); + let guard = self.contested_names.lock_or_recover(); !guard.is_empty() }; @@ -2049,7 +2050,7 @@ impl ScreenLike for DPNSScreen { match self.dpns_subscreen { DPNSSubscreen::Active => { let has_any = { - let guard = self.contested_names.lock().unwrap(); + let guard = self.contested_names.lock_or_recover(); !guard.is_empty() }; if has_any { @@ -2060,7 +2061,7 @@ impl ScreenLike for DPNSScreen { } DPNSSubscreen::Past => { let has_any = { - let guard = self.contested_names.lock().unwrap(); + let guard = self.contested_names.lock_or_recover(); !guard.is_empty() }; if has_any { @@ -2071,7 +2072,7 @@ impl ScreenLike for DPNSScreen { } DPNSSubscreen::Owned => { let has_any = { - let guard = self.local_dpns_names.lock().unwrap(); + let guard = self.local_dpns_names.lock_or_recover(); !guard.is_empty() }; if has_any { @@ -2082,7 +2083,7 @@ impl ScreenLike for DPNSScreen { } DPNSSubscreen::ScheduledVotes => { let has_any = { - let guard = self.scheduled_votes.lock().unwrap(); + let guard = self.scheduled_votes.lock_or_recover(); !guard.is_empty() }; if has_any { diff --git a/src/ui/identities/add_new_identity_screen/by_platform_address.rs b/src/ui/identities/add_new_identity_screen/by_platform_address.rs index 72cee4d83..5c83bd0eb 100644 --- a/src/ui/identities/add_new_identity_screen/by_platform_address.rs +++ b/src/ui/identities/add_new_identity_screen/by_platform_address.rs @@ -1,4 +1,5 @@ use crate::app::AppAction; +use crate::lock_helper::RwLockExt; use crate::model::amount::Amount; use crate::model::fee_estimation::format_credits_as_dash; use crate::ui::components::amount_input::AmountInput; @@ -15,7 +16,7 @@ const CREDITS_PER_DUFF: u64 = 1000; impl AddNewIdentityScreen { fn show_platform_address_balance(&self, ui: &mut egui::Ui) { if let Some(selected_wallet) = &self.selected_wallet { - let wallet = selected_wallet.read().unwrap(); + let wallet = selected_wallet.read_or_recover(); let total_platform_balance: u64 = wallet .platform_address_info @@ -53,7 +54,7 @@ impl AddNewIdentityScreen { let network = self.app_context.network; let platform_addresses: Vec<(String, PlatformAddress, u64)> = if let Some(wallet_arc) = &self.selected_wallet { - let wallet = wallet_arc.read().unwrap(); + let wallet = wallet_arc.read_or_recover(); wallet .platform_addresses(network) .into_iter() @@ -208,7 +209,7 @@ impl AddNewIdentityScreen { ui.add_space(20.0); // Extract the step from the RwLock to minimize borrow scope - let step = *self.step.read().unwrap(); + let step = *self.step.read_or_recover(); // Display estimated fee before action button (reuse already calculated value) let dark_mode = ui.ctx().style().visuals.dark_mode; diff --git a/src/ui/identities/add_new_identity_screen/by_using_unused_asset_lock.rs b/src/ui/identities/add_new_identity_screen/by_using_unused_asset_lock.rs index faa61f56e..256679324 100644 --- a/src/ui/identities/add_new_identity_screen/by_using_unused_asset_lock.rs +++ b/src/ui/identities/add_new_identity_screen/by_using_unused_asset_lock.rs @@ -1,4 +1,5 @@ use crate::app::AppAction; +use crate::lock_helper::RwLockExt; use crate::model::fee_estimation::format_credits_as_dash; use crate::ui::identities::add_new_identity_screen::{ AddNewIdentityScreen, FundingMethod, WalletFundedScreenStep, @@ -14,7 +15,7 @@ impl AddNewIdentityScreen { }; // Read the wallet to access unused asset locks - let wallet = selected_wallet.read().unwrap(); + let wallet = selected_wallet.read_or_recover(); if wallet.unused_asset_locks.is_empty() { ui.label("No unused asset locks available."); @@ -71,7 +72,7 @@ impl AddNewIdentityScreen { )); // Update the step to ready to create identity - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::ReadyToCreate; } }); @@ -90,7 +91,7 @@ impl AddNewIdentityScreen { let mut action = AppAction::None; // Extract the step from the RwLock to minimize borrow scope - let step = *self.step.read().unwrap(); + let step = *self.step.read_or_recover(); ui.heading( format!( diff --git a/src/ui/identities/add_new_identity_screen/by_using_unused_balance.rs b/src/ui/identities/add_new_identity_screen/by_using_unused_balance.rs index e06a0a97c..4af01b516 100644 --- a/src/ui/identities/add_new_identity_screen/by_using_unused_balance.rs +++ b/src/ui/identities/add_new_identity_screen/by_using_unused_balance.rs @@ -1,4 +1,5 @@ use crate::app::AppAction; +use crate::lock_helper::RwLockExt; use crate::model::fee_estimation::format_credits_as_dash; use crate::ui::identities::add_new_identity_screen::{ AddNewIdentityScreen, FundingMethod, WalletFundedScreenStep, @@ -8,7 +9,7 @@ use egui::{Color32, RichText, Ui}; impl AddNewIdentityScreen { fn show_wallet_balance(&self, ui: &mut egui::Ui) { if let Some(selected_wallet) = &self.selected_wallet { - let wallet = selected_wallet.read().unwrap(); // Read lock on the wallet + let wallet = selected_wallet.read_or_recover(); // Read lock on the wallet let total_balance: u64 = wallet.total_balance_duffs(); // Use stored balance with UTXO fallback @@ -42,7 +43,7 @@ impl AddNewIdentityScreen { self.render_funding_amount_input(ui); // Extract the step from the RwLock to minimize borrow scope - let step = *self.step.read().unwrap(); + let step = *self.step.read_or_recover(); // Check if we have a valid amount before showing the button let has_valid_amount = self diff --git a/src/ui/identities/add_new_identity_screen/by_wallet_qr_code.rs b/src/ui/identities/add_new_identity_screen/by_wallet_qr_code.rs index 5c8b06a9a..4feba5f88 100644 --- a/src/ui/identities/add_new_identity_screen/by_wallet_qr_code.rs +++ b/src/ui/identities/add_new_identity_screen/by_wallet_qr_code.rs @@ -3,6 +3,7 @@ use crate::backend_task::BackendTask; use crate::backend_task::identity::{ IdentityRegistrationInfo, IdentityTask, RegisterIdentityFundingMethod, }; +use crate::lock_helper::RwLockExt; use crate::ui::identities::add_new_identity_screen::{ AddNewIdentityScreen, WalletFundedScreenStep, }; @@ -20,7 +21,7 @@ impl AddNewIdentityScreen { if let Some(wallet_guard) = self.selected_wallet.as_ref() { // Get the receive address if self.funding_address.is_none() { - let mut wallet = wallet_guard.write().unwrap(); + let mut wallet = wallet_guard.write_or_recover(); let receive_address = wallet.receive_address( self.app_context.network, true, @@ -31,8 +32,7 @@ impl AddNewIdentityScreen { if !has_address { self.app_context .core_client - .read() - .expect("Core client lock was poisoned") + .read_or_recover() .import_address( &receive_address, Some("Managed by Dash Evo Tool"), @@ -45,16 +45,14 @@ impl AddNewIdentityScreen { let info = self .app_context .core_client - .read() - .expect("Core client lock was poisoned") + .read_or_recover() .get_address_info(&receive_address) .map_err(|e| e.to_string())?; if !(info.is_watchonly || info.is_mine) { self.app_context .core_client - .read() - .expect("Core client lock was poisoned") + .read_or_recover() .import_address( &receive_address, Some("Managed by Dash Evo Tool"), @@ -129,7 +127,7 @@ impl AddNewIdentityScreen { } // Extract the step from the RwLock to minimize borrow scope - let step = *self.step.read().unwrap(); + let step = *self.step.read_or_recover(); ui.add_space(10.0); @@ -189,7 +187,7 @@ impl AddNewIdentityScreen { ), }; - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::WaitingForAssetLock; // Create the backend task to register the identity diff --git a/src/ui/identities/add_new_identity_screen/mod.rs b/src/ui/identities/add_new_identity_screen/mod.rs index db7301789..41aa37e0b 100644 --- a/src/ui/identities/add_new_identity_screen/mod.rs +++ b/src/ui/identities/add_new_identity_screen/mod.rs @@ -11,6 +11,7 @@ use crate::backend_task::identity::{ }; use crate::backend_task::{BackendTask, BackendTaskSuccessResult, FeeResult}; use crate::context::AppContext; +use crate::lock_helper::RwLockExt; use crate::model::wallet::Wallet; use crate::ui::components::info_popup::InfoPopup; use crate::ui::components::left_panel::add_left_panel; @@ -115,7 +116,7 @@ impl AddNewIdentityScreen { let mut selected_wallet = None; if app_context.has_wallet.load(Ordering::Relaxed) { - let wallets = &app_context.wallets.read().unwrap(); + let wallets = &app_context.wallets.read_or_recover(); // If a specific wallet seed hash is provided, use that wallet if let Some(seed_hash) = wallet_seed_hash && let Some(wallet) = wallets.get(&seed_hash) @@ -190,7 +191,7 @@ impl AddNewIdentityScreen { if let Some(wallet_lock) = &self.selected_wallet { // sanity checks { - let wallet = wallet_lock.read().unwrap(); + let wallet = wallet_lock.read_or_recover(); if !wallet.is_open() { return Err(format!( "wallet {} is not open", @@ -209,7 +210,7 @@ impl AddNewIdentityScreen { let dashpay_contract_id = app_context.dashpay_contract.id(); let default_keys = default_identity_key_specs(dashpay_contract_id); - let mut wallet = wallet_lock.write().expect("wallet lock failed"); + let mut wallet = wallet_lock.write_or_recover(); let master_key = wallet.identity_authentication_ecdsa_private_key( app_context.network, identity_id_number, @@ -261,7 +262,7 @@ impl AddNewIdentityScreen { // Check if we have access to the selected wallet if let Some(wallet_guard) = self.selected_wallet.as_ref() { - let wallet = wallet_guard.read().unwrap(); + let wallet = wallet_guard.read_or_recover(); let used_indices: HashSet = wallet.identities.keys().cloned().collect(); // Modify the selected text to include "(used)" if the current index is used @@ -318,7 +319,7 @@ impl AddNewIdentityScreen { fn render_wallet_selection(&mut self, ui: &mut Ui) -> bool { let mut selected_wallet = None; let rendered = if self.app_context.has_wallet.load(Ordering::Relaxed) { - let wallets = &self.app_context.wallets.read().unwrap(); + let wallets = &self.app_context.wallets.read_or_recover(); if wallets.len() > 1 { // Retrieve the alias of the currently selected wallet, if any let selected_wallet_alias = self @@ -359,7 +360,7 @@ impl AddNewIdentityScreen { // Reset the copied to clipboard state self.copied_to_clipboard = None; // Reset the step to choose funding method - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::ChooseFundingMethod; } } @@ -390,7 +391,7 @@ impl AddNewIdentityScreen { /// /// This function is called whenever a wallet was changed in the UI or unlocked fn update_wallet(&mut self, wallet: Arc>) { - let is_open = wallet.read().expect("wallet lock poisoned").is_open(); + let is_open = wallet.read_or_recover().is_open(); self.selected_wallet = Some(wallet); self.identity_id_number = self.next_identity_id(); @@ -410,8 +411,7 @@ impl AddNewIdentityScreen { self.selected_wallet .as_ref() .unwrap() - .read() - .unwrap() + .read_or_recover() .identities .keys() .copied() @@ -425,7 +425,7 @@ impl AddNewIdentityScreen { return; }; let funding_method_arc = self.funding_method.clone(); - let mut funding_method = funding_method_arc.write().unwrap(); // Write lock on funding_method + let mut funding_method = funding_method_arc.write_or_recover(); // Write lock on funding_method ComboBox::from_id_salt("funding_method") .selected_text(format!("{}", *funding_method)) @@ -438,14 +438,14 @@ impl AddNewIdentityScreen { ) .changed() { - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::ChooseFundingMethod; self.funding_amount = None; self.funding_amount_input = None; } let (has_unused_asset_lock, has_balance) = { - let wallet = selected_wallet.read().unwrap(); + let wallet = selected_wallet.read_or_recover(); (wallet.has_unused_asset_lock(), wallet.has_balance()) }; @@ -460,7 +460,7 @@ impl AddNewIdentityScreen { { self.ensure_correct_identity_keys() .expect("failed to initialize keys"); - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::ReadyToCreate; self.funding_amount = None; self.funding_amount_input = None; @@ -476,7 +476,7 @@ impl AddNewIdentityScreen { { self.funding_amount = None; self.funding_amount_input = None; - let mut step = self.step.write().unwrap(); // Write lock on step + let mut step = self.step.write_or_recover(); // Write lock on step *step = WalletFundedScreenStep::ReadyToCreate; } if ui @@ -487,7 +487,7 @@ impl AddNewIdentityScreen { ) .changed() { - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::WaitingOnFunds; self.funding_amount = None; self.funding_amount_input = None; @@ -495,7 +495,7 @@ impl AddNewIdentityScreen { // Check if wallet has Platform address balance let has_platform_balance = { - let wallet = selected_wallet.read().unwrap(); + let wallet = selected_wallet.read_or_recover(); wallet .platform_address_info .values() @@ -512,7 +512,7 @@ impl AddNewIdentityScreen { { self.ensure_correct_identity_keys() .expect("failed to initialize keys"); - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::ReadyToCreate; self.platform_funding_amount = None; self.platform_funding_amount_input = None; @@ -781,7 +781,7 @@ impl AddNewIdentityScreen { ), }; - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::WaitingForPlatformAcceptance; AppAction::BackendTask(BackendTask::IdentityTask( @@ -803,7 +803,7 @@ impl AddNewIdentityScreen { return AppAction::None; } - let seed = selected_wallet.read().unwrap().wallet_seed.clone(); + let seed = selected_wallet.read_or_recover().wallet_seed.clone(); tracing::debug!(selected_wallet = ?selected_wallet,?seed, "funding with wallet balance"); let identity_input = IdentityRegistrationInfo { alias_input: self.alias_input.clone(), @@ -816,7 +816,7 @@ impl AddNewIdentityScreen { ), }; - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::WaitingForAssetLock; // Create the backend task to register the identity @@ -837,7 +837,7 @@ impl AddNewIdentityScreen { return AppAction::None; } - let wallet_seed_hash = selected_wallet.read().unwrap().seed_hash(); + let wallet_seed_hash = selected_wallet.read_or_recover().seed_hash(); let mut inputs = std::collections::BTreeMap::new(); inputs.insert(platform_addr, amount); @@ -854,7 +854,7 @@ impl AddNewIdentityScreen { }, }; - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::WaitingForPlatformAcceptance; AppAction::BackendTask(BackendTask::IdentityTask(IdentityTask::RegisterIdentity( @@ -866,12 +866,12 @@ impl AddNewIdentityScreen { } fn render_funding_amount_input(&mut self, ui: &mut egui::Ui) { - let funding_method = *self.funding_method.read().unwrap(); + let funding_method = *self.funding_method.read_or_recover(); // Calculate max amount if using wallet balance let max_amount_credits = if funding_method == FundingMethod::UseWalletBalance { self.selected_wallet.as_ref().map(|wallet| { - let wallet = wallet.read().unwrap(); + let wallet = wallet.read_or_recover(); // Convert duffs to credits (1 duff = 1000 credits) wallet.total_balance_duffs() * 1000 }) @@ -908,7 +908,7 @@ impl AddNewIdentityScreen { /// If the master key is not set, this function is a no-op and returns Ok(false). fn update_identity_key(&mut self) -> Result { if let Some(wallet_guard) = self.selected_wallet.as_ref() { - let mut wallet = wallet_guard.write().unwrap(); + let mut wallet = wallet_guard.write_or_recover(); let identity_index = self.identity_id_number; // Update the master private key and keys input from the wallet @@ -957,7 +957,7 @@ impl AddNewIdentityScreen { security_level: SecurityLevel, ) { if let Some(wallet_guard) = self.selected_wallet.as_ref() { - let mut wallet = wallet_guard.write().unwrap(); + let mut wallet = wallet_guard.write_or_recover(); let new_key_index = self.identity_keys.keys_input.len() as u32 + 1; // Add a new key with default parameters (no contract bounds for manually added keys) @@ -984,7 +984,7 @@ impl ScreenLike for AddNewIdentityScreen { if message_type == MessageType::Error { self.error_message = Some(format!("Error registering identity: {}", message)); // Reset step so we stop showing "Waiting for Platform acknowledgement" - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::ReadyToCreate; } else { self.error_message = Some(message.to_string()); @@ -996,12 +996,12 @@ impl ScreenLike for AddNewIdentityScreen { { self.successful_qualified_identity_id = Some(qualified_identity.identity.id()); self.completed_fee_result = Some(fee_result); - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::Success; return; } - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); let current_step = *step; match current_step { WalletFundedScreenStep::ChooseFundingMethod => {} @@ -1034,7 +1034,7 @@ impl ScreenLike for AddNewIdentityScreen { return false; }; if let Some(wallet) = &self.selected_wallet { - let wallet = wallet.read().unwrap(); + let wallet = wallet.read_or_recover(); wallet.known_addresses.contains_key(&address) } else { false @@ -1092,7 +1092,7 @@ impl ScreenLike for AddNewIdentityScreen { } ScrollArea::vertical().show(ui, |ui| { - let step = {*self.step.read().unwrap()}; + let step = {*self.step.read_or_recover()}; if step == WalletFundedScreenStep::Success { inner_action |= self.show_success(ui); return; @@ -1151,7 +1151,7 @@ impl ScreenLike for AddNewIdentityScreen { // Display the heading with an info icon that shows a tooltip on hover ui.horizontal(|ui| { let wallet_guard = self.selected_wallet.as_ref().unwrap(); - let wallet = wallet_guard.read().unwrap(); + let wallet = wallet_guard.read_or_recover(); if wallet.identities.is_empty() { ui.heading(format!( "{}. Choose an identity index for the wallet. Leaving this 0 is recommended.", @@ -1258,7 +1258,7 @@ impl ScreenLike for AddNewIdentityScreen { ui.separator(); // Extract the funding method from the RwLock to minimize borrow scope - let funding_method = *self.funding_method.read().unwrap(); + let funding_method = *self.funding_method.read_or_recover(); if funding_method == FundingMethod::NoSelection { return; diff --git a/src/ui/identities/funding_common.rs b/src/ui/identities/funding_common.rs index 98c35555e..c252c5d60 100644 --- a/src/ui/identities/funding_common.rs +++ b/src/ui/identities/funding_common.rs @@ -5,6 +5,7 @@ use image::Luma; use qrcode::QrCode; use std::sync::{Arc, RwLock}; +use crate::lock_helper::RwLockExt; use crate::model::wallet::Wallet; use dash_sdk::dashcore_rpc::dashcore::Address; use dash_sdk::dpp::dashcore::{OutPoint, TxOut}; @@ -59,7 +60,7 @@ pub fn capture_qr_funding_utxo_if_available( funding_address: Option<&Address>, ) -> Option<(OutPoint, TxOut, Address)> { if !matches!( - *step.read().expect("wallet funding step lock poisoned"), + *step.read_or_recover(), WalletFundedScreenStep::WaitingOnFunds ) { return None; @@ -70,9 +71,7 @@ pub fn capture_qr_funding_utxo_if_available( let wallet_arc = wallet?; let candidate_utxo = { - let wallet = wallet_arc - .read() - .expect("wallet lock poisoned while checking funding UTXO"); + let wallet = wallet_arc.read_or_recover(); wallet.utxos.get(&address).and_then(|utxos| { utxos .iter() @@ -83,9 +82,7 @@ pub fn capture_qr_funding_utxo_if_available( }; if let Some((outpoint, tx_out)) = candidate_utxo { - let mut step = step - .write() - .expect("wallet funding step write lock poisoned"); + let mut step = step.write_or_recover(); *step = WalletFundedScreenStep::FundsReceived; Some((outpoint, tx_out, address)) } else { diff --git a/src/ui/identities/identities_screen.rs b/src/ui/identities/identities_screen.rs index 816b5606a..1571a0d11 100644 --- a/src/ui/identities/identities_screen.rs +++ b/src/ui/identities/identities_screen.rs @@ -3,6 +3,7 @@ use crate::app::{AppAction, BackendTasksExecutionMode, DesiredAppAction}; use crate::backend_task::BackendTask; use crate::backend_task::identity::IdentityTask; use crate::context::AppContext; +use crate::lock_helper::{MutexExt, RwLockExt}; use crate::model::qualified_identity::PrivateKeyTarget::{ PrivateKeyOnMainIdentity, PrivateKeyOnVoterIdentity, }; @@ -109,7 +110,7 @@ impl IdentitiesScreen { /// Reorders `self.identities` to match the order of the provided list of IDs. /// Any IDs not present in the provided list are left in their current position. fn reorder_map_to(&self, new_order: Vec) { - let mut lock = self.identities.lock().unwrap(); + let mut lock = self.identities.lock_or_recover(); if lock.is_empty() || new_order.is_empty() { return; } @@ -196,7 +197,7 @@ impl IdentitiesScreen { IdentitiesSortOrder::Descending => ordering.reverse(), } }); - let mut lock = self.identities.lock().unwrap(); + let mut lock = self.identities.lock_or_recover(); *lock = list .iter() .map(|qi| (qi.identity.id(), qi.clone())) @@ -262,7 +263,7 @@ impl IdentitiesScreen { // Up/down reorder methods fn move_identity_up(&mut self, identity_id: &Identifier) { - let mut lock = self.identities.lock().unwrap(); + let mut lock = self.identities.lock_or_recover(); if let Some(idx) = lock.get_index_of(identity_id) && idx > 0 { @@ -274,7 +275,7 @@ impl IdentitiesScreen { // arrow down fn move_identity_down(&mut self, identity_id: &Identifier) { - let mut lock = self.identities.lock().unwrap(); + let mut lock = self.identities.lock_or_recover(); if let Some(idx) = lock.get_index_of(identity_id) && idx + 1 < lock.len() { @@ -286,7 +287,7 @@ impl IdentitiesScreen { // Save the current index order to DB fn save_current_order(&self) { - let lock = self.identities.lock().unwrap(); + let lock = self.identities.lock_or_recover(); let all_ids = lock.keys().cloned().collect::>(); drop(lock); self.app_context.db.save_identity_order(all_ids).ok(); @@ -295,7 +296,7 @@ impl IdentitiesScreen { /// This method merges the ephemeral-sorted `Vec` back into the IndexMap /// so the IndexMap is updated to the user’s currently displayed order. fn update_index_map_to_current_ephemeral(&self, ephemeral_list: Vec) { - let mut lock = self.identities.lock().unwrap(); + let mut lock = self.identities.lock_or_recover(); // basically reorder the underlying IndexMap to match ephemeral_list for (desired_idx, qi) in ephemeral_list.into_iter().enumerate() { let id = qi.identity.id(); @@ -311,9 +312,9 @@ impl IdentitiesScreen { if let Some(in_wallet_text) = self.wallet_seed_hash_cache.get(wallet_seed_hash) { return Some(in_wallet_text.clone()); } - let wallets = self.app_context.wallets.read().unwrap(); + let wallets = self.app_context.wallets.read_or_recover(); for wallet in wallets.values() { - let wallet_guard = wallet.read().unwrap(); + let wallet_guard = wallet.read_or_recover(); if &wallet_guard.seed_hash() == wallet_seed_hash { let in_wallet_text = if let Some(alias) = wallet_guard.alias.as_ref() { alias.clone() @@ -930,7 +931,7 @@ impl IdentitiesScreen { if ui.add(yes_button).clicked() { let identity_id = identity_to_remove.identity.id(); - let mut lock = self.identities.lock().unwrap(); + let mut lock = self.identities.lock_or_recover(); lock.shift_remove(&identity_id); self.app_context @@ -1059,7 +1060,7 @@ impl IdentitiesScreen { // Update in memory { - let mut identities = self.identities.lock().unwrap(); + let mut identities = self.identities.lock_or_recover(); if let Some(identity_to_update) = identities.get_mut(&identity_id) { identity_to_update.alias = new_alias.clone(); } @@ -1101,7 +1102,7 @@ impl IdentitiesScreen { impl ScreenLike for IdentitiesScreen { fn refresh(&mut self) { - let mut identities = self.identities.lock().unwrap(); + let mut identities = self.identities.lock_or_recover(); *identities = self .app_context .load_local_qualified_identities() @@ -1165,12 +1166,11 @@ impl ScreenLike for IdentitiesScreen { "Load Identity", DesiredAppAction::AddScreenType(Box::new(ScreenType::AddExistingIdentity)), )); - if !self.identities.lock().unwrap().is_empty() { + if !self.identities.lock_or_recover().is_empty() { // Create a vec of RefreshIdentity(identity) DesiredAppAction for each identity let backend_tasks: Vec = self .identities - .lock() - .unwrap() + .lock_or_recover() .values() .map(|qi| BackendTask::IdentityTask(IdentityTask::RefreshIdentity(qi.clone()))) .collect(); @@ -1193,7 +1193,7 @@ impl ScreenLike for IdentitiesScreen { action |= add_left_panel(ctx, &self.app_context, RootScreenType::RootScreenIdentities); let identities_vec = { - let guard = self.identities.lock().unwrap(); + let guard = self.identities.lock_or_recover(); guard.values().cloned().collect::>() }; diff --git a/src/ui/identities/keys/key_info_screen.rs b/src/ui/identities/keys/key_info_screen.rs index 22f9ac7de..590d4e2ad 100644 --- a/src/ui/identities/keys/key_info_screen.rs +++ b/src/ui/identities/keys/key_info_screen.rs @@ -1,5 +1,6 @@ use crate::app::AppAction; use crate::context::AppContext; +use crate::lock_helper::RwLockExt; use crate::model::qualified_identity::QualifiedIdentity; use crate::model::qualified_identity::encrypted_key_storage::{ PrivateKeyData, WalletDerivationPath, @@ -380,7 +381,7 @@ impl ScreenLike for KeyInfoScreen { }); } else { let wallet = - self.selected_wallet.as_ref().unwrap().read().unwrap(); + self.selected_wallet.as_ref().unwrap().read_or_recover(); match wallet.private_key_at_derivation_path( &derivation_path.derivation_path, self.app_context.network, @@ -436,7 +437,7 @@ impl ScreenLike for KeyInfoScreen { } if self.decrypted_private_key.is_none() { let wallet = - self.selected_wallet.as_ref().unwrap().read().unwrap(); + self.selected_wallet.as_ref().unwrap().read_or_recover(); match wallet.private_key_at_derivation_path( &derivation_path.derivation_path, self.app_context.network, @@ -597,7 +598,7 @@ impl KeyInfoScreen { ) -> Self { let selected_wallet = if let Some((_, Some(wallet_derivation_path))) = private_key_data.as_ref() { - let wallets = app_context.wallets.read().unwrap(); + let wallets = app_context.wallets.read_or_recover(); wallets .get(&wallet_derivation_path.wallet_seed_hash) .cloned() diff --git a/src/ui/identities/top_up_identity_screen/by_platform_address.rs b/src/ui/identities/top_up_identity_screen/by_platform_address.rs index b10723693..6cd76deb8 100644 --- a/src/ui/identities/top_up_identity_screen/by_platform_address.rs +++ b/src/ui/identities/top_up_identity_screen/by_platform_address.rs @@ -1,6 +1,7 @@ use crate::app::AppAction; use crate::backend_task::BackendTask; use crate::backend_task::identity::IdentityTask; +use crate::lock_helper::RwLockExt; use crate::model::amount::Amount; use crate::model::fee_estimation::format_credits_as_dash; use crate::model::wallet::WalletSeedHash; @@ -173,7 +174,7 @@ impl TopUpIdentityScreen { let can_top_up = self.selected_platform_address.is_some() && has_valid_amount && self.wallet.is_some(); - let step = { *self.step.read().unwrap() }; + let step = { *self.step.read_or_recover() }; ui.horizontal(|ui| { let button_text = match step { @@ -279,7 +280,7 @@ impl TopUpIdentityScreen { // Update step { - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::WaitingForPlatformAcceptance; } diff --git a/src/ui/identities/top_up_identity_screen/by_using_unused_asset_lock.rs b/src/ui/identities/top_up_identity_screen/by_using_unused_asset_lock.rs index f6fc658df..69e2fabf6 100644 --- a/src/ui/identities/top_up_identity_screen/by_using_unused_asset_lock.rs +++ b/src/ui/identities/top_up_identity_screen/by_using_unused_asset_lock.rs @@ -1,4 +1,5 @@ use crate::app::AppAction; +use crate::lock_helper::RwLockExt; use crate::model::fee_estimation::format_credits_as_dash; use crate::ui::identities::add_new_identity_screen::FundingMethod; use crate::ui::identities::top_up_identity_screen::{TopUpIdentityScreen, WalletFundedScreenStep}; @@ -14,7 +15,7 @@ impl TopUpIdentityScreen { }; // Read the wallet to access unused asset locks - let wallet = selected_wallet.read().unwrap(); + let wallet = selected_wallet.read_or_recover(); if wallet.unused_asset_locks.is_empty() { ui.label("No unused asset locks available."); @@ -63,7 +64,7 @@ impl TopUpIdentityScreen { )); // Update the step to ready to create identity - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::ReadyToCreate; } }); @@ -81,7 +82,7 @@ impl TopUpIdentityScreen { let mut action = AppAction::None; // Extract the step from the RwLock to minimize borrow scope - let step = *self.step.read().unwrap(); + let step = *self.step.read_or_recover(); ui.heading( format!( diff --git a/src/ui/identities/top_up_identity_screen/by_using_unused_balance.rs b/src/ui/identities/top_up_identity_screen/by_using_unused_balance.rs index 1bcd0227b..9c226173f 100644 --- a/src/ui/identities/top_up_identity_screen/by_using_unused_balance.rs +++ b/src/ui/identities/top_up_identity_screen/by_using_unused_balance.rs @@ -1,4 +1,5 @@ use crate::app::AppAction; +use crate::lock_helper::RwLockExt; use crate::model::fee_estimation::format_credits_as_dash; use crate::ui::identities::add_new_identity_screen::FundingMethod; use crate::ui::identities::top_up_identity_screen::{TopUpIdentityScreen, WalletFundedScreenStep}; @@ -8,7 +9,7 @@ use egui::{Color32, Frame, Margin, RichText, Ui}; impl TopUpIdentityScreen { fn show_wallet_balance(&self, ui: &mut egui::Ui) { if let Some(selected_wallet) = &self.wallet { - let wallet = selected_wallet.read().unwrap(); // Read lock on the wallet + let wallet = selected_wallet.read_or_recover(); // Read lock on the wallet let total_balance: u64 = wallet.total_balance_duffs(); // Use stored balance with UTXO fallback @@ -41,7 +42,7 @@ impl TopUpIdentityScreen { self.top_up_funding_amount_input(ui); // Extract the step from the RwLock to minimize borrow scope - let step = *self.step.read().unwrap(); + let step = *self.step.read_or_recover(); let Ok(_) = self.funding_amount.parse::() else { return action; diff --git a/src/ui/identities/top_up_identity_screen/by_wallet_qr_code.rs b/src/ui/identities/top_up_identity_screen/by_wallet_qr_code.rs index 12339a127..ad478a7ef 100644 --- a/src/ui/identities/top_up_identity_screen/by_wallet_qr_code.rs +++ b/src/ui/identities/top_up_identity_screen/by_wallet_qr_code.rs @@ -1,6 +1,7 @@ use crate::app::AppAction; use crate::backend_task::BackendTask; use crate::backend_task::identity::{IdentityTask, IdentityTopUpInfo, TopUpIdentityFundingMethod}; +use crate::lock_helper::RwLockExt; use crate::ui::identities::funding_common::{self, copy_to_clipboard, generate_qr_code_image}; use crate::ui::identities::top_up_identity_screen::{TopUpIdentityScreen, WalletFundedScreenStep}; use dash_sdk::dashcore_rpc::RpcApi; @@ -14,7 +15,7 @@ impl TopUpIdentityScreen { if let Some(wallet_guard) = self.wallet.as_ref() { // Get the receive address from the selected wallet if self.funding_address.is_none() { - let mut wallet = wallet_guard.write().unwrap(); + let mut wallet = wallet_guard.write_or_recover(); let receive_address = wallet.receive_address( self.app_context.network, true, @@ -102,7 +103,7 @@ impl TopUpIdentityScreen { } // Extract the step from the RwLock to minimize borrow scope - let step = *self.step.read().unwrap(); + let step = *self.step.read_or_recover(); ui.heading( format!( @@ -164,7 +165,7 @@ impl TopUpIdentityScreen { ), }; - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::WaitingForAssetLock; return AppAction::BackendTask(BackendTask::IdentityTask( diff --git a/src/ui/identities/top_up_identity_screen/mod.rs b/src/ui/identities/top_up_identity_screen/mod.rs index 7bc8fae6f..b98974223 100644 --- a/src/ui/identities/top_up_identity_screen/mod.rs +++ b/src/ui/identities/top_up_identity_screen/mod.rs @@ -9,6 +9,7 @@ use crate::backend_task::core::CoreItem; use crate::backend_task::identity::{IdentityTask, IdentityTopUpInfo, TopUpIdentityFundingMethod}; use crate::backend_task::{BackendTask, BackendTaskSuccessResult, FeeResult}; use crate::context::AppContext; +use crate::lock_helper::RwLockExt; use crate::model::amount::Amount; use crate::model::fee_estimation::format_credits_as_dash; use crate::model::qualified_identity::QualifiedIdentity; @@ -95,12 +96,12 @@ impl TopUpIdentityScreen { let mut step_update_method: Option = None; let rendered = if self.app_context.has_wallet.load(Ordering::Relaxed) { - let wallets_guard = self.app_context.wallets.read().unwrap(); + let wallets_guard = self.app_context.wallets.read_or_recover(); let wallets = &*wallets_guard; if wallets.len() > 1 { // Cache current funding method to avoid holding the lock across UI callbacks - let funding_method = *self.funding_method.read().unwrap(); + let funding_method = *self.funding_method.read_or_recover(); // Retrieve the alias of the currently selected wallet, if any let selected_wallet_alias = self @@ -115,7 +116,7 @@ impl TopUpIdentityScreen { .show_ui(ui, |ui| { for wallet in wallets.values() { let (wallet_alias, has_required_resources) = { - let wallet_read = wallet.read().unwrap(); + let wallet_read = wallet.read_or_recover(); let alias = wallet_read .alias .clone() @@ -149,11 +150,11 @@ impl TopUpIdentityScreen { } else if let Some(wallet) = wallets.values().next() { if self.wallet.is_none() { // Cache current funding method to avoid holding the lock across updates - let funding_method = *self.funding_method.read().unwrap(); + let funding_method = *self.funding_method.read_or_recover(); // Check if the wallet has the required resources let has_required_resources = { - let wallet_read = wallet.read().unwrap(); + let wallet_read = wallet.read_or_recover(); match funding_method { FundingMethod::UseWalletBalance => wallet_read.has_balance(), FundingMethod::UseUnusedAssetLock => { @@ -188,7 +189,7 @@ impl TopUpIdentityScreen { if let Some(method) = step_update_method { self.update_step_after_wallet_change(method); } else { - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::ChooseFundingMethod; } } @@ -198,7 +199,7 @@ impl TopUpIdentityScreen { /// Adjust the current step to match the funding method after a wallet switch. fn update_step_after_wallet_change(&mut self, funding_method: FundingMethod) { - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = match funding_method { FundingMethod::AddressWithQRCode => WalletFundedScreenStep::WaitingOnFunds, FundingMethod::UseUnusedAssetLock @@ -210,17 +211,17 @@ impl TopUpIdentityScreen { fn render_funding_method(&mut self, ui: &mut egui::Ui) { let funding_method_arc = self.funding_method.clone(); - let mut funding_method = funding_method_arc.write().unwrap(); + let mut funding_method = funding_method_arc.write_or_recover(); // Check if any wallet has unused asset locks, balance, or Platform address balance let (has_any_unused_asset_lock, has_any_balance, has_any_platform_balance) = { - let wallets = self.app_context.wallets.read().unwrap(); + let wallets = self.app_context.wallets.read_or_recover(); let mut has_unused_asset_lock = false; let mut has_balance = false; let mut has_platform_balance = false; for wallet in wallets.values() { - let wallet = wallet.read().unwrap(); + let wallet = wallet.read_or_recover(); if wallet.has_unused_asset_lock() { has_unused_asset_lock = true; } @@ -256,7 +257,7 @@ impl TopUpIdentityScreen { ) .changed() { - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::ReadyToCreate; } }); @@ -270,7 +271,7 @@ impl TopUpIdentityScreen { ) .changed() { - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::ReadyToCreate; } }); @@ -284,7 +285,7 @@ impl TopUpIdentityScreen { ) .changed() { - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::ReadyToCreate; } }); @@ -297,7 +298,7 @@ impl TopUpIdentityScreen { ) .changed() { - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::WaitingOnFunds; } }); @@ -320,7 +321,7 @@ impl TopUpIdentityScreen { ), }; - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::WaitingForPlatformAcceptance; AppAction::BackendTask(BackendTask::IdentityTask(IdentityTask::TopUpIdentity( @@ -355,7 +356,7 @@ impl TopUpIdentityScreen { ), }; - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::WaitingForAssetLock; // Create the backend task to top_up the identity @@ -368,7 +369,7 @@ impl TopUpIdentityScreen { } fn top_up_funding_amount_input(&mut self, ui: &mut egui::Ui) { - let funding_method = *self.funding_method.read().unwrap(); + let funding_method = *self.funding_method.read_or_recover(); // Only apply max amount restriction when using wallet balance // For QR code funding, funds come from external source so no max applies @@ -377,7 +378,7 @@ impl TopUpIdentityScreen { let max_amount_duffs = self .wallet .as_ref() - .map(|w| w.read().unwrap().total_balance_duffs()) + .map(|w| w.read_or_recover().total_balance_duffs()) .unwrap_or(0); // Convert Duffs to Credits (1 Duff = 1000 Credits) let total_credits = max_amount_duffs * 1000; @@ -431,7 +432,7 @@ impl ScreenLike for TopUpIdentityScreen { if message_type == MessageType::Error { self.error_message = Some(format!("Error topping up identity: {}", message)); // Reset step so UI is not stuck on waiting messages - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); if *step == WalletFundedScreenStep::WaitingForPlatformAcceptance || *step == WalletFundedScreenStep::WaitingForAssetLock { @@ -455,12 +456,12 @@ impl ScreenLike for TopUpIdentityScreen { self.copied_to_clipboard = None; self.error_message = None; - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::Success; return; } - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); let current_step = *step; match current_step { WalletFundedScreenStep::ChooseFundingMethod => {} @@ -493,7 +494,7 @@ impl ScreenLike for TopUpIdentityScreen { return false; }; if let Some(wallet) = &self.wallet { - let wallet = wallet.read().unwrap(); + let wallet = wallet.read_or_recover(); wallet.known_addresses.contains_key(&address) } else { false @@ -552,7 +553,7 @@ impl ScreenLike for TopUpIdentityScreen { } ScrollArea::vertical().show(ui, |ui| { - let step = { *self.step.read().unwrap() }; + let step = { *self.step.read_or_recover() }; if step == WalletFundedScreenStep::Success { inner_action |= self.show_success(ui); return; @@ -598,7 +599,7 @@ impl ScreenLike for TopUpIdentityScreen { ui.add_space(10.0); // Extract the funding method from the RwLock to minimize borrow scope - let funding_method = *self.funding_method.read().unwrap(); + let funding_method = *self.funding_method.read_or_recover(); if funding_method == FundingMethod::NoSelection { return; } @@ -609,7 +610,7 @@ impl ScreenLike for TopUpIdentityScreen { || funding_method == FundingMethod::UsePlatformAddress { // Check if there's more than one wallet to show selection UI - let wallet_count = self.app_context.wallets.read().unwrap().len(); + let wallet_count = self.app_context.wallets.read_or_recover().len(); if wallet_count > 1 { ui.horizontal(|ui| { diff --git a/src/ui/tokens/tokens_screen/keyword_search.rs b/src/ui/tokens/tokens_screen/keyword_search.rs index 04edb2876..df8fc6421 100644 --- a/src/ui/tokens/tokens_screen/keyword_search.rs +++ b/src/ui/tokens/tokens_screen/keyword_search.rs @@ -2,6 +2,7 @@ use crate::app::AppAction; use crate::backend_task::BackendTask; use crate::backend_task::contract::ContractTask; use crate::backend_task::tokens::TokenTask; +use crate::lock_helper::MutexExt; use crate::ui::theme::DashColors; use crate::ui::tokens::tokens_screen::{ ContractDescriptionInfo, ContractSearchStatus, TokensScreen, @@ -59,7 +60,7 @@ impl TokensScreen { if go_clicked || enter_pressed { // Clear old results, set status - self.search_results.lock().unwrap().clear(); + self.search_results.lock_or_recover().clear(); let now = Utc::now().timestamp() as u64; self.contract_search_status = ContractSearchStatus::WaitingForResult(now); self.search_current_page = 1; @@ -79,7 +80,7 @@ impl TokensScreen { // Clear the search input self.token_search_query = Some("".to_string()); // Clear the search results - self.search_results.lock().unwrap().clear(); + self.search_results.lock_or_recover().clear(); // Reset the search status self.contract_search_status = ContractSearchStatus::NotStarted; // Clear pagination state @@ -114,7 +115,7 @@ impl TokensScreen { } ContractSearchStatus::Complete => { // Show the results - let results = self.search_results.lock().unwrap().clone(); + let results = self.search_results.lock_or_recover().clone(); if results.is_empty() { ui.label("No tokens match your keyword."); } else { diff --git a/src/ui/tools/grovestark_screen.rs b/src/ui/tools/grovestark_screen.rs index 5ad4c70fa..490eb1e09 100644 --- a/src/ui/tools/grovestark_screen.rs +++ b/src/ui/tools/grovestark_screen.rs @@ -2,6 +2,7 @@ use crate::app::AppAction; use crate::backend_task::BackendTask; use crate::backend_task::grovestark::GroveSTARKTask; use crate::context::AppContext; +use crate::lock_helper::RwLockExt; use crate::model::qualified_identity::{PrivateKeyTarget, QualifiedIdentity}; use crate::ui::RootScreenType; use crate::ui::ScreenLike; @@ -360,7 +361,7 @@ impl GroveSTARKScreen { let private_key = match self.get_qualified_identity(&identity_id) { Some(qualified_identity) => { // Get the wallets for resolving encrypted keys - let wallets = app_context.wallets.read().unwrap(); + let wallets = app_context.wallets.read_or_recover(); let wallet_vec: Vec<_> = wallets.values().cloned().collect(); // Try to get the private key diff --git a/src/ui/wallets/asset_lock_detail_screen.rs b/src/ui/wallets/asset_lock_detail_screen.rs index e52ead8b0..9ae7c47f4 100644 --- a/src/ui/wallets/asset_lock_detail_screen.rs +++ b/src/ui/wallets/asset_lock_detail_screen.rs @@ -1,5 +1,6 @@ use crate::app::AppAction; use crate::context::AppContext; +use crate::lock_helper::RwLockExt; use crate::model::wallet::Wallet; use crate::ui::components::left_panel::add_left_panel; use crate::ui::components::styled::island_central_panel; @@ -37,10 +38,9 @@ impl AssetLockDetailScreen { // Find the wallet by seed hash let wallet = app_context .wallets - .read() - .unwrap() + .read_or_recover() .values() - .find(|w| w.read().unwrap().seed_hash() == wallet_seed_hash) + .find(|w| w.read_or_recover().seed_hash() == wallet_seed_hash) .cloned(); Self { @@ -68,7 +68,7 @@ impl AssetLockDetailScreen { Option, )> { self.wallet.as_ref().and_then(|wallet| { - let wallet = wallet.read().unwrap(); + let wallet = wallet.read_or_recover(); wallet .unused_asset_locks .get(self.asset_lock_index) @@ -217,7 +217,7 @@ impl AssetLockDetailScreen { if (!needs_unlock || unlocked) && let Some(wallet_arc) = self.wallet.clone() { - let wallet = wallet_arc.read().unwrap(); + let wallet = wallet_arc.read_or_recover(); // Find the private key for this address if let Some(derivation_path) = wallet.known_addresses.get(&address).cloned() { @@ -228,7 +228,7 @@ impl AssetLockDetailScreen { ui.label(RichText::new("••••••••••••••••••••").font(egui::FontId::monospace(12.0)).color(DashColors::text_secondary(dark_mode))); if ui.small_button("View").clicked() { // Retrieve the private key when View is clicked - let wallet = wallet_arc.write().unwrap(); + let wallet = wallet_arc.write_or_recover(); match wallet.private_key_at_derivation_path(&derivation_path, self.app_context.network) { Ok(private_key) => { self.private_key_wif = Some(private_key.to_wif()); diff --git a/src/ui/wallets/create_asset_lock_screen.rs b/src/ui/wallets/create_asset_lock_screen.rs index eea9978b7..852b3129b 100644 --- a/src/ui/wallets/create_asset_lock_screen.rs +++ b/src/ui/wallets/create_asset_lock_screen.rs @@ -2,6 +2,7 @@ use crate::app::AppAction; use crate::backend_task::core::{CoreItem, CoreTask}; use crate::backend_task::{BackendTask, BackendTaskSuccessResult}; use crate::context::AppContext; +use crate::lock_helper::RwLockExt; use crate::model::amount::Amount; use crate::model::qualified_identity::QualifiedIdentity; use crate::model::wallet::Wallet; @@ -64,7 +65,7 @@ impl CreateAssetLockScreen { // Calculate next unused identity index let identity_index = { - let wallet_guard = wallet.read().unwrap(); + let wallet_guard = wallet.read_or_recover(); wallet_guard .identities .keys() @@ -103,7 +104,7 @@ impl CreateAssetLockScreen { } fn generate_funding_address(&mut self) -> Result<(), String> { - let mut wallet = self.wallet.write().unwrap(); + let mut wallet = self.wallet.write_or_recover(); // Generate a new asset lock funding address let receive_address = @@ -114,8 +115,7 @@ impl CreateAssetLockScreen { if !has_address { self.app_context .core_client - .read() - .expect("Core client lock was poisoned") + .read_or_recover() .import_address( &receive_address, Some("Managed by Dash Evo Tool - Asset Lock"), @@ -128,16 +128,14 @@ impl CreateAssetLockScreen { let info = self .app_context .core_client - .read() - .expect("Core client lock was poisoned") + .read_or_recover() .get_address_info(&receive_address) .map_err(|e| e.to_string())?; if !(info.is_watchonly || info.is_mine) { self.app_context .core_client - .read() - .expect("Core client lock was poisoned") + .read_or_recover() .import_address( &receive_address, Some("Managed by Dash Evo Tool - Asset Lock"), @@ -231,7 +229,7 @@ impl CreateAssetLockScreen { self.selected_identity_string.clear(); // Recalculate next unused identity index self.identity_index = { - let wallet_guard = self.wallet.read().unwrap(); + let wallet_guard = self.wallet.read_or_recover(); wallet_guard .identities .keys() @@ -253,7 +251,7 @@ impl CreateAssetLockScreen { self.asset_lock_tx_id = None; self.error_message = None; self.show_advanced_options = false; - *self.step.write().unwrap() = WalletFundedScreenStep::WaitingOnFunds; + *self.step.write_or_recover() = WalletFundedScreenStep::WaitingOnFunds; } ui.add_space(100.0); @@ -362,7 +360,7 @@ impl ScreenLike for CreateAssetLockScreen { .show(ui, |ui| { // Show success screen - if *self.step.read().unwrap() == WalletFundedScreenStep::Success { + if *self.step.read_or_recover() == WalletFundedScreenStep::Success { inner_action |= self.show_success(ui); return; } @@ -526,7 +524,7 @@ impl ScreenLike for CreateAssetLockScreen { ui.add_space(10.0); // Get used indices from wallet - let wallet_guard = self.wallet.read().unwrap(); + let wallet_guard = self.wallet.read_or_recover(); let used_indices: HashSet = wallet_guard.identities.keys().cloned().collect(); drop(wallet_guard); @@ -577,7 +575,7 @@ impl ScreenLike for CreateAssetLockScreen { self.funding_utxo = Some(utxo); } - let step = *self.step.read().unwrap(); + let step = *self.step.read_or_recover(); // Request periodic repaints while waiting for funds if step == WalletFundedScreenStep::WaitingOnFunds { @@ -635,7 +633,7 @@ impl ScreenLike for CreateAssetLockScreen { if let Some(credits) = credits { // Transition to WaitingForAssetLock BEFORE dispatching to prevent duplicate dispatches { - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::WaitingForAssetLock; } @@ -729,7 +727,7 @@ impl ScreenLike for CreateAssetLockScreen { fn refresh(&mut self) {} fn display_task_result(&mut self, result: BackendTaskSuccessResult) { - let current_step = *self.step.read().unwrap(); + let current_step = *self.step.read_or_recover(); match current_step { WalletFundedScreenStep::WaitingOnFunds => { @@ -742,7 +740,7 @@ impl ScreenLike for CreateAssetLockScreen { if let Some(funding_address) = &self.funding_address && funding_address == address { - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::FundsReceived; self.funding_utxo = Some(utxo); drop(step); // Release the lock before creating new action @@ -765,7 +763,7 @@ impl ScreenLike for CreateAssetLockScreen { self.asset_lock_tx_id = Some(tx_id); } - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::Success; drop(step); self.display_message( @@ -780,7 +778,7 @@ impl ScreenLike for CreateAssetLockScreen { // This is the asset lock transaction from ZMQ if tx.special_transaction_payload.is_some() { self.asset_lock_tx_id = Some(tx.txid().to_string()); - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::Success; drop(step); self.display_message( @@ -802,7 +800,7 @@ impl ScreenLike for CreateAssetLockScreen { self.asset_lock_tx_id = Some(tx_id); } - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::Success; drop(step); self.display_message( @@ -817,7 +815,7 @@ impl ScreenLike for CreateAssetLockScreen { // This is the asset lock transaction from ZMQ if tx.special_transaction_payload.is_some() { self.asset_lock_tx_id = Some(tx.txid().to_string()); - let mut step = self.step.write().unwrap(); + let mut step = self.step.write_or_recover(); *step = WalletFundedScreenStep::Success; drop(step); self.display_message( diff --git a/src/ui/wallets/import_mnemonic_screen.rs b/src/ui/wallets/import_mnemonic_screen.rs index cc1b8e133..acfd957ed 100644 --- a/src/ui/wallets/import_mnemonic_screen.rs +++ b/src/ui/wallets/import_mnemonic_screen.rs @@ -1,5 +1,6 @@ use crate::app::AppAction; use crate::context::AppContext; +use crate::lock_helper::RwLockExt; use crate::model::wallet::single_key::SingleKeyWallet; use crate::ui::components::left_panel::add_left_panel; use crate::ui::components::styled::island_central_panel; @@ -257,7 +258,7 @@ impl ImportMnemonicScreen { })?; let wallet_arc = Arc::new(RwLock::new(wallet)); - let new_wallet_seed_hash = wallet_arc.read().unwrap().seed_hash(); + let new_wallet_seed_hash = wallet_arc.read_or_recover().seed_hash(); // Acquire a write lock and add the new wallet if let Ok(mut wallets) = self.app_context.wallets.write() {