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(dapi): getStatus cache invalidation #2155

Merged
merged 5 commits into from
Sep 26, 2024

Conversation

shumkov
Copy link
Member

@shumkov shumkov commented Sep 20, 2024

Issue being fixed or feature implemented

The getStatus results is caches in DAPI memory and cache invalidation logic doesn't work

What was done?

  • Subscribe to new blocks in the tenderdash blockchain listener
  • Resubscribe websocket client on reconnection
  • Clean cache in 3 mins in addition to new block invalidation to represent unavailable drive or tenderdash.

How Has This Been Tested?

Starting and stopping containers locally

Breaking Changes

None

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

  • New Features

    • Enhanced event handling for blockchain updates, including active subscription to new block notifications.
    • Improved logging for better traceability of blockchain events.
  • Bug Fixes

    • Streamlined subscription logic to ensure effective management of WebSocket connections.
    • Updated test cases to ensure proper activation and functionality of the blockchain listener.

Copy link
Contributor

coderabbitai bot commented Sep 25, 2024

Walkthrough

The changes involve updates to the BlockchainListener class for improved event handling and subscription management, specifically by consolidating subscription logic into a new private method. The test cases for the start method have also been modified to ensure proper activation of the listener and to validate the expected behavior when subscribing to events.

Changes

Files Change Summary
packages/dapi/lib/externalApis/tenderdash/BlockchainListener.js Refactored event handling in BlockchainListener to use a new private method #subscribe(), managing subscriptions more effectively. Added event listener for NEW_BLOCK_QUERY to emit EVENTS.NEW_BLOCK.
packages/dapi/test/integration/externalApis/tenderdash/BlockchainListener.spec.js Updated test cases for the start method to ensure the listener is active and checks for WebSocket connection before asserting behavior. Re-enabled test for emitting a new block.

Sequence Diagram(s)

sequenceDiagram
    participant WebSocketClient
    participant BlockchainListener

    WebSocketClient->>BlockchainListener: Connect
    BlockchainListener->>BlockchainListener: #subscribe()
    BlockchainListener->>WebSocketClient: Subscribe to TX_QUERY
    BlockchainListener->>WebSocketClient: Subscribe to NEW_BLOCK_QUERY
    WebSocketClient->>BlockchainListener: NEW_BLOCK event
    BlockchainListener->>WebSocketClient: Emit EVENTS.NEW_BLOCK
Loading

🐰 In the meadow where changes bloom,
A listener hops, dispelling gloom.
With blocks anew, the cache will clear,
Logging the way, we hold it dear.
A dance of code, so neat and bright,
In rabbit's world, all feels just right! 🌼


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.

@QuantumExplorer
Copy link
Member

@coderabbitai review

Copy link
Contributor

coderabbitai bot commented Sep 25, 2024

Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@QuantumExplorer QuantumExplorer added this to the v1.4.0 milestone Sep 25, 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 (1)
packages/dapi/lib/externalApis/tenderdash/BlockchainListener.js (1)

72-74: Good addition of the #subscribe method for centralized subscription logic.

The new #subscribe method effectively encapsulates the subscription logic for both new blocks and transactions. This improves code organization and reusability, aligning well with the PR objectives.

Consider adding basic error handling to make the method more robust:

 #subscribe() {
-  this.wsClient.subscribe(NEW_BLOCK_QUERY);
-  this.wsClient.subscribe(TX_QUERY);
+  try {
+    this.wsClient.subscribe(NEW_BLOCK_QUERY);
+    this.wsClient.subscribe(TX_QUERY);
+  } catch (error) {
+    logger.error('Failed to subscribe to blockchain events', { error: error.message });
+  }
 }

This change will ensure that any subscription errors are logged, making it easier to diagnose issues in production.

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 8283162 and 5c3b4fa.

Files selected for processing (2)
  • packages/dapi/lib/externalApis/tenderdash/BlockchainListener.js (1 hunks)
  • packages/dapi/lib/grpcServer/handlers/platform/getStatusHandlerFactory.js (3 hunks)
Additional comments not posted (6)
packages/dapi/lib/externalApis/tenderdash/BlockchainListener.js (2)

61-69: Excellent implementation of block subscription and connection handling!

The changes in the start method effectively address the PR objectives:

  1. Subscribing to new blocks (line 61-62) enables cache invalidation based on new block events.
  2. The 'connect' event listener (lines 63-65) ensures resubscription upon WebSocket reconnection.
  3. The immediate subscription check (lines 67-69) guarantees that subscriptions are set up if the client is already connected when start is called.

