From d93e8fa2e9f675e79c1ec0ac2606c4aa5fbaba09 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Sat, 21 Feb 2026 17:19:57 -0600 Subject: [PATCH 1/2] fix: address issue 612 error-handling inconsistencies --- src/backend_task/platform_info.rs | 3 +- src/database/contested_names.rs | 40 +++++++-- src/database/wallet.rs | 145 ++++++++++++++++++++++++++++-- 3 files changed, 172 insertions(+), 16 deletions(-) diff --git a/src/backend_task/platform_info.rs b/src/backend_task/platform_info.rs index 71cbe738c..38a6cde91 100644 --- a/src/backend_task/platform_info.rs +++ b/src/backend_task/platform_info.rs @@ -264,11 +264,10 @@ fn format_withdrawal_documents_with_daily_limit( "Withdrawal Information:\n\n\ Total Amount: {:.8} Dash\n\ Daily Withdrawal Limit: {:.8} Dash\n\ - Remaining Today: {:.8} Dash\n\n\ + Remaining Today: N/A (24h usage data unavailable)\n\n\ Recent Withdrawals:\n {}", total_amount as f64 / (dash_to_credits!(1) as f64), daily_withdrawal_limit as f64 / (dash_to_credits!(1) as f64), - daily_withdrawal_limit as f64 / (dash_to_credits!(1) as f64), // TODO: subtract actual 24h withdrawal amount when available amounts.join("\n ") )) } diff --git a/src/database/contested_names.rs b/src/database/contested_names.rs index 173e5de70..a432ccc34 100644 --- a/src/database/contested_names.rs +++ b/src/database/contested_names.rs @@ -1,5 +1,5 @@ use crate::context::AppContext; -use crate::database::Database; +use crate::database::{CorruptedBlobError, Database}; use crate::model::contested_name::{ContestState, Contestant, ContestedName}; use dash_sdk::dpp::dashcore::Network; use dash_sdk::dpp::data_contract::document_type::DocumentTypeRef; @@ -528,15 +528,41 @@ impl Database { // Serialize the document if available let deserialized_contender = contender .try_to_contender(dpns_domain_document_type, app_context.platform_version()) - .expect("expect a contender document deserialization"); - - let document = deserialized_contender.document().as_ref().unwrap().clone(); + .map_err(|e| { + rusqlite::Error::FromSqlConversionFailure( + 0, + rusqlite::types::Type::Blob, + Box::new(CorruptedBlobError(format!( + "Failed to deserialize contender for identity {}: {}", + identity_id, e + ))), + ) + })?; + + let document = deserialized_contender.document().as_ref().ok_or_else(|| { + rusqlite::Error::FromSqlConversionFailure( + 0, + rusqlite::types::Type::Blob, + Box::new(CorruptedBlobError(format!( + "Missing contender document for identity {}", + identity_id + ))), + ) + })?; let name = document .get("label") - .expect("expected name") - .as_str() - .unwrap(); + .and_then(|value| value.as_str()) + .ok_or_else(|| { + rusqlite::Error::FromSqlConversionFailure( + 0, + rusqlite::types::Type::Text, + Box::new(CorruptedBlobError(format!( + "Missing or invalid contender label for identity {}", + identity_id + ))), + ) + })?; let created_at = document.created_at(); let created_at_block_height = document.created_at_block_height(); diff --git a/src/database/wallet.rs b/src/database/wallet.rs index 092993ca4..bc85a2779 100644 --- a/src/database/wallet.rs +++ b/src/database/wallet.rs @@ -441,14 +441,24 @@ impl Database { // Reconstruct the extended public keys let master_ecdsa_extended_public_key = ExtendedPubKey::decode(&master_ecdsa_bip44_account_0_epk_bytes).map_err(|e| { - rusqlite::Error::InvalidParameterName(format!( - "Failed to decode ExtendedPubKey: {}", - e - )) + rusqlite::Error::FromSqlConversionFailure( + 4, + rusqlite::types::Type::Blob, + Box::new(CorruptedBlobError(format!( + "Failed to decode ExtendedPubKey: {}", + e + ))), + ) })?; let seed_hash_array: [u8; 32] = seed_hash.try_into().map_err(|_| { - rusqlite::Error::InvalidParameterName("Seed hash should be 32 bytes".to_string()) + rusqlite::Error::FromSqlConversionFailure( + 0, + rusqlite::types::Type::Blob, + Box::new(CorruptedBlobError( + "Seed hash should be 32 bytes".to_string(), + )), + ) })?; let closed_wallet_seed = ClosedKeyItem { seed_hash: seed_hash_array, @@ -462,8 +472,12 @@ impl Database { } else { WalletSeed::Open(OpenWalletSeed { seed: encrypted_seed.try_into().map_err(|_| { - rusqlite::Error::InvalidParameterName( - "Seed should be 64 bytes for open wallet".to_string(), + rusqlite::Error::FromSqlConversionFailure( + 1, + rusqlite::types::Type::Blob, + Box::new(CorruptedBlobError( + "Seed should be 64 bytes for open wallet".to_string(), + )), ) })?, wallet_info: closed_wallet_seed, @@ -1256,7 +1270,9 @@ impl From for rusqlite::Error { mod tests { use super::*; use crate::database::test_helpers::create_test_database; + use dash_sdk::dpp::dashcore::secp256k1::Secp256k1; use dash_sdk::dpp::key_wallet::bip32::DerivationPath; + use dash_sdk::dpp::key_wallet::bip32::{ChildNumber, ExtendedPrivKey}; use std::str::FromStr; fn create_test_address(network: Network) -> Address { @@ -1273,6 +1289,121 @@ mod tests { hash } + fn create_test_master_epk_bytes(network: Network) -> Vec { + let seed = [7u8; 64]; + let secp = Secp256k1::new(); + let master = ExtendedPrivKey::new_master(network, &seed).expect("master key"); + let path = DerivationPath::from(vec![ + ChildNumber::Hardened { index: 44 }, + ChildNumber::Hardened { index: 1 }, + ChildNumber::Hardened { index: 0 }, + ]); + let account = master + .derive_priv(&secp, &path) + .expect("derive bip44 account"); + ExtendedPubKey::from_priv(&secp, &account).encode() + } + + #[test] + fn test_get_wallets_invalid_epk_uses_from_sql_conversion_failure() { + let db = create_test_database().expect("Failed to create test database"); + let seed_hash = create_test_seed_hash(); + + { + let conn = db.conn.lock().unwrap(); + conn.execute( + "INSERT INTO wallet ( + seed_hash, encrypted_seed, salt, nonce, master_ecdsa_bip44_account_0_epk, + alias, is_main, uses_password, password_hint, network, + confirmed_balance, unconfirmed_balance, total_balance + ) VALUES (?, ?, ?, ?, ?, NULL, 0, 0, NULL, 'testnet', 0, 0, 0)", + rusqlite::params![ + seed_hash.as_slice(), + vec![0u8; 64], + vec![0u8; 16], + vec![0u8; 12], + vec![0u8; 78], + ], + ) + .expect("Failed to insert test wallet"); + } + + let err = db + .get_wallets(&Network::Testnet) + .expect_err("expected failure"); + match err { + rusqlite::Error::FromSqlConversionFailure(4, rusqlite::types::Type::Blob, _) => {} + _ => panic!("unexpected error variant: {}", err), + } + } + + #[test] + fn test_get_wallets_invalid_seed_hash_length_uses_from_sql_conversion_failure() { + let db = create_test_database().expect("Failed to create test database"); + let valid_epk = create_test_master_epk_bytes(Network::Testnet); + + { + let conn = db.conn.lock().unwrap(); + conn.execute( + "INSERT INTO wallet ( + seed_hash, encrypted_seed, salt, nonce, master_ecdsa_bip44_account_0_epk, + alias, is_main, uses_password, password_hint, network, + confirmed_balance, unconfirmed_balance, total_balance + ) VALUES (?, ?, ?, ?, ?, NULL, 0, 0, NULL, 'testnet', 0, 0, 0)", + rusqlite::params![ + vec![0u8; 31], + vec![0u8; 64], + vec![0u8; 16], + vec![0u8; 12], + valid_epk, + ], + ) + .expect("Failed to insert test wallet"); + } + + let err = db + .get_wallets(&Network::Testnet) + .expect_err("expected failure"); + match err { + rusqlite::Error::FromSqlConversionFailure(0, rusqlite::types::Type::Blob, _) => {} + _ => panic!("unexpected error variant: {}", err), + } + } + + #[test] + fn test_get_wallets_invalid_open_seed_length_uses_from_sql_conversion_failure() { + let db = create_test_database().expect("Failed to create test database"); + let seed_hash = create_test_seed_hash(); + let valid_epk = create_test_master_epk_bytes(Network::Testnet); + + { + let conn = db.conn.lock().unwrap(); + conn.execute( + "INSERT INTO wallet ( + seed_hash, encrypted_seed, salt, nonce, master_ecdsa_bip44_account_0_epk, + alias, is_main, uses_password, password_hint, network, + confirmed_balance, unconfirmed_balance, total_balance + ) VALUES (?, ?, ?, ?, ?, NULL, 0, 0, NULL, 'testnet', 0, 0, 0)", + rusqlite::params![ + seed_hash.as_slice(), + vec![0u8; 63], + vec![0u8; 16], + vec![0u8; 12], + valid_epk, + ], + ) + .expect("Failed to insert test wallet"); + } + + let err = db + .get_wallets(&Network::Testnet) + .expect_err("expected failure"); + match err { + rusqlite::Error::FromSqlConversionFailure(1, rusqlite::types::Type::Blob, _) => {} + _ => panic!("unexpected error variant: {}", err), + } + } + #[test] fn test_wallet_balance_update() { let db = create_test_database().expect("Failed to create test database"); From 3aedd8827b84b4e1edcbf89c1c80b09392db0073 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Sat, 21 Feb 2026 18:18:45 -0600 Subject: [PATCH 2/2] fix: replace remaining expect() with FromSqlConversionFailure in identity loading Address CodeRabbit nitpick: the wallet seed hash conversion in the identity loading path (get_wallets) still used .expect() which would panic on corrupted data. Replace with the same FromSqlConversionFailure + CorruptedBlobError pattern used for the other three conversions. --- src/database/wallet.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/database/wallet.rs b/src/database/wallet.rs index bc85a2779..c03cb08c3 100644 --- a/src/database/wallet.rs +++ b/src/database/wallet.rs @@ -878,9 +878,15 @@ impl Database { let wallet_seed_hash: Vec = row.get(1)?; let wallet_index: u32 = row.get(2)?; - let wallet_seed_hash_array: [u8; 32] = wallet_seed_hash - .try_into() - .expect("Seed hash should be 32 bytes"); + let wallet_seed_hash_array: [u8; 32] = wallet_seed_hash.try_into().map_err(|_| { + rusqlite::Error::FromSqlConversionFailure( + 1, + rusqlite::types::Type::Blob, + Box::new(CorruptedBlobError( + "Identity wallet seed hash should be 32 bytes".to_string(), + )), + ) + })?; Ok((data, wallet_seed_hash_array, wallet_index)) })?;