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(rfq): active quoting API #3128

Draft
wants to merge 43 commits into
base: master
Choose a base branch
from
Draft

feat(rfq): active quoting API #3128

wants to merge 43 commits into from

Conversation

dwasse
Copy link
Collaborator

@dwasse dwasse commented Sep 13, 2024

Summary by CodeRabbit

  • New Features

    • Introduced enhanced quote management functionality, including active and passive RFQ handling.
    • Added WebSocket support for real-time quote requests and responses.
    • Implemented user quote request submission via a new API endpoint.
    • Added new methods for subscribing to active quotes and handling user quote requests.
    • Expanded model retrieval for more comprehensive quote management.
  • Bug Fixes

    • Updated request handling to ensure compatibility with new naming conventions for relayer quotes.
  • Documentation

    • Improved semantic clarity in request and response structures related to quotes.

@dwasse dwasse marked this pull request as draft September 13, 2024 21:00
Copy link
Contributor

coderabbitai bot commented Sep 13, 2024

Walkthrough

This pull request introduces significant changes to the RFQ (Request for Quote) service, including modifications to request handling structures, the addition of new types for user quote requests and responses, and enhancements to WebSocket functionality. The changes enhance the overall structure and handling of quote requests while maintaining existing functionalities across multiple files.

Changes

File Path Change Summary
services/rfq/api/client/client.go Modified PutQuote method to accept PutRelayerQuoteRequest, added SubscribeActiveQuotes, and updated NewAuthenticatedClient to accept a WebSocket URL. Introduced PutUserQuoteRequest in UnauthenticatedClient.
services/rfq/api/db/sql/base/base.go Expanded GetAllModels function to include ActiveQuoteRequest and ActiveQuoteResponse models for comprehensive model retrieval.
services/rfq/api/db/api_db.go Added new enumerations and structs for active quote requests and responses, enhancing database interaction capabilities.
services/rfq/api/rest/rfq.go Introduced handleActiveRFQ and handlePassiveRFQ functions for managing RFQs, determining the best quotes based on received data.
services/rfq/api/rest/rfq_test.go Added tests for active RFQ handling, including scenarios for single relayer responses, expired requests, multiple relayers, and fallback to passive quotes.
services/rfq/relayer/quoter/export_test.go Changed return type of GenerateQuotes method from PutQuoteRequest to PutRelayerQuoteRequest to reflect new quote handling.
services/rfq/relayer/quoter/quoter.go Transitioned all quote handling in Manager struct to use PutRelayerQuoteRequest, modifying related methods accordingly.
services/rfq/relayer/service/relayer.go Updated instantiation of RFQ API client in NewRelayer function to include an additional nil argument in the NewAuthenticatedClient call.

Possibly related PRs

  • [SLT-149] feat(rfq): relay rate limiting #2933: The changes in this PR involve modifications to the handling of quote requests, which may relate to the new PutUserQuoteRequest method introduced in the main PR, as both involve quote submission processes.

Suggested labels

M-docs, M-synapse-interface, javascript, Typescript, Sol

Poem

🐇 In the meadow where quotes do play,
A new name hops in, brightening the day.
With webs of requests, both active and bold,
The RFQ dance is a sight to behold!
So let’s cheer for the changes, both swift and spry,
For clarity blooms as the bunnies hop by! 🌼


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.

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.

@github-actions github-actions bot added go Pull requests that update Go code size/m labels Sep 13, 2024
Copy link

codecov bot commented Sep 13, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 36.35331%. Comparing base (da1678e) to head (c6db31f).
Report is 12 commits behind head on master.

Additional details and impacted files
@@                 Coverage Diff                 @@
##              master       #3128         +/-   ##
===================================================
- Coverage   38.22938%   36.35331%   -1.87607%     
===================================================
  Files            423         438         +15     
  Lines          24353       25530       +1177     
  Branches         119          82         -37     
===================================================
- Hits            9310        9281         -29     
- Misses         14301       15504       +1203     
- Partials         742         745          +3     
Flag Coverage Δ
opbot 0.48721% <ø> (-0.00541%) ⬇️
promexporter 6.82171% <ø> (?)
solidity ?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link

cloudflare-workers-and-pages bot commented Sep 13, 2024

Deploying sanguine-fe with  Cloudflare Pages  Cloudflare Pages

Latest commit: c6db31f
Status: ✅  Deploy successful!
Preview URL: https://3e2a6218.sanguine-fe.pages.dev
Branch Preview URL: https://feat-active-rfq-api.sanguine-fe.pages.dev

View logs

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)
services/rfq/api/rest/ws.go (1)

1-110: Excellent work on implementing the WebSocket client!

The code follows a clear and modular structure, separating the interface definition from the concrete implementation. The use of channels for asynchronous communication allows for concurrent handling of requests and responses, and the Run method effectively manages the WebSocket connection.

Some suggestions for further improvement:

  1. Consider adding more detailed error messages in the logger.Error statements to provide better context for debugging.
  2. Address the TODO comment on line 104 and implement a mechanism to keep the connection alive, such as sending periodic ping messages.

Overall, the implementation is solid and well-structured. Great job!

Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

Commits

Files that changed from the base of the PR and between da1678e and 8aa16cb.

Files selected for processing (7)
  • services/rfq/api/model/request.go (2 hunks)
  • services/rfq/api/model/response.go (2 hunks)
  • services/rfq/api/rest/handler.go (2 hunks)
  • services/rfq/api/rest/mocks/ws_client.go (1 hunks)
  • services/rfq/api/rest/rfq.go (1 hunks)
  • services/rfq/api/rest/server.go (9 hunks)
  • services/rfq/api/rest/ws.go (1 hunks)
Files skipped from review due to trivial changes (2)
  • services/rfq/api/model/request.go
  • services/rfq/api/rest/mocks/ws_client.go
Additional comments not posted (24)
services/rfq/api/rest/rfq.go (3)

13-29: LGTM!

The getBestQuote function correctly compares two quotes and returns the one with the higher destination amount. It handles nil pointers and converts the destination amounts from strings to big integers for comparison.


31-82: LGTM!

The handleActiveRFQ function correctly handles active RFQ by publishing the quote request to all connected clients, collecting responses within the expiration window, and returning the best quote. It uses appropriate synchronization primitives to ensure thread safety and correctness.


84-129: LGTM!

The handlePassiveRFQ function correctly handles passive RFQ by retrieving quotes from the database, validating the origin amount, calculating the effective destination amount after applying fixed fees, and returning the best quote. The logic is sound and the implementation is correct.

services/rfq/api/model/response.go (8)

52-56: LGTM!

The ActiveRFQMessage struct is well-defined and serves its purpose of representing WebSocket messages for Active RFQ. The Content field being an interface{} allows flexibility in the message content.


59-63: LGTM!

The PutUserQuoteRequest struct is well-defined and captures the necessary information for a user quote request. The QuoteData field being a separate struct allows for clean separation of concerns.


66-72: LGTM!

The PutUserQuoteResponse struct is well-defined and captures the necessary information for a response to a user quote request. The Reason field allows for providing additional context in case of failure.


75-79: LGTM!

The QuoteRequest struct is well-defined and captures the necessary information for a quote request. The RequestID field allows for unique identification of the request, and the CreatedAt field captures the timestamp of the request creation.


82-91: LGTM!

The QuoteData struct is well-defined and captures the necessary information for the data within a quote request. The use of pointers for DestAmount and RelayerAddress allows for optional fields.


94-98: LGTM!

The RelayerWsQuoteRequest struct is well-defined and captures the necessary information for a quote request to a relayer. It is similar to the QuoteRequest struct, but specifically tailored for relayer requests.


101-107: LGTM!

The NewRelayerWsQuoteRequest function is well-defined and serves its purpose of creating a new RelayerWsQuoteRequest with a unique RequestID and the current timestamp. It encapsulates the creation logic and provides a clean interface for creating new instances of the struct.


