Skip to content

Rabbit insipired unwrap removal#382

Merged
grunch merged 7 commits into
mainfrom
rabbit-unwrap-remove
Nov 1, 2024
Merged

Rabbit insipired unwrap removal#382
grunch merged 7 commits into
mainfrom
rabbit-unwrap-remove

Conversation

@arkanoider

@arkanoider arkanoider commented Oct 30, 2024

Copy link
Copy Markdown
Collaborator

@grunch

thanks to rabbit!

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced new utility functions for improved NOSTR client management, enhancing error handling.
  • Bug Fixes

    • Improved error handling across various functions to prevent runtime panics and ensure graceful failures.
  • Refactor

    • Transitioned from global NOSTR client usage to a function-based approach for better encapsulation and modularity.
    • Enhanced control flow and error handling in invoice processing and order management functions.
  • Style

    • Enhanced code readability through whitespace adjustments and clearer error logging.

@coderabbitai

coderabbitai Bot commented Oct 30, 2024

Copy link
Copy Markdown
Contributor

Walkthrough

The pull request introduces significant modifications across multiple files, primarily focused on enhancing error handling and restructuring how the Nostr client is accessed. The changes involve replacing direct references to a global Nostr client with a new utility function, get_nostr_client(), which improves modularity and error management. Additionally, several functions have been updated to streamline conditional logic and improve clarity, particularly regarding invoice handling, order processing, and logging. Overall, these changes aim to improve the robustness and readability of the code without altering its core functionality.

Changes

File Change Summary
src/app/add_invoice.rs Restructured error handling for invoice validation; clarified messaging; retained core functionality.
src/app/admin_cancel.rs Replaced NOSTR_CLIENT with get_nostr_client(); improved error handling for client retrieval; updated import statements.
src/app/admin_settle.rs Similar to admin_cancel.rs, replaced NOSTR_CLIENT with get_nostr_client(); enhanced error handling; updated import statements.
src/app/admin_take_dispute.rs Changed to use get_nostr_client() instead of NOSTR_CLIENT; improved error handling to avoid panic on client retrieval failure; updated import statements.
src/app/dispute.rs Transitioned from NOSTR_CLIENT to get_nostr_client(); improved error handling for event sending; updated import statements.
src/app/take_buy.rs Enhanced error handling and logging; streamlined conditional logic for order kind; consolidated fiat amount handling; minor whitespace adjustments.
src/app/take_sell.rs Added conditional block for processing fiat amounts; refined payment request handling; ensured only Pending orders are processed; updated order status handling.
src/main.rs Improved error handling for Nostr client connection; replaced static client access with dynamic retrieval; minor comment adjustments.
src/scheduler.rs Updated to use get_nostr_client() for Nostr client access; modified relay event sending logic; improved modularity in client interactions.
src/util.rs Introduced get_nostr_client() and get_nostr_relays() functions for improved client management; enhanced error handling in existing functions; updated imports.
src/app/admin_add_solver.rs Updated send_dm function call to include sender keys; improved error handling for key retrieval.

Possibly related PRs

  • Relays scheduled job publish only connected relays #381: The changes in src/scheduler.rs involve modifications to the handling of relay information and error management, which may indirectly relate to the overall messaging and event handling improvements in the add_invoice_action function, particularly in the context of ensuring reliable communication within the system.

Suggested reviewers

  • arkanoider

Poem

🐰 In the code where rabbits play,
Error handling leads the way.
With clients now so smart and bright,
We hop along, our code's delight!
From invoices to orders' fate,
We celebrate, oh isn't it great? 🎉


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 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.

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

🧹 Outside diff range and nitpick comments (14)
src/app/take_buy.rs (1)

Line range hint 14-108: Consider potential race conditions in order processing.

The time window between status validation and order updates could allow concurrent order takes. Consider implementing optimistic locking or a transaction to ensure atomicity.

src/app/admin_settle.rs (1)

Line range hint 26-133: Consider refactoring for improved maintainability.

The admin_settle_action function handles multiple responsibilities:

  1. Dispute validation
  2. Order status management
  3. Invoice settlement
  4. Event notifications
  5. Payment processing

Consider breaking this down into smaller, focused functions to improve maintainability and testability.

Consider extracting these responsibilities into separate functions:

async fn validate_dispute_solver(pool: &Pool<Sqlite>, sender: &PublicKey, order_id: u32) -> Result<()>
async fn handle_dispute_settlement(dispute: &mut Dispute, my_keys: &Keys) -> Result<Event>
async fn notify_participants(order: &Order, message: &str, sender: &PublicKey) -> Result<()>

This would make the main function more readable and easier to maintain:

pub async fn admin_settle_action(...) -> Result<()> {
    let order_id = msg.get_inner_message_kind().id.ok_or_else(|| Error::msg("No order id"))?;
    validate_dispute_solver(pool, &event.sender, order_id).await?;
    // ... rest of the logic
}
src/app/take_sell.rs (3)

