Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
48 changes: 40 additions & 8 deletions packages/rs-platform-wallet-ffi/src/shielded_send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,9 +311,11 @@ pub unsafe extern "C" fn platform_wallet_manager_shielded_transfer(
// calling thread.
let result = block_on_worker(async move {
let prover = CachedOrchardProver::new();
wallet
let r = wallet
.shielded_transfer_to(&coordinator, account, &recipient, amount, memo, &prover)
.await
.await;
poke_sync_on_unconfirmed(&r, &coordinator);
r
});
map_spend_result(result, "shielded transfer")
}
Expand Down Expand Up @@ -363,9 +365,11 @@ pub unsafe extern "C" fn platform_wallet_manager_shielded_unshield(

let result = block_on_worker(async move {
let prover = CachedOrchardProver::new();
wallet
let r = wallet
.shielded_unshield_to(&coordinator, account, &to_addr_str, amount, &prover)
.await
.await;
poke_sync_on_unconfirmed(&r, &coordinator);
r
});
map_spend_result(result, "shielded unshield")
}
Expand Down Expand Up @@ -412,7 +416,7 @@ pub unsafe extern "C" fn platform_wallet_manager_shielded_withdraw(

let result = block_on_worker(async move {
let prover = CachedOrchardProver::new();
wallet
let r = wallet
.shielded_withdraw_to(
&coordinator,
account,
Expand All @@ -421,11 +425,37 @@ pub unsafe extern "C" fn platform_wallet_manager_shielded_withdraw(
core_fee_per_byte,
&prover,
)
.await
.await;
poke_sync_on_unconfirmed(&r, &coordinator);
r
});
map_spend_result(result, "shielded withdraw")
}

/// On the AMBIGUOUS outcome (broadcast accepted, result unconfirmed),
/// kick an immediate forced shielded sync so the first re-drive check —
/// nullifier re-check, then re-broadcast of the persisted transition —
/// happens now instead of at the next background tick. Fire-and-forget:
/// the spend's own result is already decided, and the sync pass owns
/// resolution from here (`redrive_pending_spends` + the prune backstop).
fn poke_sync_on_unconfirmed<T>(
result: &Result<T, PlatformWalletError>,
coordinator: &std::sync::Arc<platform_wallet::wallet::shielded::NetworkShieldedCoordinator>,
) {
let ambiguous = matches!(
result,
Err(PlatformWalletError::ShieldedSpendUnconfirmed { .. })
| Err(PlatformWalletError::ShieldedBroadcastUnconfirmed { .. })
);
if !ambiguous {
return;
}
let coordinator = std::sync::Arc::clone(coordinator);
runtime().spawn(async move {
let _ = coordinator.sync(true).await;
});
Comment thread
QuantumExplorer marked this conversation as resolved.
}
Comment thread
QuantumExplorer marked this conversation as resolved.
Outdated

/// Map a shielded operation outcome (shield / unshield / transfer /
/// withdraw) to a typed FFI result, mirroring the identity-create sibling's
/// code split so hosts can tell "definitively failed, safe to retry" from
Expand Down Expand Up @@ -600,7 +630,7 @@ pub unsafe extern "C" fn platform_wallet_manager_shielded_identity_create_from_p
// `Signer<IdentityPublicKey>`.
let identity_signer: &VTableSigner = &*(signer_identity_addr as *const VTableSigner);
let prover = CachedOrchardProver::new();
wallet
let r = wallet
.shielded_identity_create_from_pool(
&coordinator,
account,
Expand All @@ -611,7 +641,9 @@ pub unsafe extern "C" fn platform_wallet_manager_shielded_identity_create_from_p
identity_signer,
&prover,
)
.await
.await;
poke_sync_on_unconfirmed(&r, &coordinator);
r
});

match result {
Expand Down
37 changes: 31 additions & 6 deletions packages/rs-platform-wallet/src/wallet/shielded/coordinator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -638,13 +638,38 @@ impl NetworkShieldedCoordinator {
// `notes` result and `notes.changeset` the receipts do.
let newly_spent_per_sub = notes.per_subwallet_newly_spent.clone();

// Residual-spend reconcile: `sync_notes_across` above marked every
// landed spend (clearing its reservation). Now release any still-
// pending pre-scan reservation whose recorded anchor Platform had
// already pruned before the scan — a spend broadcast-accepted but
// never landed, otherwise stranded for the session. Runs before the
// balance read so freed notes are reflected in this pass's balances.
// Residual-spend resolution: `sync_notes_across` above marked every
// landed spend (clearing its reservation and dropping its redrive
// record via the store hook). Two passes over what's left, both
// judged against the PRE-scan recorded-anchor set:
//
// 1. Re-drive — for each armed unconfirmed spend whose anchor is
// still recorded, re-broadcast the stored byte-identical
// transition (bounded by MAX_REDRIVE_ATTEMPTS) to actively
// resolve the ambiguity instead of waiting out the retention
// window.
// 2. Prune backstop — release any still-pending pre-scan
// reservation whose anchor was already pruned (the spend can
// never execute).
//
// Runs before the balance read so freed notes are reflected in
// this pass's balances.
if let Some((snapshot, recorded)) = stranded_release {
{
let persisters = self.persisters.read().await;
for (id, _) in &subwallets {
let persister = persisters.get(&id.wallet_id).cloned();
super::operations::redrive_pending_spends(
&self.sdk,
&self.store,
persister.as_ref(),
id.wallet_id,
*id,
&recorded,
)
.await;
}
}
self.release_stranded_spends(snapshot, &recorded).await;
Comment thread
QuantumExplorer marked this conversation as resolved.
}

Expand Down
Loading
Loading