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

feat(sdk)!: ban addresses failed in sdk #2351

Open
wants to merge 11 commits into
base: v1.7-dev
Choose a base branch
from

Conversation

shumkov
Copy link
Member

@shumkov shumkov commented Nov 26, 2024

Issue being fixed or feature implemented

We should ban failed nodes in case if SDK verification logic failed too. Currently, it's happening only on DAPI client (transport) level.

What was done?

  • Refactored AddressList to remove RwLock which doesn't fit greatly for mixed async and sync code.
  • Ban and unban nodes in SDK retry
  • Removed unused AddressList.available
  • Removed AddressList.into_iter which shouldn't be a case.
  • Ignore missing address in the list on ban and unban. The background process (SML) could remove/add addresses to sync with masternode list. Which is fine.

How Has This Been Tested?

With existing tests

Breaking Changes

  • AddressList.into_iter and AddressList.available removed

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 address management with improved concurrency support in the AddressList.
    • Simplified DapiClient structure for more straightforward address list handling.
    • Introduced a new function for banning failed addresses in the DAPI client.
    • Updated retry logic to utilize the address list across various components.
  • Bug Fixes

    • Streamlined error handling and address management in the retry mechanism.
  • Documentation

    • Updated comments and documentation to reflect changes in address management and retry mechanisms.
  • Chores

    • Removed unused imports to clean up the codebase.
    • Adjusted a script to use a static value for configuration instead of a dynamic calculation.

Copy link
Contributor

coderabbitai bot commented Nov 26, 2024

Walkthrough

The pull request introduces significant modifications to the rs-dapi-client and rs-sdk packages. Key changes include the addition of a new development dependency tokio, updates to the AddressList structure for improved concurrency, and alterations to the DapiClient to simplify its ownership model. Various methods across the SDK have been adjusted to utilize the new address list handling, particularly in retry mechanisms. Additionally, several methods and structures related to address management have been restructured for clarity and efficiency.

Changes

File Path Change Summary
packages/rs-dapi-client/Cargo.toml Added new [dev-dependencies] section with tokio = { version = "1.40", features = ["macros"] }.
packages/rs-dapi-client/src/address_list.rs Simplified Address struct; added AddressStatus struct; updated AddressList to use Arc<RwLock<HashMap<Address, AddressStatus>>; modified methods for address management.
packages/rs-dapi-client/src/dapi_client.rs Changed address_list from Arc<RwLock<AddressList>> to AddressList; updated related methods for direct access.
packages/rs-drive-proof-verifier/src/error.rs Removed unused import: GroveDBProof.
packages/rs-sdk/src/platform/fetch.rs Updated fetch_with_metadata_and_proof method to use sdk.address_list() in the retry call.
packages/rs-sdk/src/platform/fetch_many.rs Modified fetch_many_with_metadata_and_proof and fetch_many methods to use sdk.address_list() in retry calls.
packages/rs-sdk/src/platform/fetch_unproved.rs Updated fetch_unproved_with_settings to use sdk.address_list() in the retry call.
packages/rs-sdk/src/platform/transition/broadcast.rs Modified broadcast and wait_for_response methods to include sdk.address_list() in retry calls.
packages/rs-sdk/src/sdk.rs Changed address_list method return type from Result<AddressList, String> to &AddressList.
packages/rs-sdk/src/sync.rs Updated retry function to accept &AddressList as a new parameter for address management logic.
packages/rs-sdk/tests/fetch/evonode.rs Altered iteration over addresses to use a tuple destructuring approach for clarity in assertions.
packages/rs-dapi-client/src/lib.rs Added new function ban_failed_address to the public interface of the dapi_client module; updated exports to include this function.

Possibly related PRs

  • feat(sdk): get node status #2139: Changes in the main PR regarding the AddressList struct and its methods are related to the modifications made in this PR, which also involves enhancements to the AddressList struct, including new methods and functionality.
  • feat(sdk): fetch many and return metadata and proof to client #2331: The main PR's updates to the ExecutionResponse struct and the introduction of new methods in the FetchMany trait for fetching metadata and proof are related to the enhancements made in this PR, which focuses on improving the functionality of fetching multiple items along with their associated metadata and proof.
  • feat(sdk)!: retry broadcast operations #2337: The changes in the main PR regarding the retry logic in the DapiClient and the introduction of new methods for handling execution results are related to the modifications made in this PR, which implements retry capabilities for broadcast operations, enhancing error handling and response management.

Suggested labels

enhancement, rs-sdk

Suggested reviewers

  • QuantumExplorer
  • lklimek

Poem

🐇 In the meadow where the code does play,
A new map of addresses leads the way.
With tokio added, oh what a sight,
Concurrency blooms, making everything right!
So hop along, let’s ban and unban,
In the world of code, we’re the best we can! 🌼


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 57c2871 and 16b8b0a.

