0xMiden / 0xMiden/note-transport-service

StreamNotes ignores the request `cursor`, so clients can't resume a stream

Open
#96 4 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
3
Forks
10
Avg merge
2h 23m
Merged PRs (30d)
4

Description

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?

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.