Skip to content

feat: shortened time interval to update keys list of subscription#42

Draft
arkanoider wants to merge 12 commits intomainfrom
subscription-messages
Draft

feat: shortened time interval to update keys list of subscription#42
arkanoider wants to merge 12 commits intomainfrom
subscription-messages

Conversation

@arkanoider
Copy link
Collaborator

@arkanoider arkanoider commented Mar 19, 2026

Summary

Replace timed polling for order trade DMs with Nostr relay subscriptions so incoming Gift Wrap messages are pushed via client.notifications() instead of being fetched on an interval.

What changed

Background listener (listen_for_order_messages)

  • Before: Periodic fetch of events for active orders (timed “pull” model).
  • After:
    • Single long-lived notifications.recv() loop for RelayPoolNotification::Event.
    • Gift Wrap only (nostr_sdk::Kind::GiftWrap), matched to the trade pubkey.
    • A short interval (currently 3s) re-scans active_order_trade_indices and calls client.subscribe() for any trade pubkey not yet in subscribed_pubkeys.
    • pubkey_to_order maps relay event pubkey → (order_id, trade_index) for decryption and UI handling (parse_dm_events, handle_trade_dm_for_order).

The interval is subscription housekeeping only (discover new active trade keys), not message polling.

Request/response flows (wait_for_dm)

Unchanged contract, documented in code:

  1. Open client.notifications().
  2. Subscribe first to the trade key’s Gift Wrap filter (with auto-close options where used).
  3. Then run the send_dm future (e.g. new order, take order, add invoice).
  4. Wait (with timeout) for the first matching event.

So immediate confirmations (e.g. send_new_order) are not subject to the background listener’s tick delay.

Technical notes

  • Subscriptions use filters like pubkey(trade_pubkey) + kind(GiftWrap) with limit(0) → effectively live traffic after subscribe; no historical backfill on that filter.
  • Failed subscribe attempts roll back subscribed_pubkeys so the next tick can retry.

Risks / limitations

  • There remains a small window between an order appearing in active_order_trade_indices and the next subscription tick where a background Gift Wrap could be missed (same as any “subscribe after activation” design without backfill).
  • wait_for_dm paths explicitly avoid that for the paired request/response.

How to test

  • Create order → confirmation still received (wait_for_dm).
  • Active order receives subsequent Mostro DMs → UI / message list updates without relying on fetch interval.
  • Multiple active orders → each trade key gets subscribed once; no duplicate subscribe spam after warm-up.
  • cargo fmt --all, cargo clippy --all-targets --all-features, cargo test, cargo build.

Docs

  • See docs/MESSAGE_FLOW_AND_PROTOCOL.md (section on waiting for response / wait_for_dm) for the request/response side of the flow.

Summary by CodeRabbit

  • New Features

    • Orders now automatically open real-time message subscriptions when created or taken, triggered immediately from relevant UI actions.
  • Bug Fixes

    • Prevented cross-subscription message noise and improved detection so only truly new order messages generate notifications.
    • Notifications stop when an order reaches a terminal status.
  • Performance

    • Switched from periodic polling to subscription-driven message handling for faster, more efficient updates.

@arkanoider
Copy link
Collaborator Author

@coderabbitai review!

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 19, 2026

@arkanoider Sure! I'll start reviewing the PR right away.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 19, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ae9bb6f5-2fa9-482b-bc5b-9aabb43f4917

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

Refactors DM flow from polling to subscription-driven. Adds OrderDmSubscriptionCmd, threads a DM-subscription channel through UI and order functions, creates long-lived relay subscriptions per trade pubkey, routes GiftWrap events to specific orders, and performs event-driven cleanup on terminal order statuses.

Changes

