diff --git a/packages/rs-platform-wallet-ffi/src/persistence.rs b/packages/rs-platform-wallet-ffi/src/persistence.rs index 09e06b1fe14..74b50812839 100644 --- a/packages/rs-platform-wallet-ffi/src/persistence.rs +++ b/packages/rs-platform-wallet-ffi/src/persistence.rs @@ -9989,14 +9989,12 @@ mod tests { } #[test] - fn asset_lock_reconciliation_requires_every_callback_leg() { + fn asset_lock_reconciliation_requires_atomic_persistence_callbacks() { fn complete_callbacks() -> PersistenceCallbacks { PersistenceCallbacks { on_changeset_begin_fn: Some(noop_begin), on_changeset_end_fn: Some(noop_end), on_persist_asset_locks_fn: Some(noop_asset_locks), - on_load_wallet_list_fn: Some(noop_load_wallets), - on_load_wallet_list_free_fn: Some(noop_free_wallets), ..Default::default() } } @@ -10012,18 +10010,8 @@ mod tests { missing_end.on_changeset_end_fn = None; let mut missing_asset_locks = complete_callbacks(); missing_asset_locks.on_persist_asset_locks_fn = None; - let mut missing_load = complete_callbacks(); - missing_load.on_load_wallet_list_fn = None; - let mut missing_load_free = complete_callbacks(); - missing_load_free.on_load_wallet_list_free_fn = None; - - for callbacks in [ - missing_begin, - missing_end, - missing_asset_locks, - missing_load, - missing_load_free, - ] { + + for callbacks in [missing_begin, missing_end, missing_asset_locks] { assert!(!declared_persister(callbacks, required) .persistence_capabilities() .contains(required)); diff --git a/packages/rs-platform-wallet-storage/tests/integration/sqlite_persist_roundtrip.rs b/packages/rs-platform-wallet-storage/tests/integration/sqlite_persist_roundtrip.rs index d13e50be66f..7e2ce7943dc 100644 --- a/packages/rs-platform-wallet-storage/tests/integration/sqlite_persist_roundtrip.rs +++ b/packages/rs-platform-wallet-storage/tests/integration/sqlite_persist_roundtrip.rs @@ -450,8 +450,8 @@ fn tc010_asset_lock_roundtrip() { /// reconstruction's status, admitted by the V004 CHECK widening — /// round-trips through the writer's TEXT status mapping and the /// lifecycle blob, chain proof included. -#[test] -fn tc010b_recovered_from_chain_lock_roundtrip() { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn tc010b_recovered_from_chain_lock_roundtrip() { use dashcore::hashes::Hash; use dashcore::{OutPoint, Transaction, Txid}; use dpp::identity::state_transition::asset_lock_proof::chain::ChainAssetLockProof; @@ -485,6 +485,15 @@ fn tc010b_recovered_from_chain_lock_roundtrip() { locks.asset_locks.insert(outpoint, entry.clone()); let (persister, tmp, path) = fresh_persister(); + assert!( + persister.persistence_capabilities().contains( + platform_wallet::changeset::PersistenceCapabilities::ASSET_LOCK_RECONCILIATION + ), + "SQLite must support reconciliation of the asset-lock rows it restores" + ); + assert!(!persister + .persistence_capabilities() + .contains(platform_wallet::changeset::PersistenceCapabilities::WALLET_RESTORE)); let w = wid(0xFB); ensure_wallet_meta(&persister, &w); persister @@ -499,20 +508,45 @@ fn tc010b_recovered_from_chain_lock_roundtrip() { drop(persister); let p2 = SqlitePersister::open(SqlitePersisterConfig::new(&path)).unwrap(); - let bucketed = platform_wallet_storage::sqlite::schema::asset_locks::load_state( - &p2.lock_conn_for_test(), - &w, - &platform_wallet_storage::LoadCtx::strict(), - ) - .unwrap(); - let tracked = &bucketed[&0][&outpoint]; + let state = p2 + .load() + .expect("public load restores recovered asset locks"); + let tracked = &state.wallets[&w].unused_asset_locks[&0][&outpoint]; assert_eq!(tracked.status, AssetLockStatus::RecoveredFromChain); - match &tracked.proof { - Some(dpp::prelude::AssetLockProof::Chain(chain)) => { - assert_eq!(chain.core_chain_locked_height, 411_495); - } - other => panic!("chain proof must survive the roundtrip, got {other:?}"), - } + assert_eq!(tracked.proof, entry.proof); + assert_eq!(tracked.amount, entry.amount_duffs); + assert_eq!(tracked.funding_type, entry.funding_type); + + struct NoopHandler; + impl platform_wallet::events::EventHandler for NoopHandler {} + impl platform_wallet::PlatformEventHandler for NoopHandler {} + + let sdk = std::sync::Arc::new(dash_sdk::SdkBuilder::new_mock().build().unwrap()); + let manager = platform_wallet::PlatformWalletManager::new( + sdk, + std::sync::Arc::new(p2), + std::sync::Arc::new(NoopHandler), + ); + manager + .load_from_persistor() + .await + .expect("manager hydration"); + let wallet_manager = manager.wallet_manager_arc(); + let wallets = wallet_manager.read().await; + let restored = &wallets + .get_wallet_info(&w) + .expect("restored wallet") + .tracked_asset_locks[&outpoint]; + assert_eq!(restored.status, tracked.status); + assert_eq!(restored.proof, tracked.proof); + assert_eq!(restored.out_point, tracked.out_point); + assert_eq!(restored.transaction, tracked.transaction); + assert_eq!(restored.amount, tracked.amount); + assert_eq!(restored.account_index, tracked.account_index); + assert_eq!(restored.identity_index, tracked.identity_index); + assert_eq!(restored.funding_type, tracked.funding_type); + drop(wallets); + assert!(manager.shutdown().await.all_clean()); drop(tmp); } diff --git a/packages/rs-platform-wallet/src/changeset/persistence_capabilities.rs b/packages/rs-platform-wallet/src/changeset/persistence_capabilities.rs index 6c17b550b1e..df857d41b23 100644 --- a/packages/rs-platform-wallet/src/changeset/persistence_capabilities.rs +++ b/packages/rs-platform-wallet/src/changeset/persistence_capabilities.rs @@ -131,14 +131,15 @@ impl PersistenceCapabilities { pub const SHIELDED_FVK_RESTART: Self = Self(Self::ATOMIC_CHANGESETS.0 | Self::SHIELDED_VIEWING_KEYS.0); - /// Capabilities required to durably reconcile an asset-lock status and - /// restore that exact row after process restart. + /// Capabilities required to atomically persist reconciliation of an + /// already-loaded tracked asset lock. pub const ASSET_LOCK_RECONCILIATION: Self = - Self(Self::ATOMIC_CHANGESETS.0 | Self::TRACKED_ASSET_LOCKS.0 | Self::WALLET_RESTORE.0); + Self(Self::ATOMIC_CHANGESETS.0 | Self::TRACKED_ASSET_LOCKS.0); /// Durable correlation and funding-key indices required for wallet drain. pub const WALLET_DRAIN: Self = Self( Self::ASSET_LOCK_RECONCILIATION.0 + | Self::WALLET_RESTORE.0 | Self::ASSET_LOCK_FUNDING_INDICES.0 | Self::ASSET_LOCK_OPERATION_IDS.0, ); @@ -273,10 +274,26 @@ mod tests { assert_eq!(PersistenceCapabilities::WALLET_DRAIN.bits(), 0x2285); assert_eq!( PersistenceCapabilities::ASSET_LOCK_RECONCILIATION.bits(), - 0x281 + 0x201 ); } + #[test] + fn reconciliation_requires_atomic_tracked_asset_lock_persistence() { + let required = PersistenceCapabilities::ATOMIC_CHANGESETS + .union(PersistenceCapabilities::TRACKED_ASSET_LOCKS); + assert!(required.contains(PersistenceCapabilities::ASSET_LOCK_RECONCILIATION)); + assert_eq!(required, PersistenceCapabilities::ASSET_LOCK_RECONCILIATION); + for incomplete in [ + PersistenceCapabilities::NONE, + PersistenceCapabilities::ATOMIC_CHANGESETS, + PersistenceCapabilities::TRACKED_ASSET_LOCKS, + PersistenceCapabilities::WALLET_RESTORE, + ] { + assert!(!incomplete.contains(PersistenceCapabilities::ASSET_LOCK_RECONCILIATION)); + } + } + #[test] fn required_sets_report_only_missing_bits() { let actual = diff --git a/packages/rs-platform-wallet/src/wallet/asset_lock/sync/tracking.rs b/packages/rs-platform-wallet/src/wallet/asset_lock/sync/tracking.rs index e97ae38bb77..57739e5427a 100644 --- a/packages/rs-platform-wallet/src/wallet/asset_lock/sync/tracking.rs +++ b/packages/rs-platform-wallet/src/wallet/asset_lock/sync/tracking.rs @@ -327,7 +327,7 @@ impl AssetLockManager { /// marker is stored and flushed synchronously. A failure rolls back the /// in-memory mutation only when the backend has not committed the store and /// did not retain a transient retry buffer. Before mutating, the backend - /// must attest atomic tracked-asset-lock persistence and restart restore. + /// must attest atomic tracked-asset-lock persistence. pub(crate) async fn mark_asset_lock_consumption_unknown( &self, out_point: &OutPoint, diff --git a/packages/rs-platform-wallet/src/wallet/shielded/fund_from_asset_lock.rs b/packages/rs-platform-wallet/src/wallet/shielded/fund_from_asset_lock.rs index 92cbd26b73e..ef4f800d19f 100644 --- a/packages/rs-platform-wallet/src/wallet/shielded/fund_from_asset_lock.rs +++ b/packages/rs-platform-wallet/src/wallet/shielded/fund_from_asset_lock.rs @@ -1139,6 +1139,7 @@ mod tests { fail_next_flush: Mutex>, store_commits_inline: AtomicBool, omit_reconciliation_capabilities: AtomicBool, + use_full_wallet_restore: AtomicBool, /// Attest the full wallet-drain contract instead of bare reconciliation. wallet_drain: AtomicBool, } @@ -1149,12 +1150,16 @@ mod tests { } fn persistence_capabilities(&self) -> PersistenceCapabilities { + let writes = PersistenceCapabilities::ATOMIC_CHANGESETS + .union(PersistenceCapabilities::TRACKED_ASSET_LOCKS); if self.omit_reconciliation_capabilities.load(Ordering::SeqCst) { - PersistenceCapabilities::NONE + PersistenceCapabilities::ATOMIC_CHANGESETS } else if self.wallet_drain.load(Ordering::SeqCst) { PersistenceCapabilities::WALLET_DRAIN + } else if self.use_full_wallet_restore.load(Ordering::SeqCst) { + writes.union(PersistenceCapabilities::WALLET_RESTORE) } else { - PersistenceCapabilities::ASSET_LOCK_RECONCILIATION + writes } } @@ -1634,6 +1639,9 @@ mod tests { AssetLockFundingType::AssetLockShieldedAddressTopUp, ] { let ctx = consumption_report_context_for(funding_type).await; + ctx.persistence + .use_full_wallet_restore + .store(true, Ordering::SeqCst); let error = ctx .manager .reconcile_asset_lock_submit_result::<()>(