Skip to content

Fix cloud backup restore and recovery coverage - #886

Open
praveenperera wants to merge 66 commits into
masterfrom
cloud-backup-regression-fixes
Open

praveenperera wants to merge 66 commits into
masterfrom
cloud-backup-regression-fixes

Conversation

@praveenperera

Copy link
Copy Markdown
Member

Summary

Fix cloud backup regressions around restore, passkeys, iCloud metadata, and recovery coverage.

Cove treated backup coverage too loosely. Cancelled checks, wallet-set changes, and integrity downgrades could leave the UI thinking a wallet still had a confirmed cloud recovery copy. Restore also failed to handle leftover Keychain items, local Keychain conflicts, deleted iCloud paths, and passkey request errors after the system prompt.

This branch:

  • Counts only confirmed cloud backups as recovery coverage
  • Keeps coverage across cancelled checks and wallet-set changes
  • Drops coverage when backup integrity is downgraded
  • Hardens passkey match and restore, including leftover Keychain adoption and local conflict reporting
  • Runs iOS passkey work on the main thread, waits for prompts, and cancels stale queued actions
  • Requires user verification for Android passkeys
  • Confirms trusted local iCloud inventory with the provider, hides deleted paths, and reuses settled metadata generations
  • Keeps restore download order when open slots exist

Also included on this branch:

  • Wipe and deletion cleanup: clear wallet managers and sensitive sessions, preserve the setup flag across a full wipe, and fix wipe-PIN launch
  • Release tooling: compose iOS TestFlight from bump, bindings, and upload; move Google Play release into xtask

Testing

Not re-run as part of opening this PR. The branch adds Rust and iOS tests for recovery coverage, passkey match and restore, presentation handoff, and TestFlight xtask composition.

Platform Coverage

  • Tested on iOS device
  • Tested on Android device
  • Tested on iOS simulator
  • Tested on Android simulator
  • Not tested

Checklist

@coderabbitai

coderabbitai Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository: bitcoinppl/cove/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 47bca034-dbdf-4815-8bbb-5280a23240b6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


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.

@praveenperera
praveenperera force-pushed the cloud-backup-regression-fixes branch from b866a1b to c157895 Compare September 18, 2026 16:16
@praveenperera
praveenperera marked this pull request as ready for review September 18, 2026 16:16
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-18T16:23:32.271873Z c157895 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c157895d5d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ios/Cove/FFI/ICloudDriveHelper.swift Outdated
Comment thread rust/src/manager/cloud_backup_manager/error.rs Outdated
@greptile-apps

greptile-apps Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 3/5

[Critical risk] Rebuilds wallet wipe, backup restore, and recovery flows.

The PR does not appear safe to merge until incomplete restores are reported accurately and Android cannot unlock with a stale post-wipe projection.

Findings

  1. P1 Incomplete Restore Reports Success ▶
  2. P1 Wipe Unlocks With Stale State ▶

Summary

The PR tightens cloud-backup recovery coverage and restore handling, and adds wipe-state propagation and release-tooling changes.

  • Restore activation now persists configured state and dirty-wallet rows together and rolls back keychain changes on failure.
  • iOS and Android apply a Rust-provided post-wipe state; passkey and iCloud presentation paths receive additional handling.
  • A terminal passkey failure can still be reported as a successful partial restore, and Android can unlock after failing to apply its post-wipe projection.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Match cloud namespaces] --> B{Authentication of next namespace}
  B -->|Succeeds| C[Restore wallets from matched namespaces]
  B -->|Fails after earlier match| D[Keep earlier matches]
  D --> C
  C --> E[Report based only on matched wallet records]
Loading

Reviews (2) · Last reviewed commit: "Let Rust own the state after a full wipe"

Comment thread rust/src/manager/cloud_backup_manager/actors/restore.rs Outdated
Comment thread rust/src/database.rs Outdated
@praveenperera

Copy link
Copy Markdown
Member Author

@coderabbitai review

@praveenperera

Copy link
Copy Markdown
Member Author

