Skip to content

fix: phantom orders in order book and My Trades on daemon timeout - #159

Merged
Catrya merged 5 commits into
mainfrom
fix/phantom-orders
Jun 26, 2026
Merged

fix: phantom orders in order book and My Trades on daemon timeout#159
Catrya merged 5 commits into
mainfrom
fix/phantom-orders

Conversation

@Catrya

@Catrya Catrya commented Jun 25, 2026

Copy link
Copy Markdown
Member

fix #157

Problem

When creating an order, if the Mostro node rejected late or never responded, the order still showed up — in the public order book and in My Trades, as if it had been created, with a cancel option, even though no Kind 38383 event was ever published. It only disappeared on restart (order book) or lingered forever (My Trades).

Root cause: create_order waited a few seconds for the daemon confirmation and, on timeout, took an optimistic path, inserting the order into the same in-memory store that feeds the public book and persisting a TradeInfo to the DB.

Fix

Aligned the create flow with the v1 mobile until the node acks it):

  • **Public order book sourced from Kind 383longer inserts the optimistic order into the store that feeds the public book.
  • **Treat a daemon timeout as "not created"rder` returns an error and persists nothing instead of faking success. The add-order sce ("No response received, check your
    connection and try again later") and createed from 5s to 10s to tolerate a merely-slow node.
  • Defensive startup sweep. A one-shot se absent from the book and past a grace age, self-healing phantoms persisted by older ok guard ensures real pending/taken orders
    are never deleted.

The success path is unchanged: it goes throck (DaemonConfirmation::Confirmed), not the timeout branch.

Commits

  1. `fix(orders): source public order book f
  2. `feat(db): add delete_trade to remove a
  3. fix(orders): fail order creation on dae it
  4. `fix(orders): defensively sweep leftover

Testing

  • Manual: created an order against a non-reresponse received…", and no order appears in
    the book or My Trades.

Notes

  • Edge case (out of scope): if the node req with PayBondInvoice, which doesn'tresolve the create_order confirmation, thiesponse" until the anti-abuse bond epic isimplemented. Documented so it isn't a regre

Summary by CodeRabbit

  • New Features
    • Added localized timeout messages for supported languages when an order session stops responding.
  • Bug Fixes
    • Improved order creation feedback so timeout cases now show a clear session timeout message instead of a raw error.
    • Increased the wait time for order confirmation, helping reduce premature timeout errors.
    • Made order status handling more consistent so orders only appear as created after confirmation is received.

Catrya added 4 commits June 25, 2026 16:17
- create_order no longer inserts the optimistic order into the public book store.
- This prevents a phantom with no Kind 38383 from appearing after a daemon timeout or late rejection.
- The maker still sees it in My Trades; the book is sourced only from Kind 38383 events.
- New Storage::delete_trade(id) deletes the trade row by its order ID.
- SQLite runs DELETE FROM trades WHERE id = ?; IndexedDB stays a stub.
- Shared infra used by the late-CantDo and orphan-expiry cleanups.
- On no daemon response, create_order returns an error and persists nothing, matching v1.
- The timeout is raised from 5s to 10s to tolerate a merely-slow node.
- The add-order screen shows a localized no response message; no fake pending order is created.
- A one-shot startup sweep removes pending trades absent from the book and past a grace age.
- It self-heals phantoms persisted by older app versions now that create_order never persists them.
- A status-and-book guard ensures real pending or taken orders are never deleted.
@coderabbitai

coderabbitai Bot commented Jun 25, 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

Run ID: adae8e3c-7624-40f4-a17e-c7f66dda1689

📥 Commits

Reviewing files that changed from the base of the PR and between 03ce43b and c36e13c.

📒 Files selected for processing (1)
  • rust/src/api/orders.rs
💤 Files with no reviewable changes (1)
  • rust/src/api/orders.rs

Walkthrough

The order submit flow now uses a localized timeout message when the daemon does not respond. The Rust order API returns NoDaemonResponse on confirmation timeout and keeps Kind 38383 UUID reconciliation separate from order-book insertion.

Changes

Order timeout handling

Layer / File(s) Summary
Daemon confirmation timeout
rust/src/api/orders.rs
create_order waits 10 seconds for daemon confirmation, returns NoDaemonResponse on timeout, and keeps the confirmed path from inserting into order_book() during creation.
Localized timeout message
lib/l10n/app_localizations*.dart, lib/l10n/app_*.arb, lib/features/order/screens/add_order_screen.dart
sessionTimeoutMessage is added across localization contracts and locale files, and _submit() shows it when NoDaemonResponse is caught.
Maker-order UUID bridge
rust/src/api/orders.rs
Kind 38383 reconciliation now uses take_pending_local_id(&ck) to map local IDs to daemon UUIDs without replacing the in-memory order-book entry, and the log text changes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • MostroP2P/app#80: Also changes the order-creation flow in rust/src/api/orders.rs and the add-order screen’s daemon-response handling.
  • MostroP2P/app#98: Also adjusts add-order snackbar/error formatting around daemon confirmation and timeout failures.

Poem

I thumped when the timeout turned mild and bright,
NoDaemonResponse blinked in the night.
I hopped through strings in every tongue,
and bridged two UUIDs while I sung.
Now the moonlit orderbook hums along.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive The timeout and public-book contamination fixes are present, but the summary doesn't confirm the required late CantDo cleanup path. Verify the CantDo/rejection handler removes stale local orders and trade records after timeout, or document why no cleanup is needed.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and directly describes the timeout-driven phantom order fix.
Out of Scope Changes check ✅ Passed All changes support the timeout and phantom-order fix, including localized timeout messaging and daemon order handling.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/phantom-orders

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
rust/src/api/orders.rs (1)

