fix: back up hardware wallet activity tags - #1163
Open
jvsena42 wants to merge 13 commits into
Open
Conversation
This was referenced Aug 18, 2026
…g a partial payload would replace the stored ones
Member
Author
jvsena42
marked this pull request as ready for review
August 18, 2026 15:57
Greptile SummaryThe PR adds hardware-wallet activity tags to metadata backups and rewrites successfully migrated legacy Activity and Metadata envelopes in their current wallet-scoped form.
Confidence Score: 5/5The 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.
|
| 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
Reviews (1): Last reviewed commit: "Merge branch 'master' into fix/1046-hw-b..." | Re-trigger Greptile
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1046
This PR:
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_tagsis constrained byFOREIGN 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
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; andpersistHwSnapshotraising the tag signal only when activities were actually removed.just compile,just testandjust lintpass.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.
FOREIGN KEY constraint failed, which is what the previous approach failed on.trezor:scope, keyed as intended — the received one by address withis_receive = 1, the sent one by payment id with no address — and withfee_rate,is_transferandchannel_idleft unset so Core cannot overwrite the activity it attaches to.created_atcame back in epoch millis, matching every other pre-activity metadata record.ACTIVITYuploads against 1METADATA, confirming the metadata envelope no longer follows ordinary activity traffic. The remainingMETADATAupload 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_metadatadeletes 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.