@greptileai review

@coderabbitai

coderabbitai Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Review skipped: 115 files exceed the limit of 100.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Comment thread rust/src/manager/cloud_backup_manager/actors/restore.rs
Comment thread android/app/src/main/java/org/bitcoinppl/cove/AuthManager.kt Outdated
@praveenperera
praveenperera force-pushed the cloud-backup-regression-fixes branch from 303171a to 17de442 Compare September 30, 2026 04:54
Increments Android `versionCode` from 39 to 40 and iOS `CURRENT_PROJECT_VERSION` from 116 to 117 across project targets/configurations. This advances internal build metadata for the next release while leaving the app version name unchanged (`1.4.0`).
Hot wallets with a confirmed iCloud recovery copy no longer
block cloud backup enable as unverified.
Bind each cloud-only restore/delete confirmation dialog to its
row so SwiftUI presentation ownership stays with the presenter.
Record why verification is required so a wallet-set
change can keep the prior proof for recovery coverage,
while integrity loss and unconfirmed recovery keys still
block coverage.
Unverified backups were left unchanged on startup integrity
downgrade, so wallet-set coverage could stay valid. Re-mark
verification required so recovery coverage drops until the
user verifies again.
Update `CURRENT_PROJECT_VERSION` from 117 to 118 in the Xcode project so the app and related targets use the next iOS build number consistently across configurations.
Keep the fast local-snapshot Cloud Backup detail, then finish
the same refresh with a provider listing so Restore All and
other provider-gated actions unlock on iOS.
After a wipe PIN unlock, land on NewWallet.Select with an empty wallet
list and navigation stack. Keep failed cleanup locked, clear cached
session state before unlock, show the normal launch cover during wipe,
and use fixed failure copy.
A full wipe replaces the database. Restore only the completed-setup
flag so wiped devices keep finished onboarding state.
Associated-domain checks must see the final HTTP 200 body.
Following redirects could accept a wrong file as valid.
Cloud backup passkeys must demand verification on create
and assert. Also map unexpected credential types to a typed
failure instead of a generic missing-credential error.
Native passkey failures need request-mode and presentation
timing context so delayed or missing anchors are diagnosable.
Sensitive enable and verification actions must wait for the
current prompt to finish dismissing. Staging the action until
presenter readiness avoids reopening the old prompt.
Enable now inspects namespaces once for hints and matching,
with bounded concurrent wrapper reads. Restore cancels
cooperatively, keeps already matched namespaces, and no
longer depends on a fixed onboarding timeout. Busy copy
shows discovery while existing backups are checked.
Keep each process in its own cove-test temp root so
parallel nextest runs do not share ~/.data, and sweep
stale dirs after an hour.
Lock ManagerCache clearWalletManager so in-flight loads
cancel and the related send flow manager is cleared too.
The testflight recipe always bumps the build number first. When the
bump is already committed, as with build 119, there was no recipe that
archives and uploads the current number. This wraps the existing
xtask upload-testflight command so the CLI path can be used as is.
Keep LocalWalletConflict typed through the restore actor so an
all-conflict run shows that local data was left unchanged instead
of a generic cloud backup failure.
A bare platform cancelled code after Face ID does not prove the
reader cancelled. State that the request did not complete, and
log the discovery timing for later diagnosis.
After a coordinated delete succeeds, record process-local
tombstones so stale provider snapshots cannot revive deleted
paths for up to 60 seconds. Clear a file tombstone after a
successful upload, and keep partial delete successes when
cancellation wins.
set_authenticate_result now keeps a sticky default after
queued results are consumed, so multi-call tests can reuse
the configured authenticate response.
Provider redirects can differ from the requested path; tombstone
both so deleted backups stay hidden from metadata listings.
Let restore tests cancel mid-save so keychain and persisted state
stay consistent across one commit.
Cancellation between keychain writes and configured-state persist
could leave a restored namespace half-activated.
Say "this device" instead of "iPhone" so the same restore
conflict messages work on Android.
Replaced unconditional removal of the legacy `~/.data/test` directory with a guarded cleanup that only deletes directories positively identified as old Cove test DB storage (`cove_<alnum>.db` files only). Added focused tests covering safe deletion, unrelated/mixed contents, empty dirs, and nested dirs to prevent accidental data loss.
set_authenticate_result now only clears the queue and sets
the sticky default, so tests can push one-shot failures
without a duplicate queue entry of the same value.
Move cloud restore planning into its own module, replace
RestoreEntry with Option writes, and drop unused cleanup
warning tuples from the public restore result.
Pass keychain, passkey, namespace, state, and wallet ids as one
RestoredNamespaceCommit, and share persist helpers between
restore state writes.
Temp-dir test databases no longer leave ~./data/test behind, so the
one-shot home cleanup and its tests are unused.
Move hot wallet metadata and keychain key suffixes into one place so
backup and restore tests stop duplicating them and can assert by
suffix name.
cargo test runs all tests in one process. Wipe-phase tests put the
process-global coordinator in PreparingFullWipe. Persister tests that
write through begin_persistence_operation then fail with
CoordinatorBusy.

