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

fix(sdk)!: create channel error due to empty address #2317

Open
wants to merge 2 commits into
base: v1.6-dev-ugly
Choose a base branch
from

Conversation

shumkov
Copy link
Member

@shumkov shumkov commented Nov 8, 2024

Issue being fixed or feature implemented

New SDK 1.5.1 fails on android with:

rs_dapi_client::dapi_client: request failed error=ExecutionError { inner: Transport(Grpc(Status { code: FailedPrecondition, message: "Channel creation failed: transport error", source: None })), retries: 0, address: Some(Address { ban_count: 0, banned_until: None, uri: https://52.13.132.146:1443/ }) }

What was done?

  • Do not allow to create invalid Address
  • Return invalid argument instead of failed precondition in TransportClient::new_with_uri
  • AddressList::add_uri is deprecated

How Has This Been Tested?

Running tests

Breaking Changes

  • From<Uri> for Address replaced with TryFrom<Uri> for Address
  • From<&str> for AddressList replaced with FromStr for AddressList
  • FromIterator<Uri> for AddressList replaced with FromIterator<Address> for AddressList

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced error handling for address parsing and gRPC channel creation.
    • Transitioned from URI strings to a structured Address type for improved type safety.
  • Bug Fixes

    • Improved error reporting for address-related operations and retry mechanisms.
  • Documentation

    • Updated examples and comments to reflect the new Address type usage.
  • Tests

    • Refined test cases to align with the new address handling and error management.

@shumkov shumkov added this to the v1.6.0 milestone Nov 8, 2024
Copy link
Contributor

coderabbitai bot commented Nov 8, 2024

Walkthrough

The changes in this pull request primarily focus on enhancing the Address and AddressList structures within the rs-dapi-client module. Key modifications include transitioning from From to TryFrom for Address, updating error handling to improve clarity, and refining the conversion processes. The EvoNode struct and related test cases have also been adapted to utilize the new Address type. Additionally, adjustments in the gRPC transport layer have altered error reporting formats. Overall, these changes improve type safety and error handling across the affected components.

Changes

File Path Change Summary
packages/rs-dapi-client/src/address_list.rs - Modified FromStr implementation for Address to TryFrom<Uri> with improved error handling.
- Updated FromStr for AddressList to parse Address instead of Uri.
- Changed InvalidAddressUri to accept String.
- Updated from_iter method to accept Address.
packages/rs-dapi-client/src/transport/grpc.rs - Changed error handling in with_uri and with_uri_and_settings methods from failed_precondition to invalid_argument.
- Updated error message formatting.
packages/rs-sdk/examples/read_contract.rs - Updated import statement to include Address.
- Changed URI construction to use Address directly instead of http::Uri.
packages/rs-sdk/src/platform/types/evonode.rs - Updated example usage to instantiate EvoNode with Address instead of http::Uri.
packages/rs-sdk/src/sync.rs - Expanded AsyncError enum to include Generic(String).
- Updated logic in block_on and retry functions for improved error handling and retry mechanisms.
packages/rs-sdk/tests/fetch/config.rs - Updated import statement to include Address.
- Changed variable type from String to Address in address_list method.
packages/rs-sdk/tests/fetch/evonode.rs - Updated tests to replace http::Uri with Address.
- Modified error handling in tests to reflect the change to Address.

Possibly related PRs

Suggested labels

rs-sdk, enhancement

Suggested reviewers

  • QuantumExplorer
  • lklimek

🐰 In the land of code where rabbits play,
New structures hop in, brightening the day.
From strings to addresses, we leap with delight,
Error handling's clearer, everything feels right!
With each little change, our code's a bit spry,
A hop and a skip, watch our features fly! 🐇✨


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.

@shumkov shumkov changed the title fix(sdk): create channel error due to empty address fix(sdk)!: create channel error due to empty address Nov 8, 2024
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 and nitpick comments (10)
packages/rs-sdk/tests/fetch/evonode.rs (2)

65-69: Add test coverage for invalid address creation

While this test covers the connection refusal scenario, given the PR's focus on preventing invalid Address creation, consider adding explicit test cases for:

  1. Invalid URI to Address conversion scenarios
  2. Empty address cases that were causing issues on Android

Example addition:

#[test]
fn test_invalid_address_creation() {
    // Test empty host
    let result = Address::try_from(Uri::from_static("http:///path"));
    assert!(result.is_err());

    // Test missing scheme
    let result = "localhost:3000".parse::<Address>();
    assert!(result.is_err());
}

Based on the code inspection, I can now generate the final response:

Add unit tests for breaking type conversion changes

The code shows the implementation of the breaking changes but lacks dedicated unit tests. Please add tests for:

  • TryFrom<Uri> for Address to verify:
    • Success case with valid URI containing host
    • Error case with URI missing host
  • FromStr for AddressList to verify:
    • Success case with comma-separated valid addresses
    • Error case with invalid URI strings
  • FromIterator<Address> for AddressList to verify:
    • Collection creation from iterator of valid addresses

These tests should be added in a new mod tests block in packages/rs-dapi-client/src/address_list.rs.

🔗 Analysis chain

Line range hint 14-72: Enhance test coverage for breaking changes

The PR introduces several breaking changes in type conversions that should be explicitly tested:

  1. Uri to Address: FromTryFrom
  2. &str to AddressList: FromFromStr
  3. Uri to AddressList: FromIterator<Uri>FromIterator<Address>

Let's check if these conversions are tested elsewhere:

Consider adding comprehensive test coverage for these breaking changes if not already covered elsewhere.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for existing tests of the breaking changes
rg -t rust "test.*(?:try_)?from.*uri|test.*address.*list.*from|test.*from.*str" ./packages/rs-dapi-client/src/

Length of output: 112


Script:

#!/bin/bash
# Let's search for test files and type conversion tests with a broader pattern
rg -t rust "(?m)^#\[test\].*\n.*(?:try_)?from|test.*address.*list|test.*uri.*address" --context 2

Length of output: 291


Script:

#!/bin/bash
# Let's also check for the actual type conversion implementations
ast-grep --pattern 'impl $_ for Address {
  $$$
}'

Length of output: 3255


Script:

#!/bin/bash
# And check for AddressList implementations
ast-grep --pattern 'impl $_ for AddressList {
  $$$
}'

Length of output: 2602


Script:

#!/bin/bash
# Let's also check all test files in the repository
fd "test.*\.rs$"

Length of output: 2278


Script:

#!/bin/bash
# Let's check for existing tests in address_list.rs
rg -U "(?s)#\[test\].*?\}" packages/rs-dapi-client/src/address_list.rs

Length of output: 72


Script:

#!/bin/bash
# Let's also check the test file where the review comment was made
rg -U "(?s)#\[test\].*?\}" packages/rs-sdk/tests/fetch/evonode.rs

Length of output: 67


Script:

#!/bin/bash
# Let's try a simpler approach to find tests
rg "#\[test\]" packages/rs-dapi-client/src/address_list.rs -A 1

Length of output: 65


Script:

#!/bin/bash
# Let's also check if there's a dedicated test module
rg "mod tests" packages/rs-dapi-client/src/address_list.rs -A 2

Length of output: 65


Script:

#!/bin/bash
# And check the actual content of address_list.rs
cat packages/rs-dapi-client/src/address_list.rs

Length of output: 6806

packages/rs-sdk/examples/read_contract.rs (2)

83-85: Consider improving error handling in this example

While the implementation correctly uses the new Address type, using expect() in an example might not demonstrate best practices for error handling.

Consider using proper error handling to make the example more educational:

-    let address = Address::from_str(&format!(
-        "https://{}:{}",
-        config.server_address, config.platform_port
-    ))
-    .expect("parse uri");
+    let address = Address::from_str(&format!(
+        "https://{}:{}",
+        config.server_address, config.platform_port
+    ))
+    .map_err(|e| format!("Failed to parse address: {}", e))?;

83-90: Add documentation about Address usage

Since this is an example file and the PR introduces breaking changes in how addresses are handled, it would be helpful to add comments explaining the new Address type usage and why it's preferred over the previous URI approach.

Add a comment block before the address creation:

+    // Create a new Address instance using HTTPS scheme
+    // Note: We use Address instead of URI for better validation and type safety
+    // The Address type ensures that only valid addresses are created
     let address = Address::from_str(&format!(
packages/rs-sdk/src/platform/types/evonode.rs (1)

Line range hint 82-86: Consider enhancing error handling specificity.

Given the PR's focus on improving error handling (changing from failed precondition to invalid argument errors), consider making the error handling in execute_transport more specific. Instead of using a generic error conversion with map_err(TransportError::Grpc), you could map specific gRPC errors to more descriptive transport errors.

Here's a suggested improvement:

             let response = client
                 .get_status(grpc_request)
-                .map_err(TransportError::Grpc)
+                .map_err(|e| match e.code() {
+                    tonic::Code::InvalidArgument => TransportError::InvalidAddress(e.message().to_string()),
+                    _ => TransportError::Grpc(e),
+                })
                 .map_ok(|response| response.into_inner())
                 .await;
packages/rs-sdk/tests/fetch/config.rs (1)

134-136: Consider more robust error handling for address parsing.

While the implementation correctly uses the new Address type and parsing mechanism, the use of expect() could make debugging harder if the configuration is invalid. Consider either:

  1. Propagating the error to provide more context about the invalid configuration, or
  2. Adding a more descriptive expect message that includes the actual address being parsed

Example improvement:

-        let address: Address = format!("{}://{}:{}", scheme, self.platform_host, self.platform_port).parse().expect("valid address");
+        let addr_str = format!("{}://{}:{}", scheme, self.platform_host, self.platform_port);
+        let address: Address = addr_str.parse().expect(&format!("Invalid address: {}", addr_str));
packages/rs-dapi-client/src/address_list.rs (3)

28-29: Consider preserving original error types for better error handling

Converting the error into a String with e.to_string() may lose valuable error information. Consider preserving the original error type to retain detailed context for debugging purposes.

You could modify the InvalidAddressUri variant to include the original error:

- InvalidAddressUri(String),
+ InvalidAddressUri(http::uri::InvalidUri),

And adjust the error mapping accordingly:

.map_err(|e| AddressListError::InvalidAddressUri(e))

55-57: Include URI in error message for better debugging

Including the problematic URI in the error message can aid in diagnosing issues by providing more context.

Apply this change:

if value.host().is_none() {
    return Err(AddressListError::InvalidAddressUri(
-       "uri must contain host".to_string()
+       format!("URI '{}' must contain host", value)
    ));
}

228-232: Rename variable uri to address for clarity

In the FromIterator<Address> implementation, the variable uri represents an Address. Renaming it to address would improve code readability and reduce confusion.

Apply this diff:

fn from_iter<T: IntoIterator<Item = Address>>(iter: T) -> Self {
    let mut address_list = Self::new();
-    for uri in iter {
-        address_list.add(uri);
+    for address in iter {
+        address_list.add(address);
    }
    address_list
}
packages/rs-sdk/src/sync.rs (1)

200-200: Fix typographical error in comment

There's a misspelling in the comment: "preceeding" should be "preceding".

Apply this diff to correct the typo:

-                    // requests sent in all preceeding attempts; user expects `settings.retries +1`
+                    // requests sent in all preceding attempts; user expects `settings.retries +1`
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 48cca1a and b8a887e.

📒 Files selected for processing (7)
  • packages/rs-dapi-client/src/address_list.rs (5 hunks)
  • packages/rs-dapi-client/src/transport/grpc.rs (1 hunks)
  • packages/rs-sdk/examples/read_contract.rs (2 hunks)
  • packages/rs-sdk/src/platform/types/evonode.rs (1 hunks)
  • packages/rs-sdk/src/sync.rs (2 hunks)
  • packages/rs-sdk/tests/fetch/config.rs (2 hunks)
  • packages/rs-sdk/tests/fetch/evonode.rs (2 hunks)
🔇 Additional comments (6)
packages/rs-sdk/tests/fetch/evonode.rs (2)

11-11: LGTM: Address import aligns with PR objectives

The addition of rs_dapi_client::Address import supports the transition from Uri to Address type as outlined in the PR objectives.


Line range hint 14-61: LGTM: Robust test implementation with proper error handling

The test case properly implements the new Address type while maintaining comprehensive error handling, timeouts, and assertions. The logging is informative and will help with debugging.

packages/rs-sdk/examples/read_contract.rs (1)

6-6: LGTM: Import statement updated correctly

The addition of Address to the import statement aligns with the PR's objective to handle addresses more safely.

packages/rs-sdk/tests/fetch/config.rs (1)

11-11: LGTM: Import aligns with new Address type usage.

The addition of the Address import is consistent with the PR's objective to prevent invalid address instances.

packages/rs-dapi-client/src/address_list.rs (1)

104-104: Ensure error messages provide sufficient context

By changing InvalidAddressUri to accept a String, ensure that error messages contain enough detail to help debug issues, especially if original error types are not preserved.

Consider reviewing all error messages to confirm they provide adequate information.

packages/rs-sdk/src/sync.rs (1)

344-344: LGTM!

The code change in the test function looks good.

Comment on lines +28 to +29
/// let address = "http://127.0.0.1:1".parse().expect("valid address");
/// let node = EvoNode::new(address);
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

Example code should use try_from instead of expect for proper error handling

The verification reveals that the example code uses expect() which can cause runtime panics, while the codebase has proper error handling patterns using TryFrom trait. The example should be updated to demonstrate proper error handling:

  • Current example:
let address = "http://127.0.0.1:1".parse().expect("valid address");
  • Should be changed to:
let address = Address::try_from("http://127.0.0.1:1".parse()?)?;

This aligns with:

  1. The codebase's error handling patterns using TryFrom trait for Address parsing
  2. The PR's objective of fixing SDK issues related to invalid addresses
  3. Best practices of propagating errors rather than panicking
🔗 Analysis chain

Example code correctly demonstrates the new Address parsing approach.

The updated example aligns with the breaking changes, showing the transition from Uri to direct Address parsing. The error handling with expect() provides clear feedback for invalid addresses.

Let's verify the error handling for invalid addresses:


Address parsing error handling needs improvement across the codebase

The verification reveals several instances where expect() is used with address parsing, which can lead to runtime panics:

  • packages/rs-sdk/src/sync.rs: Using expect() in address parsing
  • packages/rs-sdk/tests/fetch/config.rs: Using expect() in address formatting and parsing
  • packages/rs-sdk/src/mock/sdk.rs: Using expect() in mock address creation
  • packages/rs-dapi-client/src/executor.rs: Using expect() in mock address creation

Let's verify if there are better error handling patterns available:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that invalid addresses are properly handled across the codebase
# Test: Search for error handling patterns around address parsing
rg -A 3 'parse\(\).*expect\(' 

# Test: Look for other address validation patterns
ast-grep --pattern 'TryFrom<Uri> for Address {
  $$$
  fn try_from($_: Uri) -> Result<Self, Self::Error> {
    $$$
  }
}'

Length of output: 1983


Script:

#!/bin/bash
# Check for proper error handling patterns using Result or Option
rg -A 3 'parse\(\)\?|try_from|TryFrom'

# Look for Address struct implementation
ast-grep --pattern 'impl Address {
  $$$
}'

Length of output: 441675

Comment on lines +47 to +48
Err(e) => Err(dapi_grpc::tonic::Status::invalid_argument(format!(
"channel creation failed: {}",
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 consistency in error handling across methods.

The change from failed_precondition to invalid_argument is appropriate for invalid address scenarios. However, there are some inconsistencies to address:

  1. The same error scenario in other methods still uses failed_precondition:

    • PlatformGrpcClient::with_uri_and_settings
    • CoreGrpcClient::with_uri
    • CoreGrpcClient::with_uri_and_settings
  2. The error message format differs from other occurrences ("channel creation failed" vs "Channel creation failed").

Apply these changes to maintain consistency:

- Err(e) => Err(dapi_grpc::tonic::Status::invalid_argument(format!(
-     "channel creation failed: {}",
+ Err(e) => Err(dapi_grpc::tonic::Status::invalid_argument(format!(
+     "Channel creation failed: {}",
      e
  ))),

Also update the error type in other methods:

- Err(dapi_grpc::tonic::Status::failed_precondition(
+ Err(dapi_grpc::tonic::Status::invalid_argument(

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

🧹 Outside diff range and nitpick comments (1)
packages/rs-dapi-client/src/address_list.rs (1)

223-232: Consider handling whitespace in address parsing

The implementation is correct, but consider trimming whitespace for better usability:

     fn from_str(s: &str) -> Result<Self, Self::Err> {
         let uri_list: Vec<Address> = s
             .split(',')
+            .map(str::trim)
+            .filter(|s| !s.is_empty())
             .map(Address::from_str)
             .collect::<Result<_, _>>()?;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between b8a887e and cea6ca9.

📒 Files selected for processing (1)
  • packages/rs-dapi-client/src/address_list.rs (6 hunks)
🔇 Additional comments (4)
packages/rs-dapi-client/src/address_list.rs (4)

28-29: LGTM: Improved error handling in FromStr implementation

The change improves error handling by providing more descriptive error messages and properly chaining the conversions through try_from.


104-104: LGTM: Simplified error variant

The change to use String for InvalidAddressUri makes the error handling more flexible and readable.


236-243: LGTM: Safe FromIterator implementation

The change from FromIterator<Uri> to FromIterator<Address> properly aligns with the new validation approach, ensuring all addresses are valid.


51-63: LGTM: Essential validation in TryFrom implementation

The change from From to TryFrom with host validation is crucial for preventing invalid Address instances. This directly addresses the root cause of the channel creation errors.

Let's verify that there are no direct From<Uri> usages that need to be updated:

✅ Verification successful

LGTM: No direct From usages found

The codebase only contains a TryFrom<Uri> implementation for Address, with no direct From<Uri> usages. The change from From to TryFrom is safe since there are no direct .into() calls or From trait implementations that need to be updated.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any remaining From<Uri> implementations or direct .into() calls
rg -l "impl\s+From<Uri>" 
rg "\.into\(\)\s*//.*Uri.*Address"

Length of output: 63


Script:

#!/bin/bash
# Let's search more broadly for potential .into() calls and Uri to Address conversions
rg -A 2 "Uri.*into.*Address|Address.*from.*Uri"

# Also search for any direct Uri to Address conversions in tests
rg -A 2 "Uri.*Address" "**/*test*.rs"

# Search for any From trait usage with Uri
ast-grep --pattern 'impl From<$_> for $_'

Length of output: 486521


Script:

#!/bin/bash
# Search for any direct Uri to Address conversions or .into() calls
rg -A 2 "Uri.*into.*Address|Address.*from.*Uri|impl From<Uri> for Address"

# Search for any Uri type usage in the codebase
rg -A 2 "Uri.*Address"

Length of output: 537

Comment on lines +175 to +180
#[deprecated]
// TODO: Remove in favor of add
/// Add a node [Address] to [AddressList] by [Uri].
/// Returns false if the address is already in the list.
pub fn add_uri(&mut self, uri: Uri) -> bool {
self.addresses.insert(uri.into())
self.addresses.insert(uri.try_into().expect("valid uri"))
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

Consider safer implementation for deprecated method

While it's good that this method is deprecated, it currently uses expect() which could cause panics. Consider implementing a safer version that returns a Result while the method is still available:

-    pub fn add_uri(&mut self, uri: Uri) -> bool {
-        self.addresses.insert(uri.try_into().expect("valid uri"))
+    pub fn add_uri(&mut self, uri: Uri) -> Result<bool, AddressListError> {
+        let address = Address::try_from(uri)?;
+        Ok(self.addresses.insert(address))

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant