Skip to content

Fix/gift wrap status sync - #98

Merged
grunch merged 4 commits into
mainfrom
fix/gift-wrap-status-sync
Apr 9, 2026
Merged

Fix/gift wrap status sync#98
grunch merged 4 commits into
mainfrom
fix/gift-wrap-status-sync

Conversation

@grunch

@grunch grunch commented Apr 9, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Trade list now separates "waiting invoice" and "waiting payment" into distinct filters.
    • Order submission persists identity-derived state after successful creation.
    • App forwards backend logs to the client for better runtime visibility (debug-only).
  • Bug Fixes

    • Improved order status synchronization and duplicate-event suppression for more reliable updates.
    • Cleaner error messages shown on order submission failures.
  • Chores

    • Removed redundant per-update debug logging.

grunch added 3 commits April 9, 2026 12:36
- Add global Kind 1059 subscription for all known trade keys so gift-wrap
  events are captured even after app restart
- Replace local UUID with daemon UUID on NewOrder gift-wrap so subsequent
  actions (WaitingBuyerInvoice, etc.) update the correct order book entry
- Update in-memory order book alongside DB on every gift-wrap status change
  so tradeStatusProvider reflects real status without waiting for Kind 38383
- Fix FiatSentOk mapping: was Active, now correctly maps to FiatSent
- Handle Rate/RateReceived/PaymentFailed actions explicitly
- Add bridge_log() to bypass log crate when another logger is installed,
  forward Rust logs to Flutter debugPrint via _forwardRustLogs()
- Add detailed gift-wrap decryption logging ([gift-wrap] tag)
- Deduplicate gift-wrap processing across per-trade and global subscriptions
- Handle CantDo action: remove rejected order from order book, notify UI
  via OrderEvent stream, show snackbar with rejection reason