📒 Files selected for processing (1)
  • packages/rs-sdk/src/sync.rs (9 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/rs-sdk/src/sync.rs

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 self-assigned this Nov 26, 2024
@shumkov shumkov marked this pull request as ready for review November 26, 2024 15:49
@shumkov shumkov changed the title feat(sdk): ban addresses failed in sdk feat(sdk)!: ban addresses failed in sdk Nov 26, 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: 3

🧹 Outside diff range and nitpick comments (6)
packages/rs-dapi-client/Cargo.toml (1)

40-41: Consider adding version constraints

While using the exact version "6.1.0" works, consider using semantic versioning constraints to automatically receive compatible bug fixes:

-dashmap = "6.1.0"
+dashmap = "~6.1"

This will allow minor version updates within the 6.1.x range while preventing breaking changes.

packages/rs-sdk/src/platform/transition/broadcast.rs (1)

Line range hint 125-144: Enhance error messages for address banning scenarios.

While the error handling is comprehensive for timeouts and state transition details, consider adding information about banned addresses when retries are exhausted.

Add address context to timeout error message:

     Error::TimeoutReached(
         timeout,
-        format!("Timeout waiting for result of {} (tx id: {}) affecting object {}: {:?}",
+        format!("Timeout waiting for result of {} (tx id: {}) affecting object {} (available addresses: {}): {:?}",
         self.name(),
         self.transaction_id().map(hex::encode).unwrap_or("UNKNOWN".to_string()),
         self.unique_identifiers().join(","),
+        sdk.address_list().available().join(","),
          e),
     )
packages/rs-sdk/src/platform/fetch_many.rs (1)

330-330: Consider using the default trait implementation.

While the change to use sdk.address_list() is correct, this implementation could potentially be simplified by using the default trait implementation of fetch_many_with_metadata_and_proof instead of maintaining a custom one. This would reduce code duplication and maintenance overhead.

Consider removing the custom implementation if there are no specific requirements for the Document type that differ from the default implementation.

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

Line range hint 139-151: Possible Race Conditions in ban_address Method

The ban_address method removes an Address from self.addresses, modifies it, and reinserts it. In a concurrent environment, this remove-modify-insert pattern can lead to race conditions, causing inconsistent states or lost updates if multiple threads access the method simultaneously. Since DashSet is designed for concurrent access, directly removing and reinserting items without proper synchronization may result in unpredictable behavior.

Suggestion:

Consider using an atomic update mechanism to modify the Address in place without removing it from the DashSet. One approach is to switch to a DashMap<Uri, Address>, where the Uri is the key, allowing you to use methods like entry or get_mut to safely mutate the Address under a lock. This ensures thread safety and prevents potential race conditions.

Another option is to use the DashSet::get method to obtain a reference to the Address and employ interior mutability (e.g., using AtomicUsize for ban_count and AtomicOption<DateTime<Utc>> for banned_until) within the Address struct. This would allow you to modify the fields atomically without removing the Address from the set.


Line range hint 153-165: Possible Race Conditions in unban_address Method

Similar to ban_address, the unban_address method removes an Address, modifies it, and reinserts it into self.addresses. This remove-modify-insert pattern is susceptible to race conditions in concurrent usage, potentially leading to inconsistent states or lost updates when accessed by multiple threads simultaneously.

Suggestion:

Adopt an atomic update strategy as suggested for the ban_address method. Using a DashMap<Uri, Address> would allow safe in-place modifications of the Address without removing it from the collection. Alternatively, implement interior mutability within the Address struct to atomically modify its state while it remains in the DashSet. This change will enhance thread safety and prevent race conditions.

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

201-220: Refactor ban/unban logic to reduce code duplication

The ban and unban logic share similar patterns, including error handling and logging. Consider refactoring this into a common helper function to improve maintainability and reduce duplication.

Also applies to: 228-250

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 840dcec and 54c8fc4.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • packages/rs-dapi-client/Cargo.toml (1 hunks)
  • packages/rs-dapi-client/src/address_list.rs (7 hunks)
  • packages/rs-dapi-client/src/dapi_client.rs (6 hunks)
  • packages/rs-drive-proof-verifier/src/error.rs (0 hunks)
  • packages/rs-sdk/src/platform/fetch.rs (1 hunks)
  • packages/rs-sdk/src/platform/fetch_many.rs (2 hunks)
  • packages/rs-sdk/src/platform/fetch_unproved.rs (1 hunks)
  • packages/rs-sdk/src/platform/transition/broadcast.rs (2 hunks)
  • packages/rs-sdk/src/sdk.rs (1 hunks)
  • packages/rs-sdk/src/sync.rs (6 hunks)
💤 Files with no reviewable changes (1)
  • packages/rs-drive-proof-verifier/src/error.rs
🔇 Additional comments (14)
packages/rs-dapi-client/Cargo.toml (1)

40-41: LGTM! Good choice for concurrent operations

The addition of dashmap is well-aligned with the PR objectives. It's an excellent choice for replacing RwLock<HashSet> in mixed async/sync code contexts, providing better performance characteristics for concurrent operations.

packages/rs-sdk/src/platform/fetch_unproved.rs (1)

109-111: LGTM! The retry function update aligns with SDK-level address banning.

The change to include sdk.address_list() as the first parameter enables address banning during retries, which is consistent with the PR's objective of implementing address banning at the SDK level.

Let's verify that this pattern is consistently applied across the codebase:

✅ Verification successful

All retry function calls consistently use address_list as the first parameter

The verification confirms that all retry function calls across the codebase follow the expected pattern:

  • All instances pass address_list as the first parameter (either directly or via sdk.address_list())
  • The function consistently takes three parameters in the order: address_list, settings, and closure
  • This pattern is uniformly applied across different modules:
    • platform/fetch_unproved.rs
    • platform/fetch_many.rs
    • platform/fetch.rs
    • platform/transition/broadcast.rs
    • sync.rs
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify consistent usage of retry function with address_list
# Expect: All retry calls should follow the pattern: retry(sdk.address_list(), settings, closure)

# Search for retry function calls
ast-grep --pattern 'retry($_, $_, $_)'

Length of output: 3049

packages/rs-sdk/src/platform/transition/broadcast.rs (1)

55-58: LGTM! Verify error handling for address banning.

The addition of sdk.address_list() to the retry call enables proper address management during retries, aligning with the PR's objective to implement address banning at the SDK level.

Let's verify the error handling chain:

✅ Verification successful

LGTM! Error handling for address banning is properly implemented.

The verification shows that the error handling chain for address banning is correctly implemented:

  • ExecutionError properly includes address information and retry counts
  • The retry mechanism in sync.rs handles address banning/unbanning through address_list.ban_address() and address_list.unban_address()
  • The broadcast implementation correctly passes the address_list to the retry mechanism, enabling proper address management during retries
  • Error propagation includes address context for both successful and failed cases
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that ExecutionError properly propagates address information for banning
# Look for ExecutionError construction with address context in retry implementations

rg -A 3 "ExecutionError\s*{\s*inner:" packages/rs-sdk/src/

Length of output: 188


Script:

#!/bin/bash
# Let's try a different approach to find ExecutionError usage patterns
rg -A 5 "ExecutionError" packages/rs-sdk/src/

# Also search for the retry implementation to understand error handling
ast-grep --pattern 'pub async fn retry<$_>($_) -> $_'

Length of output: 7774


Script:

#!/bin/bash
# Let's examine the retry implementation in the rs-dapi-client crate
rg -A 10 "retry\(" packages/rs-sdk/src/

# Also check for address list usage in error handling
rg -A 5 "address_list" packages/rs-sdk/src/

Length of output: 17322

packages/rs-sdk/src/platform/fetch.rs (2)

198-198: LGTM: Enhanced retry mechanism with address list management

The change properly integrates address list management into the retry mechanism, enabling address banning at the SDK level as intended.


198-198: Verify thread-safety of AddressList access

Since AddressList is now shared between retries and the RwLock was removed, we should verify that the concurrent access is properly handled.

✅ Verification successful

Let me search for the AddressList implementation to verify its thread-safety.


Let me search for the AddressList struct definition and its thread-safety mechanisms.


AddressList implementation is thread-safe

The AddressList implementation is thread-safe by design:

  • It uses Arc<DashSet<Address>> internally where:
    • Arc provides thread-safe shared ownership
    • DashSet is a concurrent hash set implementation that allows multiple threads to access it safely
  • All methods operate on &self using the thread-safe DashSet operations
  • No additional synchronization is needed as the concurrent collection handles it internally
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check AddressList implementation for thread-safety mechanisms
# Look for DashMap or other concurrent collections usage in AddressList

# Search for AddressList implementation
ast-grep --pattern 'struct AddressList {
  $$$
}'

# Search for concurrent collection usage
rg --type rust 'use.*dashmap|DashMap|Arc|Mutex|RwLock' -A 5

Length of output: 157932


Script:

#!/bin/bash
# Search for AddressList implementation
ast-grep --pattern 'struct AddressList {
  $$$
}'

