@@ -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.
3255type 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.
503542func 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