Skip to content

SDK recovery primitive: private-note transport backlog drain #414

Description

@haseebrabbani

Sub-issue of #357. Spike #412 validated the underlying mechanics; this issue productizes them as an SDK recovery primitive.

Context

After recovery on a fresh device, the local store has no note-transport cursor — and in a shared dirty store, another account's sync may have advanced the cursor past notes belonging to the newly recovered account. Recovery therefore needs a full transport rescan.

Spike findings (#412):

  • Full device-loss round trip verified live against the testnet transport: a fresh store that tracks the account tag recovers a transport-delivered private note via one fetch_all_private_notes() drain in ~1.3 s.
  • Drains are idempotent; the incremental fetch afterwards is a no-op.
  • The upstream drain cannot regress the stored cursor by construction (it persists max(drain_cursor, stored_cursor)).
  • The drain is tag-scoped: a store with no tracked tags drains nothing. The account's note tag must be tracked before draining.
  • Transport delivery requires no on-chain transaction (notes arrive Expected until observed on chain).

Scope

  • TS: drainPrivateNoteBacklog(midenClient) wrapping fetchPrivate({ mode: 'all' }).
  • Rust: drain_private_note_backlog() on MultisigClient, wrapping fetch_all_private_notes().
  • Structured TransportRecoveryReport { status: completed | unavailable | failed, imported, reason? }. Transport-disabled / unreachable must be reported, not thrown — a transport failure must not abort the rest of a recovery flow.
  • Catch the upstream convergence-guard error (PaginationDidNotTerminate, 1000 iterations) and surface it as a retryable failure.
  • Load/tag regression tests (these gate whether the drain sees the account at all): load() (TS) and pull_account (Rust) insert the recovered account, its standard note tag is behaviorally tracked afterwards, and reloading an existing account stays idempotent.
  • Rename the misleading Rust helper argument imported → overwrite while in the area.

Acceptance criteria

  • Both SDKs with semantic parity; report types as above.
  • Tests: idempotence, tag-scoping (no tag → nothing drained), cursor non-regression, unavailable-transport reporting, load/tag regression suite.
  • Docs: transport recovery is bounded by transport retention/pruning — it is not a backup (senders may not use transport; blobs get pruned).

Unblocked (spike complete). Ordering: pairs naturally with the other two recovery primitives from the #357 plan.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

Projects

Relationships

None yet

Development

No branches or pull requests

Issue actions