541-564: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Rollback the fingerprint bookkeeping on rejection and timeout.

Unlike the publish-failure branch, these early returns leave trade_key_map, pending_maker_keys, and pending_local_ids populated. _run_order_subscription() later treats that content fingerprint as proof of ownership, so a later unrelated order with the same ck can be marked is_mine and reconciled against the abandoned local UUID.

Suggested fix
+    let rollback_pending_create = || {
+        if let Ok(mut m) = trade_key_map().write() {
+            m.remove(&order.id);
+            m.remove(&ck);
+        }
+        if let Ok(mut m) = pending_maker_keys().write() {
+            m.remove(&trade_pk_hex);
+        }
+        if let Ok(mut m) = pending_local_ids().write() {
+            m.remove(&ck);
+        }
+    };
+
     let daemon_id = match confirmation {
         Ok(Ok(DaemonConfirmation::Confirmed { daemon_id })) => {
             crate::api::logging::blog_info("orders", format!(
                 "create_order confirmed by daemon: {daemon_id}"
             ));
             daemon_id
         }
         Ok(Ok(DaemonConfirmation::Rejected { reason, message })) => {
+            rollback_pending_create();
             crate::api::logging::blog_warn("orders", format!(
                 "create_order rejected: {reason} — {message}"
             ));
             return Err(anyhow::anyhow!("{message}"));
         }
         _ => {
+            rollback_pending_create();
             crate::api::logging::blog_warn("orders", format!(
                 "create_order: no daemon response within 10s for id={}", order.id
             ));
             return Err(anyhow::anyhow!("NoDaemonResponse"));
         }
     };
🤖 Prompt for 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.

In `@rust/src/api/orders.rs` around lines 541 - 564, In the create_order
confirmation handling inside orders::create_order, the rejection and timeout
early returns are leaving fingerprint bookkeeping behind; make sure the same
cleanup used in the publish-failure path also removes the entry from
trade_key_map and clears the matching values from pending_maker_keys and
pending_local_ids before returning on DaemonConfirmation::Rejected and the
no-response branch. Locate the logic around the confirmation match in
create_order and apply the cleanup consistently for all non-confirmed outcomes
so _run_order_subscription() does not later misidentify abandoned state as
owned.
🤖 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 1583-1609: The startup phantom sweep in the orders cleanup path is
using “not seen in the in-memory book after a fixed delay” as the only signal to
delete pending trades, which can remove real orders. Update the sweep logic
around the startup-sleep/list_trades loop and delete_trade_if_phantom call to
require stronger evidence before deletion, such as checking for the old
optimistic/local-ID pattern or a daemon-provided terminal state instead of
relying on absence after 20s. Keep the Pending and MIN_TRADE_AGE_SECS guards,
but make the final deletion decision in this startup sweep more conservative.

In `@rust/src/db/indexeddb.rs`:
- Around line 43-45: The IndexedDB backend still leaves phantom-trade cleanup
unimplemented, so the web path never deletes stale trades. Update
IndexedDbStorage in indexeddb.rs to implement delete_trade using
indexed_db_futures for web persistence, and make sure the related trade lookup
flow used by orders::sweep_phantom_trades() and delete_trade_if_phantom() is
fully supported by the IndexedDB methods list_trades(), get_trade_by_order_id(),
and delete_trade() rather than returning a not-implemented error.

---

Outside diff comments:
In `@rust/src/api/orders.rs`:
- Around line 541-564: In the create_order confirmation handling inside
orders::create_order, the rejection and timeout early returns are leaving
fingerprint bookkeeping behind; make sure the same cleanup used in the
publish-failure path also removes the entry from trade_key_map and clears the
matching values from pending_maker_keys and pending_local_ids before returning
on DaemonConfirmation::Rejected and the no-response branch. Locate the logic
around the confirmation match in create_order and apply the cleanup consistently
for all non-confirmed outcomes so _run_order_subscription() does not later
misidentify abandoned state as owned.
🪄 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

Run ID: 0a72a886-2c42-48d5-b7a3-28f21ce0a884

📥 Commits

Reviewing files that changed from the base of the PR and between 274da13 and 03ce43b.

📒 Files selected for processing (16)
  • lib/features/order/screens/add_order_screen.dart
  • lib/l10n/app_de.arb
  • lib/l10n/app_en.arb
  • lib/l10n/app_es.arb
  • lib/l10n/app_fr.arb
  • lib/l10n/app_it.arb
  • lib/l10n/app_localizations.dart
  • lib/l10n/app_localizations_de.dart
  • lib/l10n/app_localizations_en.dart
  • lib/l10n/app_localizations_es.dart
  • lib/l10n/app_localizations_fr.dart
  • lib/l10n/app_localizations_it.dart
  • rust/src/api/orders.rs
  • rust/src/db/indexeddb.rs
  • rust/src/db/mod.rs
  • rust/src/db/sqlite.rs

Comment thread rust/src/api/orders.rs Outdated
Comment thread rust/src/db/indexeddb.rs Outdated
- The sweep treated absent from the book after 20s as proof of a phantom, risking deletion of a real pending order on slow or offline relay sync.
- create_order no longer persists unconfirmed orders, so no new phantoms appear; the sweep's only value (legacy cleanup) didn't justify the data-loss risk.
- Drops the now-unused delete_trade DB method that was added solely for the sweep.

@ermeme ermeme Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reviewed the current head. The phantom-order fix now waits for daemon confirmation before persisting, removes the optimistic order-book insertion, and cargo check passes. No blocking issues found.

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.

Phantom orders in order book after timeout / late CantDo from daemon

1 participant