Skip to content

Commit 10a9192

Browse files
committed
rfq: OracleResponse uses new AssetRate type
This commit does the following: * Make use of AssetRate in the OracleResponse type. * Modify functions queryBidFromPriceOracle and queryAskFromPriceOracle such that they return the new AssetRate types. Added TODOs noting that New*AcceptFromRequest functions will be modified similarly in future work.
1 parent 2a870aa commit 10a9192

File tree

5 files changed

+70
-57
lines changed

5 files changed

+70
-57
lines changed

rfq/negotiator.go

+28-34
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,7 @@ func NewNegotiator(cfg NegotiatorCfg) (*Negotiator, error) {
107107
// an appropriate outgoing response message which should be sent to the peer.
108108
func (n *Negotiator) queryBidFromPriceOracle(peer route.Vertex,
109109
assetId *asset.ID, assetGroupKey *btcec.PublicKey, assetAmount uint64,
110-
assetRateHint fn.Option[rfqmsg.AssetRate]) (*rfqmath.BigIntFixedPoint,
111-
uint64, error) {
110+
assetRateHint fn.Option[rfqmsg.AssetRate]) (*rfqmsg.AssetRate, error) {
112111

113112
// TODO(ffranr): Optionally accept a peer's proposed ask price as an
114113
// arg to this func and pass it to the price oracle. The price oracle
@@ -124,28 +123,28 @@ func (n *Negotiator) queryBidFromPriceOracle(peer route.Vertex,
124123
ctx, assetId, assetGroupKey, assetAmount, assetRateHint,
125124
)
126125
if err != nil {
127-
return nil, 0, fmt.Errorf("failed to query price oracle for "+
126+
return nil, fmt.Errorf("failed to query price oracle for "+
128127
"bid: %w", err)
129128
}
130129

131130
// Now we will check for an error in the response from the price oracle.
132131
// If present, we will convert it to a string and return it as an error.
133132
if oracleResponse.Err != nil {
134-
return nil, 0, fmt.Errorf("failed to query price oracle for "+
133+
return nil, fmt.Errorf("failed to query price oracle for "+
135134
"bid price: %s", oracleResponse.Err)
136135
}
137136

138137
// By this point, the price oracle did not return an error or a bid
139138
// price. We will therefore return an error.
140-
if oracleResponse.AssetRate.Coefficient.ToUint64() == 0 {
141-
return nil, 0, fmt.Errorf("price oracle did not specify a " +
139+
if oracleResponse.AssetRate.Rate.ToUint64() == 0 {
140+
return nil, fmt.Errorf("price oracle did not specify a " +
142141
"bid price")
143142
}
144143

145144
// TODO(ffranr): Check that the bid price is reasonable.
146145
// TODO(ffranr): Ensure that the expiry time is valid and sufficient.
147146

148-
return &oracleResponse.AssetRate, oracleResponse.Expiry, nil
147+
return &oracleResponse.AssetRate, nil
149148
}
150149

151150
// HandleOutgoingBuyOrder handles an outgoing buy order by constructing buy
@@ -166,7 +165,7 @@ func (n *Negotiator) HandleOutgoingBuyOrder(buyOrder BuyOrder) error {
166165

167166
if n.cfg.PriceOracle != nil {
168167
// Query the price oracle for a bid price.
169-
rate, expiryUnix, err := n.queryBidFromPriceOracle(
168+
assetRate, err := n.queryBidFromPriceOracle(
170169
*buyOrder.Peer, buyOrder.AssetID,
171170
buyOrder.AssetGroupKey, buyOrder.MinAssetAmount,
172171
fn.None[rfqmsg.AssetRate](),
@@ -180,10 +179,7 @@ func (n *Negotiator) HandleOutgoingBuyOrder(buyOrder BuyOrder) error {
180179
"request: %v", err)
181180
}
182181

183-
expiry := time.Unix(int64(expiryUnix), 0)
184-
assetRateHint = fn.Some[rfqmsg.AssetRate](
185-
rfqmsg.NewAssetRate(*rate, expiry),
186-
)
182+
assetRateHint = fn.Some[rfqmsg.AssetRate](*assetRate)
187183
}
188184

189185
request, err := rfqmsg.NewBuyRequest(
@@ -220,8 +216,7 @@ func (n *Negotiator) HandleOutgoingBuyOrder(buyOrder BuyOrder) error {
220216
// peer.
221217
func (n *Negotiator) queryAskFromPriceOracle(peer *route.Vertex,
222218
assetId *asset.ID, assetGroupKey *btcec.PublicKey, assetAmount uint64,
223-
assetRateHint fn.Option[rfqmsg.AssetRate]) (
224-
*rfqmath.BigIntFixedPoint, uint64, error) {
219+
assetRateHint fn.Option[rfqmsg.AssetRate]) (*rfqmsg.AssetRate, error) {
225220

226221
// Query the price oracle for an asking price.
227222
ctx, cancel := n.WithCtxQuitNoTimeout()
@@ -231,28 +226,28 @@ func (n *Negotiator) queryAskFromPriceOracle(peer *route.Vertex,
231226
ctx, assetId, assetGroupKey, assetAmount, assetRateHint,
232227
)
233228
if err != nil {
234-
return nil, 0, fmt.Errorf("failed to query price oracle for "+
229+
return nil, fmt.Errorf("failed to query price oracle for "+
235230
"ask price: %w", err)
236231
}
237232

238233
// Now we will check for an error in the response from the price oracle.
239234
// If present, we will convert it to a string and return it as an error.
240235
if oracleResponse.Err != nil {
241-
return nil, 0, fmt.Errorf("failed to query price oracle for "+
236+
return nil, fmt.Errorf("failed to query price oracle for "+
242237
"ask price: %s", oracleResponse.Err)
243238
}
244239

245240
// By this point, the price oracle did not return an error or an asking
246241
// price. We will therefore return an error.
247-
if oracleResponse.AssetRate.Coefficient.ToUint64() == 0 {
248-
return nil, 0, fmt.Errorf("price oracle did not specify an " +
242+
if oracleResponse.AssetRate.Rate.Coefficient.ToUint64() == 0 {
243+
return nil, fmt.Errorf("price oracle did not specify an " +
249244
"asset to BTC rate")
250245
}
251246

252247
// TODO(ffranr): Check that the asking price is reasonable.
253248
// TODO(ffranr): Ensure that the expiry time is valid and sufficient.
254249

255-
return &oracleResponse.AssetRate, oracleResponse.Expiry, nil
250+
return &oracleResponse.AssetRate, nil
256251
}
257252

258253
// HandleIncomingBuyRequest handles an incoming asset buy quote request.
@@ -315,7 +310,7 @@ func (n *Negotiator) HandleIncomingBuyRequest(
315310
defer n.Wg.Done()
316311

317312
// Query the price oracle for an asking price.
318-
assetRate, rateExpiry, err := n.queryAskFromPriceOracle(
313+
assetRate, err := n.queryAskFromPriceOracle(
319314
nil, request.AssetID, request.AssetGroupKey,
320315
request.AssetAmount, request.AssetRateHint,
321316
)
@@ -335,8 +330,9 @@ func (n *Negotiator) HandleIncomingBuyRequest(
335330
}
336331

337332
// Construct and send a buy accept message.
333+
expiry := uint64(assetRate.Expiry.Unix())
338334
msg := rfqmsg.NewBuyAcceptFromRequest(
339-
request, *assetRate, rateExpiry,
335+
request, assetRate.Rate, expiry,
340336
)
341337
sendOutgoingMsg(msg)
342338
}()
@@ -410,7 +406,7 @@ func (n *Negotiator) HandleIncomingSellRequest(
410406
// Query the price oracle for a bid price. This is the price we
411407
// are willing to pay for the asset that our peer is trying to
412408
// sell to us.
413-
assetRate, rateExpiry, err := n.queryBidFromPriceOracle(
409+
assetRate, err := n.queryBidFromPriceOracle(
414410
request.Peer, request.AssetID, request.AssetGroupKey,
415411
request.AssetAmount, request.AssetRateHint,
416412
)
@@ -430,8 +426,9 @@ func (n *Negotiator) HandleIncomingSellRequest(
430426
}
431427

432428
// Construct and send a sell accept message.
429+
expiry := uint64(assetRate.Expiry.Unix())
433430
msg := rfqmsg.NewSellAcceptFromRequest(
434-
request, *assetRate, rateExpiry,
431+
request, assetRate.Rate, expiry,
435432
)
436433
sendOutgoingMsg(msg)
437434
}()
@@ -453,11 +450,11 @@ func (n *Negotiator) HandleOutgoingSellOrder(order SellOrder) {
453450
// We calculate a proposed ask price for our peer's
454451
// consideration. If a price oracle is not specified we will
455452
// skip this step.
456-
var assetRate fn.Option[rfqmsg.AssetRate]
453+
var assetRateHint fn.Option[rfqmsg.AssetRate]
457454

458455
if n.cfg.PriceOracle != nil {
459456
// Query the price oracle for an asking price.
460-
rate, expiryUnix, err := n.queryAskFromPriceOracle(
457+
assetRate, err := n.queryAskFromPriceOracle(
461458
order.Peer, order.AssetID, order.AssetGroupKey,
462459
order.MaxAssetAmount,
463460
fn.None[rfqmsg.AssetRate](),
@@ -469,15 +466,12 @@ func (n *Negotiator) HandleOutgoingSellOrder(order SellOrder) {
469466
return
470467
}
471468

472-
expiry := time.Unix(int64(expiryUnix), 0)
473-
assetRate = fn.Some[rfqmsg.AssetRate](
474-
rfqmsg.NewAssetRate(*rate, expiry),
475-
)
469+
assetRateHint = fn.Some[rfqmsg.AssetRate](*assetRate)
476470
}
477471

478472
request, err := rfqmsg.NewSellRequest(
479473
*order.Peer, order.AssetID, order.AssetGroupKey,
480-
order.MaxAssetAmount, assetRate,
474+
order.MaxAssetAmount, assetRateHint,
481475
)
482476
if err != nil {
483477
err := fmt.Errorf("unable to create sell request "+
@@ -574,7 +568,7 @@ func (n *Negotiator) HandleIncomingBuyAccept(msg rfqmsg.BuyAccept,
574568
// We will sanity check that price by querying our price oracle
575569
// for an ask price. We will then compare the ask price returned
576570
// by the price oracle with the ask price provided by the peer.
577-
assetRate, _, err := n.queryAskFromPriceOracle(
571+
assetRate, err := n.queryAskFromPriceOracle(
578572
&msg.Peer, msg.Request.AssetID, nil,
579573
msg.Request.AssetAmount, fn.None[rfqmsg.AssetRate](),
580574
)
@@ -608,7 +602,7 @@ func (n *Negotiator) HandleIncomingBuyAccept(msg rfqmsg.BuyAccept,
608602
big.NewInt(0).SetUint64(n.cfg.AcceptPriceDeviationPpm),
609603
)
610604
acceptablePrice := msg.AssetRate.WithinTolerance(
611-
*assetRate, tolerance,
605+
assetRate.Rate, tolerance,
612606
)
613607
if !acceptablePrice {
614608
// The price is not within the acceptable tolerance.
@@ -698,7 +692,7 @@ func (n *Negotiator) HandleIncomingSellAccept(msg rfqmsg.SellAccept,
698692
// We will sanity check that price by querying our price oracle
699693
// for a bid price. We will then compare the bid price returned
700694
// by the price oracle with the bid price provided by the peer.
701-
assetRate, _, err := n.queryBidFromPriceOracle(
695+
assetRate, err := n.queryBidFromPriceOracle(
702696
msg.Peer, msg.Request.AssetID, nil,
703697
msg.Request.AssetAmount, msg.Request.AssetRateHint,
704698
)
@@ -732,7 +726,7 @@ func (n *Negotiator) HandleIncomingSellAccept(msg rfqmsg.SellAccept,
732726
big.NewInt(0).SetUint64(n.cfg.AcceptPriceDeviationPpm),
733727
)
734728
acceptablePrice := msg.AssetRate.WithinTolerance(
735-
*assetRate, tolerance,
729+
assetRate.Rate, tolerance,
736730
)
737731
if !acceptablePrice {
738732
// The price is not within the acceptable bounds.

rfq/oracle.go

+32-17
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"net/url"
99
"time"
1010

11+
"math"
12+
1113
"github.com/btcsuite/btcd/btcec/v2"
1214
"github.com/lightninglabs/taproot-assets/asset"
1315
"github.com/lightninglabs/taproot-assets/fn"
@@ -47,11 +49,7 @@ func (o *OracleError) Error() string {
4749
type OracleResponse struct {
4850
// AssetRate is the asset to BTC rate. Other asset in the transfer is
4951
// assumed to be BTC and therefore not included in the response.
50-
AssetRate rfqmath.BigIntFixedPoint
51-
52-
// Expiry is the asset to BTC rate expiry lifetime unix timestamp. The
53-
// rate is only valid until this time.
54-
Expiry uint64
52+
AssetRate rfqmsg.AssetRate
5553

5654
// Err is an optional error returned by the price oracle service.
5755
Err *OracleError
@@ -257,9 +255,17 @@ func (r *RpcPriceOracle) QueryAskPrice(ctx context.Context,
257255
return nil, err
258256
}
259257

258+
// Unmarshal the expiry timestamp.
259+
if result.Ok.AssetRates.ExpiryTimestamp > math.MaxInt64 {
260+
return nil, fmt.Errorf("expiry timestamp exceeds " +
261+
"int64 max")
262+
}
263+
expiry := time.Unix(int64(
264+
result.Ok.AssetRates.ExpiryTimestamp,
265+
), 0).UTC()
266+
260267
return &OracleResponse{
261-
AssetRate: *rate,
262-
Expiry: result.Ok.AssetRates.ExpiryTimestamp,
268+
AssetRate: rfqmsg.NewAssetRate(*rate, expiry),
263269
}, nil
264270

265271
case *oraclerpc.QueryAssetRatesResponse_Error:
@@ -344,9 +350,17 @@ func (r *RpcPriceOracle) QueryBidPrice(ctx context.Context, assetId *asset.ID,
344350
return nil, err
345351
}
346352

353+
// Unmarshal the expiry timestamp.
354+
if result.Ok.AssetRates.ExpiryTimestamp > math.MaxInt64 {
355+
return nil, fmt.Errorf("expiry timestamp exceeds " +
356+
"int64 max")
357+
}
358+
expiry := time.Unix(int64(
359+
result.Ok.AssetRates.ExpiryTimestamp,
360+
), 0).UTC()
361+
347362
return &OracleResponse{
348-
AssetRate: *rate,
349-
Expiry: result.Ok.AssetRates.ExpiryTimestamp,
363+
AssetRate: rfqmsg.NewAssetRate(*rate, expiry),
350364
}, nil
351365

352366
case *oraclerpc.QueryAssetRatesResponse_Error:
@@ -372,6 +386,7 @@ var _ PriceOracle = (*RpcPriceOracle)(nil)
372386
// MockPriceOracle is a mock implementation of the PriceOracle interface.
373387
// It returns the suggested rate as the exchange rate.
374388
type MockPriceOracle struct {
389+
// expiryDelay is the lifetime of a quote in seconds.
375390
expiryDelay uint64
376391
assetToBtcRate rfqmath.BigIntFixedPoint
377392
}
@@ -408,12 +423,12 @@ func (m *MockPriceOracle) QueryAskPrice(_ context.Context,
408423
_ *asset.ID, _ *btcec.PublicKey, _ uint64,
409424
_ fn.Option[rfqmsg.AssetRate]) (*OracleResponse, error) {
410425

411-
// Calculate the rate expiryDelay lifetime.
412-
expiry := uint64(time.Now().Unix()) + m.expiryDelay
426+
// Calculate the rate expiry timestamp.
427+
lifetime := time.Duration(m.expiryDelay) * time.Second
428+
expiry := time.Now().Add(lifetime).UTC()
413429

414430
return &OracleResponse{
415-
AssetRate: m.assetToBtcRate,
416-
Expiry: expiry,
431+
AssetRate: rfqmsg.NewAssetRate(m.assetToBtcRate, expiry),
417432
}, nil
418433
}
419434

@@ -422,12 +437,12 @@ func (m *MockPriceOracle) QueryBidPrice(_ context.Context, _ *asset.ID,
422437
_ *btcec.PublicKey, _ uint64,
423438
_ fn.Option[rfqmsg.AssetRate]) (*OracleResponse, error) {
424439

425-
// Calculate the rate expiryDelay lifetime.
426-
expiry := uint64(time.Now().Unix()) + m.expiryDelay
440+
// Calculate the rate expiry timestamp.
441+
lifetime := time.Duration(m.expiryDelay) * time.Second
442+
expiry := time.Now().Add(lifetime).UTC()
427443

428444
return &OracleResponse{
429-
AssetRate: m.assetToBtcRate,
430-
Expiry: expiry,
445+
AssetRate: rfqmsg.NewAssetRate(m.assetToBtcRate, expiry),
431446
}, nil
432447
}
433448

rfq/oracle_test.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -179,12 +179,11 @@ func runQueryAskPriceTest(t *testing.T, tc *testCaseQueryAskPrice) {
179179
// The mock server should return the asset rates hint.
180180
require.NotNil(t, resp.AssetRate)
181181
require.Equal(
182-
t, uint64(bidPrice), resp.AssetRate.Coefficient.ToUint64(),
182+
t, uint64(bidPrice), resp.AssetRate.Rate.Coefficient.ToUint64(),
183183
)
184184

185185
// Ensure that the expiry timestamp is in the future.
186-
responseExpiry := time.Unix(int64(resp.Expiry), 0)
187-
require.True(t, responseExpiry.After(time.Now()))
186+
require.True(t, resp.AssetRate.Expiry.After(time.Now()))
188187
}
189188

190189
// TestRpcPriceOracle tests the RPC price oracle client QueryAskPrice function.
@@ -276,11 +275,12 @@ func runQueryBidPriceTest(t *testing.T, tc *testCaseQueryBidPrice) {
276275

277276
// The mock server should return the asset rates hint.
278277
require.NotNil(t, resp.AssetRate)
279-
require.Equal(t, testAssetRate, resp.AssetRate.Coefficient.ToUint64())
278+
require.Equal(
279+
t, testAssetRate, resp.AssetRate.Rate.Coefficient.ToUint64(),
280+
)
280281

281282
// Ensure that the expiry timestamp is in the future.
282-
responseExpiry := time.Unix(int64(resp.Expiry), 0)
283-
require.True(t, responseExpiry.After(time.Now()))
283+
require.True(t, resp.AssetRate.Expiry.After(time.Now()))
284284
}
285285

286286
// TestRpcPriceOracle tests the RPC price oracle client QueryBidPrice function.

rfqmsg/buy_accept.go

+2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ type BuyAccept struct {
4141

4242
// NewBuyAcceptFromRequest creates a new instance of a quote accept message
4343
// given a quote request message.
44+
//
45+
// TODO(ffranr): Use new AssetRate type for assetRate arg.
4446
func NewBuyAcceptFromRequest(request BuyRequest,
4547
assetRate rfqmath.BigIntFixedPoint, expiry uint64) *BuyAccept {
4648

rfqmsg/sell_accept.go

+2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ type SellAccept struct {
4141

4242
// NewSellAcceptFromRequest creates a new instance of an asset sell quote accept
4343
// message given an asset sell quote request message.
44+
//
45+
// // TODO(ffranr): Use new AssetRate type for assetRate arg.
4446
func NewSellAcceptFromRequest(request SellRequest,
4547
assetRate rfqmath.BigIntFixedPoint, expiry uint64) *SellAccept {
4648

0 commit comments

Comments
 (0)