Cohort / File(s) Summary
DM core / subscription logic
src/util/dm_utils/mod.rs
Replaced polling loop with subscription-driven design: added OrderDmSubscriptionCmd, handle_trade_dm_for_order(), subscription tracking (subscribed_pubkeys, subscription_to_order), routing GiftWrap events by subscription_id, fallback parsing for unknown subscriptions, and terminal-status unsubscribe/cleanup. Updated wait_for_dm to check subscription IDs.
Order flow: take/send order
src/util/order_utils/take_order.rs, src/util/order_utils/send_new_order.rs
Added optional dm_subscription_tx param; emit OrderDmSubscriptionCmd::Subscribe{order_id,trade_index} before/after take/send actions; normalize missing order IDs to requested order_id; early subscription step in take_order.
UI wiring / key handling
src/main.rs, src/ui/key_handler/mod.rs, src/ui/key_handler/confirmation.rs, src/ui/key_handler/user_handlers.rs
Threaded a new unbounded sender (dm_subscription_tx) through main, handle_key_event, EnterKeyContext, and key-handler flows; pass sender into order-taking/send paths so UI actions can request DM subscriptions.
Public API export
src/util/mod.rs
Re-exported OrderDmSubscriptionCmd from dm_utils to make the command type available to callers.

Sequence Diagram(s)

sequenceDiagram
    participant UI as UI
    participant Order as OrderService
    participant SubMgr as SubManager
    participant Relay as Relay
    participant Parser as DMParser
    participant Notif as Notifier

    rect rgba(100,200,100,0.5)
    UI->>Order: send subscribe command (OrderDmSubscriptionCmd::Subscribe)
    Order->>SubMgr: derive trade pubkey, subscribe GiftWrap (limit 0)
    SubMgr->>Relay: subscribe
    Relay-->>SubMgr: subscription confirmed (subscription_id)
    SubMgr->>SubMgr: map subscription_id -> (order_id, trade_index)
    end

    rect rgba(100,150,200,0.5)
    Relay->>SubMgr: GiftWrap event (subscription_id, payload)
    SubMgr->>Parser: decrypt/parse DM for associated trade key
    Parser-->>SubMgr: parsed DM (action, timestamp, payload)
    SubMgr->>Order: handle_trade_dm_for_order(parsed DM)
    Order->>Notif: send MessageNotification / increment pending
    alt terminal status detected
      Order->>SubMgr: unsubscribe(subscription_id) and cleanup
      SubMgr->>Relay: unsubscribe
    end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • grunch
  • AndreaDiazCorreia

Poem

🐰 I hopped from polls to live subs tonight,
GiftWraps now find me with nimble delight,
Trade keys all mapped, each message I ferry,
I nibble terminals so cleanup's not scary,
Hooray — subscriptions make notifications merry! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title states 'shortened time interval to update keys list of subscription,' but the actual changes replace periodic polling entirely with subscription-based architecture and add comprehensive DM subscription infrastructure—far exceeding just interval shortening. Revise title to reflect the main change: e.g., 'feat: replace polling with Nostr relay subscriptions for order DMs' or 'feat: implement subscription-driven Gift Wrap message delivery.'
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch subscription-messages

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 and usage tips.

Tip

You can customize the high-level summary generated by CodeRabbit.

