feat(platform-wallet): add contacts and identity-key rehydration (item G)#3693
Draft
Claudius-Maginificent wants to merge 6 commits into
Draft
Conversation
Contributor
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
6 tasks
schema::identity_keys::load_state reads identity_keys rows back into a keyless IdentityKeysChangeSet keyed by (identity_id, key_id) via the existing decode_entry (PUBLIC IdentityPublicKey only — no private key material). Fail-hard on a corrupt blob; empty wallet → empty changeset. No V001 column missing (verified). RT: sqlite_identity_keys_reader (3). Co-Authored-By: Claudius the Magnificent (1M context) <noreply@anthropic.com>
…ys (G) Changeset-shape extension (rs-platform-wallet public API): - ClientWalletStartState gains keyless ContactChangeSet + IdentityKeysChangeSet slots (PUBLIC material; removed_* always empty on the rehydration feed). - New IdentityManager::apply_contacts_and_keys — single source of truth for the contact/key routing, shared by the runtime changeset-replay path (wallet::apply, now DRY-refactored to call it) and the persister rehydration path (manager::load, now routes the new slots onto the rebuilt managed identities after IdentityManager::from). 26 wallet::apply tests still green (orphan-skip / established-drops- pending / tombstone / idempotency preserved by the DRY extraction). Co-Authored-By: Claudius the Magnificent (1M context) <noreply@anthropic.com>
…d() (G) - New schema::contacts::load_changeset projects the dormant ContactsRecords reader into a keyless ContactChangeSet (PUBLIC, removed_* empty). - SqlitePersister::load() now fills ClientWalletStartState.contacts (contacts::load_changeset) + .identity_keys (identity_keys::load_state). - LOAD_UNIMPLEMENTED shrunk to ["core::last_applied_chain_lock"] (the only genuinely-deferred area left). - Flipped tc043 (contacts now rehydrate — positive assertion) + header. - E-test ClientWalletStartState constructors extended (PR-2 RT-W/S/Z/ seed-roundtrip + C wiring all still green). RT (sqlite_contacts_keys_rehydration, 3): contacts (sent+recv) + identity-keys round-trip bit-exact through load(); bare wallet → empty slots. secrets_scan green. Co-Authored-By: Claudius the Magnificent (1M context) <noreply@anthropic.com>
…ape (G) ClientWalletStartState gained contacts + identity_keys slots; the FFI keyless projection sets both to Default (empty) — the documented minimal-correct adaptation: the iOS path already reconstructs identity PUBLIC keys directly into Identity.public_keys via build_wallet_identity_bucket (feeding the slot too would double-apply), and WalletRestoreEntryFFI carries no contacts back from Swift on load (surfacing them needs a new cross-boundary struct field + Swift wiring, tracked as a follow-up). Empty slots make apply_contacts_and_keys a no-op for this path, preserving established iOS behaviour. Acceptance: cargo check --workspace AND --all-features both exit 0. Co-Authored-By: Claudius the Magnificent (1M context) <noreply@anthropic.com>
…ELECT (G) The PR-3 identity_keys::load_state reader uses prepare() for a one-shot SELECT by design; add it to READ_ONLY_PREPARE_ALLOWED so tc_p1_003 (writers must use prepare_cached) stays green without weakening the writer-side rule. Same pattern as PR-2 commit ec30c54. Co-Authored-By: Claudius the Magnificent (1M context) <noreply@anthropic.com>
b573fca to
b7508a0
Compare
d91c35f to
39690eb
Compare
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.
Issue being fixed or feature implemented
After PR-2 landed full signing-wallet rehydration (
(seed + persistor) → Wallet), the two PUBLIC-material slots — DashPay contacts and identity public keys — were still deferred:ClientWalletStartStatehad no fields for them, andLOAD_UNIMPLEMENTEDlisted both. This is item G from the rehydration plan. A freshly rehydrated wallet would therefore have empty identitypublic_keysmaps and no contact state until the next sync cycle, even though the data was already on disk and readers existed for contacts.This PR closes that gap: contacts and identity public keys now rehydrate on
load()with no additional sync required.What was done?
Changeset-shape extension (
rs-platform-wallet) —ClientWalletStartStategains two new public fields:contacts: ContactChangeSet— persisted sent/received/established DashPay contact state. Public material only;removed_*fields are always empty on the rehydration feed (deletes never reach storage as rows).identity_keys: IdentityKeysChangeSet— persisted per-identity public-key entries. No private key material;removedis always empty on the rehydration feed.Both fields have
Default::default()fallback so in-tree consumers compile without modification.New keyless reader (
rs-platform-wallet-storage) —schema::identity_keys::load_statereads all public-key rows for a given wallet and returns anIdentityKeysChangeSet. The existingcontacts::load_statereader was already sufficient; it is now wired intoload().SqlitePersister::load()wiring — both readers are called per-wallet and their results placed into the newClientWalletStartStatefields.LOAD_UNIMPLEMENTEDshrinks from["contacts", "identity_keys", "core::last_applied_chain_lock"]to["core::last_applied_chain_lock"].DRY refactor —
IdentityManager::apply_contacts_and_keys— a new shared method onIdentityManageris now the single source of truth for routing contact entries and identity-key entries onto managed identities. It replaces an approximately 88-line inline block that existed inPlatformWalletInfo::apply_changeset(the runtime changeset-replay path) and is called from both that path and the new persister rehydration path inload_from_persistor. Identity keys are applied before contacts so a contact entry never lands before its owner's keys; orphan entries are logged and skipped, never fatal.FFI consumer adaptation (
rs-platform-wallet-ffi) —build_wallet_start_statesets the two new slots toDefault::default(). The iOS path does not use them: identity public keys are already reconstructed directly intoIdentity.public_keysbybuild_wallet_identity_bucket(feeding the slot too would double-apply them), andWalletRestoreEntryFFIcarries no contact state back from Swift on load. The empty defaults makeapply_contacts_and_keysa no-op for the FFI path, preserving established iOS behaviour.Test updates —
tc043is flipped from a deferral assertion to a positive round-trip assertion: it now verifies that a persisted contact request rehydrates intostate.wallets[w].contacts.sent_requestsand thatLOAD_UNIMPLEMENTEDlists only the single remaining deferred area. Three new dedicated round-trip tests cover the full G surface:g_rt1_contacts_rehydrate_into_keyless_payload— sent + received requests rehydrate bit-exact;removed_*slots are empty.g_rt2_identity_keys_rehydrate_into_keyless_payload— two identity-key entries round-trip bit-exact;removedis empty.g_rt3_empty_slots_for_bare_wallet— a wallet with no contact or key rows produces empty (not error) slots.A
secrets_scanallow-list entry is added for theSELECTstatement inidentity_keys::load_state(the wordkeyappears in the column namepublic_key_hash; no secret material is involved).No V001 migration required — the
identity_keysandcontacts_*columns are already in V001. This PR adds no new SQL columns or tables.Known limitation (documented, tracked as a follow-up)
The FFI consumer (
rs-platform-wallet-ffi) projects both new slots as empty. This is intentional and correct for the current iOS integration:build_wallet_identity_bucketalready populatesIdentity.public_keysdirectly from the FFI restore payload; feedingidentity_keysthroughapply_contacts_and_keyson top would double-apply and overwrite keys already placed there.WalletRestoreEntryFFIcarries no contact-request fields across the C ABI — Swift does not send contacts back on load. Surfacing them would require a new FFI struct field and corresponding Swift wiring.The core non-FFI manager/load path (
SqlitePersister+load_from_persistor) fully populates both slots — no data loss for the native Rust path. The FFI follow-up is tracked as a separate task.How Has This Been Tested?
Gate commands run before submission:
cargo check --workspaceandcargo check --workspace --all-features— clean.cargo test -p rs-platform-wallet-storage— includes the three new G round-trip tests (g_rt1,g_rt2,g_rt3), the flippedtc043, and the updatedsecrets_scan/secrets_guardallow-list check.cargo test -p rs-platform-wallet-storage --features secrets— secrets-feature path clean.SqlitePersister::load()— green.Internal multi-agent review (security, QA, and consistency passes) completed. CI has not yet been run on this branch.
User story. Imagine you are building a wallet app on top of
PlatformWalletManager. Before this PR, callingload_from_persistorafter a restart would give you back balances and identity skeletons, but theirpublic_keysmaps would be empty and your contacts list would be blank — you would have to wait for a full sync pass before the wallet was usable for DashPay operations. After this PR, both the identity public keys and the contact records come back automatically on restore. The wallet is immediately usable for DashPay without waiting for a sync.Breaking Changes
None. The two new fields on
ClientWalletStartStatehaveDefaultimplementations and are additive; no existing field is removed or renamed. The FFI consumer is adapted in-tree. TheIdentityManager::apply_contacts_and_keysmethod ispub(crate).Checklist:
For repository code-owners and collaborators only
🤖 Co-authored by Claudius the Magnificent AI Agent
Rebase note (2026-05-20): Cascaded onto rebased #3692 tip (
b7508a0d47); zero conflicts; 5 commits preserved.