Line range hint 99-111: Enhance error handling for fiat amount validation.

The current implementation has several areas for improvement:

  1. The error message doesn't provide context about valid ranges, which could confuse users.
  2. The order state is mutated before all validations are complete.

Consider this approach:

    // Get amount request if user requested one for range order - fiat amount will be used below
    if let Some(am) = get_fiat_amount_requested(&order, &msg) {
+       // Validate the amount is within acceptable bounds before mutating order
+       if !is_valid_fiat_range(&order, am) {
+           send_new_order_msg(
+               Some(order.id),
+               Action::OutOfRangeFiatAmount,
+               Some(format!("Amount must be between {} and {}", order.min_amount, order.max_amount)),
+               &event.sender,
+           ).await;
+           return Ok(());
+       }
        order.fiat_amount = am;
    } else {
        send_new_order_msg(
            Some(order.id),
            Action::OutOfRangeFiatAmount,
            None,
            &event.sender,
        )
        .await;
        return Ok(());
    }

Line range hint 52-69: Enhance invoice validation error handling.

The current invoice validation could be more robust and informative:

  1. Error messages don't specify which validation failed (amount, fee, or format)
  2. No protection against potential integer overflow in amount validation

Consider this improvement:

    if let Some(payment_request) = msg.get_inner_message_kind().get_payment_request() {
        pr = {
+           // Validate basic invoice format first
+           if let Err(e) = validate_invoice_format(&payment_request) {
+               send_cant_do_msg(
+                   Some(order.id),
+                   Some("Invalid invoice format".to_string()),
+                   &event.sender,
+               ).await;
+               return Ok(());
+           }
+           // Then validate amounts
+           let total_expected = order.amount.checked_add(order.fee)
+               .ok_or_else(|| Error::msg("Amount overflow"))?;
            match is_valid_invoice(
                payment_request.clone(),
-               Some(order.amount as u64),
-               Some(order.fee as u64),
+               Some(total_expected as u64),
+               None,
            ).await {
                Ok(_) => Some(payment_request),
                Err(e) => {
                    send_cant_do_msg(
                        Some(order.id),
-                       Some(e.to_string()),
+                       Some(format!("Invoice amount mismatch: expected {} sats", total_expected)),
                        &event.sender,
                    ).await;
                    error!("{e}");
                    return Ok(());
                }
            }
        };
    }

Line range hint 71-98: Improve order status transition handling.

While the status validation works, it could be more informative and traceable:

  1. Error messages don't specify the current status vs expected status
  2. Status transitions aren't logged for audit purposes

Consider this enhancement:

    let order_status = match Status::from_str(&order.status) {
        Ok(s) => s,
        Err(e) => {
-           error!("Order Id {order_id} wrong status: {e:?}");
+           error!("Order Id {order_id} invalid status format '{order.status}': {e:?}");
            return Ok(());
        }
    };

    // Buyer can take Pending orders only
    match order_status {
-       Status::Pending => {}
+       Status::Pending => {
+           tracing::info!(
+               "Order {} status transition: {} -> WaitingBuyerInvoice",
+               order.id,
+               order_status
+           );
+       }
        _ => {
            send_new_order_msg(
                Some(order.id),
                Action::NotAllowedByStatus,
-               None,
+               Some(format!(
+                   "Order must be in Pending status (current: {})",
+                   order_status
+               )),
                &buyer_pubkey,
            )
            .await;
            return Ok(());
        }
    }
src/app/admin_cancel.rs (1)

Line range hint 17-142: Consider breaking down the admin_cancel_action function.

While the function is well-structured with proper error handling, it's quite long and handles multiple responsibilities (order validation, dispute handling, Lightning operations, notifications). Consider breaking it down into smaller, focused functions for better maintainability.

Consider extracting these logical blocks into separate functions:

  1. Order validation and status checks
  2. Lightning fund return handling
  3. Dispute status update
  4. Notification dispatch

Example structure:

async fn validate_order_status(order: &Order, event: &UnwrappedGift) -> Result<()> {
    // Status validation logic
}

async fn handle_lightning_refund(ln_client: &mut LndConnector, order: &Order) -> Result<()> {
    // Lightning operations
}

async fn update_dispute_status(
    pool: &Pool<Sqlite>,
    my_keys: &Keys,
    order_id: i64,
) -> Result<()> {
    // Dispute handling
}

async fn send_notifications(
    order: &Order,
    event: &UnwrappedGift,
    message: String,
) -> Result<()> {
    // Notification logic
}
src/main.rs (1)

Line range hint 34-35: Consider further reducing global state

While the current changes improve error handling, consider taking this further by removing the static NOSTR_CLIENT entirely. Instead, you could:

  1. Initialize the client in main
  2. Pass it as a parameter to components that need it
  3. Use dependency injection patterns

This would make the code more testable and reduce global state dependencies.

src/app/add_invoice.rs (1)

Line range hint 1-182: Consider refactoring for improved maintainability.

While the current changes are good, here are some suggestions to improve the overall code structure:

  1. Consider extracting validation logic into separate functions:
fn validate_order_status(order: &Order) -> Result<Status> {
    Status::from_str(&order.status).map_err(|e| {
        error!("Order Id {} wrong status: {e:?}", order.id);
        Error::msg("Invalid order status")
    })
}
  1. Consider splitting the function into smaller, focused functions:
  • validate_order_and_permissions
  • process_invoice
  • handle_preimage_case
  1. Make error handling more consistent by logging all error cases before returning.

Would you like me to provide a complete refactoring proposal?

src/scheduler.rs (3)

44-59: Consider logging errors when Nostr client is unavailable.

While the error handling has improved by using get_nostr_client(), errors are currently silently ignored. Consider logging these errors to help with debugging and monitoring.

-    if let Ok(client) = get_nostr_client() {
-        let _ = client.send_event(relay_ev).await;
+    match get_nostr_client() {
+        Ok(client) => {
+            if let Err(e) = client.send_event(relay_ev).await {
+                error!("Failed to send relay event: {}", e);
+            }
+        }
+        Err(e) => error!("Failed to get Nostr client: {}", e),
+    }

84-87: Apply consistent error handling for Nostr client operations.

Similar to the previous function, consider improving error logging here as well.

-    if let Ok(client) = get_nostr_client() {
-        let _ = client.send_event(info_ev).await;
+    match get_nostr_client() {
+        Ok(client) => {
+            if let Err(e) = client.send_event(info_ev).await {
+                error!("Failed to send info event: {}", e);
+            }
+        }
+        Err(e) => error!("Failed to get Nostr client: {}", e),
+    }

Line range hint 7-141: Consider standardizing Nostr client error handling.

While the changes successfully remove direct NOSTR_CLIENT usage, there's an opportunity to standardize error handling across all Nostr client operations. Consider creating a helper function that encapsulates the common pattern of getting the client and sending events with proper error handling.

Example:

async fn send_nostr_event(event: Event) {
    match get_nostr_client() {
        Ok(client) => {
            if let Err(e) = client.send_event(event).await {
                error!("Failed to send Nostr event: {}", e);
            }
        }
        Err(e) => error!("Failed to get Nostr client: {}", e),
    }
}

This would promote consistency and reduce code duplication across the codebase.

src/util.rs (3)

249-252: Improve error handling in send_dm function

While the change to use get_nostr_client() is good, we're silently ignoring both client initialization and event sending errors. Consider logging these errors for better observability.

 if let Ok(client) = get_nostr_client() {
-    let _ = client.send_event(event).await;
+    if let Err(e) = client.send_event(event).await {
+        tracing::warn!("Failed to send DM: {}", e);
+    }
+} else {
+    tracing::warn!("Failed to get NOSTR client for sending DM");
 }

610-617: Add documentation for the Result type and improve error message

Consider adding documentation for the return type and providing a more descriptive error message.

 /// Getter function with error management for nostr Client
+/// 
+/// # Returns
+/// 
+/// Returns a Result containing:
+/// - Ok(&'static Client): A reference to the initialized NOSTR client
+/// - Err: When the client hasn't been initialized
 pub fn get_nostr_client() -> Result<&'static Client> {
     if let Some(client) = NOSTR_CLIENT.get() {
         Ok(client)
     } else {
-        Err(Error::msg("Client not initialized!"))
+        Err(Error::msg("NOSTR client has not been initialized. Ensure connect_nostr() is called first."))
     }
 }

619-626: Add documentation for the Option type

Consider adding documentation for the return type to improve API clarity.

 /// Getter function with error management for nostr relays
+/// 
+/// # Returns
+/// 
+/// Returns an Option containing:
+/// - Some(HashMap<Url, Relay>): The map of active NOSTR relays when client is initialized
+/// - None: When the client hasn't been initialized
 pub async fn get_nostr_relays() -> Option<HashMap<Url, Relay>> {
     if let Some(client) = NOSTR_CLIENT.get() {
         Some(client.relays().await)
     } else {
         None
     }
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 6fdcf74 and da3beec.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • src/app/add_invoice.rs (1 hunks)
  • src/app/admin_cancel.rs (2 hunks)
  • src/app/admin_settle.rs (2 hunks)
  • src/app/admin_take_dispute.rs (2 hunks)
  • src/app/dispute.rs (2 hunks)
  • src/app/take_buy.rs (2 hunks)
  • src/app/take_sell.rs (1 hunks)
  • src/main.rs (3 hunks)
  • src/scheduler.rs (5 hunks)
  • src/util.rs (4 hunks)
🔇 Additional comments (14)
src/app/take_buy.rs (2)

Line range hint 14-108: LGTM! Robust error handling and clear control flow.

The function implements a well-structured approach to processing buy orders with:

  • Comprehensive validation steps
  • Early returns with proper error messages
  • Clear separation between validation and business logic
  • Proper error logging

Line range hint 81-92: Verify fiat amount and market price validation.

The function delegates critical financial validations to helper functions. Let's verify their implementations to ensure proper bounds checking and market price validation.

✅ Verification successful

Let me gather more information about the market price calculation and validation by checking the implementation of the helper functions used.


Let me check the range order validation and settings implementation since they're crucial for the security analysis.


Let me check the remaining implementation details of market price validation and retries.


Based on the gathered information, I can now provide a final assessment of the security implications.

Fiat amount and market price validation are properly implemented with multiple safeguards

The code implements several security measures:

  • Fiat amount validation enforces min/max range bounds for range orders
  • Market price calculation includes:
    • Currency validation against supported fiat currencies
    • Retry mechanism (4 attempts) for API reliability
    • Fee calculation based on configurable settings
    • Premium consideration in price calculation

The delegated functions implement proper validation and error handling, making the implementation secure for financial transactions.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for the implementation of helper functions
echo "Searching for fiat amount validation implementation..."
rg -A 10 "fn get_fiat_amount_requested"

echo "Searching for market price calculation implementation..."
rg -A 10 "fn get_market_amount_and_fee"

Length of output: 1477


Script:

#!/bin/bash
# Search for market quote and fee calculation implementations
echo "Searching for market quote implementation..."
rg -A 10 "fn get_market_quote"

echo "Searching for fee calculation implementation..."
rg -A 10 "fn get_fee"

# Search for range order validation
echo "Searching for range order validation..."
rg -A 5 "fn is_range_order"

Length of output: 1362


Script:

#!/bin/bash
# Search for Order struct implementation and range validation
echo "Searching for Order range validation..."
ast-grep --pattern 'impl Order {
  $$$
  is_range_order($$$) {
    $$$
  }
  $$$
}'

# Search for settings implementation
echo "Searching for Mostro settings..."
rg -A 5 "struct Settings"

# Check the complete market quote implementation
echo "Searching for complete market quote implementation..."
rg -A 20 "pub async fn get_market_quote"

Length of output: 1557


Script:

#!/bin/bash
# Search for retry implementation and error handling
echo "Searching for yadio request retry implementation..."
rg -A 15 "fn retries_yadio_request"

# Search for Order struct implementation to verify range validation
echo "Searching for Order struct methods..."
rg -A 5 "impl Order"

# Search for constants and settings
echo "Searching for MAX_RETRY constant..."
rg "MAX_RETRY\s*=|const MAX_RETRY"

Length of output: 1135

src/app/admin_settle.rs (1)

4-6: LGTM: Import changes align with the refactoring strategy.

The reorganized imports properly reflect the new approach of using get_nostr_client instead of direct NOSTR_CLIENT access.

src/app/take_sell.rs (1)

Line range hint 126-134: Add validation for market price calculations.

The market price calculation lacks important validations that could prevent edge cases:

  1. No validation of the premium value
  2. No bounds checking on calculated amounts
  3. Silent failure handling for market price fetch errors

Let's verify the market price calculation usage:

Consider adding these validations:

    if order.amount == 0 {
+       // Validate premium is within acceptable range
+       if !is_valid_premium(order.premium) {
+           send_new_order_msg(
+               Some(order.id),
+               Action::InvalidPremium,
+               Some("Premium must be between -10% and +10%".to_string()),
+               &event.sender,
+           ).await;
+           return Ok(());
+       }
        let (new_sats_amount, fee) =
            get_market_amount_and_fee(order.fiat_amount, &order.fiat_code, order.premium).await?;
+       // Validate calculated amounts
+       if new_sats_amount < MIN_SATS || new_sats_amount > MAX_SATS {
+           send_new_order_msg(
+               Some(order.id),
+               Action::InvalidAmount,
+               Some("Calculated amount is out of bounds".to_string()),
+               &event.sender,
+           ).await;
+           return Ok(());
+       }
        order.amount = new_sats_amount;
        order.fee = fee;
    }
src/app/admin_cancel.rs (1)

6-6: LGTM! Good modularization of utilities.

The transition from direct NOSTR_CLIENT usage to utility functions improves modularity and encapsulation.

src/main.rs (2)

25-25: LGTM: Good addition for graceful error handling

The addition of std::process::exit import supports the new error handling strategy, allowing for graceful program termination instead of panic.


82-84: LGTM: Clean subscription implementation

The subscription implementation cleanly uses the local client variable, aligning with the PR's goal of removing unwrap usage while maintaining the same functionality.

src/app/admin_take_dispute.rs (1)

3-3: LGTM! Import changes align with better error handling.

The reorganization of imports to use utility functions improves code modularity and supports the removal of unwrap calls.

src/app/add_invoice.rs (2)

76-82: LGTM! Clean error handling implementation.

The error handling for invalid invoice amounts is well-structured and follows good practices:

  • Clear error communication to the user via send_new_order_msg
  • Early return pattern prevents further processing
  • No unsafe unwraps, aligning with the PR's objective

Line range hint 1-182: Verify consistent error handling across related files.

Since this file interacts with shared utilities like send_new_order_msg, let's verify that error handling is consistent across related files.

✅ Verification successful

Based on the results, I can now generate the final response since I have sufficient information about error handling patterns across the codebase.

Error handling is consistent across related files using send_new_order_msg

The error handling in add_invoice.rs follows the same patterns seen across other files:

  • Early returns with Error::msg for validation failures
  • Consistent use of error! macro for logging
  • Similar error propagation with ? operator
  • Uniform pattern of returning Ok(()) after sending messages

The file maintains consistency with related files in:

  • Handling missing or invalid data (e.g., order IDs, pubkeys)
  • Status validation and transitions
  • Message sending error patterns
  • Database operation error handling
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check error handling patterns in related files

# Find all files using send_new_order_msg
echo "Files using send_new_order_msg:"
rg "send_new_order_msg" -l

# Check error handling patterns in these files
echo "\nError handling patterns:"
rg "Error::msg|error!|Ok\(\(\)|Err\(" --type rust

Length of output: 17633

src/app/dispute.rs (1)

5-5: LGTM: Import change aligns with the codebase-wide refactoring.

The replacement of NOSTR_CLIENT with get_nostr_client import is consistent with the broader initiative to improve error handling across the codebase.

src/scheduler.rs (2)

7-7: LGTM: Import changes align with the PR objectives.

The new imports properly support the transition from direct NOSTR_CLIENT usage to utility functions, improving modularity.

Also applies to: 17-17


140-141: LGTM: Good error handling pattern.

This implementation properly handles and logs errors from both client acquisition and event sending. This pattern could be applied consistently across other similar functions.

src/util.rs (1)

320-323: LGTM! Good error handling improvement

The error handling for order event updates has been improved with proper warning logs for expired orders.

Comment thread src/app/admin_settle.rs Outdated
Comment thread src/app/admin_cancel.rs Outdated
Comment thread src/main.rs
Comment on lines +74 to +80
let client = match get_nostr_client() {
Ok(client) => client,
Err(e) => {
tracing::error!("Ln node error {e}");
exit(0)
}
};

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.

⚠️ Potential issue

Improve error handling implementation

While the shift from unwrap() to proper error handling is good, there are several issues to address:

  1. Using exit code 0 suggests successful termination. Use a non-zero exit code for errors.
  2. The error message could be more descriptive about the connection failure impact.
  3. Consider cleaning up resources before exit.

Suggested improvement:

    let client = match get_nostr_client() {
        Ok(client) => client,
        Err(e) => {
-           tracing::error!("Ln node error {e}");
-           exit(0)
+           tracing::error!("Failed to initialize Nostr client. Cannot proceed: {e}");
+           // Clean up any resources if needed
+           exit(1)
        }
    };

Committable suggestion was skipped due to low confidence.

Comment thread src/app/admin_take_dispute.rs Outdated
Comment on lines +150 to +153

if let Ok(client) = get_nostr_client() {
let _ = client.send_event(event).await;
}

@coderabbitai coderabbitai Bot Oct 30, 2024

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.

⚠️ Potential issue

Improve error handling for critical nostr event publishing.

While removing unwrap() is good, silently ignoring errors in this critical dispute handling path could hide important failures. Consider explicit error handling:

-    if let Ok(client) = get_nostr_client() {
-        let _ = client.send_event(event).await;
-    }
+    let client = get_nostr_client().map_err(|e| {
+        info!("Failed to get nostr client for dispute {}: {}", dispute_id, e);
+        e
+    })?;
+    client.send_event(event).await.map_err(|e| {
+        info!("Failed to send dispute {} status event: {}", dispute_id, e);
+        e
+    })?;

This ensures:

  1. Proper error propagation for both client retrieval and event sending
  2. Logging of failures for debugging
  3. Caller awareness of communication failures in this critical path

Committable suggestion was skipped due to low confidence.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Can you commit this?

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.

@arkanoider, I'm glad you find the suggestion helpful! However, I don't have the ability to commit code directly to the repository. Please feel free to apply the suggested changes to improve the error handling in your code.

((ᵔᵕᵔ)/)

Comment thread src/app/dispute.rs
Comment on lines +176 to +179

if let Ok(client) = get_nostr_client() {
let _ = client.send_event(event).await;
}

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.

💡 Codebase verification

⚠️ Potential issue

Inconsistent error handling patterns found across Nostr client usage

The codebase shows multiple instances of Nostr client usage with varying error handling approaches:

  • Most handlers use if let Ok(client) with discarded results (let _)
  • One instance in src/util.rs includes warning logs for failed events
  • src/scheduler.rs has a case with proper success logging
  • src/main.rs shows error logging for client initialization

Given this inconsistency, the original review suggestion for improved error handling and logging remains valid. The changes should be applied consistently across:

  • src/app/dispute.rs
  • src/app/admin_settle.rs
  • src/app/admin_cancel.rs
  • src/app/admin_take_dispute.rs
  • src/util.rs
  • src/scheduler.rs
🔗 Analysis chain

Improve error handling and logging for Nostr client operations.

While the removal of unwrap improves safety, the current implementation silently ignores potential failures in both client retrieval and event sending. This could lead to disputes not being properly published without any indication of failure.

Consider this improved implementation:

-    if let Ok(client) = get_nostr_client() {
-        let _ = client.send_event(event).await;
-    }
+    let client = get_nostr_client().map_err(|e| {
+        error!("Failed to get Nostr client for dispute {}: {}", dispute.id, e);
+        e
+    })?;
+    
+    client.send_event(event).await.map_err(|e| {
+        error!("Failed to publish dispute event {}: {}", dispute.id, e);
+        Error::msg(format!("Failed to publish dispute: {}", e))
+    })?;

This change:

  1. Propagates errors up the call stack
  2. Adds proper error logging for both failure cases
  3. Includes context (dispute ID) in error messages

Let's verify the error handling pattern in other files:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check error handling patterns in similar Nostr client usage
rg -A 3 "get_nostr_client\(\)" --type rust

Length of output: 2375

arkanoider and others added 4 commits October 30, 2024 16:06
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

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

🧹 Outside diff range and nitpick comments (1)
src/main.rs (1)

77-80: Consider implementing resource cleanup.

Before exiting on error, it would be beneficial to properly clean up any resources that were initialized earlier in the main function, such as:

  • Closing the database connection pool
  • Cleaning up any temporary files
  • Shutting down any running tasks

Example implementation:

async fn cleanup(pool: Option<Pool>) {
    if let Some(pool) = pool {
        // Close database connection
        pool.close().await;
    }
    // Add other cleanup tasks
}

Then use it in the error handler:

     Err(e) => {
         tracing::error!("Failed to initialize Nostr client. Cannot proceed: {e}");
-        // Clean up any resources if needed
+        cleanup(Some(pool)).await;
         exit(1)
     }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between da3beec and e33ee8e.

📒 Files selected for processing (4)
  • src/app/admin_cancel.rs (2 hunks)
  • src/app/admin_settle.rs (2 hunks)
  • src/app/admin_take_dispute.rs (2 hunks)
  • src/main.rs (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/app/admin_settle.rs
  • src/app/admin_take_dispute.rs
🔇 Additional comments (3)
src/app/admin_cancel.rs (2)

6-6: LGTM! Import changes align with modular design.

The updated imports properly reflect the transition from direct NOSTR_CLIENT usage to utility functions, improving modularity and error handling.


111-118: LGTM! Robust error handling implementation.

The error handling implementation is thorough and follows best practices:

  • Handles both client retrieval and event sending errors
  • Includes appropriate error logging
  • Uses clear match patterns for better error handling
src/main.rs (1)

74-81: LGTM! Error handling has been improved.

The implementation now properly handles errors from get_nostr_client() with:

  • Appropriate error logging with descriptive message
  • Non-zero exit code for error cases
  • Clear error flow

Comment thread src/util.rs
@@ -28,6 +28,7 @@ use tokio::sync::mpsc::channel;
use tokio::sync::Mutex;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@coderabbitai Generate unit testing code for this file

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.

@grunch, here are unit tests for src/util.rs. You can add these tests to a tests module within your src/util.rs file or place them in a separate file like tests/util_tests.rs.

#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::Once;
    use std::collections::HashMap;
    use anyhow::Result;
    use nostr_sdk::prelude::*;
    use uuid::Uuid;

    // Setup function to initialize common settings or data before tests
    static INIT: Once = Once::new();

    fn initialize() {
        INIT.call_once(|| {
            // Any initialization code goes here
        });
    }

    #[test]
    fn test_bytes_to_string() {
        initialize();
        let bytes = vec![0xde, 0xad, 0xbe, 0xef];
        let result = bytes_to_string(&bytes);
        assert_eq!(result, "deadbeef");
    }

    #[test]
    fn test_get_fee() {
        initialize();
        // Mock the Settings::get_mostro() function
        let amount = 100000;
        let mostro_settings = Settings {
            fee: 0.02, // 2% fee
            ..Default::default()
        };
        // Assuming you have a way to set the settings for testing
        Settings::set_mostro(mostro_settings);
        let fee = get_fee(amount);
        assert_eq!(fee, ((mostro_settings.fee * amount as f64) / 2.0).round() as i64);
    }

    #[tokio::test]
    async fn test_get_bitcoin_price() {
        initialize();
        // Mock the BitcoinPriceManager to return a fixed price
        BitcoinPriceManager::set_price("USD", 20000.0);
        let price = get_bitcoin_price("USD").unwrap();
        assert_eq!(price, 20000.0);
    }

    #[tokio::test]
    async fn test_get_market_quote() {
        initialize();
        // Mock the get_market_quote function's external API call
        let fiat_amount = 1000; // $1000
        let fiat_code = "USD";
        let premium = 0;

        // Assuming you have a way to mock the API response
        let sats = get_market_quote(&fiat_amount, fiat_code, premium).await.unwrap();
        // Check that sats amount is calculated correctly
        assert!(sats > 0);
    }

    #[test]
    fn test_get_expiration_date() {
        initialize();
        let expire = Some(1_700_000_000_i64); // An example future timestamp
        let expiration = get_expiration_date(expire);
        // Verify that the expiration date is set correctly
        assert_eq!(expiration, expire.unwrap());
    }

    #[tokio::test]
    async fn test_get_nostr_client_failure() {
        initialize();
        // Assuming NOSTR_CLIENT is not initialized
        let client = get_nostr_client();
        assert!(client.is_err());
    }

    #[tokio::test]
    async fn test_get_nostr_client_success() {
        initialize();
        // Mock NOSTR_CLIENT initialization
        let keys = Keys::generate();
        let client = Client::new(&keys);
        NOSTR_CLIENT.set(client).unwrap();
        let client_result = get_nostr_client();
        assert!(client_result.is_ok());
    }

    #[test]
    fn test_bytes_to_string_empty() {
        initialize();
        let bytes: Vec<u8> = vec![];
        let result = bytes_to_string(&bytes);
        assert_eq!(result, "");
    }

    #[tokio::test]
    async fn test_send_dm() {
        initialize();
        // Mock the send_dm function
        let receiver_pubkey = Keys::generate().public_key();
        let content = "Test message".to_string();
        let result = send_dm(&receiver_pubkey, content).await;
        assert!(result.is_ok());
    }

    #[tokio::test]
    async fn test_get_fiat_amount_requested() {
        initialize();
        let order = Order {
            min_amount: Some(500),
            max_amount: Some(1500),
            fiat_amount: 1000,
            ..Default::default()
        };
        let msg = Message {
            // Assuming get_amount() returns 1000
            ..Default::default()
        };
        let amount = get_fiat_amount_requested(&order, &msg);
        assert_eq!(amount, Some(1000));
    }
}

These tests cover key functions such as bytes_to_string, get_fee, get_bitcoin_price, get_market_quote, get_expiration_date, and handling of the Nostr client. Remember to adjust the mocks and initializations according to your actual codebase and testing framework.

@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

🧹 Outside diff range and nitpick comments (1)
src/util.rs (1)

628-723: LGTM! Comprehensive test suite with room for enhancement.

The test suite provides good coverage of the new functionality. Consider adding these test cases:

  1. Test get_nostr_relays with both initialized and uninitialized client
  2. Test get_fiat_amount_requested with edge cases (amounts at boundaries)
  3. Test send_dm with failed client initialization
#[tokio::test]
async fn test_get_nostr_relays_uninitialized() {
    initialize();
    let relays = get_nostr_relays().await;
    assert!(relays.is_none());
}

#[tokio::test]
async fn test_get_fiat_amount_requested_edge_cases() {
    initialize();
    let order = Order {
        min_amount: Some(500),
        max_amount: Some(2000),
        ..Default::default()
    };
    
    // Test boundary values
    let msg_min = Message::Order(MessageKind::new(
        None,
        Action::TakeSell,
        Some(Content::Amount(500)),
    ));
    assert_eq!(get_fiat_amount_requested(&order, &msg_min), Some(500));
    
    let msg_max = Message::Order(MessageKind::new(
        None,
        Action::TakeSell,
        Some(Content::Amount(2000)),
    ));
    assert_eq!(get_fiat_amount_requested(&order, &msg_max), Some(2000));
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between e33ee8e and 92a371f.

📒 Files selected for processing (1)
  • src/util.rs (5 hunks)
🔇 Additional comments (2)
src/util.rs (2)

319-322: LGTM! Improved error handling for order event updates.

The change properly handles the case where the Nostr client is not initialized and logs a warning when the order is expired.


610-617: LGTM! Well-designed error handling for Nostr client access.

The function properly encapsulates the NOSTR_CLIENT access and provides meaningful error messages. This addresses the concerns raised in previous reviews about direct unwrapping.

Comment thread src/util.rs
Comment on lines +619 to +626
/// Getter function with error management for nostr relays
pub async fn get_nostr_relays() -> Option<HashMap<Url, Relay>> {
if let Some(client) = NOSTR_CLIENT.get() {
Some(client.relays().await)
} else {
None
}
}

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.

🛠️ Refactor suggestion

Consider adding error context for relay retrieval.

While the Option type is appropriate, consider returning a Result with context about why the relays couldn't be retrieved, which could help with debugging.

-pub async fn get_nostr_relays() -> Option<HashMap<Url, Relay>> {
+pub async fn get_nostr_relays() -> Result<HashMap<Url, Relay>> {
     if let Some(client) = NOSTR_CLIENT.get() {
-        Some(client.relays().await)
+        Ok(client.relays().await)
     } else {
-        None
+        Err(Error::msg("Cannot get relays: Nostr client not initialized"))
     }
 }

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

Comment thread src/util.rs Outdated

@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

🧹 Outside diff range and nitpick comments (2)
src/app/admin_add_solver.rs (1)

Line range hint 1-52: Overall structure looks good with room for improvement

The function has good security checks, proper error logging, and clean database operations. The addition of sender_keys aligns with the broader changes for improved client management.

A few observations:

  1. The early returns with proper error handling are well implemented
  2. The sender verification provides good security
  3. Database operations are properly logged

Consider creating a dedicated error type for this module to provide more specific error handling instead of using the generic anyhow::Result. This would make error handling more explicit and maintainable.

src/util.rs (1)

322-325: Enhance error logging in update_order_event.

The warning message could provide more context about why the event failed to send.

     if let Ok(client) = get_nostr_client() {
-        if client.send_event(event).await.is_err() {
-            tracing::warn!("order id : {} is expired", order_updated.id)
+        if let Err(e) = client.send_event(event).await {
+            tracing::warn!("Failed to send event for order {}: {}", order_updated.id, e);
         }
     }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 92a371f and 29f6449.

📒 Files selected for processing (5)
  • src/app/admin_add_solver.rs (1 hunks)
  • src/app/admin_cancel.rs (5 hunks)
  • src/app/admin_settle.rs (3 hunks)
  • src/app/admin_take_dispute.rs (4 hunks)
  • src/util.rs (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/app/admin_cancel.rs
  • src/app/admin_settle.rs
  • src/app/admin_take_dispute.rs
🔇 Additional comments (2)
src/util.rs (2)

241-254: LGTM! Improved key management and error handling.

The changes properly implement the suggested improvements:

  1. Accepts sender_keys parameter instead of generating new keys
  2. Uses get_nostr_client() for better error handling

633-729: LGTM! Comprehensive test coverage.

The test suite is well-structured and covers:

  • Basic utility functions
  • Network operations
  • Nostr client operations
  • Message handling
  • Business logic

Each test includes proper setup and covers both success and failure cases.

Comment on lines +49 to +50
let sender_keys = crate::util::get_keys().unwrap();
send_dm(&event.sender, sender_keys, message).await?;

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.

⚠️ Potential issue

Replace unwrap() with proper error handling

The use of unwrap() on get_keys() could cause a panic if key retrieval fails. This contradicts the PR's goal of improving error handling.

Consider propagating the error instead:

-    let sender_keys = crate::util::get_keys().unwrap();
+    let sender_keys = crate::util::get_keys()?;
📝 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 sender_keys = crate::util::get_keys().unwrap();
send_dm(&event.sender, sender_keys, message).await?;
let sender_keys = crate::util::get_keys()?;
send_dm(&event.sender, sender_keys, message).await?;

Comment thread src/util.rs
Comment on lines +615 to +631
/// Getter function with error management for nostr Client
pub fn get_nostr_client() -> Result<&'static Client> {
if let Some(client) = NOSTR_CLIENT.get() {
Ok(client)
} else {
Err(Error::msg("Client not initialized!"))
}
}

/// Getter function with error management for nostr relays
pub async fn get_nostr_relays() -> Option<HashMap<Url, Relay>> {
if let Some(client) = NOSTR_CLIENT.get() {
Some(client.relays().await)
} else {
None
}
}

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.

🛠️ Refactor suggestion

Consider using Result for consistent error handling.

The get_nostr_client() function properly returns a Result with error context, but get_nostr_relays() returns an Option. For consistency and better error handling, consider using Result for both functions.

-pub async fn get_nostr_relays() -> Option<HashMap<Url, Relay>> {
+pub async fn get_nostr_relays() -> Result<HashMap<Url, Relay>> {
     if let Some(client) = NOSTR_CLIENT.get() {
-        Some(client.relays().await)
+        Ok(client.relays().await)
     } else {
-        None
+        Err(Error::msg("Cannot get relays: Nostr client not initialized"))
     }
 }
📝 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
/// Getter function with error management for nostr Client
pub fn get_nostr_client() -> Result<&'static Client> {
if let Some(client) = NOSTR_CLIENT.get() {
Ok(client)
} else {
Err(Error::msg("Client not initialized!"))
}
}
/// Getter function with error management for nostr relays
pub async fn get_nostr_relays() -> Option<HashMap<Url, Relay>> {
if let Some(client) = NOSTR_CLIENT.get() {
Some(client.relays().await)
} else {
None
}
}
/// Getter function with error management for nostr Client
pub fn get_nostr_client() -> Result<&'static Client> {
if let Some(client) = NOSTR_CLIENT.get() {
Ok(client)
} else {
Err(Error::msg("Client not initialized!"))
}
}
/// Getter function with error management for nostr relays
pub async fn get_nostr_relays() -> Result<HashMap<Url, Relay>> {
if let Some(client) = NOSTR_CLIENT.get() {
Ok(client.relays().await)
} else {
Err(Error::msg("Cannot get relays: Nostr client not initialized"))
}
}

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