Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 3 additions & 15 deletions packages/rs-platform-wallet-ffi/src/persistence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
Expand All @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment thread
lklimek marked this conversation as resolved.
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,
);
Expand Down Expand Up @@ -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 =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ impl<B: TransactionBroadcaster + ?Sized> AssetLockManager<B> {
/// 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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1139,6 +1139,7 @@ mod tests {
fail_next_flush: Mutex<Option<PersistenceErrorKind>>,
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,
}
Expand All @@ -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
}
}

Expand Down Expand Up @@ -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::<()>(
Expand Down
Loading