# Search for AddressList methods
ast-grep --pattern 'impl AddressList {
  $$$
}'

# Search for AddressList usage in retry function
rg --type rust 'retry.*AddressList' -A 5

Length of output: 7416


Script:

#!/bin/bash
# Search for AddressList struct definition and imports
rg --type rust -A 10 'struct AddressList \{'

# Search for DashSet usage in AddressList
rg --type rust -B 5 'DashSet::new\(\)'

Length of output: 1229

packages/rs-sdk/src/platform/fetch_many.rs (1)

255-255: LGTM! Verify implementations handle the address list correctly.

The change to use sdk.address_list() directly aligns with the PR objectives for improved address management at the SDK level. This allows for better control over address banning during retries.

Let's verify that all FetchMany implementations handle the address list correctly:

packages/rs-dapi-client/src/dapi_client.rs (5)

8-8: Import Arc for shared ownership

The addition of use std::sync::Arc; is necessary for managing shared ownership in the updated code.


75-75: Simplify address_list ownership by removing Arc<RwLock>

Changing the address_list field to own AddressList directly simplifies concurrency management, assuming AddressList internally handles synchronization.


89-89: Directly initialize address_list in the constructor

Assigning address_list directly in the new constructor aligns with the updated struct definition and ensures consistency.


98-98: Update address_list method to return a reference

Returning a reference to AddressList simplifies access and reflects the updated ownership model.


143-146: Directly retrieve live address without locking

The code correctly retrieves a live address from address_list using get_live_address(), leveraging internal concurrency mechanisms.

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

193-261: Ensure thread safety when accessing AddressList in asynchronous context

The address_list is being accessed and modified inside an asynchronous closure to ban and unban addresses based on execution results. Verify that the AddressList implementation is thread-safe and that its methods (ban_address and unban_address) can be safely called concurrently without causing data races or synchronization issues.


267-277: ⚠️ Potential issue

Review retry logic for correct calculation of remaining retries

In the when closure of the backon::Retryable::retry function, the calculation of all_requests_sent and the update of settings.retries may lead to potential inaccuracies:

  • Ensure that all_requests_sent accurately reflects the total number of requests made, including retries.
  • Verify that settings.retries is correctly updated to prevent negative values, which could cause unexpected behavior.
  • Confirm that the condition all_requests_sent <= max_retries properly guards against exceeding the maximum allowed retries.
packages/rs-sdk/src/sdk.rs (1)

557-560: LGTM: Simplifying address_list method by returning a reference

Changing the address_list method to return a reference to AddressList directly simplifies the API and eliminates unnecessary error handling. This aligns with the refactoring goals and may improve performance by avoiding cloning.

Comment on lines 201 to 220
match self.address_list.unban_address(&address) {
Ok(_) => {
tracing::debug!(
?address,
"unban successfully responded address {}",
address
);
}
// The address might be already removed from the list
// by background process (i.e., SML update), and it's fine.
Err(AddressListError::AddressNotFound(_)) => {
tracing::debug!(
?address,
"unable to unban address {address}. it's not in the list"
);
}
})?;
Err(AddressListError::InvalidAddressUri(_)) => {
unreachable!("unban address doesn't return InvalidAddressUri")
}
}
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 26, 2024

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Handle InvalidAddressUri error explicitly in unban logic

Using unreachable!() for InvalidAddressUri may cause unintended panics. Even if this error is unexpected, it's safer to handle it explicitly to prevent potential crashes.

Recommend updating the error handling as follows:

Err(AddressListError::InvalidAddressUri(_)) => {
    tracing::error!(
        ?address,
        "Failed to unban address due to invalid URI"
    );
    // Handle the error appropriately, possibly returning an error or continuing
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Agree, you never know what someone will change in the other function in future

Copy link
Contributor

Choose a reason for hiding this comment

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

@lklimek, glad we are in agreement. Handling the InvalidAddressUri error explicitly will enhance the robustness of the code and prevent potential panics.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, I changed to boolean

Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment on lines 228 to 250
match self.address_list.ban_address(&address) {
Ok(_) => {
tracing::warn!(
?address,
?error,
"received server error, banning address {address}"
);
}
// The address might be already removed from the list
// by background process (i.e., SML update), and it's fine.
Err(AddressListError::AddressNotFound(_)) => {
tracing::debug!(
?address,
?error,
"unable to unban address {address}. it's not in the list"
);
}
})?;
Err(AddressListError::InvalidAddressUri(_)) => {
unreachable!(
"unban address doesn't return InvalidAddressUri"
)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Correct log message and handle errors explicitly in ban logic

  • The log message in Err(AddressListError::AddressNotFound(_)) incorrectly states "unable to unban address" when it should be "unable to ban address". Please correct this to accurately reflect the operation.

  • Similar to the unban logic, using unreachable!() for InvalidAddressUri may cause unexpected panics. Handle this error variant explicitly to improve robustness.

Apply the following changes:

// Correct the log message
-    "unable to unban address {address}. it's not in the list"
+    "unable to ban address {address}. it's not in the list"

// Handle `InvalidAddressUri` appropriately
Err(AddressListError::InvalidAddressUri(_)) => {
    tracing::error!(
        ?address,
        "Failed to ban address due to invalid URI"
    );
    // Handle the error appropriately
}

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

Comment on lines +159 to 161
address_list: &AddressList,
settings: RequestSettings,
future_factory_fn: FutureFactoryFn,
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

Update function documentation to include the address_list parameter

The retry function signature now includes the address_list: &AddressList parameter, but the function's documentation does not reflect this change. Please update the documentation to describe the address_list parameter and explain how it is used within the retry mechanism for banning and unbanning addresses.

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

🧹 Outside diff range and nitpick comments (1)
packages/rs-sdk/tests/fetch/evonode.rs (1)

Line range hint 26-52: Consider adding test coverage for address banning

Since the PR introduces address banning at the SDK level, consider adding test cases to verify:

  • Address gets banned after failures
  • Banned addresses are not retried
  • Address unbanning works correctly

Would you like me to help draft these additional test cases?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 54c8fc4 and 5aadfd0.

📒 Files selected for processing (1)
  • packages/rs-sdk/tests/fetch/evonode.rs (2 hunks)
🔇 Additional comments (2)
packages/rs-sdk/tests/fetch/evonode.rs (2)

8-8: LGTM: Import aligns with new dereferencing usage


22-23: LGTM: Correctly adapted to new AddressList API

The changes appropriately handle the removal of into_iter by using iter() with explicit dereferencing.

Let's verify the removal of into_iter implementation:

✅ Verification successful

Verified: IntoIterator implementation has been removed from AddressList

The search results confirm that the IntoIterator implementation for AddressList has been removed. The struct now only implements FromIterator and provides iter() method, which aligns with the changes made in the test file. The adaptation in the test code is correct.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that into_iter is completely removed from AddressList
# Expected: No matches should be found

rg "impl.*IntoIterator.*for.*AddressList" 

Length of output: 43


Script:

#!/bin/bash
# Let's try a broader search to find any IntoIterator implementations
rg "impl.*IntoIterator" -A 3

# Also search for AddressList struct definition and its implementations
rg "struct AddressList|impl.*AddressList" -A 3

Length of output: 11217

// Ban or unban the address based on the result
match &result {
Ok(response) => {
// Unban the address if it was banned and node responded successfully this time
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure if it will happen - it should be already unbanned by lower layer (but we can keep it here as a precaution)

let result = (*func)(*settings).await;

// Ban or unban the address based on the result
match &result {
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe this logic should be converted into a function shared between dapi-client and sdk, to not repeat similar code? not sure about that, just an idea

Comment on lines 201 to 220
match self.address_list.unban_address(&address) {
Ok(_) => {
tracing::debug!(
?address,
"unban successfully responded address {}",
address
);
}
// The address might be already removed from the list
// by background process (i.e., SML update), and it's fine.
Err(AddressListError::AddressNotFound(_)) => {
tracing::debug!(
?address,
"unable to unban address {address}. it's not in the list"
);
}
})?;
Err(AddressListError::InvalidAddressUri(_)) => {
unreachable!("unban address doesn't return InvalidAddressUri")
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Agree, you never know what someone will change in the other function in future

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 (4)
packages/rs-dapi-client/src/lib.rs (1)

Line range hint 71-89: Consider enhancing error handling integration.

Given the new address banning functionality, consider integrating it more tightly with the CanRetry trait. This could provide a more cohesive approach to handling retries and address banning.

Consider:

  1. Adding a method to CanRetry that indicates if a failure should trigger address banning
  2. Documenting the relationship between retries and address banning in the trait documentation
packages/rs-dapi-client/src/dapi_client.rs (2)

142-146: Enhance error logging with additional context

Consider adding more context to the error log by including the specific error type or category. This would help in debugging and monitoring banned addresses.

 tracing::warn!(
     ?address,
     ?error,
+    error_type = %std::any::type_name::<E>(),
     "ban address {address} due to error: {error}"
 );

275-284: Consider simplifying execution result construction

The success case mapping could be more concise by using struct initialization shorthand.

 let execution_result = result
     .map(|inner| {
         tracing::trace!(response = ?inner, "received {} response", response_name);
         ExecutionResponse {
-            inner,
-            retries,
-            address: address.clone(),
+            inner,
+            retries,
+            address,
         }
     })
packages/rs-sdk/src/sync.rs (1)

Line range hint 365-376: Add test coverage for address banning behavior

While the current tests verify retry counts, they don't explicitly verify that addresses are correctly banned and unbanned.

Consider adding test cases that verify:

  1. Addresses are banned after failures
  2. Banned addresses are not reused in subsequent retries
  3. Addresses are correctly unbanned on success

Example addition:

#[tokio::test]
async fn test_address_banning() {
    let address_list = AddressList::default();
    let test_uri = Uri::from_static("http://localhost").into();
    
    // Verify address is banned after failure
    let result = retry(&address_list, RequestSettings::default(), |s| {
        async move {
            Err(ExecutionError {
                inner: MockError::Generic,
                retries: 0,
                address: Some(test_uri.clone()),
            })
        }
    }).await;
    
    assert!(result.is_err());
    assert!(address_list.is_banned(&test_uri));
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 5aadfd0 and 945ddf6.

📒 Files selected for processing (4)
  • packages/rs-dapi-client/src/dapi_client.rs (6 hunks)
  • packages/rs-dapi-client/src/lib.rs (1 hunks)
  • packages/rs-sdk/src/sync.rs (8 hunks)
  • packages/rs-sdk/tests/fetch/evonode.rs (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/rs-sdk/tests/fetch/evonode.rs
🔇 Additional comments (6)
packages/rs-dapi-client/src/dapi_client.rs (3)

Line range hint 76-99: LGTM: Simplified ownership model

The removal of Arc<RwLock<AddressList>> in favor of direct AddressList ownership simplifies the code and aligns with the PR objective of eliminating RwLock for mixed async/sync code.


130-132: Handle InvalidAddressUri error explicitly

Using unreachable!() for InvalidAddressUri may cause unintended panics. Even if this error is unexpected, it's safer to handle it explicitly.

Also applies to: 157-159


295-299: LGTM: Well-integrated address banning

The integration of ban_failed_address is well-placed after execution but before returning the result, ensuring proper address management without affecting the execution flow.

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

9-10: LGTM: Import changes align with new functionality

The new imports support the address banning feature and enhanced error handling capabilities.


160-167: Update function documentation to include the address_list parameter

The function signature has been updated but the documentation doesn't reflect the new parameter.


194-199: Verify address banning integration

The address banning logic is correctly placed after obtaining the execution result. Let's verify the integration with the address list management system.

✅ Verification successful

Based on the results, I can generate the final response as we have sufficient information about the address banning integration:

Address banning integration is properly implemented

The implementation correctly uses the centralized ban_failed_address function from rs-dapi-client, which:

  • Is properly imported and used in the retry logic
  • Integrates with the configurable settings system via ban_failed_address flag
  • Follows the consistent error handling pattern used elsewhere in the codebase
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the usage of ban_failed_address across the codebase
# to ensure consistent error handling patterns

# Check for other occurrences of ban_failed_address
rg -A 3 "ban_failed_address" 

# Check for any custom address banning implementations that might conflict
ast-grep --pattern 'fn $name($params) {
  $$$
  ban($$$)
  $$$
}'

Length of output: 3268

@@ -17,7 +17,7 @@ pub use address_list::Address;
pub use address_list::AddressList;
pub use address_list::AddressListError;
pub use connection_pool::ConnectionPool;
pub use dapi_client::{DapiClient, DapiClientError};
pub use dapi_client::{ban_failed_address, DapiClient, DapiClientError};
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

Documentation is present but could be more descriptive

The ban_failed_address function has basic documentation, but it could benefit from more detailed documentation:

  • The current doc comment is brief and doesn't explain the function's parameters
  • The conditions under which addresses are banned/unbanned aren't clearly documented
  • Return type and possible error cases aren't documented

Consider adding:

  • Parameter descriptions using @param or equivalent
  • More detailed explanation of the banning/unbanning logic
  • Examples of usage
  • Documentation of any panics or error conditions
🔗 Analysis chain

Verify documentation for the newly exported function.

The new ban_failed_address function is being exported as part of the public API, but its documentation might be missing.

Let's verify the documentation and implementation:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for documentation and implementation of ban_failed_address

# Check for documentation
rg -A 5 "ban_failed_address" packages/rs-dapi-client/src/dapi_client.rs

# Check if the function is properly tested
rg -g "*.rs" "ban_failed_address.*test" packages/rs-dapi-client/

Length of output: 845


Script:

#!/bin/bash
# Let's check for documentation comments (///), implementation, and test coverage

# Check for documentation and full implementation context
rg -B 5 -A 15 "pub fn ban_failed_address" packages/rs-dapi-client/src/dapi_client.rs

# Check for test modules
ast-grep --pattern 'mod $_ {
  #[test]
  $$$
  fn $_ban_failed_address$_() {
    $$$
  }
  $$$
}'

Length of output: 1009

Comment on lines 104 to 177
/// Ban address in case of retryable error or unban it
/// if it was banned, and the request was successful.
pub fn ban_failed_address<R, E>(
address_list: &AddressList,
result: &ExecutionResult<R, E>,
applied_settings: &AppliedRequestSettings,
) where
E: CanRetry + Display + Debug,
{
match &result {
Ok(response) => {
// Unban the address if it was banned and node responded successfully this time
if response.address.is_banned() {
match address_list.unban_address(&response.address) {
Ok(_) => {
tracing::debug!(address = ?response.address, "unban successfully responded address {}", response.address);
}
// The address might be already removed from the list
// by background process (i.e., SML update), and it's fine.
Err(AddressListError::AddressNotFound(_)) => {
tracing::debug!(
address = ?response.address,
"unable to unban address {} because it's not in the list anymore",
response.address
);
}
Err(AddressListError::InvalidAddressUri(_)) => {
unreachable!("unban address doesn't return InvalidAddressUri")
}
}
}
}
Err(error) => {
if error.can_retry() {
if let Some(address) = error.address.as_ref() {
if applied_settings.ban_failed_address {
match address_list.ban_address(address) {
Ok(_) => {
tracing::warn!(
?address,
?error,
"ban address {address} due to error: {error}"
);
}
// The address might be already removed from the list
// by background process (i.e., SML update), and it's fine.
Err(AddressListError::AddressNotFound(_)) => {
tracing::debug!(
?address,
?error,
"unable to ban address {address} because it's not in the list anymore"
);
}
Err(AddressListError::InvalidAddressUri(_)) => {
unreachable!("ban address doesn't return InvalidAddressUri")
}
}
} else {
tracing::debug!(
?error,
?address,
"we should ban the address {address} due to the error but banning is disabled"
);
}
} else {
tracing::debug!(
?error,
"we should ban an address due to the error but address is absent"
);
}
}
}
};
}
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

Test coverage missing for address banning functionality

The code inspection reveals that while the ban_address and unban_address methods are implemented in address_list.rs, and used in dapi_client.rs, there are no visible test cases covering this critical functionality. This includes:

  • No unit tests for the ban_failed_address function
  • No tests verifying the address banning/unbanning behavior
  • No integration tests covering this feature
🔗 Analysis chain

Verify test coverage for address banning

Let's verify that the new address banning functionality has proper test coverage.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for test cases covering the new ban_failed_address function
rg -l "ban_failed_address.*test" --type rust

# Look for test functions that verify address banning behavior
ast-grep --pattern 'fn $test_name(_: $_) {
  $$$
  ban_failed_address($$$)
  $$$
}'

Length of output: 132


Script:

#!/bin/bash
# Let's try a broader search for test files and ban-related tests
rg -l "mod tests" packages/rs-dapi-client/src/

# Search for any test functions that might be testing address banning
rg -A 10 "#\[test\]" packages/rs-dapi-client/src/

# Look for any usage of ban_address or unban_address in tests
rg "(ban_address|unban_address)" packages/rs-dapi-client/src/ --type rust

Length of output: 654


Script:

#!/bin/bash
# Let's check the address_list.rs file for tests
rg -A 20 "mod tests" packages/rs-dapi-client/src/address_list.rs

# Look for test files in the tests directory
fd "test" packages/rs-dapi-client/tests/

# Check for integration tests that might cover this functionality
rg -l "address.*ban" packages/rs-dapi-client/tests/

Length of output: 161

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 (6)
packages/rs-dapi-client/src/address_list.rs (5)

57-61: Add more detailed documentation for the uri method.

While the method is straightforward, consider adding more context about when this method should be used and any important considerations when working with the Uri.

     /// Get [Uri] of a node.
+    /// Returns a reference to the underlying Uri that can be used for making connections.
+    /// This Uri is guaranteed to be valid as it was validated during Address creation.
     pub fn uri(&self) -> &Uri {
         &self.0
     }

66-69: Add documentation for struct fields.

The struct fields would benefit from detailed documentation explaining their purpose and constraints.

 pub struct AddressStatus {
+    /// Number of times this address has been banned
+    /// Used to calculate exponential backoff duration
     ban_count: usize,
+    /// Timestamp until which the address is banned
+    /// None if the address is not currently banned
     banned_until: Option<chrono::DateTime<Utc>>,
 }

Line range hint 73-78: Protect against potential overflow in ban duration calculation.

The exponential calculation could lead to overflow for large ban counts. Consider adding a maximum ban duration.

     pub fn ban(&mut self, base_ban_period: &Duration) {
+        const MAX_COEFFICIENT: f64 = 100.0; // Example maximum
         let coefficient = (self.ban_count as f64).exp();
+        let coefficient = coefficient.min(MAX_COEFFICIENT);
         let ban_period = Duration::from_secs_f64(base_ban_period.as_secs_f64() * coefficient);
 
         self.banned_until = Some(chrono::Utc::now() + ban_period);
         self.ban_count += 1;
     }

107-108: Consider using parking_lot::RwLock for better performance.

The standard library's RwLock can be less performant and more prone to writer starvation compared to parking_lot's implementation.

+use parking_lot::RwLock;
-use std::sync::RwLock;

 pub struct AddressList {
-    addresses: Arc<RwLock<HashMap<Address, AddressStatus>>>,
+    addresses: Arc<RwLock<HashMap<Address, AddressStatus>>>, // Now using parking_lot::RwLock
     base_ban_period: Duration,
 }

253-253: Address the TODO comment about FromStr implementation.

The comment indicates that the FromStr implementation needs to be changed. Consider creating a tracking issue for this work.

Would you like me to help create a GitHub issue to track this TODO item?

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

254-285: Consider extracting result mapping to a separate function

The result mapping logic (lines 261-279) is quite complex and could benefit from being extracted into a separate function for better readability and maintainability.

Consider refactoring like this:

- let execution_result = result
-     .map(|inner| {
-         tracing::trace!(response = ?inner, "received {} response", response_name);
-         ExecutionResponse {
-             inner,
-             retries,
-             address: address.clone(),
-         }
-     })
-     .map_err(|inner| {
-         tracing::debug!(error = ?inner, "received error: {inner}");
-         ExecutionError {
-             inner,
-             retries,
-             address: Some(address.clone()),
-         }
-     });
+ let execution_result = map_transport_result(result, retries, address.clone(), response_name);

+ fn map_transport_result<R>(
+     result: Result<R, TransportError>,
+     retries: usize,
+     address: Address,
+     response_name: &str,
+ ) -> ExecutionResult<R, DapiClientError> {
+     result
+         .map(|inner| {
+             tracing::trace!(response = ?inner, "received {} response", response_name);
+             ExecutionResponse {
+                 inner,
+                 retries,
+                 address: address.clone(),
+             }
+         })
+         .map_err(|inner| {
+             tracing::debug!(error = ?inner, "received error: {inner}");
+             ExecutionError {
+                 inner,
+                 retries,
+                 address: Some(address),
+             }
+         })
+ }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 945ddf6 and db7ce77.

📒 Files selected for processing (5)
  • packages/rs-dapi-client/Cargo.toml (1 hunks)
  • packages/rs-dapi-client/src/address_list.rs (7 hunks)
  • packages/rs-dapi-client/src/dapi_client.rs (6 hunks)
  • packages/rs-dapi-client/src/lib.rs (1 hunks)
  • scripts/configure_test_suite_network.sh (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • scripts/configure_test_suite_network.sh
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/rs-dapi-client/Cargo.toml
  • packages/rs-dapi-client/src/lib.rs
🔇 Additional comments (4)
packages/rs-dapi-client/src/address_list.rs (1)

Line range hint 1-240: Verify thread safety of the new implementation.

The changes introduce concurrent access patterns that should be thoroughly tested.

✅ Verification successful

Thread safety implementation is correct and well-designed

The implementation uses appropriate synchronization mechanisms:

  • Uses Arc<RwLock<HashMap>> for thread-safe concurrent access
  • Read operations (get_live_address, is_banned) use read locks
  • Write operations (ban, unban, add, remove) use write locks
  • No potential for deadlocks as locks are not held across async boundaries
  • Lock granularity is appropriate with a single lock protecting the entire collection

The design follows Rust's safety guarantees and standard concurrent programming patterns.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for potential deadlock scenarios in tests
rg -l "AddressList.*\.(ban|unban|get_live_address)" -t rust | xargs rg "tokio::(test|spawn)"

# Search for existing concurrent tests
rg -l "tokio::test.*AddressList" -t rust

Length of output: 6290


Script:

#!/bin/bash
# Let's search for tests specifically related to AddressList
rg -l "AddressList" -t rust | xargs rg "test.*AddressList" -B 2 -A 5

# Let's also check for any concurrent operations in the implementation
ast-grep --pattern 'impl AddressList {
  $$$
  async fn $$$($$$) {
    $$$
  }
  $$$
}'

# Check for any test files containing AddressList
fd "test.*\.rs" | xargs rg "AddressList"

Length of output: 199


Script:

#!/bin/bash
# Let's check for unit tests in the same file or module
rg -t rust "mod\s+tests" -A 10 packages/rs-dapi-client/src/address_list.rs

# Let's also check how AddressList is used in the codebase
rg "AddressList::" -A 3 -B 1

# Check for any mutex or lock usage patterns
rg "RwLock|Mutex" -A 2 packages/rs-dapi-client/src/address_list.rs

Length of output: 3394

packages/rs-dapi-client/src/dapi_client.rs (3)

104-165: LGTM: Well-structured address banning implementation

The new ban_failed_address function is well-implemented with:

  • Comprehensive error handling
  • Appropriate logging levels
  • Clear handling of edge cases (e.g., addresses removed by background processes)

However, there's a missing test coverage concern that was previously raised.


207-211: LGTM: Simplified address retrieval

The address retrieval has been simplified while maintaining proper error handling.


76-76: Breaking change: Simplified ownership model

The ownership model has been simplified by removing Arc<RwLock<>> wrapper from address_list. While this is a good improvement for mixed async/sync code, it's a breaking change that affects the public API.

Let's verify the impact of this breaking change:

Also applies to: 90-90, 99-99

packages/rs-dapi-client/src/address_list.rs Show resolved Hide resolved
Comment on lines 208 to 224
pub fn get_live_address(&self) -> Option<Address> {
let mut guard = self.addresses.read().unwrap();

self.unbanned().into_iter().choose(&mut rng)
}
let mut rng = SmallRng::from_entropy();

/// Get all addresses that are not banned.
fn unbanned(&self) -> Vec<&Address> {
let now = chrono::Utc::now();

self.addresses
guard
.iter()
.filter(|addr| {
addr.banned_until
.filter(|(addr, status)| {
status
.banned_until
.map(|banned_until| banned_until < now)
.unwrap_or(true)
})
.collect()
}

/// Get number of available, not banned addresses.
pub fn available(&self) -> usize {
self.unbanned().len()
.choose(&mut rng)
.map(|(addr, _)| addr.clone())
Copy link
Contributor

@coderabbitai coderabbitai bot Nov 28, 2024

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize get_live_address implementation.

The current implementation holds the read lock while performing random selection, which could block other operations unnecessarily. Consider collecting valid addresses first.

     pub fn get_live_address(&self) -> Option<Address> {
-        let mut guard = self.addresses.read().unwrap();
-
         let mut rng = SmallRng::from_entropy();
         let now = chrono::Utc::now();
 
-        guard
-            .iter()
-            .filter(|(addr, status)| {
-                status
-                    .banned_until
-                    .map(|banned_until| banned_until < now)
-                    .unwrap_or(true)
-            })
-            .choose(&mut rng)
-            .map(|(addr, _)| addr.clone())
+        // Release the lock quickly by collecting valid addresses
+        let valid_addresses: Vec<Address> = {
+            let guard = self.addresses.read().unwrap();
+            guard
+                .iter()
+                .filter(|(_, status)| {
+                    status
+                        .banned_until
+                        .map(|banned_until| banned_until < now)
+                        .unwrap_or(true)
+                })
+                .map(|(addr, _)| addr.clone())
+                .collect()
+        };
+
+        valid_addresses.into_iter().choose(&mut rng)
     }
📝 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
pub fn get_live_address(&self) -> Option<Address> {
let mut guard = self.addresses.read().unwrap();
self.unbanned().into_iter().choose(&mut rng)
}
let mut rng = SmallRng::from_entropy();
/// Get all addresses that are not banned.
fn unbanned(&self) -> Vec<&Address> {
let now = chrono::Utc::now();
self.addresses
guard
.iter()
.filter(|addr| {
addr.banned_until
.filter(|(addr, status)| {
status
.banned_until
.map(|banned_until| banned_until < now)
.unwrap_or(true)
})
.collect()
}
/// Get number of available, not banned addresses.
pub fn available(&self) -> usize {
self.unbanned().len()
.choose(&mut rng)
.map(|(addr, _)| addr.clone())
pub fn get_live_address(&self) -> Option<Address> {
let mut rng = SmallRng::from_entropy();
let now = chrono::Utc::now();
// Release the lock quickly by collecting valid addresses
let valid_addresses: Vec<Address> = {
let guard = self.addresses.read().unwrap();
guard
.iter()
.filter(|(_, status)| {
status
.banned_until
.map(|banned_until| banned_until < now)
.unwrap_or(true)
})
.map(|(addr, _)| addr.clone())
.collect()
};
valid_addresses.into_iter().choose(&mut rng)
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Do you think allocating new vector is much faster then selecting a random element from 100?

Copy link
Contributor

Choose a reason for hiding this comment

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

@shumkov, you make a valid point. Allocating a new vector may introduce unnecessary overhead when dealing with a relatively small number of addresses (around 100). Holding the read lock during random selection in this context is likely acceptable. Thank you for the clarification.


✏️ Learnings added
Learnt from: shumkov
PR: dashpay/platform#2351
File: packages/rs-dapi-client/src/address_list.rs:208-224
Timestamp: 2024-11-28T14:07:48.516Z
Learning: When working with small collections (e.g., around 100 items), avoid introducing additional allocations if the performance benefit is negligible.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

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

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

Line range hint 63-90: LGTM: Well-implemented ban management with exponential backoff

The AddressStatus struct provides a clean separation of concerns for ban management. The exponential backoff in the ban duration is a good practice for handling problematic nodes.

Consider adding documentation for the exponential coefficient calculation to explain why e^ban_count was chosen as the multiplier.

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

1021-1025: Consider allowing custom initial addresses in mock mode

While the current implementation correctly initializes an empty address list, consider allowing custom initial addresses to be provided in mock mode for more flexible testing scenarios.

Consider modifying the builder to accept optional initial addresses:

 inner: SdkInstance::Mock {
     mock: mock_sdk.clone(),
     dapi,
-    address_list: AddressList::new(),
+    address_list: self.mock_addresses.unwrap_or_else(AddressList::new),
     version: self.version,
 },
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between db7ce77 and 57c2871.

📒 Files selected for processing (3)
  • packages/rs-dapi-client/src/address_list.rs (7 hunks)
  • packages/rs-sdk/src/sdk.rs (3 hunks)
  • packages/rs-sdk/tests/fetch/evonode.rs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/rs-sdk/tests/fetch/evonode.rs
🧰 Additional context used
📓 Learnings (1)
packages/rs-dapi-client/src/address_list.rs (1)
Learnt from: shumkov
PR: dashpay/platform#2351
File: packages/rs-dapi-client/src/address_list.rs:241-250
Timestamp: 2024-11-28T14:08:53.428Z
Learning: In the `IntoIterator` implementation for `AddressList` in `packages/rs-dapi-client/src/address_list.rs`, since `self` is consumed, holding a write lock while converting to an iterator does not block other operations.
🔇 Additional comments (6)
packages/rs-dapi-client/src/address_list.rs (4)

Line range hint 20-61: LGTM: Clean Address struct implementation

The simplified Address struct with proper trait implementations and encapsulated URI access follows Rust best practices.


Line range hint 190-238: LGTM: Well-implemented address management methods

The methods provide a complete set of operations for address management. The get_live_address() implementation appropriately handles random selection while holding the read lock, which is suitable for the expected size of the address list.


241-250: LGTM: Correct IntoIterator implementation

The implementation properly consumes self and handles the transfer of ownership. As discussed in previous reviews, holding the write lock is acceptable here since self is consumed.


Line range hint 107-188: Verify RwLock usage against PR objectives

The PR objectives mention removing RwLock as it was "unsuitable for mixed async and sync code", but the implementation still uses RwLock. Please clarify if this is intentional or if further changes are planned.

The current implementation with Arc looks correct for concurrent access, and the Entry API usage is idiomatic. However, if async code needs to interact with this list, consider using tokio::sync::RwLock instead.

Run this script to check for async code usage:

packages/rs-sdk/src/sdk.rs (2)

187-187: LGTM: Addition of address_list field to Mock variant

The addition of the address_list field to SdkInstance::Mock provides consistent address list handling between mock and real implementations.


557-562: LGTM: Simplified address_list method signature

The method now returns a direct reference to AddressList instead of a Result, which simplifies the API while maintaining functionality. The implementation correctly handles both real and mock SDK instances.

#[cfg_attr(feature = "mocks", serde(with = "http_serde::uri"))]
uri: Uri,
}
pub struct Address(#[cfg_attr(feature = "mocks", serde(with = "http_serde::uri"))] Uri);
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we still need the Address struct? Maybe we can just type Address = Uri ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Better to have newtype so we can implement traits we need and extend in future without breaking changes

address: Some(address.clone()),
}
})?;
let retries = retries_counter.load(std::sync::atomic::Ordering::Acquire);
Copy link
Contributor

Choose a reason for hiding this comment

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

This operation will execute in one thread anyway, so maybe we can use Relaxed here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Tokio runtime is using a pool of threads to execute tasks so they might be in different threads, ins't?


let retries = retries_counter.load(std::sync::atomic::Ordering::Acquire);
ban_failed_address::<R::Response, DapiClientError>(
Copy link
Contributor

Choose a reason for hiding this comment

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

the name is a bit confusing - it looks like the address will be banned after this line. Maybe ban_address_if_failed ? or update_banned_status? Or sth?

Copy link
Member Author

Choose a reason for hiding this comment

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

update_address_banned_status ?

@shumkov shumkov deleted the branch v1.7-dev November 29, 2024 10:09
@shumkov shumkov closed this Nov 29, 2024
@shumkov shumkov reopened this Nov 29, 2024
@shumkov shumkov changed the base branch from v1.6-dev to v1.7-dev November 29, 2024 10:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

2 participants