Skip to content

fix: add wallet-scoped backup migrations #113

Description

@ovitrif

Problem

Android and iOS backup restore currently decode app-owned backup JSON directly into generated Core model types before calling Core.

That is brittle for wallet-scoped activity changes. If a backup was written before walletId existed, the app can fail while decoding Activity, ActivityTags, or PreActivityMetadata, before Core gets a chance to apply defaults or migrate the data.

Current app restore paths:

  • Android ActivityBackupV1 contains activities: List<Activity>, activityTags: List<ActivityTags>, and closedChannels: List<ClosedChannelDetails>. BackupRepo decodes that payload, then ActivityRepo.restoreFromBackup() calls Core upsertActivities, upsertTags, and upsertClosedChannels.
  • Android MetadataBackupV1 contains tagMetadata: List<PreActivityMetadata>. BackupRepo decodes that payload, then upserts the metadata through Core.
  • iOS has the same backup shapes in ActivityBackupV1 and MetadataBackupV1. BackupService decodes them with JSONDecoder, then calls Core upsertList, upsertTags, upsertClosedChannelList, and upsertPreActivityMetadata.
  • Android's normal wallet id is bitkit via ActivityWalletType.BITKIT.id; Trezor wallet ids are trezor:{sha256(sorted xpubs joined by \n)}.
  • iOS uses WalletScope.default = getDefaultWalletId() for the normal wallet; Trezor wallet ids are derived in HwWalletId with the same trezor:{sha256(sorted xpubs joined by \n)} shape when xpubs are available.
  • Both apps already pass wallet ids to Core for hardware-wallet activity reads/writes/deletes. The backup problem is the typed decode boundary, not a need for platform-specific JSON patching.

TransactionDetails is not in the current Android/iOS activity backup envelopes. It still needs DB migration coverage because Core persists it and scopes it by (walletId, txId).

Requested change

Add Core-owned migration support at both Core-owned boundaries:

  • migrate the activity DB on init, because Core owns the persisted activity layer.
  • migrate Core-owned JSON slices embedded in Android/iOS VSS backup envelopes, so the apps can rewrite those backups with current entries.

The apps should not hand-edit Core model JSON or decode legacy Core models before migration.

Required helpers:

  • migrate activity arrays from backup JSON into current Activity entries.
  • migrate activity tag arrays into current ActivityTags entries.
  • migrate pre-activity metadata arrays into current PreActivityMetadata entries.
  • migrate transaction details JSON if/when it is included in backups.
  • provide a way for apps to get current/migrated backup JSON for the Core-owned fields so VSS can be updated after migration.

Migration rules:

  • Missing walletId means DEFAULT_WALLET_ID / bitkit.
  • Empty walletId should normalize to DEFAULT_WALLET_ID only for legacy migration input. Normal current writes should still reject empty wallet ids.
  • Preserve non-default wallet ids such as trezor:{hash}.
  • Preserve activity ids, txids, tags, closed channel data, timestamps, contact fields, seen state, and pre-activity metadata fields.
  • Do not require Android or iOS to hand-edit Core model JSON.

DB migration

Core should own activity DB migration.

Required DB behavior:

  • On init, tables without wallet_id migrate to wallet-scoped primary keys.
  • Existing activity rows, tags, pre-activity metadata, and transaction details without a wallet scope migrate to DEFAULT_WALLET_ID.
  • activity_tags, pre_activity_metadata, and transaction_details use wallet-scoped keys after migration.
  • Migration preserves user metadata where possible: tags, contacts, seen state, and pre-activity metadata.
  • There is no separate cleanup/wipe requirement for dev-only pre-release DB states in this issue.

App/VSS responsibility

Android and iOS own the VSS backup envelopes. Core owns the activity data inside those envelopes.

Expected app-side flow:

  1. Read the app-owned VSS backup envelope as raw JSON.
  2. Keep app-owned fields, such as cache/settings/pubky session data, in the app layer.
  3. Pass the raw Core-owned activity/metadata JSON to Core migration/import APIs.
  4. Let Core decode, migrate, and persist Core-owned activity data into the activity DB.
  5. Ask Core for the migrated/current Core-owned backup entries.
  6. Write the updated app-owned VSS backup envelope with those migrated entries.

The app responsibility is to refresh VSS with v2/current Core entries after migration. The app should not own the Core model migration itself.

Search and scoping checks

After DB migration or backup migration:

  • walletId = DEFAULT_WALLET_ID returns only the normal Bitkit wallet rows.
  • walletId = trezor:{hash} returns only that hardware wallet's rows.
  • global list/search calls that intentionally pass no wallet id can merge wallets, but must not duplicate a logical row.
  • activity id and txid collisions across wallet ids stay scoped.
  • tags and transaction details are looked up with the same wallet scope as the activity.

Acceptance criteria

  • Android-shaped backup JSON with activities, activityTags, and tagMetadata missing walletId migrates without app-side JSON patching.
  • iOS-shaped backup JSON with the same Core-owned fields missing walletId migrates without app-side JSON patching.
  • Migrated legacy backup rows use DEFAULT_WALLET_ID.
  • Existing non-default wallet ids survive backup migration unchanged.
  • Core DB migration covers activities, onchain_activity, lightning_activity, activity_tags, pre_activity_metadata, and transaction_details.
  • Core exposes generated Android and iOS binding APIs for migrating/importing Core-owned backup JSON and exporting migrated/current backup entries.
  • Tests cover legacy backup JSON, current wallet-scoped backup JSON, DB migration from tables without wallet_id, VSS backup rewrite data, and scoped lookup/search/tag/detail behavior after migration.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions