Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring #431

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open

Refactoring #431

wants to merge 23 commits into from

Conversation

arkanoider
Copy link
Collaborator

@arkanoider arkanoider commented Jan 16, 2025

Hi @grunch ,

ok starting with a draft to let you review what i am doing and see if we can really get something better in the end.

I will try to describe the big parts i am trying to do:

  • Better/Cleaner error management
  • Using more helper functions to improve code readability.
  • Diminish at least all CantDo messages call via centralization in main loop.
  • Some cosmetics, small functions unions and so on...

I DON'T want to touch any part of the logic, there are many changes but only as "cosmetics" modifications.

I will try to describe a bit more the three main points starting from errors:

errors.rs is now inside mostro-core and now mostro error is an enum with two variants as below:

#[derive(Debug, PartialEq, Eq)]
pub enum MostroError {
    MostroInternalErr(ServiceError),
    MostroCantDo(CantDoReason),
}
  • Internal error are for just log on mostro
  • CantDo error are the error sent to users via cantDo messages

This errors enum can be splitted and updated to have more enum splitting errors in different categories, this was an old idea to have a more handwritten approach with Rust errors. You will see much more map_err around the code, but this is the way to do that.

Now cantDo message are enqued in main loop, here the part in app.rs where the error messages are queued:

 if inner_message.verify() {
                        if let Some(action) = message.inner_action() {
                            if let Err(e) = handle_message_action(
                                &action,
                                message.clone(),
                                &event,
                                &my_keys,
                                &pool,
                                ln_client,
                            )
                            .await
                            {
                                match e.downcast::<MostroError>() {
                                    Ok(err) => {
                                        match err {
                                            MostroError::MostroCantDo(cause) => {
                                                enqueue_cant_do_msg(
                                                    inner_message.request_id,
                                                    inner_message.id,
                                                    cause,
                                                    event.rumor.pubkey,
                                                )
                                                .await
                                            }
                                            MostroError::MostroInternalErr(e) => {
                                                warning_msg(&action, e)
                                            }
                                        };
                                    }
                                    Err(_) => {
                                        todo!()
                                        // warning_msg(&action, e);
                                    }
                                }
                            }
                        }
                    }

There is dequeue in scheduler.rs that checks every second and in case dequeues messages. This has a nice effect on many part of the code like here in take_sell.rs

 if let Err(cause) = order.is_sell_order() {
        return Err(MostroCantDo(cause));
    };
    // Check if the order status is pending
    if let Err(cause) = order.check_status(Status::Pending) {
        return Err(MostroCantDo(cause));
    }

    // Validate that the order was sent from the correct maker
    if let Err(cause) = order.sent_from_maker(event.rumor.pubkey.to_hex()) {
        return Err(MostroCantDo(cause));
    }

Just returning the error should automatically enqueue the message in main loop, and basically after max 1s scheduler will clean queue....

Finally compiles, all the work in these days was related to add other helpers in mostro-core and compiling and started testing, did some basic testing:

  • [ x] TakeSell ( no range )
  • [ x] TakeBuy ( no range )
  • [ x] Rate users

Will work for other testing in the next days, will make this PR official so rabbit can sniff it and see what he sees, also if it's quite big.

Then @grunch and @Catrya please use your eyes to check everything and see if you like my modifications, still a lot of room, maybe start with takesell, takebuy and order files and check if they seem more ordered now.

Note: you need to use the same error-manage branch for mostro-core to compile.

Now let's make rabbit speak...

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Introduced enhanced trade actions and message queuing that contribute to smoother user interactions.
    • Added a new function for handling public key trades.
    • Implemented new functions for invoice validation and quote calculation.
  • Refactor

    • Streamlined order processing, invoice validation, dispute resolution, and user ratings to improve overall system reliability.
    • Overhauled error management across payment and administrative processes for clearer feedback.
    • Updated messaging functions to utilize a queue system for better performance.
    • Simplified scheduler operations and message handling capabilities.
  • Chores

    • Updated package dependencies and external integrations to bolster performance and maintainability.

Copy link
Contributor

coderabbitai bot commented Jan 16, 2025

Walkthrough

This pull request standardizes error handling and messaging across the codebase while refactoring many control flows. In Cargo.toml, the dependency version is downgraded, a dependency is commented out, and a patch for a local dependency is added. Multiple files in the src/app module update function signatures to return a specific error type, improve invoice validation, order and trade processing logic, and introduce helper functions. Related modules (lightning, lnurl, main, scheduler, util) adopt similar changes, and an obsolete error module is removed.

Changes

