Skip to content

fix: back up hardware wallet activity tags - #675

Open
jvsena42 wants to merge 5 commits into
masterfrom
fix/backup-hardware-wallet-tags
Open

fix: back up hardware wallet activity tags#675
jvsena42 wants to merge 5 commits into
masterfrom
fix/backup-hardware-wallet-tags

Conversation

@jvsena42

@jvsena42 jvsena42 commented Aug 19, 2026

Copy link
Copy Markdown
Member

This PR:

  1. Carries hardware wallet activity tags in the metadata backup, so tags a user adds to Trezor activity are no longer lost on restore.
  2. Rewrites the Activity and Metadata VSS backups after a legacy envelope is migrated, so later restores read current wallet-scoped entries instead of re-running the legacy path forever.

Ports synonymdev/bitkit-android#1163 and completes the app half of synonymdev/bitkit-core#113, subtask 6 of the Trezor epic #589. The migrate-before-decode step landed here in 8cd698d; this PR adds the rewrite and the missing tag coverage.

Description

Core made activity data wallet-scoped, where the normal wallet is the default wallet id and a paired Trezor is trezor:{hash}. Two gaps remained on the app side.

Tagging a Trezor activity stores the tag under that device's wallet id, and those tags never reached the backup, so they were lost on restore. Hardware activities themselves are deliberately still not backed up: the device watcher rebuilds them on every reconnect, so backing them up would upload data that is immediately overwritten.

That leaves an ordering problem. Activity tags are constrained by a foreign key onto the activity they belong to, and on restore a hardware tag has no parent activity yet, because the device has not been paired again. Restoring hardware tags as activity tags therefore fails the constraint, and since the activity restore upserts activities, tags and closed channels together, one orphan tag takes the whole category down.

So hardware tags travel as pre-activity metadata instead. That table has no foreign key, it is already backed up across every wallet scope, and Core already re-attaches pre-activity metadata when an activity appears, matching received activities on address and sent activities on payment id. The app writes hardware tags in that shape at backup time, where the activity is still available to pick the right key, and Core reattaches them by itself once the watcher recreates the rows. No deferred replay or ordering hook is needed in the app.

The backup server also never received the migrated envelope. Restoring an older backup asked Core to fill in missing wallet ids, used the result, then left the old envelope in place, so every future restore repeated the migration. The app now detects when Core actually changed a field and re-uploads that category once the restore finishes. A category is only rewritten after Core has persisted the migrated rows, otherwise a failure mid-restore could replace a good backup with empty state. Envelopes that already carry wallet ids are compared as parsed JSON rather than raw text, so key reordering by Core cannot trigger a pointless upload.

The metadata backup is also now marked stale only when tags in a hardware scope actually change, rather than on every payment and sync, so ordinary activity traffic no longer re-uploads that envelope. A watcher snapshot raises the signal only when it deleted rows, since a plain upsert cannot drop a tag.

Envelope format is unchanged, no app-side wallet id migration semantics were added for Core models, and bitkit-core stays at 0.5.3.

Companion Android PR: synonymdev/bitkit-android#1163

Linked Issues/Tasks

Screenshot / Video

QA Notes

Requires a paired Trezor (the bitkit-docker emulator is enough) and a wallet with existing activity.

Manual Tests

  • 1. Pair a hardware wallet → Hardware Wallets → tag one device activity → Home → tag one normal activity: both tags show on their activities.
  • 2a. Wipe wallet → restore from seed → Home → open the tagged normal activity: its tag is present, and the restore completes with no foreign key error.
    • 2b. Re-pair the hardware wallet → wait for the watcher to sync → open the tagged device activity: its tag is present.
  • 3. Restore the same wallet a second time → Settings → Data Backups: Transaction Log and Tags are not re-uploaded, because the envelope already carries wallet ids.
  • 4. regression: Hardware Wallets → unpair a device: its activities and tags are removed and the backup syncs.