These improvements enhance the robustness of the blockchain listening mechanism and should resolve the caching issues mentioned in the PR objectives.


Line range hint 1-87: Overall, excellent implementation addressing the PR objectives.

The changes to the BlockchainListener class effectively solve the caching and subscription issues mentioned in the PR objectives. The code is well-structured, with clear separation of concerns and improved error handling. The new #subscribe method and the enhanced start method work together to ensure robust subscription management and event handling.

Key improvements:

  1. Subscription to new blocks for cache invalidation.
  2. Resubscription logic on WebSocket reconnection.
  3. Immediate subscription when the client is already connected.

The suggested enhancements for error handling and logging will further improve the robustness and observability of the system. Great job on this implementation!

packages/dapi/lib/grpcServer/handlers/platform/getStatusHandlerFactory.js (4)

12-12: Logger import added correctly

The addition of the logger import is appropriate for the new logging functionality.


22-22: 'cleanCacheTimeout' variable initialized

Initializing cleanCacheTimeout to null sets up the variable for managing cache cleanup scheduling.


24-34: Cache cleanup function is well-implemented

The cleanCache function effectively clears the cached response and cancels any existing scheduled cache cleanup. The logging statement enhances traceability.


37-37: Subscribed to 'NEW_BLOCK' event for cache invalidation

Subscribing to the NEW_BLOCK event ensures the cache is invalidated upon new block detection, aligning with the cache invalidation strategy.

Comment on lines 61 to 74
this.wsClient.on(NEW_BLOCK_QUERY, (message) => this.emit(EVENTS.NEW_BLOCK, message));

this.wsClient.on('connect', () => {
this.#subscribe();
});

if (this.wsClient.isConnected) {
this.#subscribe();
}
}

#subscribe() {
this.wsClient.subscribe(NEW_BLOCK_QUERY);
this.wsClient.subscribe(TX_QUERY);
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 enhancing logging for better traceability of the subscription process.

The changes effectively implement the required improvements to the blockchain listening mechanism. To further enhance the system's observability, consider adding more detailed logging:

 start() {
   const processLogger = logger.child({
     process: 'BlockchainListener',
   });

-  processLogger.info('Subscribed to state transition results');
+  processLogger.info('Starting BlockchainListener');

   // ... (existing code)

   this.wsClient.on(NEW_BLOCK_QUERY, (message) => {
+    processLogger.debug('Received new block', { height: message.block.header.height });
     this.emit(EVENTS.NEW_BLOCK, message)
   });

   this.wsClient.on('connect', () => {
+    processLogger.info('WebSocket connected, subscribing to events');
     this.#subscribe();
   });

   if (this.wsClient.isConnected) {
+    processLogger.info('WebSocket already connected, subscribing to events');
     this.#subscribe();
   }
 }

 #subscribe() {
+  processLogger.debug('Subscribing to blockchain events');
   try {
     this.wsClient.subscribe(NEW_BLOCK_QUERY);
     this.wsClient.subscribe(TX_QUERY);
+    processLogger.info('Successfully subscribed to blockchain events');
   } catch (error) {
     logger.error('Failed to subscribe to blockchain events', { error: error.message });
   }
 }

These logging enhancements will provide better visibility into the subscription process, making it easier to track the system's behavior and diagnose any issues that may arise.

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
this.wsClient.on(NEW_BLOCK_QUERY, (message) => this.emit(EVENTS.NEW_BLOCK, message));
this.wsClient.on('connect', () => {
this.#subscribe();
});
if (this.wsClient.isConnected) {
this.#subscribe();
}
}
#subscribe() {
this.wsClient.subscribe(NEW_BLOCK_QUERY);
this.wsClient.subscribe(TX_QUERY);
this.wsClient.on(NEW_BLOCK_QUERY, (message) => {
processLogger.debug('Received new block', { height: message.block.header.height });
this.emit(EVENTS.NEW_BLOCK, message)
});
this.wsClient.on('connect', () => {
processLogger.info('WebSocket connected, subscribing to events');
this.#subscribe();
});
if (this.wsClient.isConnected) {
processLogger.info('WebSocket already connected, subscribing to events');
this.#subscribe();
}
}
#subscribe() {
processLogger.debug('Subscribing to blockchain events');
try {
this.wsClient.subscribe(NEW_BLOCK_QUERY);
this.wsClient.subscribe(TX_QUERY);
processLogger.info('Successfully subscribed to blockchain events');
} catch (error) {
logger.error('Failed to subscribe to blockchain events', { error: error.message });
}
}

Comment on lines +225 to +227
// Clean cache in 3 minutes
cleanCacheTimeout = setTimeout(cleanCache, 3 * 60 * 1000);

Copy link
Contributor

@coderabbitai coderabbitai bot Sep 25, 2024

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential for multiple timeouts—ensure a single scheduled cache cleanup

Each time getStatusHandler generates a new cachedResponse, a new cleanCacheTimeout is set without clearing any existing timeout. This could lead to multiple timeouts running concurrently, causing redundant executions of cleanCache.

To prevent multiple scheduled timeouts, clear any existing timeout before setting a new one:

+        // Cancel any existing scheduled cache cleanup
+        if (cleanCacheTimeout !== null) {
+          clearTimeout(cleanCacheTimeout);
+          cleanCacheTimeout = null;
+        }
         // Clean cache in 3 minutes
         cleanCacheTimeout = setTimeout(cleanCache, 3 * 60 * 1000);
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
// Clean cache in 3 minutes
cleanCacheTimeout = setTimeout(cleanCache, 3 * 60 * 1000);
// Cancel any existing scheduled cache cleanup
if (cleanCacheTimeout !== null) {
clearTimeout(cleanCacheTimeout);
cleanCacheTimeout = null;
}
// Clean cache in 3 minutes
cleanCacheTimeout = setTimeout(cleanCache, 3 * 60 * 1000);

🛠️ Refactor suggestion

Consider using 'setInterval' for consistent cache cleanup

Using setInterval can simplify the cache cleanup mechanism by consistently invalidating the cache every three minutes, regardless of cache usage patterns.

Implement setInterval to schedule regular cache cleanups:

-        // Clean cache in 3 minutes
-        cleanCacheTimeout = setTimeout(cleanCache, 3 * 60 * 1000);
+        // Schedule cache cleanup every 3 minutes if not already scheduled
+        if (cleanCacheTimeout === null) {
+          cleanCacheTimeout = setInterval(cleanCache, 3 * 60 * 1000);
+        }

Modify the cleanCache function to retain the interval:

     function cleanCache() {
         cachedResponse = null;

-        // cancel scheduled cache cleanup
-        if (cleanCacheTimeout !== null) {
-          clearTimeout(cleanCacheTimeout);
-          cleanCacheTimeout = null;
-        }

         logger.trace({ endpoint: 'getStatus' }, 'cleanup cache');
     }

This approach ensures the cache is invalidated regularly without the need to manage timeouts manually.

Committable suggestion was skipped due to low confidence.

Copy link
Member Author

Choose a reason for hiding this comment

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

Rabbit misunderstood logic. We cleanup cache by block or in 3 mintues after it was generated by a user request. Interval doesn't work for our case.

Copy link
Contributor

Choose a reason for hiding this comment

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

@shumkov, thank you for the clarification. I apologize for the misunderstanding. I now understand that the cache is cleaned up by block or in 3 minutes after it was generated by a user request, so using an interval doesn't fit your requirements.

(・・?)


✏️ Learnings added
Learnt from: shumkov
PR: dashpay/platform#2155
File: packages/dapi/lib/grpcServer/handlers/platform/getStatusHandlerFactory.js:231-233
Timestamp: 2024-09-26T14:33:28.944Z
Learning: In this codebase, the cache is cleaned up by block or in 3 minutes after it was generated by a user request. Using 'setInterval' does not work for this case.

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.

shumkov

This comment was marked as duplicate.

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/dapi/lib/externalApis/tenderdash/BlockchainListener.js (2)

54-64: LGTM: Improved event handling and logging.

The changes enhance the visibility of transaction processing and simplify the NEW_BLOCK_QUERY event handling. These improvements align well with the PR objectives.

Consider adding more detailed logging for the new block event:

 this.wsClient.on(NEW_BLOCK_QUERY, (message) => {
-  this.processLogger.trace('Received new platform block');
+  this.processLogger.trace('Received new platform block', { height: message.block.header.height });

   this.emit(EVENTS.NEW_BLOCK, message)
 });

This will provide more context about each new block received.


75-78: LGTM: Well-organized subscription logic.

The new #subscribe() method effectively encapsulates the subscription logic, improving code organization and maintainability. It correctly subscribes to both new blocks and transactions, which is consistent with the class's purpose.

Consider adding error handling to make the subscription process more robust:

 #subscribe() {
