-
Notifications
You must be signed in to change notification settings - Fork 33
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-relayer): relayer supports active quoting #3198
Changes from 5 commits
d93e20f
481f043
fdbc865
77c51e8
d10d56d
d2b1701
f06a64b
f6300a1
2646149
ea61286
dcd264a
02bf53c
a83253f
6febf7b
3ce1bd3
460f5ac
9ec49cb
92b49ec
e85ff62
f4ed5b5
0c0b562
1742fe3
0d7a7c4
4828dfc
ab9513d
a2a2079
69ed171
fbccdf8
4b098f9
135f1ac
b4969e9
bf3adaa
82ed6bb
896d52d
0018269
c3f0eb3
50b969e
64389c4
0c07fe4
719361a
26c9174
ccd24b3
9688fa7
b98ca8c
739472d
7d7f6df
6a5590f
56afeff
bdb7539
ab15e5d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -3,6 +3,7 @@ package quoter | |||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
import ( | ||||||||||||||||||||||||||||||
"context" | ||||||||||||||||||||||||||||||
"encoding/json" | ||||||||||||||||||||||||||||||
"errors" | ||||||||||||||||||||||||||||||
"fmt" | ||||||||||||||||||||||||||||||
"math/big" | ||||||||||||||||||||||||||||||
|
@@ -31,6 +32,7 @@ import ( | |||||||||||||||||||||||||||||
"github.com/synapsecns/sanguine/ethergo/signer/signer" | ||||||||||||||||||||||||||||||
rfqAPIClient "github.com/synapsecns/sanguine/services/rfq/api/client" | ||||||||||||||||||||||||||||||
"github.com/synapsecns/sanguine/services/rfq/api/model" | ||||||||||||||||||||||||||||||
"github.com/synapsecns/sanguine/services/rfq/api/rest" | ||||||||||||||||||||||||||||||
"github.com/synapsecns/sanguine/services/rfq/relayer/inventory" | ||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
|
@@ -42,6 +44,8 @@ var logger = log.Logger("quoter") | |||||||||||||||||||||||||||||
type Quoter interface { | ||||||||||||||||||||||||||||||
// SubmitAllQuotes submits all quotes to the RFQ API. | ||||||||||||||||||||||||||||||
SubmitAllQuotes(ctx context.Context) (err error) | ||||||||||||||||||||||||||||||
// SubscribeActiveRFQ subscribes to the RFQ websocket API. | ||||||||||||||||||||||||||||||
SubscribeActiveRFQ(ctx context.Context) (err error) | ||||||||||||||||||||||||||||||
// ShouldProcess determines if a quote should be processed. | ||||||||||||||||||||||||||||||
// We do this by either saving all quotes in-memory, and refreshing via GetSelfQuotes() through the API | ||||||||||||||||||||||||||||||
// The first comparison is does bridge transaction OriginChainID+TokenAddr match with a quote + DestChainID+DestTokenAddr, then we look to see if we have enough amount to relay it + if the price fits our bounds (based on that the Relayer is relaying the destination token for the origin) | ||||||||||||||||||||||||||||||
|
@@ -251,6 +255,83 @@ func (m *Manager) SubmitAllQuotes(ctx context.Context) (err error) { | |||||||||||||||||||||||||||||
return m.prepareAndSubmitQuotes(ctx, inv) | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// SubscribeActiveRFQ subscribes to the RFQ websocket API. | ||||||||||||||||||||||||||||||
// This function is blocking and will run until the context is cancelled. | ||||||||||||||||||||||||||||||
func (m *Manager) SubscribeActiveRFQ(ctx context.Context) (err error) { | ||||||||||||||||||||||||||||||
chainIDs := []int{} | ||||||||||||||||||||||||||||||
for chainID := range m.config.Chains { | ||||||||||||||||||||||||||||||
chainIDs = append(chainIDs, chainID) | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
req := model.SubscribeActiveRFQRequest{ | ||||||||||||||||||||||||||||||
ChainIDs: chainIDs, | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
reqChan := make(chan *model.ActiveRFQMessage) | ||||||||||||||||||||||||||||||
respChan, err := m.rfqClient.SubscribeActiveQuotes(ctx, &req, reqChan) | ||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||
return fmt.Errorf("error subscribing to active quotes: %w", err) | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
for { | ||||||||||||||||||||||||||||||
select { | ||||||||||||||||||||||||||||||
case <-ctx.Done(): | ||||||||||||||||||||||||||||||
return | ||||||||||||||||||||||||||||||
case msg := <-respChan: | ||||||||||||||||||||||||||||||
resp, err := m.generateActiveRFQ(ctx, msg) | ||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||
return fmt.Errorf("error generating active RFQ message: %w", err) | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
Comment on lines
+294
to
+296
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Handle errors without terminating the subscription loop Returning an error here will exit the Apply this diff to adjust error handling: resp, err := m.generateActiveRFQ(ctx, msg)
if err != nil {
- return fmt.Errorf("error generating active RFQ message: %w", err)
+ span.RecordError(err)
+ logger.Error("error generating active RFQ message", "error", err)
+ continue
}
|
||||||||||||||||||||||||||||||
respChan <- resp | ||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential deadlock: Sending responses to In the select loop, you are sending responses back to Apply this diff to fix the issue: case msg := <-respChan:
resp, err := m.generateActiveRFQ(ctx, msg)
if err != nil {
return fmt.Errorf("error generating active RFQ message: %w", err)
}
- respChan <- resp
+ reqChan <- resp 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// getActiveRFQ handles an active RFQ message. | ||||||||||||||||||||||||||||||
func (m *Manager) generateActiveRFQ(ctx context.Context, msg *model.ActiveRFQMessage) (resp *model.ActiveRFQMessage, err error) { | ||||||||||||||||||||||||||||||
if msg.Op != rest.RequestQuoteOp { | ||||||||||||||||||||||||||||||
return nil, nil | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
inv, err := m.inventoryManager.GetCommittableBalances(ctx, inventory.SkipDBCache()) | ||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||
return nil, fmt.Errorf("error getting committable balances: %w", err) | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
var rfqRequest model.WsRFQRequest | ||||||||||||||||||||||||||||||
err = json.Unmarshal(msg.Content, &rfqRequest) | ||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||
return nil, fmt.Errorf("error unmarshalling quote data: %w", err) | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
quoteInput := QuoteInput{ | ||||||||||||||||||||||||||||||
OriginChainID: rfqRequest.Data.OriginChainID, | ||||||||||||||||||||||||||||||
DestChainID: rfqRequest.Data.DestChainID, | ||||||||||||||||||||||||||||||
OriginTokenAddr: common.HexToAddress(rfqRequest.Data.OriginTokenAddr), | ||||||||||||||||||||||||||||||
DestTokenAddr: common.HexToAddress(rfqRequest.Data.DestTokenAddr), | ||||||||||||||||||||||||||||||
OriginBalance: inv[rfqRequest.Data.OriginChainID][common.HexToAddress(rfqRequest.Data.OriginTokenAddr)], | ||||||||||||||||||||||||||||||
DestBalance: inv[rfqRequest.Data.DestChainID][common.HexToAddress(rfqRequest.Data.DestTokenAddr)], | ||||||||||||||||||||||||||||||
Comment on lines
+334
to
+335
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Possible nil map access without existence checks Accessing Apply this diff to safely access the map entries: quoteInput := QuoteInput{
OriginChainID: rfqRequest.Data.OriginChainID,
DestChainID: rfqRequest.Data.DestChainID,
OriginTokenAddr: common.HexToAddress(rfqRequest.Data.OriginTokenAddr),
DestTokenAddr: common.HexToAddress(rfqRequest.Data.DestTokenAddr),
- OriginBalance: inv[rfqRequest.Data.OriginChainID][common.HexToAddress(rfqRequest.Data.OriginTokenAddr)],
- DestBalance: inv[rfqRequest.Data.DestChainID][common.HexToAddress(rfqRequest.Data.DestTokenAddr)],
+ OriginBalance: func() *big.Int {
+ if chainBalances, ok := inv[rfqRequest.Data.OriginChainID]; ok {
+ return chainBalances[common.HexToAddress(rfqRequest.Data.OriginTokenAddr)]
+ }
+ return nil
+ }(),
+ DestBalance: func() *big.Int {
+ if chainBalances, ok := inv[rfqRequest.Data.DestChainID]; ok {
+ return chainBalances[common.HexToAddress(rfqRequest.Data.DestTokenAddr)]
+ }
+ return nil
+ }(),
}
if quoteInput.OriginBalance == nil || quoteInput.DestBalance == nil {
return nil, fmt.Errorf("insufficient inventory balances for the provided chain IDs and token addresses")
} 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
rawQuote, err := m.generateQuote(ctx, quoteInput) | ||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||
return nil, fmt.Errorf("error generating quote: %w", err) | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
rfqResp := model.WsRFQResponse{ | ||||||||||||||||||||||||||||||
RequestID: rfqRequest.RequestID, | ||||||||||||||||||||||||||||||
DestAmount: rawQuote.DestAmount, | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
respBytes, err := json.Marshal(rfqResp) | ||||||||||||||||||||||||||||||
if err != nil { | ||||||||||||||||||||||||||||||
return nil, fmt.Errorf("error serializing response: %w", err) | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
resp = &model.ActiveRFQMessage{ | ||||||||||||||||||||||||||||||
Op: rest.SendQuoteOp, | ||||||||||||||||||||||||||||||
Content: respBytes, | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
return resp, nil | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// GetPrice gets the price of a token. | ||||||||||||||||||||||||||||||
func (m *Manager) GetPrice(parentCtx context.Context, tokenName string) (_ float64, err error) { | ||||||||||||||||||||||||||||||
ctx, span := m.metricsHandler.Tracer().Start(parentCtx, "GetPrice") | ||||||||||||||||||||||||||||||
|
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.
Adding a new method to public interface
Quoter
may introduce breaking changesAdding
SubscribeActiveRFQ(ctx context.Context) (err error)
to theQuoter
interface can break existing implementations. Any external packages implementingQuoter
will now fail to compile until they implement this new method. Consider the impact on external dependencies and possibly introduce a new interface to extendQuoter
.