-
Notifications
You must be signed in to change notification settings - Fork 56
feat(swift-sdk): compact-filter rescan button in Core Sync Status #4103
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
Changes from all commits
7ce4ed4
f894a33
f08071d
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 | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -21,6 +21,13 @@ struct CoreContentView: View { | |||||||||||||||||||||||||||||||||||||||||
| @State private var dashPayLastSync: Date? | ||||||||||||||||||||||||||||||||||||||||||
| // Progress values come from PlatformWalletManager (polled from FFI each second) | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| /// Rescan controls: the height-choice dialog and the follow-up | ||||||||||||||||||||||||||||||||||||||||||
| /// custom-height alert + its numeric field. The arm outcome is | ||||||||||||||||||||||||||||||||||||||||||
| /// logged to the console, matching Start/Clear β no UI surface. | ||||||||||||||||||||||||||||||||||||||||||
| @State private var showRescanDialog = false | ||||||||||||||||||||||||||||||||||||||||||
| @State private var showCustomHeightAlert = false | ||||||||||||||||||||||||||||||||||||||||||
| @State private var customHeightText = "" | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| /// All persisted platform addresses across every wallet. Summed | ||||||||||||||||||||||||||||||||||||||||||
| /// directly here so the global Sync Status view survives app | ||||||||||||||||||||||||||||||||||||||||||
| /// restarts / wallet reconfigures without depending on the | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -172,6 +179,17 @@ var body: some View { | |||||||||||||||||||||||||||||||||||||||||
| .tint(isSpvRunning ? .orange : .blue) | ||||||||||||||||||||||||||||||||||||||||||
| .controlSize(.mini) | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| Button(action: { showRescanDialog = true }) { | ||||||||||||||||||||||||||||||||||||||||||
| Text("Rescan") | ||||||||||||||||||||||||||||||||||||||||||
| .font(.caption) | ||||||||||||||||||||||||||||||||||||||||||
| .fontWeight(.medium) | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| .buttonStyle(.borderedProminent) | ||||||||||||||||||||||||||||||||||||||||||
| .tint(.indigo) | ||||||||||||||||||||||||||||||||||||||||||
| .controlSize(.mini) | ||||||||||||||||||||||||||||||||||||||||||
| .disabled(walletIdsOnNetwork.isEmpty) | ||||||||||||||||||||||||||||||||||||||||||
| .accessibilityIdentifier("coreSync.rescanFiltersButton") | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| Button(action: clearSyncData) { | ||||||||||||||||||||||||||||||||||||||||||
| Text("Clear") | ||||||||||||||||||||||||||||||||||||||||||
| .font(.caption) | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -185,6 +203,34 @@ var body: some View { | |||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| .padding(.vertical, 4) | ||||||||||||||||||||||||||||||||||||||||||
| .confirmationDialog( | ||||||||||||||||||||||||||||||||||||||||||
| "Rescan Compact Filters", | ||||||||||||||||||||||||||||||||||||||||||
| isPresented: $showRescanDialog, | ||||||||||||||||||||||||||||||||||||||||||
| titleVisibility: .visible | ||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||
| Button("Last 1,000 blocks") { armRescan(lastBlocks: 1_000) } | ||||||||||||||||||||||||||||||||||||||||||
| Button("Last 10,000 blocks") { armRescan(lastBlocks: 10_000) } | ||||||||||||||||||||||||||||||||||||||||||
| Button("Everything (from height 0)") { armRescan(fromHeight: 0) } | ||||||||||||||||||||||||||||||||||||||||||
| Button("Custom heightβ¦") { showCustomHeightAlert = true } | ||||||||||||||||||||||||||||||||||||||||||
| Button("Cancel", role: .cancel) {} | ||||||||||||||||||||||||||||||||||||||||||
| } message: { | ||||||||||||||||||||||||||||||||||||||||||
| Text("Rewind the filter scan to re-download and re-match " | ||||||||||||||||||||||||||||||||||||||||||
| + "compact filters for every wallet on this network.") | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| .alert("Rescan From Height", isPresented: $showCustomHeightAlert) { | ||||||||||||||||||||||||||||||||||||||||||
| TextField("Block height", text: $customHeightText) | ||||||||||||||||||||||||||||||||||||||||||
| .keyboardType(.numberPad) | ||||||||||||||||||||||||||||||||||||||||||
| Button("Rescan") { | ||||||||||||||||||||||||||||||||||||||||||
| if let height = customHeightValue { | ||||||||||||||||||||||||||||||||||||||||||
| armRescan(fromHeight: height) | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| customHeightText = "" | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| .disabled(customHeightValue == nil) | ||||||||||||||||||||||||||||||||||||||||||
| Button("Cancel", role: .cancel) { customHeightText = "" } | ||||||||||||||||||||||||||||||||||||||||||
| } message: { | ||||||||||||||||||||||||||||||||||||||||||
| Text("Enter the core block height to rescan compact filters from.") | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
182
to
+233
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. π¬ Nitpick: [Carried forward β STILL VALID] Rescan control still has no UI or unit test coverage The cumulative PR adds dialog, parsing, preset resolution, per-wallet iteration, and failure aggregation, all without UI or unit test coverage. Targeted unit tests on source: ['codex-general', 'sonnet5-general'] |
||||||||||||||||||||||||||||||||||||||||||
| } header: { | ||||||||||||||||||||||||||||||||||||||||||
| Text("Core Sync Status") | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -854,6 +900,62 @@ var body: some View { | |||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| // MARK: - Rescan | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| /// Height the "last N blocks" rescan choices count back from: | ||||||||||||||||||||||||||||||||||||||||||
| /// filter-scan tip, falling back to filter-headers then headers | ||||||||||||||||||||||||||||||||||||||||||
| /// when the earlier stages haven't produced a height yet. | ||||||||||||||||||||||||||||||||||||||||||
| private var rescanReferenceTip: UInt32 { | ||||||||||||||||||||||||||||||||||||||||||
| let filters = walletManager.spvProgress.filters?.currentHeight ?? 0 | ||||||||||||||||||||||||||||||||||||||||||
| if filters > 0 { return filters } | ||||||||||||||||||||||||||||||||||||||||||
| let filterHeaders = walletManager.spvProgress.filterHeaders?.currentHeight ?? 0 | ||||||||||||||||||||||||||||||||||||||||||
| if filterHeaders > 0 { return filterHeaders } | ||||||||||||||||||||||||||||||||||||||||||
| return walletManager.spvProgress.headers?.currentHeight ?? 0 | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| private func armRescan(lastBlocks: UInt32) { | ||||||||||||||||||||||||||||||||||||||||||
| let tip = rescanReferenceTip | ||||||||||||||||||||||||||||||||||||||||||
| armRescan(fromHeight: tip > lastBlocks ? tip - lastBlocks : 0) | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+908
to
+919
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: [Carried forward β STILL VALID] Relative rescan presets collapse to a full rescan once SPV is stopped
Suggested change
source: ['codex-general', 'sonnet5-general'] |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| /// The custom-height field parsed as a block height, or `nil` when | ||||||||||||||||||||||||||||||||||||||||||
| /// blank / non-numeric. Shared by the alert's disabled check and | ||||||||||||||||||||||||||||||||||||||||||
| /// its action so the two can't drift out of sync. | ||||||||||||||||||||||||||||||||||||||||||
| private var customHeightValue: UInt32? { | ||||||||||||||||||||||||||||||||||||||||||
| UInt32(customHeightText.trimmingCharacters(in: .whitespaces)) | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| /// Rewind the filter-scan checkpoint to `fromHeight` for every | ||||||||||||||||||||||||||||||||||||||||||
| /// wallet on the active network. Collects per-wallet failures | ||||||||||||||||||||||||||||||||||||||||||
| /// instead of aborting on the first, then logs the outcome to the | ||||||||||||||||||||||||||||||||||||||||||
| /// console (matching Start/Clear β no UI surface). | ||||||||||||||||||||||||||||||||||||||||||
| private func armRescan(fromHeight: UInt32) { | ||||||||||||||||||||||||||||||||||||||||||
| let ids = walletIdsOnNetwork | ||||||||||||||||||||||||||||||||||||||||||
| guard !ids.isEmpty else { return } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| var failures: [String] = [] | ||||||||||||||||||||||||||||||||||||||||||
| for walletId in ids { | ||||||||||||||||||||||||||||||||||||||||||
| do { | ||||||||||||||||||||||||||||||||||||||||||
| try walletManager.spvRescanFilters(walletId: walletId, fromHeight: fromHeight) | ||||||||||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||||||||||
| failures.append("\(rescanShortId(walletId)): \(error.localizedDescription)") | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| let armed = ids.count - failures.count | ||||||||||||||||||||||||||||||||||||||||||
| let applied = isSpvRunning ? "" : " (applies on next SPV start)" | ||||||||||||||||||||||||||||||||||||||||||
| print("π Rescan armed from height \(fromHeight.formatted()) " | ||||||||||||||||||||||||||||||||||||||||||
| + "for \(armed) wallet\(armed == 1 ? "" : "s")\(applied)") | ||||||||||||||||||||||||||||||||||||||||||
| if !failures.isEmpty { | ||||||||||||||||||||||||||||||||||||||||||
| print("β Rescan failed for: \(failures.joined(separator: ", "))") | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+932
to
+952
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: [Carried forward β STILL VALID] Rescan reports wallets as armed without verifying an actual rewind occurred The per-wallet loop in source: ['codex-general', 'sonnet5-general'] |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| /// First 4 bytes of a wallet id as hex, for compact failure lines. | ||||||||||||||||||||||||||||||||||||||||||
| private func rescanShortId(_ walletId: Data) -> String { | ||||||||||||||||||||||||||||||||||||||||||
| walletId.prefix(4).map { String(format: "%02x", $0) }.joined() | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| @MainActor | ||||||||||||||||||||||||||||||||||||||||||
| private func refreshMasternodeFlag() async { | ||||||||||||||||||||||||||||||||||||||||||
| // Best-effort: honor trusted mode reported by the Platform SDK. | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.