-  this.wsClient.subscribe(NEW_BLOCK_QUERY);
-  this.wsClient.subscribe(TX_QUERY);
-  this.processLogger.debug('Subscribed to platform blockchain events');
+  try {
+    this.wsClient.subscribe(NEW_BLOCK_QUERY);
+    this.wsClient.subscribe(TX_QUERY);
+    this.processLogger.debug('Subscribed to platform blockchain events');
+  } catch (error) {
+    this.processLogger.error('Failed to subscribe to blockchain events', { error: error.message });
+  }
 }

This will ensure that any subscription errors are properly logged and handled.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 3c3fedd and c62880e.

📒 Files selected for processing (1)
  • packages/dapi/lib/externalApis/tenderdash/BlockchainListener.js (2 hunks)
🔇 Additional comments not posted (3)
packages/dapi/lib/externalApis/tenderdash/BlockchainListener.js (3)

16-21: LGTM: Improved logging with dedicated process logger.

The addition of a dedicated processLogger using logger.child() is a good practice. It allows for contextualized logging, which will enhance debugging and monitoring capabilities for the BlockchainListener class.


66-72: LGTM: Robust subscription handling implemented.

The addition of the 'connect' event listener and the check for an existing connection ensure that the client always subscribes to events, both on new connections and reconnections. This directly addresses the PR objective of ensuring the websocket client resubscribes upon reconnection.

The use of the private #subscribe() method improves code organization and maintainability.


Line range hint 1-78: Verify cache invalidation implementation.

The changes effectively implement subscription to new blocks and resubscription on reconnection. However, the PR objectives mention clearing the cache every three minutes to account for scenarios where a drive or tenderdash may become unavailable. This cache invalidation logic is not present in the current file.

Could you please clarify where the cache invalidation every three minutes is implemented? If it's not yet implemented, consider adding it to ensure all PR objectives are met.

To help locate the cache invalidation implementation, you can run the following script:

This script will search for potential cache invalidation logic and timer setups that might be used for the 3-minute cache clearing mentioned in the PR objectives.

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/dapi/test/integration/externalApis/tenderdash/BlockchainListener.spec.js (1)

87-98: Improved test case for subscription events

The changes to this test case are well-implemented and align with the PR objectives. By setting wsClientMock.isConnected = true and explicitly calling blockchainListener.start(), the test now more accurately reflects the expected behavior of the BlockchainListener.

The verification of both transaction and new block subscriptions is a good addition, ensuring that the listener is properly set up for all required events.

Consider adding a test case for when the WebSocket client is not connected to ensure the listener behaves correctly in that scenario as well.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between c62880e and f345597.

📒 Files selected for processing (2)
  • packages/dapi/lib/externalApis/tenderdash/BlockchainListener.js (2 hunks)
  • packages/dapi/test/integration/externalApis/tenderdash/BlockchainListener.spec.js (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/dapi/lib/externalApis/tenderdash/BlockchainListener.js
🔇 Additional comments (3)
packages/dapi/test/integration/externalApis/tenderdash/BlockchainListener.spec.js (3)

101-103: Re-enabled and improved test for new block emission

Great job on re-enabling this test case. The addition of blockchainListener.start() at the beginning of the test ensures that the listener is properly initialized before testing the new block emission. This change improves the reliability of the test and aligns well with the PR objectives.


114-115: Improved test initialization for transaction emission

The addition of blockchainListener.start() at the beginning of this test case is a good improvement. It ensures that the BlockchainListener is properly initialized before testing the transaction emission, which increases the reliability of the test. This change maintains consistency with the other test cases in the file.


Line range hint 1-126: Overall assessment of changes

The modifications to this test file are well-implemented and align closely with the PR objectives. The changes improve the test coverage, particularly for the start method of the BlockchainListener class. By ensuring that the WebSocket client is connected and explicitly calling blockchainListener.start() in each test, the reliability of the tests has been enhanced.

The re-enabled test for new block emission and the improved checks for both transaction and new block subscriptions contribute to a more comprehensive test suite. These changes will help ensure the proper functioning of the cache invalidation logic and the handling of new blocks as described in the PR objectives.

@QuantumExplorer QuantumExplorer merged commit 7832021 into v1.4-dev Sep 26, 2024
21 checks passed
@QuantumExplorer QuantumExplorer deleted the fix/dapi/get-status-cache branch September 26, 2024 17:04
shumkov added a commit that referenced this pull request Sep 27, 2024
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