110-115: LGTM!

The RelayerWsQuoteResponse struct is well-defined and captures the necessary information for a response to a quote request. The RequestID field allows for matching the response to the corresponding request, and the QuoteID field provides a unique identifier for the quote. The UpdatedAt field captures the timestamp of the response.

services/rfq/api/rest/handler.go (2)

66-66: LGTM!

The change in request type from *model.PutQuoteRequest to *model.PutRelayerQuoteRequest aligns with the updated parseDBQuote function signature and indicates that the function is now specifically handling relayer quote requests. The rest of the function logic remains unchanged.


136-136: LGTM!

The change in request type from model.PutQuoteRequest to model.PutRelayerQuoteRequest aligns with the updated ModifyQuote function. The function is correctly parsing the fields from the new request type, and the rest of the function logic remains unchanged.

services/rfq/api/rest/server.go (11)

13-13: Verify the necessity and security of the xsync package.

The xsync package is a third-party package that provides synchronization primitives. Please ensure that:

  • The functionality provided by xsync is necessary and cannot be achieved using the standard library.
  • The package has been thoroughly vetted for security and performance.
  • The package is actively maintained and has a good track record.

24-24: Verify the necessity and security of the websocket package.

The websocket package from github.com/gorilla/websocket is a third-party package for WebSocket implementation. Please ensure that:

  • The package is necessary for the WebSocket functionality required in the project.
  • The package has been thoroughly vetted for security and performance.
  • The package is actively maintained and has a good track record.

53-53: LGTM!

The addition of the upgrader field of type websocket.Upgrader to the QuoterAPIServer struct is necessary for handling WebSocket connections in the API server.


64-67: LGTM!

The addition of the wsClients field of type *xsync.MapOf[string, WsClient] to the QuoterAPIServer struct is necessary for managing WebSocket connections and sending quote requests to the connected clients. The use of xsync.MapOf provides synchronization for concurrent access to the map.


139-139: LGTM!

The initialization of the wsClients field with a new instance of xsync.MapOf[WsClient]() is necessary to ensure that it is ready to be used for managing WebSocket connections.


166-169: LGTM!

The addition of the new API endpoints QuoteRequestsRoute and PutQuoteRequestRoute is in line with the PR objectives of introducing active quoting API. These endpoints are necessary for handling WebSocket connections and user quote requests.


198-202: LGTM!

The addition of the activeRFQGet route group with the QuoteRequestsRoute endpoint is necessary for handling active quote requests via WebSocket. The use of AuthMiddleware ensures that only authenticated clients can access the endpoint, and the GetActiveRFQWebsocket method is responsible for upgrading the HTTP connection to a WebSocket connection and managing the connection.


208-210: Verify the security implications of allowing unauthenticated access to the PutUserQuoteRequest method.

The addition of the route for handling RFQ requests without the AuthMiddleware allows users to submit quote requests without authentication. While this may be necessary for user convenience, it is important to ensure that the PutUserQuoteRequest method is secure and does not expose any sensitive information or allow unauthorized actions.

Please thoroughly review the implementation of the PutUserQuoteRequest method and ensure that it follows security best practices and does not introduce any vulnerabilities.


240-240: LGTM!

The renaming of the req variable from model.PutQuoteRequest to model.PutRelayerQuoteRequest is consistent with the alteration mentioned in the AI-generated summary. The renaming improves clarity and indicates that the request is specifically for relayer quotes.


400-438: LGTM!

The implementation of the GetActiveRFQWebsocket method follows the necessary steps for handling WebSocket connections for active quote requests. It upgrades the HTTP connection to a WebSocket connection, retrieves the relayer address from the context, ensures that only one connection per relayer is allowed, creates a new WsClient instance, stores it in the wsClients map, and runs the WsClient instance in a separate goroutine.

The use of the relayer address as the connection ID ensures uniqueness and allows for easy identification of the connected relayer. The check for existing connections prevents multiple connections from the same relayer. The creation and storage of the WsClient instance allows for managing the connection and sending quote requests to the connected relayer. Running the WsClient instance in a separate goroutine ensures that it can handle incoming messages concurrently.

Overall, the implementation looks solid and follows the necessary steps for handling WebSocket connections for active quote requests.


440-443: LGTM!

The use of constants quoteTypeActive and quoteTypePassive provides a clear and readable way to differentiate between active and passive quotes. Using constants helps avoid typos and ensures consistency throughout the codebase.

@github-actions github-actions bot added size/l and removed size/m labels Sep 17, 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: 16

Outside diff range and nitpick comments (10)
services/rfq/api/config/config.go (3)

30-30: Typo in field name: Consider renaming WebsocketPort to WebSocketPort

To adhere to standard terminology and improve readability, consider renaming WebsocketPort to WebSocketPort. The term "WebSocket" is typically written with a capital "S".

Apply this diff:

-	WebsocketPort   *string           `yaml:"websocket_port"`
+	WebSocketPort   *string           `yaml:"websocket_port"`

30-30: Add a comment for the WebSocketPort field

To maintain consistency and enhance code documentation, please add a comment for the WebSocketPort field in the Config struct.

Apply this diff:

 // Config is the configuration for the RFQ Quoter.
 type Config struct {
 	Database   DatabaseConfig `yaml:"database"`
 	OmniRPCURL string         `yaml:"omnirpc_url"`
 	// bridges is a map of chainid->address
 	Bridges         map[uint32]string `yaml:"bridges"`
 	Port            string            `yaml:"port"`
 	RelayAckTimeout time.Duration     `yaml:"relay_ack_timeout"`
 	MaxQuoteAge     time.Duration     `yaml:"max_quote_age"`
+	// WebSocketPort is the port for WebSocket connections
 	WebSocketPort   *string           `yaml:"websocket_port"`
 }

Line range hint 61-61: Typo in error message: 'unmarshall' should be 'unmarshal'

There's a typo in the error message on line 61. The term should be "unmarshal" with a single 'l'.

Apply this diff to correct the typo:

-return Config{}, fmt.Errorf("could not unmarshall config %s: %w", ellipsis.Shorten(string(input), 30), err)
+return Config{}, fmt.Errorf("could not unmarshal config %s: %w", ellipsis.Shorten(string(input), 30), err)
services/rfq/api/rest/ws.go (1)

51-51: Correct typo in comment: 'successfuly' → 'successfully'

There's a minor typo in the comment on line 51. Correcting it improves code readability.

Apply this diff to fix the typo:

         case resp = <-c.responseChan:
-            // successfuly received
+            // successfully received
             return resp, nil
services/rfq/api/client/client_test.go (2)

Line range hint 3-4: Fix typos in the TODO comment and consider addressing it.

The TODO comment contains typos and could be clarified for better understanding. Correcting the typos will improve readability and help in tracking the pending tasks.

Apply this diff to fix the typos:

-// TODO: @aurelius tese tests make a lot less sesnes w/ a composite index
+// TODO: @aurelius these tests make a lot less sense with a composite index

Line range hint 10-18: Consider reducing code duplication by creating a helper function for request initialization.

The initialization of model.PutRelayerQuoteRequest with the same fields is repeated across multiple test functions. Refactoring this into a helper function can enhance maintainability and reduce redundancy.

For example, you can define a helper function:

func newTestPutRelayerQuoteRequest() model.PutRelayerQuoteRequest {
	return model.PutRelayerQuoteRequest{
		OriginChainID:   1,
		OriginTokenAddr: "0xOriginTokenAddr",
		DestChainID:     42161,
		DestTokenAddr:   "0xDestTokenAddr",
		DestAmount:      "100",
		MaxOriginAmount: "200",
		FixedFee:        "10",
	}
}

Then, in your tests, you can use:

req := newTestPutRelayerQuoteRequest()

This approach reduces duplication and makes it easier to update the test data in the future.

Also applies to: 43-60, 101-112, 138-149

services/rfq/api/client/client.go (2)

47-47: Add documentation for PutUserQuoteRequest method

The PutUserQuoteRequest method has been added to the UnauthenticatedClient interface. To maintain code clarity and comply with Go documentation standards, consider adding a comment that describes the purpose and usage of this method.

Apply this diff to add the missing documentation:

+// PutUserQuoteRequest allows a user to submit a quote request without authentication.
+// It returns a response containing details of the submitted quote request.
 func (c unauthenticatedClient) PutUserQuoteRequest(ctx context.Context, q *model.PutUserQuoteRequest) (*model.PutUserQuoteResponse, error) {

68-68: Reorder parameters in NewAuthenticatedClient for better readability

In the NewAuthenticatedClient function, consider reordering the parameters to group related items together. Placing both URLs (rfqURL and wsURL) adjacent to each other can improve readability and maintain consistency.

Apply this diff to reorder the parameters:

-func NewAuthenticatedClient(metrics metrics.Handler, rfqURL string, wsURL *string, reqSigner signer.Signer) (AuthenticatedClient, error) {
+func NewAuthenticatedClient(metrics metrics.Handler, rfqURL string, reqSigner signer.Signer, wsURL *string) (AuthenticatedClient, error) {
services/rfq/api/rest/server_test.go (1)

Inconsistent Usage of PutQuoteRequest and PutRelayerQuoteRequest Detected

The verification process has revealed an inconsistency in the codebase. While PutRelayerQuoteRequest is being used in some files, particularly in the api directory, PutQuoteRequest is still present in multiple locations, especially in the relayer and quoter directories.

Please address the following:

  • Update all remaining instances of PutQuoteRequest to PutRelayerQuoteRequest throughout the codebase.
  • Pay special attention to files in the services/rfq/relayer/quoter/ directory, where PutQuoteRequest is still widely used.
  • Ensure that any related logic, function signatures, and method calls are updated accordingly.
  • After making the changes, thoroughly test the affected components to prevent any regression or unexpected behavior.

This inconsistency could lead to potential errors and should be resolved to maintain code integrity and prevent future issues.

Analysis chain

Line range hint 404-409: Ensure Consistent Usage of PutRelayerQuoteRequest Across the Codebase

Please verify that all instances of model.PutQuoteRequest have been updated to model.PutRelayerQuoteRequest throughout the codebase to prevent potential inconsistencies or errors.

You can run the following script to check for any remaining usages of PutQuoteRequest:

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Search for occurrences of 'PutQuoteRequest' in Go files.
# Expected result: No matches found, indicating all instances have been updated.

rg --type go 'PutQuoteRequest'

Length of output: 3287


Script:

#!/bin/bash
# Description: Search for occurrences of 'PutRelayerQuoteRequest' in Go files.
# Expected result: Find instances of the updated struct name.

rg --type go 'PutRelayerQuoteRequest'

Length of output: 1216

services/rfq/api/rest/rfq_test.go (1)

25-25: Consider adjusting the channel buffer size

The buffer size for reqChan is set to 1000. Unless there is a specific need for such a large buffer, consider reducing it to a more appropriate size to prevent unnecessary memory allocation.

Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 8aa16cb and c557a28.

Files selected for processing (12)
  • services/rfq/api/client/client.go (8 hunks)
  • services/rfq/api/client/client_test.go (4 hunks)
  • services/rfq/api/client/suite_test.go (1 hunks)
  • services/rfq/api/config/config.go (1 hunks)
  • services/rfq/api/model/response.go (2 hunks)
  • services/rfq/api/rest/auth.go (1 hunks)
  • services/rfq/api/rest/rfq.go (1 hunks)
  • services/rfq/api/rest/rfq_test.go (1 hunks)
  • services/rfq/api/rest/server.go (11 hunks)
  • services/rfq/api/rest/server_test.go (1 hunks)
  • services/rfq/api/rest/suite_test.go (5 hunks)
  • services/rfq/api/rest/ws.go (1 hunks)
Files skipped from review due to trivial changes (2)
  • services/rfq/api/rest/auth.go
  • services/rfq/api/rest/rfq.go
Files skipped from review as they are similar to previous changes (2)
  • services/rfq/api/model/response.go
  • services/rfq/api/rest/server.go
Additional comments not posted (14)
services/rfq/api/client/client_test.go (4)

Line range hint 10-18: Update to PutRelayerQuoteRequest is appropriate.

The change from model.PutQuoteRequest to model.PutRelayerQuoteRequest aligns with the new active quoting API and correctly updates the test to use the new request type.


Line range hint 43-60: Use of PutRelayerQuoteRequest in bulk quotes is correct.

The Quotes slice now correctly utilizes model.PutRelayerQuoteRequest, ensuring consistency with the updated API.


Line range hint 101-112: TestGetSpecificQuote updated appropriately with new request type.

The test now uses model.PutRelayerQuoteRequest, which is consistent with the recent changes to the quote request structure.


Line range hint 138-149: TestGetQuoteByRelayerAddress updated to use PutRelayerQuoteRequest.

The use of the updated request type in this test function aligns with the modifications made to support the active quoting API.

services/rfq/api/client/suite_test.go (1)

106-106: Update aligns with new NewAuthenticatedClient function signature

The addition of nil as the third argument in the NewAuthenticatedClient function call correctly reflects the updated function signature. The parameters are appropriately passed, and error handling is properly managed.

services/rfq/api/client/client.go (1)

94-111: Verify the correctness of the getAuthHeader implementation

In the getAuthHeader function, ensure that the message formatting and signature generation align with Ethereum's signing standards. The current implementation uses the Unix timestamp as the message, which may not be sufficient for authentication purposes and could be vulnerable to replay attacks.

Consider these actions:

  • Confirm that the message prefix and length are correctly applied according to Ethereum's standards.
  • Evaluate whether additional data (e.g., a nonce or unique request identifier) should be included to enhance security.
  • Ensure that both the client and server correctly interpret the signed message.

Run the following script to review where getAuthHeader is used and how the signature is validated on the server side:

Verification successful

Authentication implementation verified and secure

The getAuthHeader function and its corresponding server-side validation using EIP191Auth are correctly implemented. The system uses EIP-191 signatures with timestamps, providing a secure authentication mechanism that protects against replay attacks.

Key points:

  • Client-side: getAuthHeader generates the authorization header
  • Server-side: EIP191Auth validates the header
  • AuthMiddleware integrates authentication into the request processing pipeline

The implementation is consistent between client and server, ensuring proper authentication for all requests.

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find all usages of `getAuthHeader` and related server-side validation code.

# Find calls to `getAuthHeader` in the client code
rg --type go "getAuthHeader"

# Find server-side code that validates the auth header
rg --type go "ValidateAuthHeader"  # Replace with the actual validation function name if different

Length of output: 365


Script:

#!/bin/bash
# Description: Investigate auth header generation and validation

# Get more context around getAuthHeader calls
rg --type go -C 5 "getAuthHeader"

# Search for server-side code handling requests and authentication
rg --type go "(http.HandlerFunc|http.Handler|ServeHTTP|authentication|authorize)"

# Look for functions processing headers in server code
rg --type go "(Header|header).Get.*auth"

Length of output: 251648


Script:

#!/bin/bash
# Description: Investigate server-side authentication and auth header processing

# Search for authentication-related code in the server implementation
rg --type go "func.*auth|middleware.*auth|EIP191Auth"

# Look for functions that might be processing the auth header
rg --type go "func.*(Header|header).*auth|auth.*header"

# Investigate the EIP191Auth implementation
rg --type go "type.*EIP191Auth|func.*EIP191Auth"

Length of output: 197592

services/rfq/api/rest/server_test.go (1)

Line range hint 406-410: Confirm All Required Fields Are Initialized

Ensure that all mandatory fields for model.PutRelayerQuoteRequest are properly initialized. Missing any required fields could lead to request failures or unexpected behavior.

services/rfq/api/rest/suite_test.go (7)

43-43: New fields added to ServerSuite struct look appropriate

The addition of relayerWallets, wsPort, originChainID, and destChainID fields to the ServerSuite struct enhances the test suite's ability to manage multiple wallets and chain IDs for comprehensive testing.

Also applies to: 47-49


56-57: Initialization of relayerWallets in NewServerSuite is correct

Initializing relayerWallets as an empty slice prepares the test suite for adding wallets during setup.


68-69: Setting chain IDs in SetupTest is appropriate

Assigning c.originChainID = 1 and c.destChainID = 42161 correctly sets up the test environment for Ethereum and Arbitrum networks.


Line range hint 80-93: Configuration setup correctly includes WebSocket settings

The testConfig now includes WebsocketPort, and wsPortStr is properly formatted. The configuration changes enhance the test server's WebSocket capabilities.


146-151: Relayer wallets are initialized and appended correctly

Initializing c.relayerWallets with the test wallet and appending additional random wallets prepares the test suite for multi-wallet scenarios.


153-155: Funding relayer wallets ensures transaction capability

Funding each relayer wallet with 1 Ether provides sufficient balance for executing transactions during tests.


185-190: Granting relayer role to all wallets is properly implemented

Looping over c.relayerWallets to grant the relayer role ensures all test wallets have the necessary permissions. Error handling after each transaction is correctly enforced with c.Require().NoError(err).

Comment on lines 30 to 31
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Consider adding a GetWebSocketPort method with a default value

To provide a default WebSocket port when none is specified in the configuration, consider adding a GetWebSocketPort method, similar to GetRelayAckTimeout and GetMaxQuoteAge.

Add the following code:

const defaultWebSocketPort = ":8080"

// GetWebSocketPort returns the WebSocket port, defaulting to ":8080" if not set
func (c Config) GetWebSocketPort() string {
    if c.WebSocketPort == "" {
        return defaultWebSocketPort
    }
    return c.WebSocketPort
}

This ensures your application has a default port for WebSocket connections, improving robustness.

@@ -26,6 +26,7 @@
Port string `yaml:"port"`
RelayAckTimeout time.Duration `yaml:"relay_ack_timeout"`
MaxQuoteAge time.Duration `yaml:"max_quote_age"`
WebsocketPort *string `yaml:"websocket_port"`
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider using string instead of *string for WebSocketPort

Unless there's a specific need to distinguish between an unset value (nil) and an empty string, it might be simpler to declare WebSocketPort as a string instead of a *string. This would be consistent with the Port field and simplify field access.

Apply this diff:

-WebSocketPort   *string           `yaml:"websocket_port"`
+WebSocketPort   string            `yaml:"websocket_port"`

Committable suggestion was skipped due to low confidence.

for {
_, msg, err := c.conn.ReadMessage()
if err != nil {
logger.Error("Error reading websocket message: %s", err)
Copy link
Contributor

Choose a reason for hiding this comment

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

Standardize logging by using key-value pairs consistently

The logging statements use a mix of formatted strings and key-value pairs, which can lead to inconsistencies in log outputs. Adopting a consistent logging style improves maintainability and log readability.

Update the logging statements to use key-value pairs:

-            logger.Error("Error reading websocket message: %s", err)
+            logger.Error("Error reading WebSocket message", "error", err)

-            logger.Error("Error marshalling quote request: %s", err)
+            logger.Error("Error marshalling quote request", "error", err)

-            logger.Error("Error unmarshalling websocket message: %s", err)
+            logger.Error("Error unmarshalling WebSocket message", "error", err)

-            logger.Error("Unexpected websocket message content for send_quote", "content", rfqMsg.Content)
+            logger.Error("Unexpected WebSocket message content for send_quote", "content", rfqMsg.Content)

-            logger.Errorf("Received unexpected operation from relayer: %s", rfqMsg.Op)
+            logger.Error("Received unexpected operation from relayer", "operation", rfqMsg.Op)

Also applies to: 93-93, 105-105, 115-115, 122-122

Comment on lines +21 to +26
type wsClient struct {
conn *websocket.Conn
requestChan chan *model.RelayerWsQuoteRequest
responseChan chan *model.RelayerWsQuoteResponse
doneChan chan struct{}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Re-evaluate channel buffer sizes to prevent potential resource exhaustion

The channels requestChan, responseChan, and messageChan are initialized with a buffer size of 1000. While buffering helps prevent blocking, large buffer sizes can lead to increased memory consumption under heavy load. It's advisable to assess the expected load and adjust the buffer sizes accordingly to optimize resource usage.

Consider tuning the buffer sizes:

     requestChan  chan *model.RelayerWsQuoteRequest
     responseChan chan *model.RelayerWsQuoteResponse
     doneChan     chan struct{}
 }
 
 func newWsClient(conn *websocket.Conn) *wsClient {
     return &wsClient{
         conn:         conn,
-        requestChan:  make(chan *model.RelayerWsQuoteRequest, 1000),
-        responseChan: make(chan *model.RelayerWsQuoteResponse, 1000),
+        requestChan:  make(chan *model.RelayerWsQuoteRequest, 100),
+        responseChan: make(chan *model.RelayerWsQuoteResponse, 100),
         doneChan:     make(chan struct{}),
     }
 }
 
 func (c *wsClient) Run(ctx context.Context) (err error) {
-    messageChan := make(chan []byte, 1000)
+    messageChan := make(chan []byte, 100)

Adjust the buffer sizes based on the application's performance testing and expected concurrency levels.

Also applies to: 31-33, 69-69

Op: requestQuoteOp,
Content: json.RawMessage(rawData),
}
c.conn.WriteJSON(msg)
Copy link
Contributor

Choose a reason for hiding this comment

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

Add error handling for c.conn.WriteJSON(msg) to prevent silent failures

Currently, the error returned by c.conn.WriteJSON(msg) is not being checked. If an error occurs during the write operation, it could lead to silent failures, and the client may not be aware of the issue. It's important to handle this error to ensure the reliability of the WebSocket communication.

Apply this diff to handle the error:

             msg := model.ActiveRFQMessage{
                 Op:      requestQuoteOp,
                 Content: json.RawMessage(rawData),
             }
-            c.conn.WriteJSON(msg)
+            err = c.conn.WriteJSON(msg)
+            if err != nil {
+                logger.Error("Error writing JSON to WebSocket", "error", err)
+            }
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
c.conn.WriteJSON(msg)
err = c.conn.WriteJSON(msg)
if err != nil {
logger.Error("Error writing JSON to WebSocket", "error", err)
}

putData := model.PutQuoteRequest{
OriginChainID: 1,
OriginTokenAddr: "0xOriginTokenAddr",
putData := model.PutRelayerQuoteRequest{
Copy link
Contributor

Choose a reason for hiding this comment

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

Update Function Name to Reflect New Request Structure

The function sendPutQuoteRequest now utilizes model.PutRelayerQuoteRequest instead of model.PutQuoteRequest. To enhance clarity and maintain consistency, consider renaming the function to sendPutRelayerQuoteRequest.

Comment on lines +70 to +76
// Create a user client
userWallet, err := wallet.FromRandom()
c.Require().NoError(err)
userSigner := localsigner.NewSigner(userWallet.PrivateKey())
userClient, err := client.NewAuthenticatedClient(metrics.Get(), url, nil, userSigner)
c.Require().NoError(err)

Copy link
Contributor

Choose a reason for hiding this comment

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

Refactor duplicate client initialization code in test setup

The code for initializing the user client is repeated across multiple test functions. Consider refactoring this into a helper function or a setup method to reduce duplication and improve maintainability.

Also applies to: 133-138, 193-198, 269-274, 354-359

Comment on lines +83 to +95
// Prepare a user quote request
userRequestAmount := big.NewInt(1_000_000)
userQuoteReq := &model.PutUserQuoteRequest{
Data: model.QuoteData{
OriginChainID: originChainID,
OriginTokenAddr: originTokenAddr,
DestChainID: destChainID,
DestTokenAddr: destTokenAddr,
OriginAmount: userRequestAmount.String(),
ExpirationWindow: 10_000,
},
QuoteTypes: []string{"active"},
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Refactor repeated test data setup for common variables

The initialization of common variables like originChainID, originTokenAddr, destChainID, destTokenAddr, and userQuoteReq is repeated in several test functions. Consider extracting these into shared helper functions or using setup methods to reduce code duplication and enhance readability.

Also applies to: 146-157, 206-217, 281-313, 366-398

Comment on lines 63 to 123
func (c *ServerSuite) TestActiveRFQSingleRelayer() {
// Start the API server
c.startQuoterAPIServer()

url := fmt.Sprintf("http://localhost:%d", c.port)
wsURL := fmt.Sprintf("ws://localhost:%d", c.wsPort)

// Create a user client
userWallet, err := wallet.FromRandom()
c.Require().NoError(err)
userSigner := localsigner.NewSigner(userWallet.PrivateKey())
userClient, err := client.NewAuthenticatedClient(metrics.Get(), url, nil, userSigner)
c.Require().NoError(err)

// Common variables
originChainID := 1
originTokenAddr := "0x1111111111111111111111111111111111111111"
destChainID := 2
destTokenAddr := "0x2222222222222222222222222222222222222222"

// Prepare a user quote request
userRequestAmount := big.NewInt(1_000_000)
userQuoteReq := &model.PutUserQuoteRequest{
Data: model.QuoteData{
OriginChainID: originChainID,
OriginTokenAddr: originTokenAddr,
DestChainID: destChainID,
DestTokenAddr: destTokenAddr,
OriginAmount: userRequestAmount.String(),
ExpirationWindow: 10_000,
},
QuoteTypes: []string{"active"},
}

// Prepare the relayer quote response
originAmount := userRequestAmount.String()
destAmount := new(big.Int).Sub(userRequestAmount, big.NewInt(1000)).String()
quoteResp := &model.RelayerWsQuoteResponse{
Data: model.QuoteData{
OriginChainID: originChainID,
OriginTokenAddr: originTokenAddr,
DestChainID: destChainID,
DestTokenAddr: destTokenAddr,
DestAmount: &destAmount,
OriginAmount: originAmount,
},
}
respCtx, cancel := context.WithCancel(c.GetTestContext())
defer cancel()
runMockRelayer(c, respCtx, c.relayerWallets[0], quoteResp, url, wsURL)

// Submit the user quote request
userQuoteResp, err := userClient.PutUserQuoteRequest(c.GetTestContext(), userQuoteReq)
c.Require().NoError(err)

// Assert the response
c.Assert().True(userQuoteResp.Success)
c.Assert().Equal("active", userQuoteResp.QuoteType)
c.Assert().Equal(destAmount, *userQuoteResp.Data.DestAmount)
c.Assert().Equal(originAmount, userQuoteResp.Data.OriginAmount)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Add test case for handling invalid or malformed relayer responses

Currently, the test TestActiveRFQSingleRelayer assumes that the relayer always sends valid responses. Consider adding a test case to simulate the relayer sending invalid or malformed responses to ensure that the API correctly handles such scenarios without unexpected failures.

Comment on lines 75 to +77
c.port = uint16(port)
wsPort, err := freeport.GetFreePort()
c.wsPort = uint16(wsPort)
Copy link
Contributor

Choose a reason for hiding this comment

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

Error handling missing after freeport.GetFreePort() call

The error returned by freeport.GetFreePort() when obtaining port is not checked before use. This could lead to issues if the function fails.

Apply this diff to add the missing error check:

 port, err := freeport.GetFreePort()
+c.Require().NoError(err)
 c.port = uint16(port)

 wsPort, err := freeport.GetFreePort()
 c.wsPort = uint16(wsPort)
+c.Require().NoError(err)

This ensures that any errors are caught immediately after each function call.

Committable suggestion was skipped due to low confidence.

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 (4)
services/rfq/api/rest/handler.go (2)

Line range hint 66-70: Ensure safe type assertion for relayerAddr

In parseDBQuote, you're using a forced type assertion relayerAddr.(string). While the context should guarantee that relayerAddr is a string, it's safer to perform a type assertion check to prevent possible panics.

Consider modifying the code to check the type:

relayerAddrStr, ok := relayerAddr.(string)
if !ok {
	return nil, fmt.Errorf("relayerAddr is not a string")
}

And then use relayerAddrStr in place of relayerAddr.(string).


248-250: Use consistent naming for query parameters

In the GetQuotes function, consider ensuring consistent capitalization for query parameters. For example, originChainID and destChainId have inconsistent capitalization (ID vs. Id). This could lead to confusion or errors when clients interact with the API.

services/rfq/api/db/api_db.go (2)

145-157: Consider indexing frequently queried fields in ActiveQuoteRequest

While RequestID is marked as a primary key, consider adding indexes on fields like UserAddress, OriginChainID, and Status to optimize query performance for operations that frequently filter by these fields.


181-193: Consider indexing frequently queried fields in ActiveQuoteResponse

Similarly, for the ActiveQuoteResponse struct, adding indexes on fields such as RelayerAddr, RequestID, and Status can improve database query performance for common operations.

Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

Commits

Files that changed from the base of the PR and between c557a28 and 8cae8e4.

Files selected for processing (10)
  • services/rfq/api/db/activequoterequeststatus_string.go (1 hunks)
  • services/rfq/api/db/activequoteresponsestatus_string.go (1 hunks)
  • services/rfq/api/db/api_db.go (4 hunks)
  • services/rfq/api/db/sql/base/store.go (2 hunks)
  • services/rfq/api/model/response.go (2 hunks)
  • services/rfq/api/model/util.go (0 hunks)
  • services/rfq/api/rest/handler.go (4 hunks)
  • services/rfq/api/rest/rfq.go (1 hunks)
  • services/rfq/api/rest/rfq_test.go (1 hunks)
  • services/rfq/api/rest/server.go (11 hunks)
Files not reviewed due to no reviewable changes (1)
  • services/rfq/api/model/util.go
Files skipped from review due to trivial changes (2)
  • services/rfq/api/db/activequoterequeststatus_string.go
  • services/rfq/api/db/activequoteresponsestatus_string.go
Files skipped from review as they are similar to previous changes (4)
  • services/rfq/api/model/response.go
  • services/rfq/api/rest/rfq.go
  • services/rfq/api/rest/rfq_test.go
  • services/rfq/api/rest/server.go
Additional comments not posted (7)
services/rfq/api/db/sql/base/store.go (3)

83-93: InsertActiveQuoteRequest implementation looks good

The function correctly converts the user request into a database model and handles errors appropriately.


108-118: InsertActiveQuoteResponse implementation looks good

The function properly converts the relayer response into a database model and inserts it into the database, handling errors as expected.


133-145: GetActiveQuoteRequests function implementation is correct

The function retrieves active quote requests, optionally filtering by statuses, and handles query construction appropriately.

services/rfq/api/rest/handler.go (2)

Line range hint 136-159: Update parseDBQuote function to ensure type safety

Similar to the previous comment, ensure safe type assertions when handling relayerAddr to prevent potential runtime panics.


165-179: quoteResponseFromDbQuote function implementation looks good

The function correctly converts a database quote to an API response model, including proper formatting of the timestamp.

services/rfq/api/db/api_db.go (2)

231-231: GetActiveQuoteRequests method signature is appropriate

The addition of GetActiveQuoteRequests to the APIDBReader interface enhances functionality and aligns with the database access patterns.


243-249: New methods in APIDBWriter interface are well-defined

The added methods for inserting and updating active quote requests and responses are correctly defined and enhance the database write operations.

Comment on lines +96 to +105
func (s *Store) UpdateActiveQuoteRequestStatus(ctx context.Context, requestID string, status db.ActiveQuoteRequestStatus) error {
result := s.db.WithContext(ctx).
Model(&db.ActiveQuoteRequest{}).
Where("request_id = ?", requestID).
Update("status", status)
if result.Error != nil {
return fmt.Errorf("could not update active quote request status: %w", result.Error)
}
return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Handle case when no records are updated in UpdateActiveQuoteRequestStatus

Currently, the function updates the status without checking if any records were actually updated. If the requestID does not exist, this could silently fail. Consider checking result.RowsAffected to verify that the update affected a record and handle the case where RowsAffected == 0.

Comment on lines +121 to +130
func (s *Store) UpdateActiveQuoteResponseStatus(ctx context.Context, quoteID string, status db.ActiveQuoteResponseStatus) error {
result := s.db.WithContext(ctx).
Model(&db.ActiveQuoteResponse{}).
Where("quote_id = ?", quoteID).
Update("status", status)
if result.Error != nil {
return fmt.Errorf("could not update active quote response status: %w", result.Error)
}
return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Handle case when no records are updated in UpdateActiveQuoteResponseStatus

Similar to UpdateActiveQuoteRequestStatus, consider checking result.RowsAffected to ensure that the quoteID exists and that the status update was successful.

Comment on lines +160 to +177
func FromUserRequest(req *model.PutUserQuoteRequest, requestID string) (*ActiveQuoteRequest, error) {
originAmount, err := decimal.NewFromString(req.Data.OriginAmount)
if err != nil {
return nil, fmt.Errorf("invalid origin amount: %w", err)
}
return &ActiveQuoteRequest{
RequestID: requestID,
UserAddress: req.UserAddress,
OriginChainID: uint64(req.Data.OriginChainID),
OriginTokenAddr: req.Data.OriginTokenAddr,
DestChainID: uint64(req.Data.DestChainID),
DestTokenAddr: req.Data.DestTokenAddr,
OriginAmount: originAmount,
ExpirationWindow: time.Duration(req.Data.ExpirationWindow),
CreatedAt: time.Now(),
Status: Received,
}, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Verify unit conversion for ExpirationWindow

In the FromUserRequest function, ExpirationWindow is set using time.Duration(req.Data.ExpirationWindow). Please ensure that req.Data.ExpirationWindow is in nanoseconds because time.Duration interprets its value as nanoseconds. If ExpirationWindow is provided in seconds or milliseconds, you need to multiply by the appropriate time constant (e.g., time.Second or time.Millisecond).

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

Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 8cae8e4 and fe7a774.

Files selected for processing (6)
  • services/rfq/api/client/client.go (8 hunks)
  • services/rfq/api/db/sql/base/base.go (1 hunks)
  • services/rfq/api/rest/rfq.go (1 hunks)
  • services/rfq/api/rest/rfq_test.go (1 hunks)
  • services/rfq/api/rest/suite_test.go (5 hunks)
  • services/rfq/api/rest/ws.go (1 hunks)
Files skipped from review as they are similar to previous changes (3)
  • services/rfq/api/rest/rfq.go
  • services/rfq/api/rest/rfq_test.go
  • services/rfq/api/rest/suite_test.go
Additional comments not posted (3)
services/rfq/api/db/sql/base/base.go (1)

28-28: Include New Models in Migrations

The addition of &db.ActiveQuoteRequest{} and &db.ActiveQuoteResponse{} to the GetAllModels function ensures these models are included in the database migrations. This is necessary for the proper functioning of features relying on these models.

services/rfq/api/client/client.go (2)

197-200: Handle Errors When Generating Authentication Header

In the SubscribeActiveQuotes method, if an error occurs while obtaining the authentication header, it could be due to issues with the signer or context. Ensure that the error is properly handled and consider providing additional context or retry logic if appropriate.

Run the following script to check for potential issues with the authentication header generation:

Ensure that your_auth_header_tool and your_signer are replaced with the actual tools or variables used in your environment.


104-111: Validate Signature Logic in Authentication Header

In the getAuthHeader function, the message being signed includes the length of the timestamp string and the timestamp itself. Ensure that this aligns with the expected authentication scheme and that the reqSigner.SignMessage correctly handles the message format. Incorrect signing could lead to authentication failures.

Run the following script to verify the signature logic:

Replace your_signer_tool with the actual tool or command used for signing and verifying messages.

Verification successful

Signature Logic and Authentication Header Format Verified

The signature logic in getAuthHeader aligns with the implementations found in the codebase. The SignMessage method consistently handles the hash parameter, applying Keccak256 hashing when set to true. The Encode function in the signer package properly combines the R, S, and V components of the signature.

The authentication header format, which combines a timestamp with the encoded signature, follows a common and secure practice for request signing. This approach helps prevent replay attacks by including a time-based component.

Based on the available information, the signature logic and authentication header format appear to be correctly implemented and should not lead to authentication failures.

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that the authentication header is correctly formatted and the signature is valid.

# Expected: The output should show successful verification of the signature.

echo "Testing signature verification..."
current_time=$(date +%s)
message_length=${#current_time}
data="\x19Ethereum Signed Message:\n${message_length}${current_time}"

signature=$(your_signer_tool sign-message --message "$data")
verification_result=$(your_signer_tool verify-signature --message "$data" --signature "$signature")

echo "Verification result: $verification_result"

Length of output: 640


Script:

#!/bin/bash
# Find the implementation of SignMessage method
echo "Searching for SignMessage implementation:"
ast-grep --lang go --pattern 'func ($_ $_) SignMessage($_, $_, $_) ($_, $_) {
  $$$
}'

echo "\nSearching for Encode function in signer package:"
ast-grep --lang go --pattern 'func Encode($_) $_ {
  $$$
}'

echo "\nSearching for authentication scheme documentation or usage:"
rg -n -A 5 "authentication.*scheme" --type go

Length of output: 5616

Comment on lines +137 to +145
Op: PingOp,
}
err := c.conn.WriteJSON(pingMsg)
if err != nil {
logger.Error("Error sending ping message: %s", err)
c.conn.Close()
close(c.doneChan)
return err
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Ensure Error Handling for Ping Messages is Consistent

When sending ping messages, if an error occurs during c.conn.WriteJSON(pingMsg), the error is logged and the connection is closed. However, the error message uses formatted strings inconsistently. To maintain consistency and improve log readability, use key-value pairs for logging.

Apply this diff to standardize the logging:

 err := c.conn.WriteJSON(pingMsg)
 if err != nil {
-    logger.Error("Error sending ping message: %s", err)
+    logger.Error("Error sending ping message", "error", err)
     c.conn.Close()
     close(c.doneChan)
     return err
 }
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
Op: PingOp,
}
err := c.conn.WriteJSON(pingMsg)
if err != nil {
logger.Error("Error sending ping message: %s", err)
c.conn.Close()
close(c.doneChan)
return err
}
Op: PingOp,
}
err := c.conn.WriteJSON(pingMsg)
if err != nil {
logger.Error("Error sending ping message", "error", err)
c.conn.Close()
close(c.doneChan)
return err
}

Comment on lines +71 to +88
messageChan := make(chan []byte, 1000)
pingTicker := time.NewTicker(PingPeriod)
defer pingTicker.Stop()

lastPong := time.Now()

// Goroutine to read messages from WebSocket and send to channel
go func() {
defer close(messageChan)
for {
_, msg, err := c.conn.ReadMessage()
if err != nil {
logger.Error("Error reading websocket message: %s", err)
return
}
messageChan <- msg
}
}()
Copy link
Contributor

Choose a reason for hiding this comment

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

Handle Closure of Message Channel to Prevent Goroutine Leak

In the Run method, when the read goroutine encounters an error and exits, it closes the messageChan. However, in the main loop, there is no check to handle the closure of messageChan, which can lead to a goroutine leak or panic when attempting to read from a closed channel.

Apply this diff to handle the closed messageChan:

 func (c *wsClient) Run(ctx context.Context) (err error) {
     messageChan := make(chan []byte, 1000)
     pingTicker := time.NewTicker(PingPeriod)
     defer pingTicker.Stop()

     lastPong := time.Now()

     // Goroutine to read messages from WebSocket and send to channel
     go func() {
         defer close(messageChan)
         for {
             _, msg, err := c.conn.ReadMessage()
             if err != nil {
                 logger.Error("Error reading websocket message", "error", err)
                 return
             }
             messageChan <- msg
         }
     }()

     for {
         select {
         case <-ctx.Done():
             c.conn.Close()
             close(c.doneChan)
             return nil
-        case msg := <-messageChan:
+        case msg, ok := <-messageChan:
+            if !ok {
+                c.conn.Close()
+                close(c.doneChan)
+                return fmt.Errorf("message channel closed")
+            }
             var rfqMsg model.ActiveRFQMessage
             err = json.Unmarshal(msg, &rfqMsg)
             if err != nil {
                 logger.Error("Error unmarshalling websocket message", "error", err)
                 continue
             }
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
messageChan := make(chan []byte, 1000)
pingTicker := time.NewTicker(PingPeriod)
defer pingTicker.Stop()
lastPong := time.Now()
// Goroutine to read messages from WebSocket and send to channel
go func() {
defer close(messageChan)
for {
_, msg, err := c.conn.ReadMessage()
if err != nil {
logger.Error("Error reading websocket message: %s", err)
return
}
messageChan <- msg
}
}()
messageChan := make(chan []byte, 1000)
pingTicker := time.NewTicker(PingPeriod)
defer pingTicker.Stop()
lastPong := time.Now()
// Goroutine to read messages from WebSocket and send to channel
go func() {
defer close(messageChan)
for {
_, msg, err := c.conn.ReadMessage()
if err != nil {
logger.Error("Error reading websocket message", "error", err)
return
}
messageChan <- msg
}
}()
for {
select {
case <-ctx.Done():
c.conn.Close()
close(c.doneChan)
return nil
case msg, ok := <-messageChan:
if !ok {
c.conn.Close()
close(c.doneChan)
return fmt.Errorf("message channel closed")
}
var rfqMsg model.ActiveRFQMessage
err = json.Unmarshal(msg, &rfqMsg)
if err != nil {
logger.Error("Error unmarshalling websocket message", "error", err)
continue
}

Comment on lines +254 to +258
reqChan <- &model.ActiveRFQMessage{
Op: rest.PongOp,
}
continue
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Handle Potential Blocking When Sending Pong Response

In the SubscribeActiveQuotes method, when automatically responding to a Ping message, the code sends a Pong message to reqChan. If reqChan is full or closed, this operation could block indefinitely or cause a panic. To prevent this, use a non-blocking select statement that also listens to ctx.Done() to handle context cancellation and ensure safe operation.

Apply this diff to handle potential blocking:

 if rfqMsg.Op == rest.PingOp {
-    reqChan <- &model.ActiveRFQMessage{
-        Op: rest.PongOp,
-    }
+    select {
+    case reqChan <- &model.ActiveRFQMessage{
+        Op: rest.PongOp,
+    }:
+    case <-ctx.Done():
+        return
+    case <-time.After(time.Second):
+        logger.Warn("Timed out sending Pong response")
+    }
     continue
 }
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
reqChan <- &model.ActiveRFQMessage{
Op: rest.PongOp,
}
continue
}
select {
case reqChan <- &model.ActiveRFQMessage{
Op: rest.PongOp,
}:
case <-ctx.Done():
return
case <-time.After(time.Second):
logger.Warn("Timed out sending Pong response")
}
continue
}

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

Outside diff range and nitpick comments (3)
services/rfq/api/rest/rfq.go (1)

17-45: Consider adding more detailed logging statements.

To aid with debugging and tracing, consider adding more detailed logging statements throughout the function. For example:

  • Log when publishing the quote request to clients
  • Log the number of responses received
  • Log the best quote details
  • Log any errors encountered during the process
Tools
GitHub Check: Lint (services/rfq)

[failure] 21-21:
Error return value of client.SendQuoteRequest is not checked (errcheck)

services/rfq/api/model/response.go (2)

89-90: Document the optional fields in QuoteData struct.

The fields DestAmount and RelayerAddress are pointers, indicating they may be optional. Consider adding comments to clarify their usage and whether they can be nil.

Apply this diff to add comments:

 type QuoteData struct {
     OriginChainID    int     `json:"origin_chain_id"`
     DestChainID      int     `json:"dest_chain_id"`
     OriginTokenAddr  string  `json:"origin_token_addr"`
     DestTokenAddr    string  `json:"dest_token_addr"`
     OriginAmount     string  `json:"origin_amount"`
     ExpirationWindow int64   `json:"expiration_window"`
+    // DestAmount is the optional destination amount.
     DestAmount       *string `json:"dest_amount"`
+    // RelayerAddress is the optional relayer address.
     RelayerAddress   *string `json:"relayer_address"`
 }

50-121: Consider adding unit tests for the new functionality.

To ensure the reliability of the newly added code, please add unit tests covering the new structs and functions, especially for serialization/deserialization and any business logic.

Tools
GitHub Check: Lint (services/rfq)

[failure] 50-50:
Comment should end in a period (godot)


[failure] 73-73:
Comment should end in a period (godot)


[failure] 80-80:
Comment should end in a period (godot)

Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

Commits

Files that changed from the base of the PR and between c5e9a00 and c6db31f.

Files selected for processing (6)
  • services/rfq/api/db/api_db.go (4 hunks)
  • services/rfq/api/model/response.go (2 hunks)
  • services/rfq/api/rest/rfq.go (1 hunks)
  • services/rfq/api/rest/rfq_test.go (1 hunks)
  • services/rfq/api/rest/server.go (11 hunks)
  • services/rfq/relayer/quoter/quoter_test.go (3 hunks)
Files skipped from review as they are similar to previous changes (2)
  • services/rfq/api/db/api_db.go
  • services/rfq/api/rest/rfq_test.go
Additional context used
GitHub Check: Lint (services/rfq)
services/rfq/api/model/response.go

[failure] 50-50:
Comment should end in a period (godot)


[failure] 73-73:
Comment should end in a period (godot)


[failure] 80-80:
Comment should end in a period (godot)

services/rfq/api/rest/rfq.go

[failure] 21-21:
Error return value of client.SendQuoteRequest is not checked (errcheck)

Additional comments not posted (14)
services/rfq/api/rest/rfq.go (1)

161-206: LGTM!

The function logic is correct, and the implementation looks good. It effectively handles passive RFQ requests by retrieving relevant quotes from the database, computing the destination amounts, and determining the best quote.

services/rfq/relayer/quoter/quoter_test.go (4)

30-30: LGTM!

The change in the expected quotes type from model.PutQuoteRequest to model.PutRelayerQuoteRequest is appropriate and the test logic remains valid.


58-58: LGTM!

The change in the expected quotes type from model.PutQuoteRequest to model.PutRelayerQuoteRequest is appropriate at both instances in the test function. The test logic remains valid and covers the necessary scenarios for native token quote generation.

Also applies to: 85-85


Line range hint 105-134: No changes made to the TestShouldProcess test function.

The test function remains unchanged, indicating that the behavior of the ShouldProcess method is still valid and covers the necessary scenarios.


Line range hint 136-283: No changes made to the remaining test functions.

The test functions TestIsProfitable, TestGetOriginAmount, and TestGetDestAmount remain unchanged, indicating that the behavior of the corresponding methods is still valid and covers the necessary scenarios.

services/rfq/api/rest/server.go (8)

142-143: LGTM!

The wsClients field is a suitable data structure for maintaining a registry of active WebSocket connections. Using a synchronized map ensures thread-safety when accessing the map concurrently.


145-160: LGTM!

The initialization of the WebSocket server is properly handled when the WebsocketPort configuration is set. The use of a separate Gin engine and the application of the AuthMiddleware ensures a secure and isolated WebSocket server. The defined routes and the GetActiveRFQWebsocket method provide the necessary endpoints for handling active quote requests via WebSocket.


187-194: LGTM!

Defining constants for route paths and headers related to WebSocket functionality improves code readability and maintainability. The QuoteRequestsRoute and PutQuoteRequestRoute endpoints provide a clear separation of concerns. The ChainsHeader and AuthorizationHeader allow clients to specify the chains and provide authorization during the WebSocket handshake.


240-253: LGTM!

Starting the main HTTP server and the WebSocket server separately allows them to run concurrently. Printing the starting message provides visibility into the server's configuration. Running the WebSocket server in a separate goroutine ensures that it doesn't block the main HTTP server, and handling errors from the WebSocket server is important for proper error reporting and graceful shutdown.


Line range hint 272-304: LGTM!

Adding the QuoteRequestsRoute case in the AuthMiddleware allows for authentication and authorization of WebSocket connections. Retrieving the ChainsHeader from the request headers enables clients to specify the chains they are interested in during the WebSocket handshake. Unmarshaling the JSON value of the ChainsHeader into a slice of chain IDs allows for easy access to the requested chains, and appending them to the destChainIDs slice ensures that the authentication and authorization checks are performed for each requested chain.


443-481: LGTM!

The GetActiveRFQWebsocket method properly handles WebSocket connections for active quote requests. Upgrading the HTTP connection to a WebSocket connection allows for real-time communication between the client and the server. Using the relayer address as the ID for the connection ensures that each relayer can have only one active connection, and checking for an existing connection prevents multiple connections from the same relayer. Creating a new WsClient instance, storing it in the wsClients map, and running the client allows for managing and tracking active connections. Cleaning up the wsClients map when the connection is closed ensures proper resource management.


483-486: LGTM!

Defining constants quoteTypeActive and quoteTypePassive improves code readability and maintainability. The constants provide a clear distinction between active and passive quotes and reduce the chances of typos and inconsistencies by avoiding hardcoded strings.


488-541: LGTM!

The PutUserQuoteRequest method properly handles user requests for quotes. Binding the request JSON to a struct ensures that the request data is properly validated and accessible. Generating a unique request ID allows for tracking and correlating the request

services/rfq/api/model/response.go (1)

106-113: The factory function NewRelayerWsQuoteRequest is implemented correctly.

The function correctly initializes a new RelayerWsQuoteRequest with the provided data and current timestamp.

// publish the quote request to all connected clients
relayerReq := model.NewRelayerWsQuoteRequest(request.Data, requestID)
r.wsClients.Range(func(key string, client WsClient) bool {
client.SendQuoteRequest(ctx, relayerReq)
Copy link
Contributor

Choose a reason for hiding this comment

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

Handle the error returned by client.SendQuoteRequest.

The static analysis tool has flagged that the error returned by client.SendQuoteRequest is not being checked. Ignoring errors can lead to unexpected behavior and make it difficult to diagnose issues.

Check the error and handle it appropriately. For example:

err := client.SendQuoteRequest(ctx, relayerReq)
if err != nil {
    logger.Errorf("Error sending quote request to client: %v", err)
}
Tools
GitHub Check: Lint (services/rfq)

[failure] 21-21:
Error return value of client.SendQuoteRequest is not checked (errcheck)

Comment on lines +231 to +237
// WebSocket upgrader
r.upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true // TODO: Implement a more secure check
},
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Security Issue: Implement a proper origin check for WebSocket connections.

The upgrader is configured with a CheckOrigin function that always returns true, effectively disabling the origin check and allowing connections from any origin. This can be a security risk as it allows any website to establish a WebSocket connection to the server.

It is strongly recommended to implement a proper origin check to ensure that only allowed origins can establish a WebSocket connection. The CheckOrigin function should validate the origin against a whitelist of allowed origins.

r.upgrader = websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool {
        origin := r.Header.Get("Origin")
        // Check if the origin is in the whitelist
        for _, allowedOrigin := range allowedOrigins {
            if origin == allowedOrigin {
                return true
            }
        }
        return false
    },
}

Comment on lines +228 to +229
// RFQ request without AuthMiddleware
engine.PUT(PutQuoteRequestRoute, r.PutUserQuoteRequest)
Copy link
Contributor

Choose a reason for hiding this comment

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

Tip

Codebase Verification

Security improvements needed for unauthenticated PutQuoteRequestRoute endpoint

While basic input validation is present, the lack of authentication and rate limiting for this endpoint poses potential security risks:

  • Implement rate limiting to prevent abuse and potential DoS attacks.
  • Consider adding authentication or at least some form of API key validation to restrict access.
  • Enhance input validation beyond basic JSON parsing (e.g., check for required fields, value ranges).
  • Address the TODO for implementing a more secure origin check in the WebSocket upgrader.

These measures will help protect the endpoint from potential misuse while maintaining its accessibility for legitimate requests.

Analysis chain

Verify the security implications of allowing unauthenticated access to the PutQuoteRequestRoute endpoint.

Not applying the AuthMiddleware to the PutQuoteRequestRoute endpoint allows unauthenticated users to submit quote requests. This may be intentional, but it's important to consider the security implications and ensure that appropriate validations and rate limiting are in place to prevent abuse or DoS attacks.

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify if the `PutQuoteRequestRoute` endpoint has appropriate security measures.

# Test 1: Check if the `PutUserQuoteRequest` method performs necessary validations on the user input.
ast-grep --lang go --pattern $'func (r *QuoterAPIServer) PutUserQuoteRequest(c *gin.Context) {
  $$$
  err := c.BindJSON(&req)
  if err != nil {
    $$$
  }
  $$$
}'

# Test 2: Check if there are any rate limiting middleware or mechanisms applied to the `PutQuoteRequestRoute` endpoint.
rg --type go $'engine\.PUT\(PutQuoteRequestRoute, r\.PutUserQuoteRequest\)' -A 10

Length of output: 4121

CreatedAt time.Time `json:"created_at"`
}

// QuoteData represents the data within a quote request
Copy link
Contributor

Choose a reason for hiding this comment

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

Add a period at the end of the comment.

Per the linting rule godot, comments should end with a period.

Apply this diff:

-// QuoteData represents the data within a quote request
+// QuoteData represents the data within a quote request.
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
// QuoteData represents the data within a quote request
// QuoteData represents the data within a quote request.
Tools
GitHub Check: Lint (services/rfq)

[failure] 80-80:
Comment should end in a period (godot)

@@ -41,3 +46,76 @@
// Contracts is a map of chain id to contract address
Contracts map[uint32]string `json:"contracts"`
}

// ActiveRFQMessage represents the general structure of WebSocket messages for Active RFQ
Copy link
Contributor

Choose a reason for hiding this comment

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

Add a period at the end of the comment.

Per the linting rule godot, comments should end with a period.

Apply this diff to fix the comment:

-// ActiveRFQMessage represents the general structure of WebSocket messages for Active RFQ
+// ActiveRFQMessage represents the general structure of WebSocket messages for Active RFQ.
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
// ActiveRFQMessage represents the general structure of WebSocket messages for Active RFQ
// ActiveRFQMessage represents the general structure of WebSocket messages for Active RFQ.
Tools
GitHub Check: Lint (services/rfq)

[failure] 50-50:
Comment should end in a period (godot)

Data QuoteData `json:"data"`
}

// QuoteRequest represents a request for a quote
Copy link
Contributor

Choose a reason for hiding this comment

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

Add a period at the end of the comment.

Per the linting rule godot, comments should end with a period.

Apply this diff:

-// QuoteRequest represents a request for a quote
+// QuoteRequest represents a request for a quote.
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
// QuoteRequest represents a request for a quote
// QuoteRequest represents a request for a quote.
Tools
GitHub Check: Lint (services/rfq)

[failure] 73-73:
Comment should end in a period (godot)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
go Pull requests that update Go code size/l
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant