Skip to content

Commit 5eaf8a9

Browse files
committed
rfq: preserve context in oracle query errors
Introduces a structured QueryError that wraps an arbitrary error (usually from a price oracle) with arbitrary context, and adjusts query{Buy,Sell}FromPriceOracle to return these errors where appropriate. Also adjusts createCustomRejectErr to handle QueryErrors, instead of OracleErrors.
1 parent 6ff7217 commit 5eaf8a9

File tree

1 file changed

+71
-32
lines changed

1 file changed

+71
-32
lines changed

rfq/negotiator.go

Lines changed: 71 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,29 @@ const (
2828
DefaultAcceptPriceDeviationPpm = 50_000
2929
)
3030

31+
// QueryError represents an error with additional context about the price
32+
// oracle query that led to it.
33+
type QueryError struct {
34+
// Err is the error returned from a query attempt, possibly from a
35+
// price oracle.
36+
Err error
37+
38+
// Context is the context of the price oracle query that led to the
39+
// error.
40+
Context string
41+
}
42+
43+
// Error returns a human-readable version of the QueryError, implementing the
44+
// main error interface.
45+
func (err *QueryError) Error() string {
46+
// If there's no context, just fall back to the wrapped error.
47+
if err.Context == "" {
48+
return err.Err.Error()
49+
}
50+
// Otherwise prepend the context.
51+
return err.Context + ": " + err.Err.Error()
52+
}
53+
3154
// NegotiatorCfg holds the configuration for the negotiator.
3255
type NegotiatorCfg struct {
3356
// PriceOracle is the price oracle that the negotiator will use to
@@ -142,21 +165,29 @@ func (n *Negotiator) queryBuyFromPriceOracle(assetSpecifier asset.Specifier,
142165
counterparty, metadata, intent,
143166
)
144167
if err != nil {
145-
return nil, fmt.Errorf("failed to query price oracle for "+
146-
"buy price: %w", err)
168+
return nil, &QueryError{
169+
Err: err,
170+
Context: "failed to query price oracle for buy price",
171+
}
147172
}
148173

149174
// Now we will check for an error in the response from the price oracle.
150-
// If present, we will simply relay it.
175+
// If present, we will relay it with context.
151176
if oracleResponse.Err != nil {
152-
return nil, oracleResponse.Err
177+
return nil, &QueryError{
178+
Err: oracleResponse.Err,
179+
Context: "failed to query price oracle for buy price",
180+
}
153181
}
154182

155183
// By this point, the price oracle did not return an error or a buy
156184
// price. We will therefore return an error.
157185
if oracleResponse.AssetRate.Rate.ToUint64() == 0 {
158-
return nil, fmt.Errorf("price oracle did not specify a " +
159-
"buy price")
186+
return nil, &QueryError{
187+
Err: errors.New("price oracle didn't specify " +
188+
"a price"),
189+
Context: "failed to query price oracle for buy price",
190+
}
160191
}
161192

162193
// TODO(ffranr): Check that the buy price is reasonable.
@@ -277,21 +308,29 @@ func (n *Negotiator) querySellFromPriceOracle(assetSpecifier asset.Specifier,
277308
counterparty, metadata, intent,
278309
)
279310
if err != nil {
280-
return nil, fmt.Errorf("failed to query price oracle for "+
281-
"sell price: %w", err)
311+
return nil, &QueryError{
312+
Err: err,
313+
Context: "failed to query price oracle for sell price",
314+
}
282315
}
283316

284317
// Now we will check for an error in the response from the price oracle.
285-
// If present, we will simply relay it.
318+
// If present, we will relay it with context.
286319
if oracleResponse.Err != nil {
287-
return nil, oracleResponse.Err
320+
return nil, &QueryError{
321+
Err: oracleResponse.Err,
322+
Context: "failed to query price oracle for sell price",
323+
}
288324
}
289325

290326
// By this point, the price oracle did not return an error or a sell
291327
// price. We will therefore return an error.
292328
if oracleResponse.AssetRate.Rate.Coefficient.ToUint64() == 0 {
293-
return nil, fmt.Errorf("price oracle did not specify an " +
294-
"asset to BTC rate")
329+
return nil, &QueryError{
330+
Err: errors.New("price oracle didn't specify " +
331+
"a price"),
332+
Context: "failed to query price oracle for sell price",
333+
}
295334
}
296335

297336
// TODO(ffranr): Check that the sell price is reasonable.
@@ -501,28 +540,28 @@ func (n *Negotiator) HandleIncomingSellRequest(
501540
// createCustomRejectErr creates a RejectErr with code 0 and a custom message
502541
// based on an error response from a price oracle.
503542
func createCustomRejectErr(err error) rfqmsg.RejectErr {
543+
var queryError *QueryError
544+
// Check if the error we've received is the expected QueryError, and
545+
// return an opaque rejection error if not.
546+
if !errors.As(err, &queryError) {
547+
return rfqmsg.ErrUnknownReject
548+
}
549+
504550
var oracleError *OracleError
551+
// Check if the QueryError contains the expected OracleError, and
552+
// return an opaque rejection error if not.
553+
if !errors.As(queryError, &oracleError) {
554+
return rfqmsg.ErrUnknownReject
555+
}
505556

506-
if errors.As(err, &oracleError) {
507-
// The error is of the expected type, so switch on the error
508-
// code returned by the oracle. If the code is benign, then the
509-
// RejectErr will simply relay the oracle's message. Otherwise,
510-
// we'll return an opaque rejection message.
511-
switch oracleError.Code {
512-
// The rejection message will state that the oracle doesn't
513-
// support the asset.
514-
case UnsupportedAssetOracleErrorCode:
515-
msg := oracleError.Msg
516-
return rfqmsg.ErrRejectWithCustomMsg(msg)
517-
518-
// The rejection message will be opaque, with the error
519-
// unspecified.
520-
default:
521-
return rfqmsg.ErrUnknownReject
522-
}
523-
} else {
524-
// The error is of an unexpected type, so just return an opaque
525-
// error message.
557+
switch oracleError.Code {
558+
// The price oracle has indicated that it doesn't support the asset,
559+
// so return a rejection error indicating that.
560+
case UnsupportedAssetOracleErrorCode:
561+
return rfqmsg.ErrRejectWithCustomMsg(oracleError.Msg)
562+
// The error code is either unspecified or unknown, so return an
563+
// opaque rejection error.
564+
default:
526565
return rfqmsg.ErrUnknownReject
527566
}
528567
}

0 commit comments

Comments
 (0)