-
Notifications
You must be signed in to change notification settings - Fork 29
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
base: master
Are you sure you want to change the base?
Conversation
WalkthroughThis 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
Possibly related PRs
Suggested labels
Poem
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? TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
Documentation and Community
|
Codecov ReportAll modified and coverable lines are covered by tests ✅
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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
Deploying sanguine-fe with Cloudflare Pages
|
There was a problem hiding this 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:
- Consider adding more detailed error messages in the
logger.Error
statements to provide better context for debugging.- 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
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. TheContent
field being aninterface{}
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. TheQuoteData
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. TheReason
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. TheRequestID
field allows for unique identification of the request, and theCreatedAt
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 forDestAmount
andRelayerAddress
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 theQuoteRequest
struct, but specifically tailored for relayer requests.
101-107
: LGTM!The
NewRelayerWsQuoteRequest
function is well-defined and serves its purpose of creating a newRelayerWsQuoteRequest
with a uniqueRequestID
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. TheRequestID
field allows for matching the response to the corresponding request, and theQuoteID
field provides a unique identifier for the quote. TheUpdatedAt
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 updatedparseDBQuote
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
tomodel.PutRelayerQuoteRequest
aligns with the updatedModifyQuote
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 thexsync
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 thewebsocket
package.The
websocket
package fromgithub.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 typewebsocket.Upgrader
to theQuoterAPIServer
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 theQuoterAPIServer
struct is necessary for managing WebSocket connections and sending quote requests to the connected clients. The use ofxsync.MapOf
provides synchronization for concurrent access to the map.
139-139
: LGTM!The initialization of the
wsClients
field with a new instance ofxsync.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
andPutQuoteRequestRoute
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 theQuoteRequestsRoute
endpoint is necessary for handling active quote requests via WebSocket. The use ofAuthMiddleware
ensures that only authenticated clients can access the endpoint, and theGetActiveRFQWebsocket
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 thePutUserQuoteRequest
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 thePutUserQuoteRequest
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 frommodel.PutQuoteRequest
tomodel.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 newWsClient
instance, stores it in thewsClients
map, and runs theWsClient
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 theWsClient
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
andquoteTypePassive
provides a clear and readable way to differentiate between active and passive quotes. Using constants helps avoid typos and ensures consistency throughout the codebase.
There was a problem hiding this 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 renamingWebsocketPort
toWebSocketPort
To adhere to standard terminology and improve readability, consider renaming
WebsocketPort
toWebSocketPort
. 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 theWebSocketPort
fieldTo maintain consistency and enhance code documentation, please add a comment for the
WebSocketPort
field in theConfig
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, nilservices/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 forPutUserQuoteRequest
methodThe
PutUserQuoteRequest
method has been added to theUnauthenticatedClient
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 inNewAuthenticatedClient
for better readabilityIn the
NewAuthenticatedClient
function, consider reordering the parameters to group related items together. Placing both URLs (rfqURL
andwsURL
) 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
andPutRelayerQuoteRequest
DetectedThe verification process has revealed an inconsistency in the codebase. While
PutRelayerQuoteRequest
is being used in some files, particularly in theapi
directory,PutQuoteRequest
is still present in multiple locations, especially in therelayer
andquoter
directories.Please address the following:
- Update all remaining instances of
PutQuoteRequest
toPutRelayerQuoteRequest
throughout the codebase.- Pay special attention to files in the
services/rfq/relayer/quoter/
directory, wherePutQuoteRequest
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 ofPutRelayerQuoteRequest
Across the CodebasePlease verify that all instances of
model.PutQuoteRequest
have been updated tomodel.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 sizeThe 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
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 toPutRelayerQuoteRequest
is appropriate.The change from
model.PutQuoteRequest
tomodel.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 ofPutRelayerQuoteRequest
in bulk quotes is correct.The
Quotes
slice now correctly utilizesmodel.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 usePutRelayerQuoteRequest
.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 newNewAuthenticatedClient
function signatureThe addition of
nil
as the third argument in theNewAuthenticatedClient
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 thegetAuthHeader
implementationIn 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 usingEIP191Auth
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 headerAuthMiddleware
integrates authentication into the request processing pipelineThe 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 differentLength 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 InitializedEnsure 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 toServerSuite
struct look appropriateThe addition of
relayerWallets
,wsPort
,originChainID
, anddestChainID
fields to theServerSuite
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 ofrelayerWallets
inNewServerSuite
is correctInitializing
relayerWallets
as an empty slice prepares the test suite for adding wallets during setup.
68-69
: Setting chain IDs inSetupTest
is appropriateAssigning
c.originChainID = 1
andc.destChainID = 42161
correctly sets up the test environment for Ethereum and Arbitrum networks.
Line range hint
80-93
: Configuration setup correctly includes WebSocket settingsThe
testConfig
now includesWebsocketPort
, andwsPortStr
is properly formatted. The configuration changes enhance the test server's WebSocket capabilities.
146-151
: Relayer wallets are initialized and appended correctlyInitializing
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 capabilityFunding each relayer wallet with 1 Ether provides sufficient balance for executing transactions during tests.
185-190
: Granting relayer role to all wallets is properly implementedLooping over
c.relayerWallets
to grant the relayer role ensures all test wallets have the necessary permissions. Error handling after each transaction is correctly enforced withc.Require().NoError(err)
.
} | ||
|
There was a problem hiding this comment.
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"` | |||
} |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
type wsClient struct { | ||
conn *websocket.Conn | ||
requestChan chan *model.RelayerWsQuoteRequest | ||
responseChan chan *model.RelayerWsQuoteResponse | ||
doneChan chan struct{} | ||
} |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
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{ |
There was a problem hiding this comment.
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
.
// 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) | ||
|
There was a problem hiding this comment.
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
// 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"}, | ||
} |
There was a problem hiding this comment.
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
services/rfq/api/rest/rfq_test.go
Outdated
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) | ||
} |
There was a problem hiding this comment.
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.
c.port = uint16(port) | ||
wsPort, err := freeport.GetFreePort() | ||
c.wsPort = uint16(wsPort) |
There was a problem hiding this comment.
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.
There was a problem hiding this 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 forrelayerAddr
In
parseDBQuote
, you're using a forced type assertionrelayerAddr.(string)
. While the context should guarantee thatrelayerAddr
is astring
, 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 ofrelayerAddr.(string)
.
248-250
: Use consistent naming for query parametersIn the
GetQuotes
function, consider ensuring consistent capitalization for query parameters. For example,originChainID
anddestChainId
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 inActiveQuoteRequest
While
RequestID
is marked as a primary key, consider adding indexes on fields likeUserAddress
,OriginChainID
, andStatus
to optimize query performance for operations that frequently filter by these fields.
181-193
: Consider indexing frequently queried fields inActiveQuoteResponse
Similarly, for the
ActiveQuoteResponse
struct, adding indexes on fields such asRelayerAddr
,RequestID
, andStatus
can improve database query performance for common operations.
Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
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 goodThe function correctly converts the user request into a database model and handles errors appropriately.
108-118
:InsertActiveQuoteResponse
implementation looks goodThe 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 correctThe 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
: UpdateparseDBQuote
function to ensure type safetySimilar to the previous comment, ensure safe type assertions when handling
relayerAddr
to prevent potential runtime panics.
165-179
:quoteResponseFromDbQuote
function implementation looks goodThe 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 appropriateThe addition of
GetActiveQuoteRequests
to theAPIDBReader
interface enhances functionality and aligns with the database access patterns.
243-249
: New methods inAPIDBWriter
interface are well-definedThe added methods for inserting and updating active quote requests and responses are correctly defined and enhance the database write operations.
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 | ||
} |
There was a problem hiding this comment.
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
.
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 | ||
} |
There was a problem hiding this comment.
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.
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 | ||
} |
There was a problem hiding this comment.
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
).
There was a problem hiding this 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
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 MigrationsThe addition of
&db.ActiveQuoteRequest{}
and&db.ActiveQuoteResponse{}
to theGetAllModels
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 HeaderIn 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
andyour_signer
are replaced with the actual tools or variables used in your environment.
104-111
: Validate Signature Logic in Authentication HeaderIn 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 thereqSigner.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. TheSignMessage
method consistently handles thehash
parameter, applying Keccak256 hashing when set totrue
. TheEncode
function in thesigner
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 goLength of output: 5616
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 | ||
} |
There was a problem hiding this comment.
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.
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 | |
} |
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 | ||
} | ||
}() |
There was a problem hiding this comment.
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.
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 | |
} |
reqChan <- &model.ActiveRFQMessage{ | ||
Op: rest.PongOp, | ||
} | ||
continue | ||
} |
There was a problem hiding this comment.
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.
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 | |
} |
There was a problem hiding this 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 ofclient.SendQuoteRequest
is not checked (errcheck)services/rfq/api/model/response.go (2)
89-90
: Document the optional fields inQuoteData
struct.The fields
DestAmount
andRelayerAddress
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
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 ofclient.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
tomodel.PutRelayerQuoteRequest
is appropriate and the test logic remains valid.
58-58
: LGTM!The change in the expected quotes type from
model.PutQuoteRequest
tomodel.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 theTestShouldProcess
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
, andTestGetDestAmount
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 theAuthMiddleware
ensures a secure and isolated WebSocket server. The defined routes and theGetActiveRFQWebsocket
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
andPutQuoteRequestRoute
endpoints provide a clear separation of concerns. TheChainsHeader
andAuthorizationHeader
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 theAuthMiddleware
allows for authentication and authorization of WebSocket connections. Retrieving theChainsHeader
from the request headers enables clients to specify the chains they are interested in during the WebSocket handshake. Unmarshaling the JSON value of theChainsHeader
into a slice of chain IDs allows for easy access to the requested chains, and appending them to thedestChainIDs
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 newWsClient
instance, storing it in thewsClients
map, and running the client allows for managing and tracking active connections. Cleaning up thewsClients
map when the connection is closed ensures proper resource management.
483-486
: LGTM!Defining constants
quoteTypeActive
andquoteTypePassive
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 requestservices/rfq/api/model/response.go (1)
106-113
: The factory functionNewRelayerWsQuoteRequest
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) |
There was a problem hiding this comment.
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 ofclient.SendQuoteRequest
is not checked (errcheck)
// WebSocket upgrader | ||
r.upgrader = websocket.Upgrader{ | ||
CheckOrigin: func(r *http.Request) bool { | ||
return true // TODO: Implement a more secure check | ||
}, | ||
} | ||
|
There was a problem hiding this comment.
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
},
}
// RFQ request without AuthMiddleware | ||
engine.PUT(PutQuoteRequestRoute, r.PutUserQuoteRequest) |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
// 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 |
There was a problem hiding this comment.
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.
// 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 |
There was a problem hiding this comment.
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.
// 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)
Summary by CodeRabbit
New Features
Bug Fixes
Documentation