-
Notifications
You must be signed in to change notification settings - Fork 55
fix(swift-example-app): make Platform Sync "Clear" actually clear synced data #3959
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: v3.1-dev
Are you sure you want to change the base?
Changes from 3 commits
09333ba
8d3ca39
7ee5034
93f3bf0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -8,6 +8,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| import Foundation | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import SwiftUI | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import Combine | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import SwiftData | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import SwiftDashSDK | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Observable service managing BLAST address balance sync UI state. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -189,6 +190,79 @@ class PlatformBalanceSyncService: ObservableObject { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| syncStateCancellable?.cancel() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Clear platform-address sync data for real — what the Platform | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Sync "Clear" button calls. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Plain [`clearDisplay`] only zeroes the in-memory `@Published` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// mirror, so the next sync resumed from the surviving watermark in | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// ~2s (the "Clear didn't work" symptom). This wipes the stores the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// synced data actually lives in, mirroring | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// `ShieldedService.clearLocalState`: Rust-side reset FIRST (so the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// next sync can't re-persist stale rows), then the SwiftData wipe, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// then the published-mirror reset. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Scoped to the **active network**. The SwiftData store holds rows | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// for every network at once (the UI filters them by network), so a | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// blanket `delete(model:)` would also erase other networks' cached | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// platform state. `PersistentPlatformAddress` carries no network | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// column, so it's scoped via `walletIdsOnNetwork` — the same | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// wallet-id-per-network pivot the view uses; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// `PersistentPlatformAddressesSyncState` is network-keyed and is | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// scoped by `networkRaw`. This matches the manager-level Rust reset, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// which only touches the active network's registered wallets. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Fails closed: if the Rust reset OR the SwiftData delete throws, it | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// surfaces the error in `lastError` and returns WITHOUT calling | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// `clearDisplay()` — the UI never shows a false "cleared" state over | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// data that is still on disk / in Rust memory. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func clearLocalState( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| modelContext: ModelContext, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| network: Network, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| walletIdsOnNetwork: Set<Data> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) async { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 1) Reset the Rust-owned state BEFORE touching disk. Without | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // this the in-memory watermark survives and the next "Sync | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Now" resumes incrementally (fast) instead of doing a full | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // rescan; a still-registered background pass could also | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // re-persist the rows we're about to delete. Fail closed — | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // the reset is load-bearing for the wipe, so abort (surfacing | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // the error) rather than leave a half-cleared state. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let walletManager { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| do { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try await walletManager.resetPlatformAddressSyncState() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lastError = "Failed to reset platform-address sync state: \(error.localizedDescription)" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SDKLogger.error(lastError ?? "") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 2) Delete this network's platform-address rows: the cached | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // per-address balances (scoped via the network's wallet-id | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // set) and the network-keyed sync-state watermark. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| do { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let addresses = try modelContext.fetch(FetchDescriptor<PersistentPlatformAddress>()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for row in addresses where walletIdsOnNetwork.contains(row.walletId) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| modelContext.delete(row) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+244
to
+247
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Blocking: Clear deletes durable platform-address metadata that the balance callback cannot rebuild
Suggested change
source: ['codex']
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolved in this update — Clear deletes durable platform-address metadata that the balance callback cannot rebuild no longer present. Auto-resolved by the review system based on the latest commit diff. If you believe this was closed in error, reopen the thread.
Comment on lines
+244
to
+247
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Blocking: Clear deletes platform-address derivation metadata that no later sync can rebuild PersistentPlatformAddress is durable derivation state — bech32m address, 20-byte hash, 33-byte compressed public key, account/address index, derivation path (see PersistentPlatformAddress.swift:33-71) — not a balance cache. Deleting these rows on Clear creates an asymmetry that the rest of the pipeline cannot recover from in-session:
Net effect: after Clear, the next 'Sync Now' produces balance updates against now-missing rows that silently no-op in SwiftData. The Platform Sync UI stays empty (and the spend/signing path loses its derivation-path source) until app restart triggers re-derivation through the wallet load path. This contradicts the file's own contract: 'data stays cleared until the user explicitly resyncs'. Fix by zeroing only the volatile balance/sync fields in place for
Suggested change
source: ['claude', 'codex'] |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let networkRaw = network.rawValue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let syncStates = try modelContext.fetch(FetchDescriptor<PersistentPlatformAddressesSyncState>()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for row in syncStates where row.networkRaw == networkRaw { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| modelContext.delete(row) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try modelContext.save() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lastError = "Failed to wipe persisted platform-address state: \(error.localizedDescription)" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SDKLogger.error(lastError ?? "") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // 3) Zero the published display mirror — only on full success. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| clearDisplay() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Trigger a manual sync. No-op if already syncing. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func manualSync() async { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await performSync() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.