Skip to content

fix: back up hardware wallet activity tags - #1163

Open
jvsena42 wants to merge 13 commits into
masterfrom
fix/1046-hw-backup-restore
Open

fix: back up hardware wallet activity tags#1163
jvsena42 wants to merge 13 commits into
masterfrom
fix/1046-hw-backup-restore

Conversation

@jvsena42

@jvsena42 jvsena42 commented Aug 17, 2026

Copy link
Copy Markdown
Member

Closes #1046

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.
  3. Moves the Core backup-migration calls behind the service and repository layers so the migration boundary is unit-testable.

Completes the app half of synonymdev/bitkit-core#113. The migrate-before-decode step landed in #1064; this PR adds the rewrite and the missing tag coverage. The iOS port follows after this is approved.

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: they are rebuilt by the device watcher on every reconnect, so backing them up would upload data that is immediately overwritten.

That leaves an ordering problem. activity_tags is constrained by FOREIGN KEY (wallet_id, activity_id) REFERENCES activities(wallet_id, id), 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 with it.

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.

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

Preview

tagging.mp4
restore.mp4
delete-wallet-deletes-tags.mp4

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 Wallet → 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.
    • 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.

Automated Checks

  • BackupRepoTest.kt: hardware tags reaching the metadata backup as pre-activity metadata; the activity backup excluding them; legacy Activity and Metadata envelopes reaching Core before decode; the rewrite firing only when Core changed a field; no rewrite when the Core restore fails; a failed migration skipping only its own category; both metadata reads failing loudly rather than uploading a partial payload; and ordinary activity traffic not triggering a metadata backup.
  • ActivityRepoTest.kt: the default-wallet scoping of the activity tag backup; the pre-activity metadata mapping for received (keyed by address) and sent (keyed by payment id) hardware activities, including that fee rate, transfer flag and channel id are left unset; and persistHwSnapshot raising the tag signal only when activities were actually removed.
  • Local verification: just compile, just test and just lint pass.

Device Verification

Verified end to end on regtest against the bitkit-docker Trezor emulator: pair, tag one received and one sent hardware activity, wipe, restore from seed, re-pair.

  • Restore completed with no FOREIGN KEY constraint failed, which is what the previous approach failed on.
  • Both tags were carried in the metadata envelope and restored as pre-activity metadata in the trezor: scope, keyed as intended — the received one by address with is_receive = 1, the sent one by payment id with no address — and with fee_rate, is_transfer and channel_id left unset so Core cannot overwrite the activity it attaches to.
  • created_at came back in epoch millis, matching every other pre-activity metadata record.
  • After re-pairing, Core re-attached both tags to their correct activities and consumed the pre-activity rows, leaving none behind.
  • Backup traffic during a full startup and sync was 4 ACTIVITY uploads against 1 METADATA, confirming the metadata envelope no longer follows ordinary activity traffic. The remaining METADATA upload comes from the pre-existing cache listener, which carries the balance.

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: upsert_pre_activity_metadata 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 17, 2026
@jvsena42

Copy link
Copy Markdown
Member Author

@ovitrif should this also close #998 ? The send support will be implemented in design V62

@jvsena42
jvsena42 marked this pull request as ready for review August 18, 2026 15:57
@jvsena42 jvsena42 added this to the 2.5.0 milestone Aug 18, 2026
@greptile-apps

greptile-apps Bot commented Aug 18, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds hardware-wallet activity tags to metadata backups and rewrites successfully migrated legacy Activity and Metadata envelopes in their current wallet-scoped form.

  • Converts hardware-wallet tags into pre-activity metadata so they can restore before device activities are recreated.
  • Adds a tag-specific invalidation signal to avoid metadata uploads for unrelated activity traffic.
  • Moves Core migration calls behind service and repository boundaries and conditionally rewrites migrated VSS categories.
  • Adds focused tests for migration, rewrite safety, metadata read failures, tag conversion, and backup-trigger behavior.

Confidence Score: 5/5

The PR appears safe to merge, with no concrete blocking or independently actionable non-blocking issue identified.

The changed paths fail closed when migration or metadata reads fail, rewrite only after successful persistence, and preserve hardware tags through the pre-activity metadata flow without backing up orphan ActivityTags rows.

Important Files Changed

Filename Overview
app/src/main/java/to/bitkit/repositories/ActivityRepo.kt Adds tag-specific invalidation, scopes ActivityTags backup to the default wallet, and converts hardware tags into Core-compatible pre-activity metadata.
app/src/main/java/to/bitkit/repositories/BackupRepo.kt Includes hardware tags in metadata backups, performs repository-mediated JSON migration, and safely rewrites only migrated categories whose restored Core data persisted.
app/src/main/java/to/bitkit/repositories/PreActivityMetadataRepo.kt Exposes the Core pre-activity metadata migration through the repository boundary.
app/src/main/java/to/bitkit/services/CoreService.kt Adds serialized Core migration wrappers and reports whether hardware snapshot replacement removed activities that could have cascading tag deletions.
app/src/test/java/to/bitkit/repositories/ActivityRepoTest.kt Covers tag invalidation and received/sent hardware-tag metadata mapping.
app/src/test/java/to/bitkit/repositories/BackupRepoTest.kt Covers migration, conditional rewrites, partial-read protection, hardware-tag inclusion, and metadata backup triggering.

Sequence Diagram

sequenceDiagram
    participant User
    participant ActivityRepo
    participant BackupRepo
    participant VSS
    participant Core
    participant Watcher

    User->>ActivityRepo: Tag hardware activity
    ActivityRepo->>Core: Store wallet-scoped ActivityTags
    ActivityRepo-->>BackupRepo: activityTagsChanged
    BackupRepo->>ActivityRepo: Convert hardware tags to PreActivityMetadata
    BackupRepo->>VSS: Upload Metadata envelope
    VSS-->>BackupRepo: Restore legacy/current envelope
    BackupRepo->>Core: Migrate Core-owned JSON fields
    BackupRepo->>Core: Persist pre-activity metadata
    opt Legacy field changed and persistence succeeded
        BackupRepo->>VSS: Rewrite current wallet-scoped envelope
    end
    Watcher->>Core: Recreate hardware activities
    Core->>Core: Reattach and consume matching metadata
Loading

Reviews (1): Last reviewed commit: "Merge branch 'master' into fix/1046-hw-b..." | Re-trigger Greptile

@jvsena42
jvsena42 requested a review from ovitrif August 18, 2026 16:29
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.

Hardware Wallet Data Backup & Restore

1 participant