Skip to content

feat(swift-sdk): compact-filter rescan button in Core Sync Status#4103

Merged
QuantumExplorer merged 3 commits into
v4.1-devfrom
claude/swift-example-simulator-d9d873
Jul 12, 2026
Merged

feat(swift-sdk): compact-filter rescan button in Core Sync Status#4103
QuantumExplorer merged 3 commits into
v4.1-devfrom
claude/swift-example-simulator-d9d873

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Jul 12, 2026

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

#4099 exposed SPV compact-filter rescan (spvRescanFilters(walletId:fromHeight:), a wallet synced-height rewind), but SwiftExampleApp had no way to trigger it. The only way to recheck filters was the Core sync Clear button, which throws away headers and all sync state. This adds the cheap middle option: recheck filters without nuking anything.

What was done?

All in SwiftExampleApp/Core/Views/CoreContentView.swift (app-layer orchestration only; no SDK/FFI changes):

  • Added an indigo Rescan button to the Core Sync Status controls row between Start/Pause and Clear (coreSync.rescanFiltersButton), disabled when the active network has no wallets.
  • Tapping it presents a confirmationDialog with height choices — last 1,000 blocks, last 10,000 blocks, everything (height 0), or a custom height via a numeric-input alert. "Last N" counts back from the filter-scan tip, falling back to filter-headers then headers height.
  • On selection, armRescan(fromHeight:) calls walletManager.spvRescanFilters for every wallet on the active network, collecting per-wallet failures instead of aborting on the first.
  • The outcome (armed height/wallet count, plus any per-wallet failures) is logged to the console, matching how the Start/Clear actions report.

How Has This Been Tested?

  • build_ios.sh --target sim (warnings-as-errors) passes.
  • Driven live on an iOS 26.3 simulator: the preset path ("Last 1,000 blocks" with Core sync unstarted correctly clamps to height 0) and the custom path (typed 5000 into the height alert) both arm the rescan for the network's wallet.
  • The underlying checkpoint-rewind behavior is covered by feat(platform-wallet): expose SPV filter rescan via wallet synced-height rewind #4099's tests; this PR only adds the UI trigger.

Breaking Changes

None.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added a Rescan control for SPV compact filters in the Core Sync Status section.
    • Users can rescan preset recent block ranges or enter a custom starting height.
    • Added confirmation dialogs and status messages showing successful rescans or wallet-specific failures.
    • Rescan is unavailable when no wallets exist on the active network.

Adds a Rescan button to SwiftExampleApp's Core Sync Status card that
arms an SPV compact-filter rescan (via the spvRescanFilters API from
#4099) for every wallet on the active network, with a height-choice
dialog (last 1k/10k blocks, everything, or a custom height) and a
status caption reporting the outcome.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions github-actions Bot added this to the v4.1.0 milestone Jul 12, 2026
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@QuantumExplorer, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 1 minute

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 16d70f7a-a12a-4cee-99a9-67081c90d9ec

📥 Commits

Reviewing files that changed from the base of the PR and between 7ce4ed4 and f08071d.

📒 Files selected for processing (1)
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CoreContentView.swift
📝 Walkthrough

Walkthrough

CoreContentView adds SPV compact-filter rescan controls for the active network, supporting preset ranges and custom heights, invoking rescans across active wallets, and displaying summarized success or failure status.

Changes

SPV rescan flow

Layer / File(s) Summary
Rescan controls and input flow
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CoreContentView.swift
Adds rescan state, a network-aware button, preset range confirmation, custom-height input, and transient outcome messaging.
Rescan height and wallet execution
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CoreContentView.swift
Derives the rescan reference tip, converts block ranges to heights, invokes spvRescanFilters for each active wallet, and formats failures.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CoreContentView
  participant walletManager
  User->>CoreContentView: select rescan range
  CoreContentView->>walletManager: spvRescanFilters for each active wallet
  walletManager-->>CoreContentView: rescan result or error
  CoreContentView-->>User: display rescan status
Loading

Suggested reviewers: shumkov, llbartekll, zocolini

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a compact-filter rescan button in Core Sync Status.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/swift-example-simulator-d9d873

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@thepastaclaw

thepastaclaw commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator

✅ Review complete (commit f08071d)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CoreContentView.swift`:
- Around line 232-246: Update the custom-height “Rescan” action in the
showCustomHeightAlert handler to set rescanStatus to an appropriate
invalid-input message when UInt32 parsing fails, including empty values and
values above UInt32.max. Preserve the existing armRescan(fromHeight:) path for
valid input and continue clearing customHeightText.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 842c261d-5719-4a84-9f4a-56007e81c7d2

📥 Commits

Reviewing files that changed from the base of the PR and between 0bfdc57 and 7ce4ed4.

📒 Files selected for processing (1)
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CoreContentView.swift

QuantumExplorer and others added 2 commits July 13, 2026 04:35
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@QuantumExplorer
QuantumExplorer merged commit c898b0d into v4.1-dev Jul 12, 2026
16 checks passed
@QuantumExplorer
QuantumExplorer deleted the claude/swift-example-simulator-d9d873 branch July 12, 2026 22:24

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

The latest delta fixes the invalid-custom-height path by disabling Rescan until customHeightValue parses, with no new latest-delta findings. The two prior rescan-correctness blockers remain unchanged, and the test-coverage nitpick remains. Prior reconciliation: prior-1 STILL VALID, prior-2 STILL VALID, prior-3 FIXED, prior-4 STILL VALID.

Carried-forward prior findings: 3 (2 blocking, 1 nitpick). New findings in the latest delta: none. Fixed prior findings: 1 (prior-3).

Source: reviewers gpt-5.6-sol (Sol reviewer) and claude-sonnet-5 (Sonnet reviewer); verifier claude-sonnet-5. Orchestrator openai/gpt-5.6-sol (orchestration-only).

🔴 2 blocking | 💬 1 nitpick(s)

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CoreContentView.swift`:
- [BLOCKING] packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CoreContentView.swift:908-919: [Carried forward — STILL VALID] Relative rescan presets collapse to a full rescan once SPV is stopped
  `armRescan(lastBlocks:)` computes its 'last N blocks' target relative to `rescanReferenceTip`, which reads `walletManager.spvProgress.{filters,filterHeaders,headers}?.currentHeight`. Once SPV is stopped, `SpvRuntime::stop()` drops the client, `sync_progress()` returns `None`, and the FFI layer publishes an empty progress snapshot back through Swift — every sub-progress field becomes absent/0. `rescanReferenceTip` then falls through all three `?? 0` fallbacks to 0, so both 'Last 1,000 blocks' and 'Last 10,000 blocks' silently call `armRescan(fromHeight: 0)`, behaving identically to 'Everything (from height 0)' with no indication to the user that their targeted partial rescan just became a full rescan.
- [BLOCKING] packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CoreContentView.swift:932-952: [Carried forward — STILL VALID] Rescan reports wallets as armed without verifying an actual rewind occurred
  The per-wallet loop in `armRescan(fromHeight:)` treats every non-throwing `spvRescanFilters` call as 'armed', without comparing `fromHeight` against that wallet's current checkpoint. `spv_rescan_filters_blocking` unconditionally overwrites `synced_height` and returns success even when `fromHeight` is at or above the checkpoint, so no rewind actually occurs despite the reported success — and a later height applied to an already-behind wallet can skip pending scan work. Use `walletManager.coreWalletState(for:)` to gate the call on a strict rewind.

Comment on lines +908 to +919
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)
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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