Automated Checks

  • Unit tests added: hardware tag to pre-activity metadata mapping in BitkitTests/HwActivityTagBackupTests.swift, covering received keyed by address, sent keyed by payment id, fee rate/transfer flag/channel id left unset so Core cannot overwrite the activity it attaches to, seconds to millis conversion, cross-wallet isolation, and deduplication on Core's (wallet id, payment id) primary key.
  • Unit tests added: backup envelope migration in BitkitTests/BackupFieldMigrationTests.swift, covering a legacy envelope reporting a change, an envelope whose keys Core reordered reporting no change, a failing migration keeping the original field without blocking the others, and non-object input passing through untouched.
  • Test target coverage: 895 of 900 unit tests pass locally on iPhone 16 Pro. The 5 failures are AddressTypeIntegrationTests and UtxoSelectionTests failing on Failed to deposit to address: Blocktank error, which need the bitkit-docker regtest stack and are unrelated to this change.
  • node scripts/validate-translations.js: 0 errors, no new strings.
  • swiftformat --lint: clean for every file in this branch.
  • CI: standard build and test checks run by the PR bot.

Known Limitation

Core keys received pre-activity metadata by address on both write and read, so several tagged receives to one reused address cannot all round trip: the upsert deletes by address per record, and re-attachment matches the first row for that address. Tracked in synonymdev/bitkit-core#135. Tags on distinct addresses, and all sent tags, are unaffected.

@jvsena42 jvsena42 self-assigned this Aug 19, 2026
@jvsena42
jvsena42 marked this pull request as ready for review August 19, 2026 16:08
@greptile-apps

greptile-apps Bot commented Aug 19, 2026

Copy link
Copy Markdown

Greptile Summary

This PR preserves hardware-wallet activity tags by carrying them through metadata backups and rewrites successfully migrated legacy backup envelopes.

  • Converts hardware activity tags into wallet-scoped pre-activity metadata for constraint-safe restoration.
  • Marks metadata backups stale when hardware tags are changed or deleted.
  • Detects semantic changes made by Core migration helpers and re-uploads affected backup categories after persistence.
  • Adds focused tests for metadata conversion, scope isolation, deduplication, and migration change detection.

Confidence Score: 5/5

The PR appears safe to merge, with no concrete changed-code defect identified.

The new backup representation follows the documented wallet-scoped matching and deduplication contracts, migration rewrites occur only after restored rows persist, and hardware tag mutation and deletion paths invalidate the metadata backup.

Important Files Changed

Filename Overview
Bitkit/Services/BackupFieldMigration.swift Adds parsed-JSON migration change detection with per-field failure isolation for deciding whether restored envelopes need rewriting.
Bitkit/Services/BackupService.swift Applies migrations during restore, rewrites successfully migrated categories, and includes derived hardware tags in metadata backups.
Bitkit/Services/CoreService.swift Emits metadata invalidation for hardware tag changes and exposes hardware tags as pre-activity metadata.
Bitkit/Services/HwActivityTagBackup.swift Maps wallet-scoped hardware tags to received- or sent-activity lookup keys and deduplicates metadata by Core's primary key.
BitkitTests/BackupFieldMigrationTests.swift Covers semantic migration comparison, malformed inputs, partial failures, and preservation of envelope fields.
BitkitTests/HwActivityTagBackupTests.swift Covers mapping keys, neutral attachment fields, timestamp conversion, wallet isolation, filtering, and deduplication.

Sequence Diagram

sequenceDiagram
    participant App as Bitkit App
    participant Core as Bitkit Core
    participant VSS as Backup Server
    participant HW as Hardware Watcher

    App->>Core: Read hardware activity tags and activities
    Core-->>App: Wallet-scoped tags and on-chain activities
    App->>App: Convert tags to pre-activity metadata
    App->>VSS: Upload metadata envelope

    VSS-->>App: Restore legacy/current envelopes
    App->>Core: Migrate Core-owned JSON fields
    App->>Core: Persist activities, tags, and metadata
    alt Migration changed persisted fields
        App->>VSS: Rewrite current wallet-scoped envelope
    end

    HW->>Core: Recreate hardware activities after pairing
    Core->>Core: Attach matching pre-activity metadata tags
Loading

Reviews (1): Last reviewed commit: "chore: add code comments" | Re-trigger Greptile

@jvsena42

Copy link
Copy Markdown
Member Author

Still testing

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