- Add WaitingInvoice/WaitingPayment as distinct TradeStatusFilter values
  with spec-correct colors (#7C2D12/#FED7AA)
- Remove noisy [orderBook] update log
Replace fire-and-forget order creation with a synchronous wait for the
daemon's response via a oneshot channel. On CantDo rejection, create_order
returns an error to Dart — no phantom pending order is created locally.

- Add PENDING_CONFIRMATIONS oneshot channel map keyed by trade_pubkey
- create_order registers channel before subscribing/publishing, waits up
  to 5s for NewOrder (confirmed) or CantDo (rejected) from daemon
- On Confirmed: create order book + DB entry with daemon UUID
- On Rejected: return Err(message) — Dart catch block shows snackbar
- On timeout: create locally with local UUID (optimistic fallback)
- Remove OrderEvent/OrderEventStream/on_order_event broadcast machinery
- Remove PENDING_LOCAL_BY_TRADE map (no longer needed)
- Remove rootScaffoldMessengerKey and _listenOrderEvents from Dart
- Persist trade_key_index after successful order creation
- Use regex capture group for AnyhowException stripping so non-Anyhow
  errors are not corrupted
- Wrap trade_key_index persistence in try-catch so failures don't mask
  successful order creation
- Remove duplicate doc comment and inaccurate filtering mention
- Fix timeout log message: 15s → 5s to match actual value
@coderabbitai

coderabbitai Bot commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

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: 6d706618-848d-4426-a3b7-a3ae06297aff

📥 Commits

Reviewing files that changed from the base of the PR and between 2a1f42a and 7cb6c98.

📒 Files selected for processing (2)
  • lib/features/order/screens/add_order_screen.dart
  • rust/src/api/orders.rs
✅ Files skipped from review due to trivial changes (1)
  • rust/src/api/orders.rs

Walkthrough

Order creation now awaits daemon confirmations and deduplicates gift-wraps; trade-status filters were expanded; a Rust→Flutter logging bridge was added; identity trade-key index is persisted after order creation; FFI dispatch table updated to include a new OrderBook.update_order_status wire handler.

Changes

Cohort / File(s) Summary
Home debug cleanup
lib/features/home/providers/home_order_providers.dart
Removed a per-update debugPrint that logged order counts.
Order creation & identity persistence (Dart)
lib/features/order/screens/add_order_screen.dart
After successful rust_orders.createOrder(...), call identity_api.getIdentity() and persist identity.tradeKeyIndex via IdentityService.saveTradeKeyIndex(...); wrap persistence in non-fatal try/catch. Error snackbar message now strips AnyhowException(...) wrapper.
Trade status filters & UI color
lib/features/trades/providers/trades_providers.dart, lib/features/trades/widgets/trades_list_item.dart
Added TradeStatusFilter.waitingInvoice and waitingPayment; updated orderStatusToFilter(...) mapping to map waitingBuyerInvoicewaitingInvoice and waitingPaymentwaitingPayment; extended status color mapping to handle new values.
Flutter-side Rust log forwarding
lib/main.dart
Added _forwardRustLogs() and call in main() to subscribe to Rust log stream and forward entries via debugPrint prefixed with [rust/<tag>].
Rust logging bridge
rust/src/api/logging.rs
Added bridge_log, blog_info, blog_warn, blog_debug helpers that forward to Flutter stream and stderr; install_log_bridge() now checks/set logger result and prints stderr warning on failure.
Order creation confirmation & gift-wrap handling (Rust)
rust/src/api/orders.rs
Added DaemonConfirmation and PENDING_CONFIRMATIONS to await daemon UUID/rejection (5s timeout) before finalizing orders; register confirmation channel before publish and rollback on publish failure; added OrderBook::update_order_status(); gift-wrap deduplication via PROCESSED_GW and is_duplicate_gift_wrap(); expanded rumor handling (Kind 1059 support, CantDo path) and replaced direct log calls with blog_*.
FFI generated bindings updated
rust/src/frb_generated.rs
Updated FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH; added wire handler for OrderBook_update_order_status and shifted subsequent func_id mappings upward.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Flutter Client
    participant OrderBook as OrderBook (Rust)
    participant Daemon as Daemon / Relays
    participant LogBridge as Rust Log Bridge

    Client->>OrderBook: create_order(order)
    activate OrderBook
    OrderBook->>OrderBook: register confirmation channel (PENDING_CONFIRMATIONS)
    OrderBook->>Daemon: subscribe to gift-wraps (Kind1059/...subscriptions)
    OrderBook->>Daemon: publish order request
    OrderBook->>OrderBook: await confirmation (up to 5s)

    rect rgba(100, 150, 255, 0.5)
    Note over Daemon,OrderBook: Confirmation Path
    Daemon->>OrderBook: gift-wrap with daemon UUID (Action::Pending)
    OrderBook->>OrderBook: replace local UUID with daemon UUID
    end

    rect rgba(255, 150, 100, 0.5)
    Note over Daemon,OrderBook: Rejection Path
    Daemon->>OrderBook: gift-wrap rejection (Action::CantDo)
    OrderBook->>OrderBook: signal Rejected to requester
    end

    rect rgba(150, 255, 100, 0.5)
    Note over OrderBook: Timeout Path
    OrderBook->>OrderBook: use local UUID on 5s timeout
    end

    OrderBook->>Client: return order result
    deactivate OrderBook
Loading
sequenceDiagram
    participant Rust as Rust Runtime
    participant LogBridge as Logging Bridge (logging.rs)
    participant Flutter as Flutter App (main.dart)
    participant Console as Debug Console

    Rust->>LogBridge: blog_info/blog_warn/blog_debug(tag, msg)
    activate LogBridge
    LogBridge->>LogBridge: forward_log to Flutter stream
    LogBridge->>Console: print to stderr
    deactivate LogBridge

    Flutter->>LogBridge: subscribe onLogEntry() stream
    activate Flutter
    LogBridge->>Flutter: yield log entry
    Flutter->>Console: debugPrint([rust/<tag>] msg)
    deactivate Flutter
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Poem

🐰 I hopped a log from Rust to Dart,
Kept my trade-keys close to heart,
Confirmed the gifts that cross the net,
No duplicates, no upset,
Hooray — orders settle, paws reset!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Fix/gift wrap status sync' directly addresses the main objective of this changeset, which implements gift-wrap status synchronization and order confirmation handling across multiple components.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/gift-wrap-status-sync

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.

@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: 3

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)

481-511: ⚠️ Potential issue | 🟠 Major

Rollback the pending state when publish fails.

The early publish_event_json(&event_json).await? can return after you've already populated TRADE_KEY_MAP, PENDING_MAKER_KEYS, PENDING_LOCAL_IDS, and PENDING_CONFIRMATIONS. A retry with the same fingerprint can then inherit stale ownership/ID state.

