Skip to content

feat(#182): unsubscribe + deterministic ids for per-trade subscriptions - #255

Open
codaMW wants to merge 2 commits into
MostroP2P:mainfrom
codaMW:feat/182-subscription-lifecycle
Open

feat(#182): unsubscribe + deterministic ids for per-trade subscriptions#255
codaMW wants to merge 2 commits into
MostroP2P:mainfrom
codaMW:feat/182-subscription-lifecycle

Conversation

@codaMW

@codaMW codaMW commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Closes the subscription-lifecycle half of #182 (the request_id half was done via #172; the chat side via #247).

Problem

subscribe_gift_wraps and subscribe_single_order (orders.rs) called client.subscribe(filter, None) with auto-generated ids and never unsubscribed, so the relay-side subscription outlived the event loop's exit (30-min idle timeout, shutdown, or completed trade). Every create/take spawned a new one they accumulated over a long session (bandwidth, duplicate notifications, relay pressure on mobile).

Fix

Mirrors the subscribe_incoming_chat pattern that #247 established:

  • Deterministic ids (trade_subscription_id / single_order_subscription_id) via subscribe_with_id, so a repeat subscribe for the same trade/order replaces in place instead of stacking. Full pubkey hex (not an 8-char prefix) to rule out collisions.
  • unsubscribe(&sub_id) at each loop's single exit point, so the relay-side subscription never outlives the task.

The limit(0) live-only replay-protection of subscribe_gift_wraps is unchanged only the subscription id changes, not the filter.

Scope

Central subscription registry deferred per the issue's own guidance (nostr-sdk re-establishes subscriptions on reconnect).

Tests

  • Unit tests for the deterministic-id logic: idempotent per pubkey, no cross-pubkey collision, expected format.
  • Unsubscribe-on-exit is covered by inspection at each single exit point, mirroring the proven subscribe_incoming_chat cleanup, since exercising it needs a live relay.
  • cargo test (213 passing), clippy -D warnings, flutter analyze all clean.

Summary by CodeRabbit

  • Bug Fixes

    • Improved reliability of order updates by preventing duplicate and stale subscriptions.
    • Ensured background watchers clean up subscriptions when they stop, time out, or shut down.
    • Stabilized repeated subscription and resubscription behavior to reduce missed updates and resource leaks.
  • Tests

    • Added coverage for consistent subscription identification.
    • Verified that outdated watchers cannot interfere with newer replacement watchers.

…bscriptions

The chat side (subscribe_incoming_chat) was already fixed via MostroP2P#247. This brings
the two remaining per-trade subscriptions in orders.rs to parity:

- subscribe_gift_wraps and subscribe_single_order called client.subscribe(_, None)
  with auto-generated ids and never unsubscribed, so the relay-side subscription
  survived the event loop's exit (30-min idle timeout, shutdown, or completed
  trade). Every create/take spawned a new one — they accumulated over a long
  session (bandwidth, duplicate notifications, relay pressure on mobile).

Fix, mirroring the subscribe_incoming_chat pattern:
- Deterministic ids (trade_subscription_id / single_order_subscription_id) via
  subscribe_with_id, so a repeat subscribe for the same trade/order replaces in
  place instead of stacking. Full pubkey hex, not an 8-char prefix, to rule out
  collisions.
- unsubscribe(&sub_id) at each loop's single exit point, so the relay-side
  subscription never outlives the task.

The limit(0) live-only semantics of subscribe_gift_wraps are unchanged — only
the subscription id changes, not the filter. Central subscription registry
deferred per the issue (nostr-sdk re-establishes subscriptions on reconnect).

Unit tests cover the deterministic-id logic (idempotent per pubkey, no collision,
expected format); the unsubscribe-on-exit is covered by inspection at the single
exit point, mirroring the proven subscribe_incoming_chat cleanup, since it needs
a live relay to exercise.
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 755f2cb0-2c67-48a8-a3a7-98134ef5e261

📥 Commits

Reviewing files that changed from the base of the PR and between 0989769 and aef6a17.

📒 Files selected for processing (1)
  • rust/src/api/orders.rs

Walkthrough

Relay watchers now use deterministic SubscriptionId values for gift-wrap and single-order subscriptions. Generation tracking prevents stale watchers from unsubscribing replacement subscriptions. Tests cover identifier formatting, uniqueness, generation advancement, and cleanup ownership.

Changes

Relay subscription lifecycle

Layer / File(s) Summary
Deterministic identifiers and ownership tracking
rust/src/api/orders.rs
Helper functions generate deterministic trade and order subscription IDs. Generation tracking identifies the current subscription owner. Tests verify formatting, uniqueness, generation advancement, and stale-watcher cleanup prevention.
Gift-wrap watcher lifecycle
rust/src/api/orders.rs
subscribe_gift_wraps uses a deterministic per-trade ID and unsubscribes on exit only when it owns the current subscription generation.
Single-order watcher lifecycle
rust/src/api/orders.rs
subscribe_single_order uses a deterministic per-order ID and conditionally unsubscribes on every exit path when it remains the current owner.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related issues

  • MostroP2P/app issue 182 — Covers deterministic per-order subscription IDs and explicit unsubscription in rust/src/api/orders.rs.

Suggested reviewers: grunch

Poem

I’m a rabbit guarding each relay,
With stable IDs along the way.
New watchers claim their rightful ground,
Old ones leave without a sound.
No stale paws undo the tune—
Clean subscriptions under the moon.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: unsubscribe handling and deterministic IDs for per-trade subscriptions.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch feat/182-subscription-lifecycle
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rust/src/api/orders.rs`:
- Around line 3073-3097: Add an asynchronous lifecycle regression test near
trade_subscription_id_is_deterministic_and_per_pubkey and
single_order_subscription_id_matches_expected_format that starts watcher A,
replaces it with watcher B for the same trade or order, waits for A to exit, and
verifies B remains subscribed. Exercise the existing watcher replacement/cleanup
APIs and assert the active subscription state after cleanup, without changing
the subscription ID construction tests.
- Around line 1503-1509: Make deterministic subscription cleanup
generation-aware: in rust/src/api/orders.rs lines 1503-1509, update the
gift-wrap watcher cleanup to unsubscribe only when its task still owns the
current generation or serialized subscription; in lines 2379-2382, apply the
same ownership guard to single-order cleanup. Before starting each replacement
watcher, cancel and await the existing task so stale tasks cannot unsubscribe
the replacement subscription.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c4129d97-788c-41fa-a319-b98472aebb60

📥 Commits

Reviewing files that changed from the base of the PR and between c0608d7 and 0989769.

📒 Files selected for processing (1)
  • rust/src/api/orders.rs

Comment thread rust/src/api/orders.rs
Comment thread rust/src/api/orders.rs
…le-task unsubscribe

CodeRabbit (Critical): because the subscription id is deterministic, a
re-subscribe for the same trade/order (e.g. a retry within the 30-min idle
window) creates watcher B under the same id, replacing A's relay subscription.
When A's task later exits, its unconditional unsubscribe would tear down B's
live subscription — the very thing that keeps B's trade responsive.

Fix: a per-id generation counter (subscription_generations, mirroring
pending_requests). Each watcher claims the id on subscribe (bumping the
generation) and, on exit, only unsubscribes if it still owns the current
generation. A superseded watcher skips cleanup and lets the newer owner keep
the subscription. Applied at both sites (subscribe_gift_wraps,
subscribe_single_order).

Guard-only, not cancel-and-await: the stale watcher self-terminates on its idle
timeout without touching the subscription, so tracking JoinHandles to cancel it
early adds machinery for no correctness gain.

Regression test asserts the ownership property directly: A then B claim the same
id, A no longer owns it (won't unsubscribe), B owns it (will) — testable as pure
logic without a live relay.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant