0xMiden / 0xMiden/note-transport-service
StreamNotes ignores the request `cursor`, so clients can't resume a stream
- Lenguaje dominante
- Rust
- Estrellas
- 3
- Forks
- 10
- Merge medio
- 2 h 23 min
- PR fusionados (30 d)
- 4
Descripción
It looks like `StreamNotes` quietly drops the `cursor` field that clients send in `StreamNotesRequest`. The pull-based `FetchNotes` uses it, but the streaming path doesn't, so there's no way to resume a stream from a known point.
### Background
Both requests carry a `cursor`:
- `FetchNotesRequest { tags, cursor }`
- `StreamNotesRequest { tag, cursor }`
`FetchNotes` honors it — it reads `request_data.cursor` and passes it straight into the DB query, so a client can resume from wherever it left off ([mod.rs#L234](https://github.com/0xMiden/note-transport-service/blob/91ca4de34a8b88435e452dda0ed185b90f0bcda0/crates/node/src/node/grpc/mod.rs#L234)).
`StreamNotes` only logs the cursor and then ignores it ([mod.rs#L292](https://github.com/0xMiden/note-transport-service/blob/91ca4de34a8b88435e452dda0ed185b90f0bcda0/crates/node/src/node/grpc/mod.rs#L292)). `Subface::new(id, tag, tx)` has no cursor parameter ([streaming.rs#L255](https://github.com/0xMiden/note-transport-service/blob/91ca4de34a8b88435e452dda0ed185b90f0bcda0/crates/node/src/node/grpc/streaming.rs#L255)), so a subscription always starts from the server's shared per-tag cursor instead of where the client asked.
The server also hands a `cursor` back to the client in every `StreamNotesUpdate`, which only really makes sense if the client is meant to send it back to resume — but the request side ignores it, so that round-trip is broken.
### Steps to reproduce
1. Open a `StreamNotes` for some tag and read a few updates. Note the `cursor` the server returns.
2. Drop the connection (or simulate a reconnect).
3. Open `StreamNotes` again for the same tag with `cursor` set to the last value you saw.
4. Look at what you actually receive.
### What I expected
The stream picks up roughly from the cursor I passed — I get notes after that point, with no big replay and no gap.
### What actually happens
The requested cursor is ignored. Depending on whether anyone else is subscribed to that tag, one of two things happens:
- **Lone reconnect → full replay.** When the last subscriber for a tag disconnects, the whole `TagData` (and its cursor) gets removed ([streaming.rs#L178](https://github.com/0xMiden/note-transport-service/blob/91ca4de34a8b88435e452dda0ed185b90f0bcda0/crates/node/src/node/grpc/streaming.rs#L178)). So a single client that reconnects gets a fresh `TagData` with `cursor: 0` ([streaming.rs#L262](https://github.com/0xMiden/note-transport-service/blob/91ca4de34a8b88435e452dda0ed185b90f0bcda0/crates/node/src/node/grpc/streaming.rs#L262)) and re-receives the entire history for that tag, every reconnect. For something like a wallet on a flaky connection that's a repeated full re-download.
- **Late joiner → missed notes.** If another subscriber is keeping the tag alive at a higher shared cursor, a newly connecting client inherits that cursor and silently skips every note between the cursor it asked for and the current shared value — exactly the ones it was trying to catch up on.
As a side effect, the starting point is also inconsistent between subscribers: the first subscriber to a tag starts at 0 (full history), while a later subscriber to the same tag starts at "now" (nothing), even though they made the same request.
### Notes
The root cause is just that the request cursor never reaches the streamer. `stream_notes` would need to thread `request_data.cursor` through to the subscription so each sub starts from its own requested position, rather than relying on a single shared per-tag cursor. That likely also means tracking the cursor per subscriber instead of per tag, since different subscribers can legitimately be at different points.
This is closely related to #28 (client re-connects) — resuming via the request cursor is the mechanism that makes reconnects work without gaps or full replays.
I'd be glad to take this on — could I be assigned?
Guía de contribución
Línea de trabajo
The issue is in the gRPC streaming implementation. Start by examining `crates/node/src/node/grpc/mod.rs` around line 292 where the cursor is logged and ignored, and `crates/node/src/node/grpc/streaming.rs` where `Subface::new` is defined. The fix involves modifying `stream_notes` to pass the request cursor to the subscription logic, likely changing `Subface::new` to accept a cursor and tracking it per subscriber. Run the existing tests to ensure the pull-based `FetchNotes` still works correctly.
Escrito por el modelo de indexación a partir del texto del issue.
Evaluación
- Stack tecnológico
- rust
- Área
- api, backend
- Tipo de issue
- Error
- Dificultad
- 3/5
- Tiempo estimado
- 1-2 días
- Estado de actividad
- Tranquilo
- Claridad
- Bien especificado
- Aptitud para principiantes
- 65/100