Suggested fix
-    publish_event_json(&event_json).await?;
+    if let Err(e) = publish_event_json(&event_json).await {
+        if let Ok(mut map) = pending_confirmations().lock() {
+            map.remove(&trade_pk_hex);
+        }
+        if let Ok(mut map) = pending_maker_keys().write() {
+            map.remove(&trade_pk_hex);
+        }
+        let _ = take_pending_local_id(&ck);
+        if let Ok(mut map) = trade_key_map().write() {
+            map.remove(&order.id);
+            map.remove(&ck);
+        }
+        return Err(e);
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/src/api/orders.rs` around lines 481 - 511, You must roll back the
in-memory pending state if publishing fails: after creating event_json,
inserting into TRADE_KEY_MAP via store_trade_key_index(&order.id, ..) and
store_trade_key_index(&ck,..), calling
store_pending_maker_key(&trade_pk_hex,..), store_pending_local_id(&ck,..),
inserting conf_tx into pending_confirmations() and subscribing with
subscribe_gift_wraps(...), wrap publish_event_json(&event_json).await in a
match/Result handling block and on Err perform cleanup by removing the two
trade-key indexes, removing the pending maker key and pending local id, removing
the entry from pending_confirmations() (map.remove(&trade_pk_hex)), and undoing
the subscription (unsubscribe_gift_wraps or equivalent) before propagating the
error; use the existing symbols (store_trade_key_index, store_pending_maker_key,
store_pending_local_id, pending_confirmations, subscribe_gift_wraps,
publish_event_json, trade_pk_hex, conf_tx) to locate and implement the rollback.
🧹 Nitpick comments (1)
lib/features/order/screens/add_order_screen.dart (1)

133-138: Don’t swallow persistence failures silently.

Line 138 currently hides why tradeKeyIndex persistence fails, which makes field diagnosis harder.

Suggested tweak
       try {
         final identity = await identity_api.getIdentity();
         if (identity != null) {
           await IdentityService.saveTradeKeyIndex(identity.tradeKeyIndex);
         }
-      } catch (_) {}
+      } catch (e, st) {
+        debugPrint('[add-order] saveTradeKeyIndex failed: $e\n$st');
+      }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/order/screens/add_order_screen.dart` around lines 133 - 138, The
try/catch around identity_api.getIdentity() and
IdentityService.saveTradeKeyIndex currently swallows errors (catch (_) {});
update it to catch the exception and log the error (and stack) or rethrow so
persistence failures are visible. Specifically, modify the catch to catch (e, s)
and call your app logger or debugPrint with a clear message referencing
identity_api.getIdentity and IdentityService.saveTradeKeyIndex (e.g., "Failed
saving tradeKeyIndex: $e"), or rethrow after logging.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@rust/src/api/orders.rs`:
- Around line 1065-1070: The NewOrder branch currently only logs when an ack
arrives after create_order timed out, leaving orders persisted under the local
UUID and causing future updates to use an unknown ID; in the NewOrder handling
code update the mapping in PENDING_LOCAL_IDS (lookup by content key) to replace
the local UUID with the daemon UUID when the daemon confirms the order, and also
emit or call the same reconciliation path used by create_order success (so
status/cancel flows use the daemon ID). Specifically, modify the NewOrder branch
that logs the late ack to: 1) retrieve the local ID from PENDING_LOCAL_IDS using
the content key, 2) swap the stored key/value to the confirmed daemon_id (or
remove the stale local entry and insert the daemon mapping), and 3) trigger the
existing order-confirmation handler used by create_order so persisted orders and
subsequent handlers reference the daemon UUID.
- Around line 992-1042: The logs currently leak sensitive decrypted data and
full keys: remove/redact any verbatim logging of rumor_json and content and
avoid printing full trade_pubkey_hex or raw content on errors; specifically
delete the blog_debug("gift-wrap", ... rumor_json) call, delete the blog_info
that prints decrypted content, and remove the blog_warn that prints "raw content
was: {content}" in the serde_json::from_str(&content) Err branch. Keep the
high-level blog_info about action/kind but ensure trade_pubkey_hex is truncated
(keep &trade_pubkey_hex[..8]) and ensure payload_desc for PaymentRequest does
not include the bolt11 string (only use pr.len() or mask the invoice), and
preserve only non-sensitive fields in payload_desc/msg/kind logging.
- Around line 499-509: The current race is that subscribe_gift_wraps(...) spawns
a detached task (using tokio::spawn) and returns immediately, so
client.notifications() / client.subscribe() happen inside that spawned task and
may not be active before the subsequent publish — causing fast Kind 1059 events
to be missed; fix by moving the subscription work out of the detached task so
the caller can await it before publish: either (A) refactor subscribe_gift_wraps
to perform client.notifications() and client.subscribe() synchronously (i.e.,
remove internal tokio::spawn) and return a future the caller can await, or (B)
add a new subscribe_gift_wraps_sync (or change the existing function signature)
that performs and awaits client.notifications() and client.subscribe() before
returning, and have the caller call/await that before publishing, leaving
spawning to the caller if needed; reference subscribe_gift_wraps,
client.notifications(), client.subscribe(), and the tokio::spawn usage for
locating the changes.

