Summary
IdentityChangeSet carries whole IdentityEntry records (from_managed snapshots balance, revision, keys, DPNS names, DashPay profile, contact profiles, payments in one row) and merge is last-write-wins per identity. Every path that persists an identity mutation therefore stores a full snapshot, and any store that lands after a newer snapshot regresses the persisted row to the older state until the next store of that identity.
#4611 moves the periodic DPNS marketplace and DashPay profile stores out from under the wallet-manager write guard (a store under the guard held the lock for the whole host commit, minutes on iOS). That widens an ordering window that already existed for the deferred stores in send_payment (address-pool used flip) and enqueue_contact_info_decrypt: the snapshot is taken under the guard, the guard is released, and the store call queues on the host persister's round_lock. A writer that takes the guard afterwards, mutates the same identity and stores its snapshot can — if the persister mutex lets it barge ahead of the earlier parked store — have its newer row overwritten by the older snapshot. The in-memory state stays authoritative and the next store of that identity re-snapshots it in full (the DashPay pass every 15 s, DPNS every 60 s), so the exposure is one launch after a kill inside that window.
Raised by CodeRabbit on #4611 (profile.rs:80-108, 653-701; dpns_marketplace.rs:991-1039).
Proposal
Make identity changesets deltas: optional field groups on the entry (or a dedicated IdentityFieldDelta variant) so a pass persists only the fields it owns — DashPay profile, contact profiles, DPNS names — and merge / the host apply only what is present. Then a reordered older delta can only regress its own field, which its own pass re-fetches from Platform on the next sweep. Requires the FFI/SwiftData and SQLite persistence paths to accept partial identity rows (schema/contract change), hence a follow-up rather than part of #4611.
Alternative considered and rejected: holding an ordering gate across the deferred store — every other identity writer stores under the wallet write guard, so waiting on such a gate under that guard reintroduces the freeze #4611 removes.
Summary
IdentityChangeSetcarries wholeIdentityEntryrecords (from_managedsnapshots balance, revision, keys, DPNS names, DashPay profile, contact profiles, payments in one row) and merge is last-write-wins per identity. Every path that persists an identity mutation therefore stores a full snapshot, and any store that lands after a newer snapshot regresses the persisted row to the older state until the next store of that identity.#4611 moves the periodic DPNS marketplace and DashPay profile stores out from under the wallet-manager write guard (a store under the guard held the lock for the whole host commit, minutes on iOS). That widens an ordering window that already existed for the deferred stores in
send_payment(address-pool used flip) andenqueue_contact_info_decrypt: the snapshot is taken under the guard, the guard is released, and the store call queues on the host persister'sround_lock. A writer that takes the guard afterwards, mutates the same identity and stores its snapshot can — if the persister mutex lets it barge ahead of the earlier parked store — have its newer row overwritten by the older snapshot. The in-memory state stays authoritative and the next store of that identity re-snapshots it in full (the DashPay pass every 15 s, DPNS every 60 s), so the exposure is one launch after a kill inside that window.Raised by CodeRabbit on #4611 (profile.rs:80-108, 653-701; dpns_marketplace.rs:991-1039).
Proposal
Make identity changesets deltas: optional field groups on the entry (or a dedicated
IdentityFieldDeltavariant) so a pass persists only the fields it owns — DashPay profile, contact profiles, DPNS names — andmerge/ the host apply only what is present. Then a reordered older delta can only regress its own field, which its own pass re-fetches from Platform on the next sweep. Requires the FFI/SwiftData and SQLite persistence paths to accept partial identity rows (schema/contract change), hence a follow-up rather than part of #4611.Alternative considered and rejected: holding an ordering gate across the deferred store — every other identity writer stores under the wallet write guard, so waiting on such a gate under that guard reintroduces the freeze #4611 removes.