File(s) Change Summary
Cargo.toml Downgraded mostro-core from 0.6.25 to 0.6.23, commented out openssl, and added a local patch for mostro-core.
src/app/* Updated function signatures to return Result<…, MostroError>, refactored error handling in trade, invoice, order, cancel, dispute, and settlement actions; added new helper functions (e.g., trade_pubkey_action, check_order_status, calculate_and_check_quote).
src/db.rs, src/flow.rs Updated import for rating constants; replaced direct message sends (send_new_order_msg) with enqueued messaging (enqueue_order_msg).
src/lightning/* and src/lnurl.rs Modified error handling in invoice decoding and LN communications to use standardized MostroError variants; updated method signatures for LN connector functions.
src/main.rs, src/scheduler.rs, src/util.rs Introduced a new message queue (MessageQueues and MESSAGE_QUEUES), refactored scheduler to flush message queues, and updated utility functions (e.g., added get_order and validate_invoice); renamed messaging functions to enqueue_*.
src/error.rs Removed the file that defined the old MostroError enum.

Sequence Diagram(s)

sequenceDiagram
    participant M as Message
    participant E as UnwrappedGift Event
    participant DB as Database/Pool
    participant O as Order
    participant Q as Message Queue
    M->>DB: Call get_order(msg)
    DB-->>M: Return Order
    M->>O: Check status (check_status)
    alt Valid order status
        O->>M: Validate sender pubkey (buyer/seller)
        alt Pubkey valid
            M->>Q: Enqueue order message (enqueue_order_msg)
            M->>DB: Update order in database
        else Invalid pubkey
            M-->>M: Return error (MostroCantDo)
        end
    else Invalid status
        M-->>M: Return error (MostroInternalErr)
    end
Loading
sequenceDiagram
    participant S as Scheduler
    participant MQ as MESSAGE_QUEUES
    participant DM as send_dm
    S->>MQ: Retrieve queued messages
    MQ-->>S: Return messages (queue_order_msg, queue_order_cantdo)
    S->>DM: Process and send messages
    DM-->>S: Confirm delivery
    S->>MQ: Clear message queues
Loading

Possibly related PRs

  • Remove TradePubkey #419: Addresses changes in trade action handling by altering the trade_pubkey module, closely aligning with the new trade_pubkey_action introduced in this PR.

Suggested reviewers

  • grunch

Poem

In fields of code I hop along,
Refactoring lines both short and long.
Errors now dressed in proper style,
Message queues that make us smile.
With clean design I bounce with glee,
A happy rabbit in our code spree!
🐇✨

Tip

🌐 Web search-backed reviews and chat
  • We have enabled web search-based reviews and chat for all users. This feature allows CodeRabbit to access the latest documentation and information on the web.
  • You can disable this feature by setting web_search: false in the knowledge_base settings.
  • Please share any feedback in the Discord discussion.

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@grunch
Copy link
Member

grunch commented Jan 17, 2025

Hi @arkanoider cACK, I like it, let's go further with this implementation 🚀

@arkanoider arkanoider marked this pull request as ready for review February 3, 2025 17:30
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: 2

🔭 Outside diff range comments (3)
src/app/release.rs (1)

213-246: Review Outcome – Potential Issue in do_payment Handling

The investigation confirms that in two locations (specifically, in src/app/release.rs and src/app/admin_settle.rs), the result of the asynchronous do_payment call is discarded by assigning it to _. In contrast, src/scheduler.rs properly checks and logs errors from do_payment. This inconsistency could obscure unexpected payment errors, especially if partial failures are critical to workflow integrity.

  • src/app/release.rs: do_payment(order, request_id).await is assigned to _, which discards its outcome.
  • src/app/admin_settle.rs: Similarly, do_payment(order_updated, request_id).await is ignored.
  • src/scheduler.rs: The function's error is explicitly checked and logged if the call fails.

Please review whether ignoring the outcome in the first two instances is intended or if further error handling/logging should be implemented.

🔗 Analysis chain

Consider verifying do_payment’s outcome.
do_payment(order, request_id).await is called and its result is assigned to _. If partial failures in payment logic are critical to your workflow, ensure that unexpected errors are handled or logged appropriately. Otherwise, ignoring the result might obscure possible failures.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Script to identify any place where `do_payment` is called
# but its result is discarded in the codebase.

rg 'do_payment\(.*\)' -A 2

Length of output: 746

src/lightning/mod.rs (2)

149-167: Standardize error mapping in cancel_hold_invoice.

The error handling pattern in this function is inconsistent with the rest of the file. Line 161 maps the error to a string directly, while other functions use MostroInternalErr directly.

Apply this diff to standardize the error handling:

         let cancel = self
             .client
             .invoices()
             .cancel_invoice(cancel_message)
             .await
-            .map_err(|e| e.to_string());
+            .map_err(|e| MostroInternalErr(ServiceError::LnNodeError(e.to_string())));

         match cancel {
             Ok(cancel) => Ok(cancel.into_inner()),
-            Err(e) => Err(MostroInternalErr(ServiceError::LnNodeError(e.to_string()))),
+            Err(e) => Err(e),
         }

169-262: Improve error handling consistency in send_payment.

The function uses different error types (LnNodeError and LnPaymentError) inconsistently. Consider standardizing to use LnPaymentError for all payment-related errors.

Apply this diff to standardize the error handling:

         listener
             .clone()
             .send(msg)
             .await
-            .map_err(|e| MostroInternalErr(ServiceError::LnNodeError(e.to_string())))?
+            .map_err(|e| MostroInternalErr(ServiceError::LnPaymentError(e.to_string())))?
🧹 Nitpick comments (32)
src/app/admin_add_solver.rs (3)

5-9: Remove wildcard import
Using use mostro_core::error::{MostroError, ServiceError}; might be more explicit and avoids polluting the namespace with unused items.


45-47: Consider logging underlying error
When converting from bech32, capturing the actual parsing error could aid debugging.

- .map_err(|_| MostroInternalErr(ServiceError::InvalidPubkey))?;
+ .map_err(|e| MostroInternalErr(ServiceError::InvalidPubkey(e.to_string())))?;

55-57: Preserve JSON serialization cause
Currently returning a generic MessageSerializationError—consider inserting the underlying serialization error for better troubleshooting.

src/app/take_buy.rs (3)

5-9: Unified error handling
Adopting MostroError::{self, *} and removing usage of anyhow::Result fosters consistency. However, consider avoiding wildcard imports for clarity.


46-46: Wrong amount error
MostroInternalErr(ServiceError::WrongAmountError) may be better replaced with a user-facing error if the user provided an invalid amount rather than an internal configuration error.


60-62: map_err overshadowing
Mapping errors from get_buyer_pubkey() to MostroInternalErr alone might obscure important debug details.

src/app/order.rs (2)

4-8: Wildcard import caution
While including all variants of MostroError can be convenient, consider enumerating them for clarity and to avoid accidental collisions.


92-93: Don't lose original error
.map_err(|_| MostroError::MostroInternalErr(ServiceError::InvalidOrderId))?; discards the original error context. Including e could simplify debugging.

.map_err(|e| MostroError::MostroInternalErr(
    ServiceError::InvalidOrderId(format!("Invalid order: {}", e))
))?;
src/app/take_sell.rs (1)

17-39: update_order_status function
Centralizing the status update logic fosters improved readability and reusability. However, consider handling potential unwrap() on buyer_pubkey (line 24) more gracefully to avoid panics if an order is malformed.

Here’s an optional diff to ensure robust error handling:

-    let buyer_pubkey = order.get_buyer_pubkey().unwrap();
+    let buyer_pubkey = match order.get_buyer_pubkey() {
+        Ok(pk) => pk,
+        Err(cause) => return Err(MostroCantDo(cause)),
+    };
src/app/admin_settle.rs (1)

49-60: Conditional check on cooperative cancellation.
Returning MostroCantDo(IsNotYourDispute) if the order is not cooperatively canceled might be confusing. Consider using a more specific reason, such as CantDoReason::InvalidOrderStatus, for better clarity.

-if order.check_status(Status::CooperativelyCanceled).is_err() {
-    return Err(MostroCantDo(
-        CantDoReason::IsNotYourDispute,
-    ));
+if order.check_status(Status::CooperativelyCanceled).is_err() {
+    return Err(MostroCantDo(CantDoReason::InvalidOrderStatus));
 }
src/app/admin_cancel.rs (1)

49-60: Cooperative cancellation validation.
Similar to admin_settle.rs, returning MostroCantDo(cause) for mismatched status is valid, though consider if cause should be more descriptive (e.g., InvalidOrderStatus).

src/app/rate_user.rs (1)

17-65: New helper function prepare_variables_for_vote.
Creating a dedicated function to manage buyer/seller identification and error handling is a great step towards modularity. Consider returning a small struct instead of a tuple for enhanced readability and maintainability.

-pub fn prepare_variables_for_vote(...) -> Result<(String, String, bool, bool), MostroError> {
+#[derive(Debug)]
+pub struct VoteVariables {
+    pub counterpart: String,
+    pub counterpart_trade_pubkey: String,
+    pub buyer_rating: bool,
+    pub seller_rating: bool,
+}
+
+pub fn prepare_variables_for_vote(...) -> Result<VoteVariables, MostroError> {
     ...
-    Ok((counterpart, counterpart_trade_pubkey, buyer_rating, seller_rating))
+    Ok(VoteVariables {
+        counterpart,
+        counterpart_trade_pubkey,
+        buyer_rating,
+        seller_rating,
+    })
 }
src/app/admin_take_dispute.rs (2)

114-116: Preserve the underlying error message when serialization fails.
You're currently discarding the original error value by using map_err(|_| ...). Retaining the original error details can help with debugging.

-    .map_err(|_| MostroInternalErr(ServiceError::MessageSerializationError))?;
+    .map_err(|err| MostroInternalErr(ServiceError::MessageSerializationError(
+        err.to_string()
+    )))?;

152-160: Consolidate repeated DM-sending logic.
The logic for sending direct messages to buyer and seller is almost duplicated. A helper function or shared method could reduce duplication and simplify future maintenance.

Also applies to: 162-172

src/app/dispute.rs (1)

53-53: Retain the original error context when creating dispute events.
You're discarding the details by using map_err(|_| ...); consider preserving the underlying error for easier debugging.

-    .map_err(|_| MostroInternalErr(ServiceError::DisputeEventError))?;
+    .map_err(|err| MostroInternalErr(ServiceError::DisputeEventError(
+        err.to_string()
+    )))?;
src/app.rs (2)

293-293: Consider handling the result of check_trade_index.
You're discarding the possible error from check_trade_index by assigning it to _. This may obscure user feedback or diagnostics.

-    let _ = check_trade_index(&pool, &event, &message).await;
+    if let Err(err) = check_trade_index(&pool, &event, &message).await {
+        tracing::warn!("Trade index check error: {:?}", err);
+        // Optionally handle or escalate the error here
+    }

324-325: Complete the todo!() block for error handling.
A todo!() indicates an unimplemented code path. Use an appropriate error variant or implement the necessary logic.

Would you like help implementing this logic or returning a specialized error?

src/app/release.rs (1)

82-88: Avoid repeated status checks.
These two blocks check the same set of statuses (Active, FiatSent, Dispute). Consider consolidating them into a single helper function or removing the duplication if only one check is needed here.

-    // Check if order is active, fiat sent or dispute
-    if order.check_status(Status::Active).is_err() && order.check_status(Status::FiatSent).is_err()
-        && order.check_status(Status::Dispute).is_err()
-    {
-        return Err(MostroCantDo(CantDoReason::NotAllowedByStatus));
-    }

... (some lines later) ...

-    // Check if order is active, fiat sent or dispute
-    if order.check_status(Status::Active).is_err() && order.check_status(Status::FiatSent).is_err()
-        && order.check_status(Status::Dispute).is_err()
-    {
-        return Err(MostroCantDo(CantDoReason::NotAllowedByStatus));
-    }

+    // Consider merging these checks into a single function or call:
+    fn ensure_valid_release_status(order: &Order) -> Result<(), MostroError> {
+        if order.check_status(Status::Active).is_err()
+            && order.check_status(Status::FiatSent).is_err()
+            && order.check_status(Status::Dispute).is_err()
+        {
+            return Err(MostroCantDo(CantDoReason::NotAllowedByStatus));
+        }
+        Ok(())
+    }
+    ensure_valid_release_status(&order)?;

Also applies to: 97-102

src/app/cancel.rs (3)

70-185: Complex cancellation logic could be simplified.
The code handling WaitingPayment vs WaitingBuyerInvoice with maker/taker checks is sprawling. Consider extracting these checks into small helper functions (e.g., is_cancellable_by_maker, is_cancellable_by_taker). This would reduce nesting, improve testability, and better convey intent.


282-360: Unit tests recommended.
cancel_add_invoice adds important logic around returning seller funds and reverting the order state. Currently, there's no direct test coverage for it. Automated tests would help ensure correctness, especially for edge cases (repeated calls, invalid pubkeys, etc.).

Do you want me to provide a sample unit test you can adapt for verifying edge cases?


362-444: Similar flows in cancel_pay_hold_invoice.
Like cancel_add_invoice, the cancel_pay_hold_invoice function has repeated logic for resetting the order. Consider extracting a shared helper to handle the revert-to-pending scenario.

src/util.rs (4)

109-120: Clear error handling for Yadio request.
Returning MostroInternalErr(ServiceError::NoAPIResponse) or NoCurrency or MalformedAPIRes neatly indicates the root cause. To reduce branching, consider combining repeated checks into a single match if the logic is invariably linear.


124-131: Preserve JSON parsing errors for debugging.
When calling q.json::<Yadio>(), you discard the original error detail. Including it in the MessageSerializationError or logging it could greatly help debugging malformed responses.

-.map_err(|_| MostroError::MostroInternalErr(ServiceError::MessageSerializationError))?
+.map_err(|e| {
+    tracing::error!("Failed parsing Yadio response: {e}");
+    MostroError::MostroInternalErr(ServiceError::MessageSerializationError)
+})?

761-774: Helpful get_order function.
This function neatly wraps DB lookups in a type-safe manner. It might be beneficial to mention which upstream code is responsible for verifying user ownership or permissions to access the returned order.


776-798: Efficient invoice validation with validate_invoice.
This is a nice modular approach. One minor suggestion: log invalid invoice details (at least partially or hashed) for troubleshooting, if not considered sensitive.

src/app/trade_pubkey.rs (3)

12-16: Consider adding documentation for the function parameters.

While the function signature is clear, adding documentation for msg, event, and pool parameters would improve code maintainability.

+/// Handles the trading of public keys between buyers and sellers
+/// 
+/// # Arguments
+/// * `msg` - The message containing the request details
+/// * `event` - The unwrapped gift event containing sender information
+/// * `pool` - The database connection pool
+/// 
+/// # Returns
+/// * `Result<(), MostroError>` - Success or a MostroError
 pub async fn trade_pubkey_action(
     msg: Message,
     event: &UnwrappedGift,
     pool: &Pool<Sqlite>,
 ) -> Result<(), MostroError> {

27-38: Consider using early returns for better readability.

The nested match pattern could be simplified using early returns, making the code more maintainable.

-    match (
-        order.master_buyer_pubkey.as_ref(),
-        order.master_seller_pubkey.as_ref(),
-    ) {
-        (Some(master_buyer_pubkey), _) if master_buyer_pubkey == &event.sender.to_string() => {
-            order.buyer_pubkey = Some(event.rumor.pubkey.to_string());
-        }
-        (_, Some(master_seller_pubkey)) if master_seller_pubkey == &event.sender.to_string() => {
-            order.seller_pubkey = Some(event.rumor.pubkey.to_string());
-        }
-        _ => return Err(MostroInternalErr(ServiceError::InvalidPubkey)),
-    };
+    if let Some(master_buyer_pubkey) = order.master_buyer_pubkey.as_ref() {
+        if master_buyer_pubkey == &event.sender.to_string() {
+            order.buyer_pubkey = Some(event.rumor.pubkey.to_string());
+            return Ok(());
+        }
+    }
+    
+    if let Some(master_seller_pubkey) = order.master_seller_pubkey.as_ref() {
+        if master_seller_pubkey == &event.sender.to_string() {
+            order.seller_pubkey = Some(event.rumor.pubkey.to_string());
+            return Ok(());
+        }
+    }
+    
+    return Err(MostroInternalErr(ServiceError::InvalidPubkey));

52-55: Consider adding error context for better debugging.

The database error could include more context about what operation failed.

     order
         .update(pool)
         .await
-        .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
+        .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(
+            format!("Failed to update order {}: {}", order.id, e)
+        )))?;
src/lnurl.rs (1)

20-34: Consider adding error context for better debugging.

While the error handling is correct, adding context about which validation failed would help with debugging.

     let (user, domain) = match address.split_once('@') {
         Some((user, domain)) => (user, domain),
-        None => return Err(MostroInternalErr(ServiceError::LnAddressParseError)),
+        None => return Err(MostroInternalErr(ServiceError::LnAddressParseError(
+            format!("Invalid lightning address format: {}", address)
+        ))),
     };
src/flow.rs (1)

90-98: Consider grouping related messages for better maintainability.

The buyer and seller messages could be grouped into a helper function for better code organization.

+async fn send_order_messages(
+    request_id: Option<u64>,
+    order_id: i64,
+    buyer_action: Action,
+    seller_action: Action,
+    order_data: Option<Payload>,
+    buyer_pubkey: PublicKey,
+    seller_pubkey: PublicKey,
+) {
+    enqueue_order_msg(
+        request_id,
+        Some(order_id),
+        buyer_action,
+        order_data,
+        buyer_pubkey,
+        None,
+    ).await;
+
+    enqueue_order_msg(
+        request_id,
+        Some(order_id),
+        seller_action,
+        None,
+        seller_pubkey,
+        None,
+    ).await;
+}

Also applies to: 101-109

src/lightning/invoice.rs (1)

11-17: Consider adding specific error messages for better debugging.

While the error handling is correct, adding context about why the invoice is invalid would help with debugging.

     let invoice = Bolt11Invoice::from_str(payment_request)
-        .map_err(|_| MostroInternalErr(ServiceError::InvoiceInvalidError))?;
+        .map_err(|e| MostroInternalErr(ServiceError::InvoiceInvalidError(
+            format!("Failed to parse invoice: {}", e)
+        )))?;
src/main.rs (1)

37-45: Consider adding documentation for the message queue system.

The new message queue system is a critical component for handling various types of messages. Please add documentation to explain:

  • The purpose and lifecycle of each queue type
  • The thread-safety guarantees provided by the implementation
  • The expected message flow and processing patterns
+/// A thread-safe container for managing different types of message queues in the system.
+///
+/// # Fields
+/// * `queue_order_msg` - Queue for regular order-related messages
+/// * `queue_order_cantdo` - Queue for order rejection/failure messages
+/// * `queue_order_rate` - Queue for order rating events
 #[derive(Debug, Clone, Default)]
 pub struct MessageQueues {
     pub queue_order_msg: Arc<Mutex<Vec<(Message, PublicKey)>>>,
     pub queue_order_cantdo: Arc<Mutex<Vec<(Message, PublicKey)>>>,
     pub queue_order_rate: Arc<Mutex<Vec<Event>>>,
 }

+/// Global instance of message queues, lazily initialized and protected by a read-write lock.
 static MESSAGE_QUEUES: LazyLock<RwLock<MessageQueues>> =
     LazyLock::new(|| RwLock::new(MessageQueues::default()));
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a61f09d and e8de8a8.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (25)
  • Cargo.toml (1 hunks)
  • src/app.rs (7 hunks)
  • src/app/add_invoice.rs (1 hunks)
  • src/app/admin_add_solver.rs (3 hunks)
  • src/app/admin_cancel.rs (5 hunks)
  • src/app/admin_settle.rs (4 hunks)
  • src/app/admin_take_dispute.rs (6 hunks)
  • src/app/cancel.rs (5 hunks)
  • src/app/dispute.rs (6 hunks)
  • src/app/fiat_sent.rs (1 hunks)
  • src/app/order.rs (2 hunks)
  • src/app/rate_user.rs (8 hunks)
  • src/app/release.rs (10 hunks)
  • src/app/take_buy.rs (1 hunks)
  • src/app/take_sell.rs (1 hunks)
  • src/app/trade_pubkey.rs (1 hunks)
  • src/db.rs (1 hunks)
  • src/error.rs (0 hunks)
  • src/flow.rs (3 hunks)
  • src/lightning/invoice.rs (6 hunks)
  • src/lightning/mod.rs (12 hunks)
  • src/lnurl.rs (1 hunks)
  • src/main.rs (3 hunks)
  • src/scheduler.rs (4 hunks)
  • src/util.rs (17 hunks)
💤 Files with no reviewable changes (1)
  • src/error.rs
✅ Files skipped from review due to trivial changes (1)
  • src/db.rs
🔇 Additional comments (122)
src/app/admin_add_solver.rs (4)

2-2: No concerns
New usage of send_dm is consistent with your approach to direct messaging.


21-21: Consistent error type
Returning Result<(), MostroError> aligns with your new error handling approach across the codebase.


42-42: Confirm error type
You're returning a MostroInternalErr for an invalid pubkey. Consider whether this scenario is purely internal or if a user-friendly error (MostroCantDo) might be more appropriate.


60-62: Good approach
Using map_err with MostroInternalErr in send_dm ensures unified error handling for potential Nostr-specific failures.

src/app/take_buy.rs (8)

2-2: Helpful utility usage
Adding references to get_fiat_amount_requested, get_market_amount_and_fee, get_order, and show_hold_invoice aligns well with your refactoring efforts.


20-20: Harmonized return type
Returning Result<(), MostroError> fits the new approach of explicit error handling.


23-23: Verify get_order usage
get_order(&msg, pool).await? might fail if the order doesn't exist or is invalid. Confirm that scenarios where the order is missing are handled elsewhere or tested comprehensively.


28-34: Properly blocking invalid orders
Using MostroCantDo(cause) if the order isn't a buy or hasn't pending status is a straightforward approach that informs the user appropriately.


37-40: Security measure
Ensuring the event is from the original maker prevents unauthorized manipulation of the order. Good practice.


49-57: Market amount fallback
Falling back to get_market_amount_and_fee() for a zero-amount order is sensible, but confirm that invalid or unavailable exchange rates are surfaced to the user appropriately.


68-69: Timestamping
order.set_timestamp_now(); ensures the take action is recorded—this is a good approach for order tracking.


71-85: Consistent user feedback
All show_hold_invoice failures become internal errors. Verify if any scenario could be more informative to end users, for instance, a "can't do" message if invoice creation fails due to user input.

src/app/order.rs (7)

2-2: Centralized invoice validation
Importing validate_invoice along with other related utilities consolidates invoice handling logic neatly.


9-10: Expanded core references
Importing Message, Order, and SmallOrder indicates enhanced utility for multiple order operations in this module.


55-55: Unified error type
-> Result<(), MostroError> improves consistency across the system, making error handling more predictable.


60-61: Direct invoice validation
validate_invoice(&msg, &Order::from(order.clone())).await? is a neat approach; ensure partial or malformed invoices yield clear user feedback.


67-69: Range order limits
Using MostroCantDo if check_range_order_limits fails gives the user explicit feedback on invalid range requests.


71-73: Zero amount with premium
Rejecting an order that sets an impossible premium with zero amount is sensible.


78-78: Quote validation
Invoking calculate_and_check_quote for each fiat amount ensures robust verification of multi-amount orders.

src/app/fiat_sent.rs (13)

1-9: Imports look consistent with new error handling and queue utilities.
They align well with the refactoring goals of centralizing error definitions in mostro_core::error and using enqueue_order_msg for message dispatch.


15-15: Doc comment addition is clear.
The new comment clarifies this function's purpose without affecting logic.


21-23: Returning Result<(), MostroError> is consistent with the project's new error strategy.
This signature change follows the refactoring scope: any function that may fail should return a MostroError.


25-26: Status validation is appropriate.
Returning MostroCantDo for invalid statuses ensures consistent error semantics.


31-32: Buyer identity check.
Verifying order.get_buyer_pubkey() matches event.rumor.pubkey prevents mismatched ownership. Good approach.


35-39: Careful handling of next trade key.
Graceful fallback to None or returning MostroInternalErr helps avoid undefined states.


44-46: Order and event update integration.
Mapping errors to an internal error type makes the flow consistent with the new MostroError scheme.


48-51: Database update error handling is well-defined.
Using map_err ensures that any sqlx failure is translated into a MostroInternalErr.


54-54: Seller pubkey retrieval is correct.
Mapping any retrieval error directly to MostroInternalErr is consistent.


60-65: Enqueuing seller message.
This centralized queuing approach fits the PR's goal of simplifying message dispatch.


72-77: Buyer notification logic.
Similar to the seller, properly enqueued with minimal duplication.


84-96: Synchronized next trade handling.
Updating next_trade_pubkey and next_trade_index only after verifying the sent_from_maker condition is a solid approach.


98-99: Final return.
Function cleanly concludes with Ok(()), maintaining consistent error-handling patterns.

src/app/add_invoice.rs (18)

1-3: New import references for invoice validation.
Bringing validate_invoice into scope aligns with the refactor that centralizes invoice checks.


5-5: Use of anyhow::Result replaced by MostroError below.
Retaining anyhow::Result for local convenience is fine, given final errors map to MostroError.


7-11: Consolidated error and order modules.
Imports group relevant modules (MostroCantDo, ServiceError, Status, etc.) for the new approach.


17-45: check_order_status function fosters clarity and modularity.
It encapsulates status checks and messages, reducing duplication in add_invoice_action. Good reusability.


52-54: Returning Result<(), MostroError> in add_invoice_action.
Consistent with the application-wide change to unify error handling types.


56-58: Validating order status.
Mapping the status fetch failure to an internal error ensures uniform error propagation.


60-63: Validating order kind is essential.
Returning MostroInternalErr enforces consistent error semantics.


65-66: Buyer public key resolution.
Gracefully propagates any data retrieval failure through MostroInternalErr.


69-70: Restricting add-invoice action to buyer only.
Rejecting mismatched pubkeys as MostroCantDo is correct for user-facing error messages.


73-74: validate_invoice usage.
Offers a neat encapsulation for invoice correctness checks, now integrated with the main logic.


76-77: Delegating status check to check_order_status.
Enhances readability by separating status logic from main invoice flow.


78-79: Seller pubkey retrieval.
Fetching the counterpart's pubkey with error mapping is consistent with the new pattern.


83-84: Creating a smaller SmallOrder struct.
This smaller variant is beneficial for message dispatch, reducing data overhead.


86-88: Replacing the order event.
Uses the same approach seen in other modules to keep order data in sync with on-chain/off-chain changes.


90-100: Enqueuing message for seller.
Refactor from send_new_order_msg to enqueue_order_msg unifies messaging in the codebase.


101-110: Enqueuing message for buyer.
Mirrors the seller logic, ensuring both parties remain informed at each stage.


110-119: Fallback to show_hold_invoice for missing preimage.
Properly extends the same error handling strategy, returning MostroInternalErr on failure.


124-125: Conclusion of add_invoice_action.
The function ends cleanly with Ok(()), fulfilling the new error pattern.

src/app/take_sell.rs (14)

2-3: New utility imports.
Including validate_invoice and get_fiat_amount_requested centralizes order data retrieval and invoice checks.


6-11: Errors consolidated under MostroError and ServiceError plus order definitions.
This import strategy is consistent with the shift to typed error returns.


41-46: Updated signature to return Result<(), MostroError>.
Aligns with the new approach for typed error handling within the application.


48-49: Order retrieval.
Pulling order from the database via get_order is consistent with the rest of the refactor.


53-60: Ensuring it's a sell order and in Pending status.
Returning MostroCantDo for user-facing rejections preserves uniform error semantics.


62-65: Validates the maker identity.
Requiring that the event.rumor.pubkey match ensures only the original maker can finalize the order.


67-67: Fetching seller pubkey.
Maps potential errors to MostroInternalErr, consistent with the approach in other modules.


70-70: Invoice validation via validate_invoice.
Streamlines the logic by delegating invoice checks to a single utility function.


77-77: Returning MostroInternalErr for a wrong amount.
Ensures the function terminates gracefully when the user-submitted or derived fiat amount is invalid.


81-81: Populating the buyer’s pubkey.
Ensures the order data remains consistent with the actual user taking the sell offer.


87-87: Timestamp update.
Captures the time at which the buyer interacts with the order. Fits the new logic for tracking.


90-98: Market-based amount and fee checks.
This is a convenient approach to handle unfilled amount through external logic in get_market_amount_and_fee.


100-106: Branch when no payment request is present.
Calls update_order_status to set WaitingBuyerInvoice, simplifying the flow for late invoice provisioning.


109-123: Showing a hold invoice if payment request exists.
This fallback suitably handles the invoice interaction, with consistent error mapping to MostroInternalErr.

src/app/admin_settle.rs (16)

5-5: Imports updated to align with refactored helpers.
The inclusion of enqueue_order_msg, get_nostr_client, get_order, settle_seller_hold_invoice, and update_order_event in one place is consistent with the PR’s goal of centralizing logic and improving readability.


9-10: Explicit error imports for clarity.
Using the MostroError variants and ServiceError fosters more expressive error handling and keeps error definitions centralized.


28-28: Refined return type for improved error reporting.
Changing the function signature to return Result<(), MostroError> instead of a generic Result<()> adds clarity and consistency.


31-32: Centralized order retrieval.
Switching to get_order is a clean approach, ensuring uniform access to order data and simplifying error management.


34-38: Solver assignment verification.
This block neatly checks whether the user handling the dispute is indeed the assigned solver. Good use of MostroCantDo(IsNotYourDispute) to clarify unauthorized access attempts.


41-43: Database access errors are properly wrapped.
Converting the database error into MostroInternalErr(ServiceError::DbAccessError) clarifies the nature of DB failures and simplifies error propagation.


65-67: Dispute status enforcement.
Ensuring the order is in Dispute state before proceeding helps avoid unintended flows. This is a solid guard clause.


68-71: Seller hold invoice settlement.
Calling settle_seller_hold_invoice with a specialized Action::AdminSettled accurately reflects the administrative override. Good usage of the new error type.


72-75: Consistent event update after invoice settlement.
Leveraging update_order_event with Status::SettledHoldInvoice maintains uniform status transitions and clear event history.


78-78: Potential silent failure when fetching dispute.
If find_dispute_by_order_id fails, the code silently ignores that error. Consider logging or explicitly handling the error to maintain clear visibility into DB issues.


84-86: Dispute update with robust error handling.
Wrapping DB update errors under MostroInternalErr(ServiceError::DbAccessError(...)) ensures clarity when diagnosing dispute-related failures.


104-105: Constructing Nostr event with meaningful error checks.
Wrapping potential Nostr errors into MostroInternalErr(ServiceError::NostrError(...)) is a good approach to unify error reporting.


118-128: Message dispatch to the event creator.
Centralizing calls to enqueue_order_msg aligns with the PR objective of unifying message handling logic.


130-142: Seller notification via enqueue_order_msg.
Parsing the seller_pubkey and mapping any parse error to InvalidPubkey is straightforward and consistent with other error flows.


143-155: Buyer notification mirrored to seller flow.
Reusing the same pattern for the buyer ensures consistency, avoids code duplication, and cleanly handles pubkey parsing.


158-159: Ignoring the result of do_payment.
Storing do_payment(...) into _ swallows any potential errors. If payments occasionally fail, consider handling or logging the outcome for greater transparency.

src/app/admin_cancel.rs (14)

7-7: Extended utility imports for centralized message handling.
Integrating enqueue_order_msg, get_order, and update_order_event supports the PR’s aim of unifying helper calls.


10-14: Expanded error variants for precise problem reporting.
Introducing CantDoReason and distinct MostroError types (e.g., MostroCantDo, MostroInternalErr) clarifies error origin and severity.


29-29: Method signature clarified with Result<(), MostroError>.
Propagating domain-specific error types elevates code maintainability and debugging clarity.


33-33: Consistent order retrieval.
get_order systematically fetches the order, matching the approach across the codebase.


35-37: Immediate solver assignment check.
Returning MostroCantDo(CantDoReason::IsNotYourDispute) quickly halts unauthorized actions, promoting security.


40-43: Proper DB error wrapping.
Using MostroInternalErr(ServiceError::DbAccessError(e.to_string())) ensures that database issues are uniformly handled.


63-65: Dispute check ensures correct workflow.
Restricting actions for a disputed order helps avoid conflicting states.


76-76: Potential silent error for dispute retrieval.
If the retrieval fails, the Ok(...) pattern is not triggered, leaving an error unchecked. Consider adding logs for debugging.


82-84: Dispute update with robust DB error conversion.
This maintains consistency with the rest of the system’s error management.


101-103: Creating a new Nostr event for dispute status.
Wrapping potential Nostr errors in MostroInternalErr(ServiceError::NostrError(...)) is a clear, consistent approach.


116-122: Order event update.
Updating both the event in Nostr and the local DB with CanceledByAdmin thoroughly reflects the final state.


127-128: Trade index usage.
Ensuring the correct trade_index is passed along preserves traceability for front-end recipients or logs.


144-150: Seller/buyer pubkey parsing.
Returning MostroInternalErr(ServiceError::InvalidPubkey) on parse failure is more explicit than older generic errors, improving debug speed.


152-157: Direct messaging the parties.
Using send_dm for admin, seller, and buyer not only improves clarity but also aligns with the new unified error approach.

src/app/rate_user.rs (15)

1-1: Unified helpers for rating logic.
Pulling in enqueue_order_msg and get_order harmonizes the approach to message dispatch and data retrieval.


5-5: Transition away from generic anyhow::Result.
Though anyhow::Result remains in scope, shifting to MostroError in function signatures fosters domain-centric error handling.


6-8: Expanded error coverage for rating process.
Importing MostroError, CantDoReason, and ServiceError ensures the code can differentiate between user-facing and internal failures.


67-98: Refined user reputation fetching.
Switching to -> Result<Option<Rating>, MostroError> allows consistent error handling across external calls (e.g., Nostr). Sorting events by creation time ensures the latest rating is returned.


105-105: Specific error type for rating updates.
Utilizing Result<(), MostroError> clarifies that rating failures are part of a well-defined error domain.


106-107: Single call for uniform order retrieval.
get_order(&msg, pool) usage furthers the goal of consistent data access and error reporting.


109-111: Status check ensures the order succeeded.
Rejecting attempts to rate an incomplete or invalid order with InvalidOrderStatus helps avoid inconsistent data.


114-116: Variable extraction for rating.
prepare_variables_for_vote is invoked here, providing a clean separation of logic.


131-135: Safe retrieval of user-supplied rating.
The get_rating() function call is gracefully wrapped with map_err(MostroInternalErr), ensuring rating data is validated.


138-141: Database check for user presence.
is_user_present plus descriptive error mapping ensures that an invalid or non-existent user flow is handled predictably.


143-143: Adjusting rating on the found user record.
user_to_vote.update_rating(new_rating) elegantly modifies user stats in memory before persisting.


153-154: Rating tagging logic.
to_tags() method and associated error handling unify the rating data structure with Nostr’s event-based model.


168-171: Detailed DB update error context.
Embedding the specific DB error string in ServiceError::DbAccessError is beneficial for debugging.


181-191: Precise error handling when updating rating event.
Wrapping potential DB issues under MostroInternalErr(ServiceError::DbAccessError(...)) maintains a consistent error approach across the codebase.


194-202: Final rating acknowledgment message.
Posting RateReceived promptly notifies the user that the rating was successfully processed. Including the Payload::RatingUser(new_rating) is a clean way to convey relevant data.

src/app/admin_take_dispute.rs (1)

3-3: Imports and updated function signature look good!
No major issues identified with the new imports, updated function signature, or error definitions.

Also applies to: 6-10, 47-47

src/app/dispute.rs (1)

10-11: Overall error handling refactor looks consistent.
No major issues identified in these changed lines. The new error types and usage appear correct.

Also applies to: 12-18, 19-19, 69-69, 74-74, 84-88, 93-93, 101-101, 103-103, 105-109, 129-129, 136-136, 139-139, 150-152, 156-160, 165-166, 169-172, 177-178, 182-188, 193-198, 200-207, 210-212

src/app.rs (1)

18-19: General improvements are correct.
No major issues found in these line changes. The new module references and refined error handling match the broader refactoring goals.

Also applies to: 33-33, 39-39, 44-48, 53-53, 70-82, 97-99, 107-112, 131-132, 159-166, 170-178, 182-185, 189-190, 193-196, 197-201, 202-203, 205-207, 307-323

src/app/release.rs (1)

24-28: Good introduction of check_failure_retries.
This function decouples the retry logic from the main payment flow, improving maintainability. Consider adding a short doc example that clarifies how retry counts are persisted and how clients should interpret partial retry attempts.

src/app/cancel.rs (1)

35-39: Properly preventing redundant cancellations.
Returning MostroCantDo(CantDoReason::OrderAlreadyCanceled) is clear and straightforward. This prevents confusion over canceled orders being canceled again.

src/util.rs (2)

178-201: Validate partial failures in publish_order.
If database insertion succeeds (lines 196–201) but publishing the Nostr event fails, you could end up with inconsistent states. Consider implementing a rollback or additional logging to handle partial failures.


353-385: Event queue approach for rating updates looks neat.
Offloading Nostr event emission via MESSAGE_QUEUES is a clean pattern. Ensure concurrency on rating updates is tested, especially if multiple rating requests can happen simultaneously.

src/app/trade_pubkey.rs (1)

1-11: LGTM! Clean and well-organized imports.

The imports are logically grouped and necessary for the functionality.

src/lnurl.rs (1)

8-8: LGTM! Clean error type imports.

The error type imports are well-organized and follow the new error handling strategy.

src/flow.rs (1)

63-71: LGTM! Consistent message handling.

The transition to enqueue_order_msg improves message handling consistency.

Also applies to: 73-81

src/lightning/invoice.rs (1)

96-105: LGTM! Well-structured test case.

The test case is clear and properly validates the error handling.

Cargo.toml (2)

54-55: Review the use of local path dependency.

Using a local path for mostro-core in production could cause issues. This should be temporary for development/testing only.

Please confirm:

  1. Is this patch intended for development only?
  2. Should it be removed before deploying to production?
  3. Are there plans to publish the local changes to crates.io?

41-47: Verify the impact of downgrading mostro-core.

The downgrade from version 0.6.25 to 0.6.23 could affect the error handling changes. Also, the openssl dependency has been commented out.

Run the following script to check for breaking changes:

Comment on lines +15 to +48

async fn calculate_and_check_quote(
order: &SmallOrder,
fiat_amount: &i64,
) -> Result<(), MostroError> {
// Get mostro settings
let mostro_settings = Settings::get_mostro();
// Calculate quote
let quote = match order.amount {
0 => match get_bitcoin_price(&order.fiat_code) {
Ok(price) => {
let quote = *fiat_amount as f64 / price;
(quote * 1E8) as i64
}
Err(_) => {
return Err(MostroInternalErr(ServiceError::NoAPIResponse));
}
},
_ => order.amount,
};

// Check amount is positive - extra safety check
if quote < 0 {
return Err(MostroCantDo(CantDoReason::InvalidAmount));
}

if quote > mostro_settings.max_order_amount as i64
|| quote < mostro_settings.min_payment_amount as i64
{
return Err(MostroCantDo(CantDoReason::OutOfRangeSatsAmount));
}

Ok(())
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Potential floating-point precision issues
calculate_and_check_quote casts a floating value multiplied by 1E8 into an i64. Consider using fixed-decimal or rational arithmetic to avoid floating-point rounding problems in financial calculations.

Comment on lines +33 to +65
async fn job_flush_messages_queue() {
// Clone for closure owning with Arc
let order_msg_list = MESSAGE_QUEUES.read().await.queue_order_msg.clone();
let cantdo_msg_list = MESSAGE_QUEUES.read().await.queue_order_cantdo.clone();

// Spawn a new task to flush the messages queue
tokio::spawn(async move {
loop {
// Send message to event creator
for message in order_msg_list.lock().await.iter() {
info!("Flushing order messages in queue");
let destination_key = message.1;
if let Ok(message) = message.0.as_json() {
let sender_keys = crate::util::get_keys().unwrap();
let _ = send_dm(destination_key, sender_keys, message, None).await;
}
}
for message in cantdo_msg_list.lock().await.iter() {
info!("Flushing cant do messages in queue");
let destination_key = message.1;
if let Ok(message) = message.0.as_json() {
let sender_keys = crate::util::get_keys().unwrap();
let _ = send_dm(destination_key, sender_keys, message, None).await;
}
}
// Clear the queues
cantdo_msg_list.lock().await.clear();
order_msg_list.lock().await.clear();

tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
}
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance message queue processing reliability.

The message queue processing implementation has several areas for improvement:

  1. No error handling for message sending failures
  2. No backoff strategy for failed messages
  3. No batching for efficient processing
  4. No metrics for monitoring queue sizes and processing rates

Consider implementing these improvements:

 async fn job_flush_messages_queue() {
     // Clone for closure owning with Arc
     let order_msg_list = MESSAGE_QUEUES.read().await.queue_order_msg.clone();
     let cantdo_msg_list = MESSAGE_QUEUES.read().await.queue_order_cantdo.clone();
 
     // Spawn a new task to flush the messages queue
     tokio::spawn(async move {
+        let mut consecutive_failures = 0;
         loop {
+            let start_time = std::time::Instant::now();
+            let mut processed = 0;
+            let mut failed = 0;
+
             // Send message to event creator
             for message in order_msg_list.lock().await.iter() {
                 info!("Flushing order messages in queue");
                 let destination_key = message.1;
                 if let Ok(message) = message.0.as_json() {
                     let sender_keys = crate::util::get_keys().unwrap();
-                    let _ = send_dm(destination_key, sender_keys, message, None).await;
+                    match send_dm(destination_key, sender_keys, message, None).await {
+                        Ok(_) => {
+                            processed += 1;
+                            consecutive_failures = 0;
+                        }
+                        Err(e) => {
+                            error!("Failed to send message: {}", e);
+                            failed += 1;
+                            consecutive_failures += 1;
+                        }
+                    }
                 }
             }
             // Similar changes for cantdo_msg_list...
 
             // Clear the queues
             cantdo_msg_list.lock().await.clear();
             order_msg_list.lock().await.clear();
 
-            tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
+            // Log metrics
+            let elapsed = start_time.elapsed();
+            info!(
+                "Message queue metrics - Processed: {}, Failed: {}, Duration: {:?}",
+                processed, failed, elapsed
+            );
+
+            // Implement exponential backoff on consecutive failures
+            let delay = if consecutive_failures > 0 {
+                std::cmp::min(
+                    1 << consecutive_failures,
+                    60 // Max delay of 60 seconds
+                )
+            } else {
+                1
+            };
+            tokio::time::sleep(tokio::time::Duration::from_secs(delay)).await;
         }
     });
 }

Committable suggestion skipped: line range outside the PR's diff.

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: 1

🧹 Nitpick comments (3)
src/app/fiat_sent.rs (1)

81-96: Consider extracting the next trade fields update logic.

The next trade fields update logic could be moved to a separate function for better maintainability.

+async fn update_next_trade_fields(
+    order: &mut Order,
+    next_trade: Option<(String, u32)>,
+    pool: &Pool<Sqlite>,
+) -> Result<(), MostroError> {
+    if let Some((pubkey, index)) = next_trade {
+        order.next_trade_pubkey = Some(pubkey);
+        order.next_trade_index = Some(index as i64);
+        order.update(pool)
+            .await
+            .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
+    }
+    Ok(())
+}
src/app/release.rs (2)

212-284: Consider extracting payment status handling logic.

The payment status handling logic in do_payment is complex and could be extracted into separate functions for better readability and maintainability.

+async fn handle_payment_status(
+    status: PaymentStatus,
+    order: &mut Order,
+    msg: PaymentUpdate,
+    buyer_pubkey: PublicKey,
+    my_keys: &Keys,
+    request_id: Option<u64>,
+) -> Result<()> {
+    match status {
+        PaymentStatus::Succeeded => {
+            info!(
+                "Order Id {}: Invoice with hash: {} paid!",
+                order.id, msg.payment.payment_hash
+            );
+            payment_success(order, buyer_pubkey, my_keys, request_id).await?;
+        }
+        PaymentStatus::Failed => {
+            info!(
+                "Order Id {}: Invoice with hash: {} has failed!",
+                order.id, msg.payment.payment_hash
+            );
+            if let Ok(failed_payment) = check_failure_retries(order, request_id).await {
+                info!(
+                    "Order id {} has {} failed payments retries",
+                    failed_payment.id, failed_payment.payment_attempts
+                );
+            }
+        }
+        _ => {}
+    }
+    Ok(())
+}

436-442: Consider consolidating duplicate message sending logic.

The message sending logic in notify_invalid_amount is duplicated for buyer and seller. Consider consolidating it into a single function.

+async fn send_invalid_amount_message(
+    request_id: Option<u64>,
+    order_id: uuid::Uuid,
+    pubkey: PublicKey,
+) {
+    enqueue_cant_do_msg(
+        request_id,
+        Some(order_id),
+        CantDoReason::InvalidAmount,
+        pubkey,
+    )
+    .await;
+}

Also applies to: 444-450

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e8de8a8 and 0596e27.

📒 Files selected for processing (2)
  • src/app/fiat_sent.rs (1 hunks)
  • src/app/release.rs (10 hunks)
🔇 Additional comments (4)
src/app/fiat_sent.rs (4)

1-7: LGTM! Clean imports organization.

The imports are well-organized, with clear separation between internal and external dependencies.


15-21: LGTM! Function signature updated for better error handling.

The return type change from Result<()> to Result<(), MostroError> improves error specificity.


35-39: LGTM! Improved error handling for next trade key.

The error handling is now more specific with map_err(MostroInternalErr).


25-33: Verify buyer's public key validation.

The code checks if the pubkey matches the buyer, but consider adding logging before returning the error to help with debugging.

 if order.get_buyer_pubkey().ok() != Some(event.rumor.pubkey) {
+    tracing::debug!("Invalid pubkey: expected {:?}, got {:?}", order.get_buyer_pubkey().ok(), event.rumor.pubkey);
     return Err(MostroCantDo(CantDoReason::InvalidPubkey));
 }
✅ Verification successful

I'll start by checking if there is an existing logging statement for the buyer's public key validation in the file. This will help us determine whether the suggested logging should be added. For example, we can search for any usage of tracing::debug within src/app/fiat_sent.rs.


Action: Add debug logging for buyer's public key validation

The search confirms that no debug logging is present in the buyer pubkey validation block, so incorporating a tracing::debug! call—as suggested—is indeed a valuable improvement for debugging.

  • File: src/app/fiat_sent.rs (lines 25-33) lacks a logging statement before returning the error for an invalid pubkey.
  • Suggestion: Add a logging statement to capture the expected pubkey and the supplied value.
🏁 Scripts executed

The following scripts were executed for the analysis:

Error: No shell scripts or web queries found


Script:

#!/bin/bash
rg "tracing::debug" src/app/fiat_sent.rs -n .

Length of output: 45

src/app/release.rs Outdated Show resolved Hide resolved
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: 2

🔭 Outside diff range comments (1)
src/app/release.rs (1)

243-272: Enhance payment status handling.

The payment status handling could be more robust:

  1. Consider handling intermediate states like InFlight
  2. Add timeout handling for stuck payments
  3. Log payment amounts for better debugging
 if let Ok(status) = PaymentStatus::try_from(msg.payment.status) {
     match status {
         PaymentStatus::Succeeded => {
+            info!(
+                "Order Id {}: Invoice with hash: {} paid! Amount: {}",
+                order.id, msg.payment.payment_hash, msg.payment.value
+            );
             let _ = payment_success(&mut order, buyer_pubkey, &my_keys, request_id).await;
         }
         PaymentStatus::Failed => {
             info!(
-                "Order Id {}: Invoice with hash: {} has failed!",
+                "Order Id {}: Invoice with hash: {} has failed! Amount: {}",
-                order.id, msg.payment.payment_hash
+                order.id, msg.payment.payment_hash, msg.payment.value
             );
             // Mark payment as failed
             if let Ok(failed_payment) = check_failure_retries(&order, request_id).await {
                 info!(
                     "Order id {} has {} failed payments retries",
                     failed_payment.id, failed_payment.payment_attempts
                 );
             }
         }
+        PaymentStatus::InFlight => {
+            info!(
+                "Order Id {}: Payment in flight. Hash: {}, Amount: {}",
+                order.id, msg.payment.payment_hash, msg.payment.value
+            );
+        }
         _ => {}
     }
 }
🧹 Nitpick comments (2)
src/app/release.rs (2)

32-34: Consider using the ? operator for cleaner error handling.

The error mapping can be simplified using the ? operator with a type conversion implementation.

-    let pool = db::connect()
-        .await
-        .map_err(|cause| MostroInternalErr(ServiceError::DbAccessError(cause.to_string())))?;
+    let pool = db::connect().await.map_err(|e| e.into())?;

175-188: Consider using an enum for trade roles.

The repeated checks against creator_pubkey could be replaced with an enum to improve type safety and readability.

enum TradeRole {
    Seller,
    Buyer,
}

impl Order {
    fn get_trade_role(&self) -> TradeRole {
        if &self.creator_pubkey == self.seller_pubkey.as_ref().unwrap() {
            TradeRole::Seller
        } else {
            TradeRole::Buyer
        }
    }
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0596e27 and b2db8bf.

📒 Files selected for processing (1)
  • src/app/release.rs (10 hunks)

Comment on lines 297 to 299
let pool = db::connect().await?;
if let Ok(order) = order_updated.update(&pool).await {
// Send dm to buyer to rate counterpart
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve database error handling in payment_success.

The database operations should use consistent error handling with the rest of the codebase.

-        let pool = db::connect().await?;
-        if let Ok(order) = order_updated.update(&pool).await {
+        let pool = db::connect()
+            .await
+            .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
+        let order = order_updated
+            .update(&pool)
+            .await
+            .map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let pool = db::connect().await?;
if let Ok(order) = order_updated.update(&pool).await {
// Send dm to buyer to rate counterpart
let pool = db::connect()
.await
.map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
let order = order_updated
.update(&pool)
.await
.map_err(|e| MostroInternalErr(ServiceError::DbAccessError(e.to_string())))?;
// Send dm to buyer to rate counterpart

Comment on lines +96 to +97
// Settle seller hold invoice
settle_seller_hold_invoice(event, ln_client, Action::Released, false, &order).await?;
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Atomicity is not enforced for the payment settlement operations.

After inspecting the code in src/util.rs for the settle_seller_hold_invoice function—and noting that the broader payment flow in src/app/release.rs does not wrap its call in any transactional context (no use of keywords like transaction, begin_transaction, or commit)—there is no evidence that hold invoice settlement and order status updates occur atomically.

  • The function neither manages a transaction nor is it enclosed within a transactional block from the caller.
  • This leaves scope for potential race conditions in the payment flow.

It is recommended to wrap these operations within a transaction to enforce atomicity.

🔗 Analysis chain

Verify payment settlement atomicity.

The hold invoice settlement and order status update should be atomic to prevent race conditions. Consider wrapping these operations in a transaction.


I'll open a new investigation to check if the settle_seller_hold_invoice function and associated order update logic are wrapped in a transactional context. We need to verify whether these operations are executed atomically.

Let's search for the definition of settle_seller_hold_invoice in the repository to see if it uses a transaction internally.


Let's inspect the definition of settle_seller_hold_invoice and its surrounding context to verify if it includes transactional handling for atomicity.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for transaction handling in similar payment flows
ast-grep --pattern 'transaction|begin_transaction|commit'

Length of output: 59


Script:

#!/bin/bash
# Find the file that defines settle_seller_hold_invoice
rg -l "fn settle_seller_hold_invoice" .

Length of output: 55


Script:

#!/bin/bash
# Show context of settle_seller_hold_invoice function definition in src/util.rs
rg -C 20 "fn settle_seller_hold_invoice" ./src/util.rs

Length of output: 1062

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.

2 participants