---

Outside diff comments:
In `@rust/src/api/orders.rs`:
- Around line 481-511: You must roll back the in-memory pending state if
publishing fails: after creating event_json, inserting into TRADE_KEY_MAP via
store_trade_key_index(&order.id, ..) and store_trade_key_index(&ck,..), calling
store_pending_maker_key(&trade_pk_hex,..), store_pending_local_id(&ck,..),
inserting conf_tx into pending_confirmations() and subscribing with
subscribe_gift_wraps(...), wrap publish_event_json(&event_json).await in a
match/Result handling block and on Err perform cleanup by removing the two
trade-key indexes, removing the pending maker key and pending local id, removing
the entry from pending_confirmations() (map.remove(&trade_pk_hex)), and undoing
the subscription (unsubscribe_gift_wraps or equivalent) before propagating the
error; use the existing symbols (store_trade_key_index, store_pending_maker_key,
store_pending_local_id, pending_confirmations, subscribe_gift_wraps,
publish_event_json, trade_pk_hex, conf_tx) to locate and implement the rollback.

---

Nitpick comments:
In `@lib/features/order/screens/add_order_screen.dart`:
- Around line 133-138: The try/catch around identity_api.getIdentity() and
IdentityService.saveTradeKeyIndex currently swallows errors (catch (_) {});
update it to catch the exception and log the error (and stack) or rethrow so
persistence failures are visible. Specifically, modify the catch to catch (e, s)
and call your app logger or debugPrint with a clear message referencing
identity_api.getIdentity and IdentityService.saveTradeKeyIndex (e.g., "Failed
saving tradeKeyIndex: $e"), or rethrow after logging.
🪄 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: 158b95f7-aad8-4cdd-81e0-40ce68e483a3

📥 Commits

Reviewing files that changed from the base of the PR and between 287b8d2 and 2a1f42a.

📒 Files selected for processing (8)
  • lib/features/home/providers/home_order_providers.dart
  • lib/features/order/screens/add_order_screen.dart
  • lib/features/trades/providers/trades_providers.dart
  • lib/features/trades/widgets/trades_list_item.dart
  • lib/main.dart
  • rust/src/api/logging.rs
  • rust/src/api/orders.rs
  • rust/src/frb_generated.rs
💤 Files with no reviewable changes (1)
  • lib/features/home/providers/home_order_providers.dart

Comment thread rust/src/api/orders.rs
Comment thread rust/src/api/orders.rs Outdated
Comment thread rust/src/api/orders.rs Outdated
…ubscription race, publish rollback

- NewOrder handler reconciles local UUID with daemon UUID when no caller
  is waiting (timeout/cold-start scenario) using PENDING_LOCAL_IDS lookup
- Remove raw rumor JSON, decrypted content, and raw error content from
  gift-wrap logs — keep only action/kind with truncated trade pubkey
- Await relay subscription setup synchronously in subscribe_gift_wraps
  before returning, then spawn event loop — prevents race where fast
  daemon response arrives before subscription is active
- Rollback TRADE_KEY_MAP, PENDING_MAKER_KEYS, PENDING_LOCAL_IDS, and
  PENDING_CONFIRMATIONS entries if publish_event_json fails
- Log identity persistence failures instead of silently swallowing
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