Description
Problem
A shielded sync pass commits its receipts and advances each subwallet's watermark in the Rust store, then delivers the changeset to the host. If the host rejects that round, FFIPersister rolls it back and keeps nothing, and the coordinator's delivery (queue_pass_changeset, and the sync() fan-out before #5277) only logs the error. Causes include a SwiftData save() failing in endChangeset, any callback in the round failing, or, since #5277, a failed existing-row read in persistShieldedNotes. The receipts are then in memory but not on the host. The next pass skips them because they are below the in-memory watermark, yet still publishes the advanced watermark. After a restart, restore trusts that durable watermark, so the missing notes, with their balance and receipt history, stay missing until an explicit rescan.
This predates #5277: any failed round already leaves this gap. #5277 adds one more way for a round to fail.
Proposed direction
Keep failed publication recoverable in the Rust coordinator and persistence layer:
- Retain a rejected round's changeset, or mark the affected subwallets dirty, and re-deliver it before or merged with the next round, following the backend's retry classification (
PlatformWalletPersistRC.transientvs fatal). - Never publish a durable watermark beyond receipts that haven't reached the host. For example, hold the published watermark at the lowest unpublished receipt position until it lands, or rewind the in-memory watermark when a round fails so the next pass re-delivers those receipts.
Tests: reject a receipt round, run a successful pass, then restore from the resulting host state; the note must be present.
Raised in review of #5277 (#5277 (comment)).
Activity