Use in-memory storage for those persister tests. Keep persistent
storage only for terminal wipe and deletion tests that already hold
global_state_test_lock.
macOS accepted sockets inherit the listener's non-blocking
mode. A read before the request bytes arrive would fail
with WouldBlock.
Restore used to save the new keychain, persist configured
state, then mark wallets dirty. A later write could leave the
new keychain in place or wipe the previous one. Capture the
prior keychain first, persist configured state and dirty
wallet rows in one transaction, and restore the snapshot if
that write fails.
When targeted authentication fails after the system prompt,
matching already stops. Earlier successful namespace matches
in that session stay restorable, same as native cancellation.
iOS and Android releases each copied the build number snapshot,
bump, and rollback flow, and each validated key paths on its own.
Both now use one bump_for_release helper and shared argument and
file checks. The AASA check uses the existing no-redirect reqwest
client instead of parsing curl output from stderr.
Review of this branch found duplicated retry paths, repeated
keychain and database reads, and copied presentation wiring.
Passkey cancellation is one non-optional flag, restore reads each
keychain item once, forget_wallet uses one transaction, and shared
helpers replace copies of timing logs, passkey hints, and
settings updates. iOS moves the dismiss-then-dispatch handoff and
prompt reconcile into one place, and Android drops dead exception
arms and skips unused Rust reads on security toggles.
After a wipe, iOS and Android each re-read Rust state and decided
what a wiped app looks like, and they had drifted: Android used a
hardcoded fallback route and default auth values, and only iOS
cleared the Cloud Backup enable completion. The wipe call now
returns a FullWipeCompletion with the committed route, settings,
onboarding flag, and auth settings, which each platform applies
before unlocking. Follow-up reconcile messages carry the same
values, auth PIN updates carry their state, and Cloud Backup drops
a stale enable completion when it becomes disabled.
Full wipe deleted the receive session without the session store
lock. A concurrent load that upgrades a legacy session could then
save the old private key after the delete, and the next receive
would resume it after a successful wipe. The wipe now deletes
through the locked store.
If manager cleanup threw while applying the wipe completion, the
app still unlocked but could keep its old wallet list and route.
The visible state is now replaced before cleanup runs, and only
the cleanup is guarded so a failure there cannot strand the lock
screen or leave pre-wipe state on screen.
App Store Connect can accept an upload even when xcodebuild then
reports failure. Rolling the build number back in that case made
the next upload reuse an accepted number. The rollback now covers
only the build and archive; a failed upload keeps the number and
points to upload-testflight to retry, matching Android.
Remove assertions and unit tests that only restated
stable request fields, error mapping, or recovery rules
already covered elsewhere.
Those clap flag checks only mirrored the command
definitions and did not protect release behavior.
@praveenperera
praveenperera force-pushed the cloud-backup-regression-fixes branch from 63f5362 to ae764c3 Compare October 1, 2026 18:17
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.

1 participant