Skip to content

Fix cloud backup restore and recovery coverage - #886

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

praveenperera wants to merge 57 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: d44117ca-a652-4f0c-9f6e-31bb43364bda

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

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.

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.
Install a temporary data root before tests start, check cleanup paths,
and remove the root on normal exit. Bootstrap wallet lifecycle tests
before their actor close paths access storage.
Delete unsigned transactions, saved wallet selections, and KeyTeleport
receive secrets with wallet cleanup. Keep mode changes atomic and
update both mobile bindings for the typed cleanup stages.
Release coin control and NFC sessions with their wallet, keep send-flow
updates in the correct wallet, and avoid retaining discarded mnemonic
managers. Keep Android locked when switching to main mode fails.
Queue wallet exports and Cloud Backup actions until the current prompt
finishes dismissing. Cancel pending actions when their host disappears.
Keep finished authorization objects alive until the main queue releases
them. Include underlying error codes in diagnostics without private
error text.
Copy prior TestFlight notes and assign the uploaded build to me-only,
with export compliance and external beta review handled after upload.
Keep accepted build numbers when distribution fails.

Add Android internal-release recipes with Google Play credential checks.
Only a missing credential can start passkey discovery. Keep request
failures separate so verification does not start a second prompt or
report a usable passkey as missing.
Show the newer queued prompt after dismissal instead of running the old
action and leaving the new prompt hidden.
Make release-ios bump the iOS build, rebuild
release bindings, then upload. Point testflight
and tf at that recipe so they share the same path.
Match the iOS TestFlight flow: bump, build, and upload from
one command, and restore versionCode when failure happens
before Google Play is invoked.
Advance the store build numbers for the next release uploads.
After an iOS reinstall the Keychain keeps wallet secrets while
the database is gone. Compare those leftovers to the backup and
adopt matches instead of treating the wallet id as occupied.
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.
@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

The PR is not yet safe to merge because restore cancellation can leave cloud credentials inconsistent with persisted state, and test startup can recursively delete an unverified directory under the developer’s home.

Findings

  1. P1 Cancellation Splits Restore State
  2. P1 Test Startup Deletes Home Data

Summary

This PR substantially hardens cloud-backup recovery, verification coverage, passkey authorization, iCloud inventory handling, wipe cleanup, and mobile presentation sequencing. It also adds composed TestFlight and Google Play release workflows.

  • Adds conservative recovery-coverage calculation based on confirmed cloud state and verification provenance.
  • Adopts matching Keychain remnants during restore while preserving conflicting local data.
  • Adds bounded, cancellable passkey and cloud-restore coordination across Rust, iOS, and Android.
  • Introduces iCloud metadata settling and deletion tombstones.
  • Clears additional wallet/session state during deletion and full wipe while retaining onboarding completion.
  • Adds extensive Rust and iOS regression coverage plus release automation.
  • Two correctness issues remain: restore activation can commit Keychain state before a cancellation-safe database commit, and test startup recursively deletes an unverified home-directory path.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Cloud restore finds and decrypts wallets] --> B[Restore wallets locally]
    B --> C[Save passkey, master key, and namespace to Keychain]
    C --> D{Cancellation check}
    D -->|Current| E[Persist configured backup state]
    D -->|Cancelled| F[Restore exits as cancelled]
    F --> G[Keychain has new namespace]
    F --> H[Database retains old configuration]
    G --> I[Inconsistent cloud-backup identity]
    H --> I
Loading

Reviews (1) · Last reviewed commit: "Bump iOS build number to 124"

Comment thread rust/src/manager/cloud_backup_manager/actors/restore.rs Outdated
Comment thread rust/src/database.rs Outdated
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.
CI can take more than one second to read a small local file. The
helper then tries metadata and returns SyncPending.
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.
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