0xMiden / 0xMiden/note-transport-service
Add a way for senders to check whether a sent note has been fetched
- Lenguaje dominante
- Rust
- Estrellas
- 3
- Forks
- 10
- Merge medio
- 2 h 23 min
- PR fusionados (30 d)
- 4
Descripción
## Summary
The NTL currently exposes no way for a sender to verify whether a note they submitted via `SendNote` has been picked up by a recipient. Once `SendNote` returns `Ok`, the sender has no further visibility — the note may have been fetched, may still be sitting in storage, or may have been silently dropped after the retention period elapsed. This issue tracks adding some form of delivery / fetch status query, while being honest about the privacy tradeoff that any such API introduces.
## Motivation
Reported by a pioneer building on Miden. Their use case is delivery confirmation in a flow that hands off a private note to a counterparty via the NTL. When something goes wrong (note never picked up, recipient polled the wrong tag, stale tag subscription), the sender has no way to diagnose which side of the handoff failed.
Today the only signal a sender gets is `SendNoteResponse{}` (empty) on [L19 of the proto](https://github.com/0xMiden/miden-note-transport/blob/main/proto/proto/miden_note_transport.proto#L19), which only confirms the note was accepted into server storage — not that it was delivered.
## Current behavior
The proto exposes four RPCs: `SendNote`, `FetchNotes`, `StreamNotes`, `Stats`. None of them are sender-keyed or note-keyed for status queries. The `notes` table schema ([`schema.rs`](https://github.com/0xMiden/miden-note-transport/blob/main/crates/node/src/database/sqlite/schema.rs)) records `(seq, id, tag, header, details, created_at)` with no fetch counters, last-polled-at column, or recipient identity. Notes are not deleted on fetch — multiple subscribers polling the same tag all see the same row, and the server has no way to say which (if any) recipient grabbed a given note. Notes are dropped after `retention_days` (default 30) by the maintenance task in [`maintenance.rs`](https://github.com/0xMiden/miden-note-transport/blob/main/crates/node/src/database/maintenance.rs), fetched or not.
So today there is no information for the server to surface — because the server doesn't track it.
## Proposed direction
This is more of a design question than a single proposal, because any sender-visible status signal trades against the privacy model the RFC ([miden-proposals#4](https://github.com/0xMiden/miden-proposals/issues/4)) calls out: *"operator may see Alice→Bob but not the content"*. Adding "operator can also see whether/when Bob picked it up" is a real expansion of what the operator learns and can correlate.
Sketching the option space:
### Option A — Per-note "has been fetched at least once" boolean
A new RPC `GetNoteStatus(note_id) -> { fetched: bool, fetched_at: optional }`, backed by a `fetched_at TIMESTAMP NULL` column on the `notes` table set on first matching `FetchNotes` / `StreamNotes` poll.
- **Pros**: privacy-cheap (doesn't identify the fetcher, only that someone fetched), small implementation footprint, gives the sender something.
- **Cons**: operationally near-useless for the actual question — any third party polling the same tag (legitimately or as noise) flips the bit, so `fetched: true` does not mean "Bob got it." Also leaks fetch timing to the sender, which the current design does not.
### Option B — Counter of fetches per note
Same as A but with a `fetch_count` integer instead of a bool. Same limitations, only marginally more informative.
### Option C — Per-recipient ACK on fetch
Recipient explicitly ACKs receipt of a specific note (e.g. via `AckNote(note_id)`), recorded server-side. Sender queries `GetNoteStatus(note_id)` and sees the ACK if it arrived.
- **Pros**: actually answers "did the intended recipient receive it" if recipient cooperates.
- **Cons**: introduces a notion of recipient identity at the server level (or at least an ACK channel), which moves further away from the pull-based-anonymous-broadcast model. ACKs are also unauthenticated unless we layer a signature scheme on top, in which case adversaries can spoof.
### Option D — Out-of-band: on-chain receipt note
Recipient consumes the private note and emits a public ACK note (e.g. small-amount note tagged with the original note id's commitment). Sender watches the chain for the ACK.
- **Pros**: keeps the transport layer dumb, preserves the pull-based privacy model entirely, ACK is cryptographically tied to actual consumption (not just receipt).
- **Cons**: requires a chain transaction by the recipient (not free), adds latency, doesn't help if the recipient never even received the note in the first place (which is precisely the failure mode senders care about diagnosing).
### Option E — Document explicitly that this is out of scope
If the team's position is that delivery confirmation belongs at the application layer (Option D) and the NTL should remain dumb, document this in the README or RFC so pioneers don't expect a status API.
## Important notes
- Header encryption is not on the roadmap and the header includes the sender's account ID, so any status query that returns sender-keyed data is already operating on plaintext-to-the-operator data. The privacy delta of adding a status API depends on whether it leaks *recipient* signal, not sender signal.
- This composes with the open work to encrypt `details` ([miden-note-transport#48](https://github.com/0xMiden/miden-note-transport/issues/48), [miden-client#1613](https://github.com/0xMiden/miden-client/issues/1613)). If encryption ships, Option D becomes more attractive because the chain ACK pattern can be done without revealing what was ACKed to the operator.
## Open questions
1. Is the team's preferred direction (D) — keep the transport dumb, delivery confirmation as an application-layer / on-chain pattern? If so, close this issue with a note in the README.
2. If a transport-layer status API is desirable, is Option A's weak bool acceptable as a stopgap, or does it need to be Option C with an explicit ACK from the recipient?
3. Should the `Stats` RPC be extended to include per-tag fetch activity (currently `notes_per_tag` is left as a TODO in [`grpc/mod.rs`](https://github.com/0xMiden/miden-note-transport/blob/main/crates/node/src/node/grpc/mod.rs))? That would give some signal without note-level granularity.
Guía de contribución
Evaluación
Este issue todavía no se ha evaluado.