Configure the reviews.high_level_summary_instructions setting to provide custom instructions for generating the high-level summary.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/util/dm_utils/mod.rs`:
- Around line 256-264: The logic marks a same-second different-action event as
new via is_new_message (involving existing_message and
existing.message.get_inner_message_kind().action), but later sort_by(timestamp)
followed by dedup_by_key(order_id) can collapse distinct actions into one row
and drop the new message before it reaches messages; fix by making the de-dup
step consider action (or a composite key) when sorting/deduping so rows with the
same order_id but different action at the same timestamp are preserved—update
the sort/dedup logic that currently uses sort_by(timestamp) +
dedup_by_key(order_id) to use a key that includes action (and timestamp if
needed) or filter earlier to ensure messages retains the new-action row.
- Around line 248-269: The code currently holds messages.lock() while later
locking pending_notifications, which can deadlock with other code that locks
them in the opposite order; to fix, avoid nested locking by acquiring the
minimal data needed from messages while holding messages_lock (e.g., clone
existing_message.timestamp and existing_action via
existing.message.get_inner_message_kind().action.clone()), then drop
messages_lock and compute is_new_message, and only after that lock
pending_notifications to increment it when needed; reference symbols: messages,
pending_notifications, existing_message, get_inner_message_kind(), timestamp,
and action.
- Around line 333-336: The background task and wait_for_dm() both consume the
same client.notifications() stream and currently accept the first
RelayPoolNotification::Event without verifying which subscription it belongs to,
causing cross-order interference; update wait_for_dm() to filter notifications
by matching either the subscription_id from RelayPoolNotification::Event against
the subscription ID returned by client.subscribe() or by checking the event's
pubkey against trade_keys.public_key(), and ensure the background task when
storing/using subscribed_pubkeys and pubkey_to_order consistently uses the same
subscription IDs so wait_for_dm() only returns events for the expected
subscription (reference Client::subscribe(), client.notifications(),
wait_for_dm(), RelayPoolNotification::Event, subscription_id, and
trade_keys.public_key()).
- Around line 365-382: The code is using event.pubkey to correlate incoming Gift
Wrap events but NIP-59 uses a one-time pubkey so that will never match the
stored trade pubkey; change the subscription correlation to use the
subscription_id returned by client.subscribe() instead: capture the
subscription_id from client.subscribe(...) where you currently call it (near the
block that inserts into subscribed_pubkeys and pubkey_to_order), store the
mapping from subscription_id -> (order_id, trade_index) (replacing or augmenting
pubkey_to_order), and update the RelayPoolNotification::Event handler to extract
and use subscription_id (instead of event.pubkey) to look up the trade tuple;
also stop using the temporary one-time pubkey as the lookup key and remove its
use in the incoming-event path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 43f37fd7-3a8c-4837-a8d1-ecb0da8596b0

📥 Commits

Reviewing files that changed from the base of the PR and between 3e95c19 and 3d99983.

📒 Files selected for processing (1)
  • src/util/dm_utils/mod.rs

Copy link
Contributor

@mostronatorcoder mostronatorcoder bot left a comment

Choose a reason for hiding this comment

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

Review Summary

Concept

Good architectural change: moving from polling (fetch every 2s) to push-based subscriptions via client.notifications(). This is more efficient and reduces latency for incoming DMs.

CI Status

Issues Found by CodeRabbit (Still Unresolved)

1. Lock Inversion Deadlock Risk

Lines 248-268: Code holds messages.lock() while acquiring pending_notifications.lock(). If any other code path acquires these in reverse order, deadlock occurs.

Fix: Drop messages_lock before taking pending_notifications:

let is_new_message = {
    let messages_lock = messages.lock().unwrap();
    let existing_message = messages_lock.iter()...
    // compute is_new_message
}; // messages_lock dropped here

if is_new_message {
    let mut pending = pending_notifications.lock().unwrap();
    *pending += 1;
}

let mut messages_lock = messages.lock().unwrap();
messages_lock.push(order_message);

2. Dedup Logic Inconsistency

Lines 257-263 + 283-285:

  • is_new_message treats timestamp == existing && action != existing_action as new
  • But dedup_by_key(order_id) ignores action, potentially removing the newly-added message

Fix:

if is_new_message {
    messages_lock.retain(|m| m.order_id != Some(order_id));
    messages_lock.push(order_message);
    messages_lock.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
}

Minor Notes

  1. Subscription cleanup: When an order is removed from active_order_trade_indices, its pubkey remains in subscribed_pubkeys and pubkey_to_order. Consider periodic cleanup or removal when order completes.

  2. 3s interval: The PR title says "shortened time interval" but 3s for subscription housekeeping seems reasonable. Document why this value was chosen.

Summary

Item Status
Architecture (push vs poll) Good
CI Passing
Lock inversion risk Needs fix
Dedup logic Needs fix
Subscription cleanup Nice-to-have

Verdict: Request Changes — Fix the two concurrency issues flagged by CodeRabbit before merge.

@arkanoider arkanoider marked this pull request as draft March 19, 2026 22:31
@arkanoider arkanoider force-pushed the subscription-messages branch from 067e407 to 939da2b Compare March 21, 2026 14:32
Made-with: Cursor

# Conflicts:
#	.gitignore
#	src/ui/key_handler/confirmation.rs
#	src/ui/key_handler/mod.rs
#	src/util/dm_utils/mod.rs
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