armRescan(lastBlocks:) computes its 'last N blocks' target relative to rescanReferenceTip, which reads walletManager.spvProgress.{filters,filterHeaders,headers}?.currentHeight. Once SPV is stopped, SpvRuntime::stop() drops the client, sync_progress() returns None, and the FFI layer publishes an empty progress snapshot back through Swift — every sub-progress field becomes absent/0. rescanReferenceTip then falls through all three ?? 0 fallbacks to 0, so both 'Last 1,000 blocks' and 'Last 10,000 blocks' silently call armRescan(fromHeight: 0), behaving identically to 'Everything (from height 0)' with no indication to the user that their targeted partial rescan just became a full rescan.

Suggested change
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)
}
private func armRescan(lastBlocks: UInt32) {
let tip = rescanReferenceTip
guard tip > 0 else {
print("⚠️ Start SPV before using a relative rescan preset, or choose a specific height.")
return
}
armRescan(fromHeight: tip > lastBlocks ? tip - lastBlocks : 0)
}

source: ['codex-general', 'sonnet5-general']

Comment on lines +932 to +952
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: ", "))")
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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 armRescan(fromHeight:) treats every non-throwing spvRescanFilters call as 'armed', without comparing fromHeight against that wallet's current checkpoint. spv_rescan_filters_blocking unconditionally overwrites synced_height and returns success even when fromHeight is at or above the checkpoint, so no rewind actually occurs despite the reported success — and a later height applied to an already-behind wallet can skip pending scan work. Use walletManager.coreWalletState(for:) to gate the call on a strict rewind.

source: ['codex-general', 'sonnet5-general']

Comment on lines 182 to +233
@@ -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.")
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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 rescanReferenceTip/armRescan would have caught the two blocking defects above directly.

source: ['codex-general', 'sonnet5-general']

bezibalazs added a commit that referenced this pull request Jul 13, 2026
…nce executor + mnemonic error codes

Three lifecycle blockers from review at 97cd577:

- Use-after-free window on teardown: scope.cancel() is cooperative, so a
  coroutine already inside a blocking JNI call (the DashPay contact-crypto
  drain, a mid-call poll) kept running while close() freed the boxed
  PersistenceCallbacks context via nativeDestroy — a late
  persister.store() then dereferenced freed memory through a dropped
  GlobalRef. close() now joins the manager scope before nativeDestroy so
  every in-flight native call has returned first.

- Executor leak per network switch: the handler's owned non-daemon
  "dash-persistence" single-thread executor was created inline as a
  default arg and never shut down. The handler now tracks ownership
  (injected dispatchers are never closed), implements AutoCloseable, and
  the manager closes it after nativeDestroy quiesces callbacks.

- Mnemonic failures lost their code: FFIError::clean() resets code to
  Success, and mnemonic.rs read error.code AFTER clean() — every failure
  surfaced as 0/InternalError. Capture the code first (an invalid word
  count keeps InvalidParameter).

Review suggestions in the same round:

- Surface the exact DashPay send fee: the JNI now returns a 40-byte packed
  txid[32] || fee(u64 LE) and Dashpay.sendPayment returns
  SendPaymentResult(txid, feeDuffs) — the Swift (txid, feeDuffs) shape.
  (The prior comment claiming the fee was readable from the persisted
  payment entry was wrong — DashpayPaymentEntity has no fee column.)

- PARITY.md: CoreContentView row downgraded to partial — the Swift-only
  compact-filter Rescan button + height picker (#4103 over the #4099
  rescan FFI) is documented as a deferred Android gap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants