Skip to content

Commit 57a6e8d

Browse files
committed
rfq+rfqmsg: use new AssetRate type in BuyRequest.AssetRateHint
This commit includes the following changes: * Rename the `BuyRequest.SuggestedAssetRate` field to `AssetRateHint` with the new `AssetRate` type. * Update the `queryAskFromPriceOracle` function to accept the `AssetRate` type as an argument. * Update the `PriceOracle.QueryAskPrice` function to accept the `AssetRate` type as an argument. This change provides the price oracle with the asset rate hint's expiry timestamp from the BuyRequest, instead of relying on the default expiry lifetime.
1 parent 88613f7 commit 57a6e8d

File tree

5 files changed

+92
-96
lines changed

5 files changed

+92
-96
lines changed

rfq/negotiator.go

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -215,15 +215,15 @@ func (n *Negotiator) HandleOutgoingBuyOrder(buyOrder BuyOrder) error {
215215
// peer.
216216
func (n *Negotiator) queryAskFromPriceOracle(peer *route.Vertex,
217217
assetId *asset.ID, assetGroupKey *btcec.PublicKey, assetAmount uint64,
218-
suggestedAssetRate fn.Option[rfqmath.BigIntFixedPoint]) (
218+
assetRateHint fn.Option[rfqmsg.AssetRate]) (
219219
*rfqmath.BigIntFixedPoint, uint64, error) {
220220

221221
// Query the price oracle for an asking price.
222222
ctx, cancel := n.WithCtxQuitNoTimeout()
223223
defer cancel()
224224

225225
oracleResponse, err := n.cfg.PriceOracle.QueryAskPrice(
226-
ctx, assetId, assetGroupKey, assetAmount, suggestedAssetRate,
226+
ctx, assetId, assetGroupKey, assetAmount, assetRateHint,
227227
)
228228
if err != nil {
229229
return nil, 0, fmt.Errorf("failed to query price oracle for "+
@@ -312,7 +312,7 @@ func (n *Negotiator) HandleIncomingBuyRequest(
312312
// Query the price oracle for an asking price.
313313
assetRate, rateExpiry, err := n.queryAskFromPriceOracle(
314314
nil, request.AssetID, request.AssetGroupKey,
315-
request.AssetAmount, request.SuggestedAssetRate,
315+
request.AssetAmount, request.AssetRateHint,
316316
)
317317
if err != nil {
318318
// Send a reject message to the peer.
@@ -455,7 +455,7 @@ func (n *Negotiator) HandleOutgoingSellOrder(order SellOrder) {
455455
rate, _, err := n.queryAskFromPriceOracle(
456456
order.Peer, order.AssetID, order.AssetGroupKey,
457457
order.MaxAssetAmount,
458-
fn.None[rfqmath.BigIntFixedPoint](),
458+
fn.None[rfqmsg.AssetRate](),
459459
)
460460
if err != nil {
461461
err := fmt.Errorf("negotiator failed to "+
@@ -568,8 +568,7 @@ func (n *Negotiator) HandleIncomingBuyAccept(msg rfqmsg.BuyAccept,
568568
// by the price oracle with the ask price provided by the peer.
569569
assetRate, _, err := n.queryAskFromPriceOracle(
570570
&msg.Peer, msg.Request.AssetID, nil,
571-
msg.Request.AssetAmount,
572-
fn.None[rfqmath.BigIntFixedPoint](),
571+
msg.Request.AssetAmount, fn.None[rfqmsg.AssetRate](),
573572
)
574573
if err != nil {
575574
// The price oracle returned an error. We will return

rfq/oracle.go

Lines changed: 6 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,6 @@ import (
1818
"google.golang.org/grpc/credentials/insecure"
1919
)
2020

21-
const (
22-
// defaultAssetRateExpirySeconds is the default asset units to BTC rate
23-
// expiry lifetime in seconds. 600s = 10 minutes.
24-
//
25-
// TODO(ffranr): This const is currently used in conjunction with the
26-
// AcceptSuggestedPrices flag. It is used to set the expiry time of the
27-
// asset units to BTC rate in the accept message. This is a temporary
28-
// solution and should be replaced with an expiry time provided by the
29-
// peer in the quote request message.
30-
defaultAssetRateExpirySeconds = 600
31-
)
32-
3321
// OracleError is a struct that holds an error returned by the price oracle
3422
// service.
3523
type OracleError struct {
@@ -111,7 +99,7 @@ type PriceOracle interface {
11199
// from another peer to provide the specified asset amount.
112100
QueryAskPrice(ctx context.Context, assetId *asset.ID,
113101
assetGroupKey *btcec.PublicKey, assetAmount uint64,
114-
assetRateHint fn.Option[rfqmath.BigIntFixedPoint]) (
102+
assetRateHint fn.Option[rfqmsg.AssetRate]) (
115103
*OracleResponse, error)
116104

117105
// QueryBidPrice returns the bid price for a given asset amount.
@@ -204,7 +192,7 @@ func NewRpcPriceOracle(addrStr string, dialInsecure bool) (*RpcPriceOracle,
204192
// QueryAskPrice returns the ask price for the given asset amount.
205193
func (r *RpcPriceOracle) QueryAskPrice(ctx context.Context,
206194
assetId *asset.ID, assetGroupKey *btcec.PublicKey, assetAmount uint64,
207-
assetRateHint fn.Option[rfqmath.BigIntFixedPoint]) (*OracleResponse,
195+
assetRateHint fn.Option[rfqmsg.AssetRate]) (*OracleResponse,
208196
error) {
209197

210198
// For now, we only support querying the ask price with an asset ID.
@@ -222,38 +210,9 @@ func (r *RpcPriceOracle) QueryAskPrice(ctx context.Context,
222210
copy(subjectAssetId, assetId[:])
223211

224212
// Construct the RPC asset rates hint.
225-
var (
226-
rpcAssetRatesHint *oraclerpc.AssetRates
227-
err error
228-
)
229-
assetRateHint.WhenSome(func(rate rfqmath.BigIntFixedPoint) {
230-
// Compute an expiry time using the default expiry delay.
231-
expiryTimestamp := uint64(time.Now().Unix()) +
232-
defaultAssetRateExpirySeconds
233-
234-
// Marshal the subject asset rate.
235-
subjectAssetRate, err := oraclerpc.MarshalBigIntFixedPoint(
236-
rate,
237-
)
238-
if err != nil {
239-
return
240-
}
241-
242-
// Marshal the payment asset rate. For now, we only support BTC
243-
// as the payment asset.
244-
paymentAssetRate, err := oraclerpc.MarshalBigIntFixedPoint(
245-
rfqmsg.MilliSatPerBtc,
246-
)
247-
if err != nil {
248-
return
249-
}
250-
251-
rpcAssetRatesHint = &oraclerpc.AssetRates{
252-
SubjectAssetRate: subjectAssetRate,
253-
PaymentAssetRate: paymentAssetRate,
254-
ExpiryTimestamp: expiryTimestamp,
255-
}
256-
})
213+
rpcAssetRatesHint, err := fn.MapOptionZ(
214+
assetRateHint, oraclerpc.MarshalAssetRates,
215+
).Unpack()
257216
if err != nil {
258217
return nil, err
259218
}
@@ -437,7 +396,7 @@ func NewMockPriceOracleSatPerAsset(expiryDelay uint64,
437396
// QueryAskPrice returns the ask price for the given asset amount.
438397
func (m *MockPriceOracle) QueryAskPrice(_ context.Context,
439398
_ *asset.ID, _ *btcec.PublicKey, _ uint64,
440-
_ fn.Option[rfqmath.BigIntFixedPoint]) (*OracleResponse, error) {
399+
_ fn.Option[rfqmsg.AssetRate]) (*OracleResponse, error) {
441400

442401
// Calculate the rate expiryDelay lifetime.
443402
expiry := uint64(time.Now().Unix()) + m.expiryDelay

rfq/oracle_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/lightninglabs/taproot-assets/fn"
1414
"github.com/lightninglabs/taproot-assets/internal/test"
1515
"github.com/lightninglabs/taproot-assets/rfqmath"
16+
"github.com/lightninglabs/taproot-assets/rfqmsg"
1617
"github.com/lightninglabs/taproot-assets/taprpc/priceoraclerpc"
1718
"github.com/lightningnetwork/lnd/lnwire"
1819
"github.com/stretchr/testify/require"
@@ -158,10 +159,12 @@ func runQueryAskPriceTest(t *testing.T, tc *testCaseQueryAskPrice) {
158159
inAssetRate := rfqmath.NewBigIntFixedPoint(
159160
tc.suggestedAssetRate, 3,
160161
)
162+
expiry := time.Now().Add(rfqmsg.DefaultQuoteLifetime).UTC()
163+
assetRateHint := rfqmsg.NewAssetRate(inAssetRate, expiry)
161164

162165
resp, err := client.QueryAskPrice(
163166
ctx, tc.assetId, tc.assetGroupKey, assetAmount,
164-
fn.Some(inAssetRate),
167+
fn.Some(assetRateHint),
165168
)
166169

167170
// If we expect an error, ensure that it is returned.

rfqmsg/buy_request.go

Lines changed: 58 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package rfqmsg
22

33
import (
44
"fmt"
5+
"math"
6+
"time"
57

68
"github.com/btcsuite/btcd/btcec/v2"
79
"github.com/lightninglabs/taproot-assets/asset"
@@ -40,33 +42,39 @@ type BuyRequest struct {
4042
// requesting a quote.
4143
AssetAmount uint64
4244

43-
// SuggestedAssetRate represents a proposed conversion rate between the
45+
// AssetRateHint represents a proposed conversion rate between the
4446
// subject asset and BTC. This rate is an initial suggestion intended to
4547
// initiate the RFQ negotiation process and may differ from the final
4648
// agreed rate.
47-
SuggestedAssetRate fn.Option[rfqmath.BigIntFixedPoint]
49+
AssetRateHint fn.Option[AssetRate]
4850
}
4951

5052
// NewBuyRequest creates a new asset buy quote request.
5153
func NewBuyRequest(peer route.Vertex, assetID *asset.ID,
5254
assetGroupKey *btcec.PublicKey, assetAmount uint64,
53-
suggestedAssetRate fn.Option[rfqmath.BigIntFixedPoint]) (*BuyRequest,
54-
error) {
55+
rateHint fn.Option[rfqmath.BigIntFixedPoint]) (*BuyRequest, error) {
5556

5657
id, err := NewID()
5758
if err != nil {
5859
return nil, fmt.Errorf("unable to generate random "+
5960
"quote request id: %w", err)
6061
}
6162

63+
// Construct a suggested asset rate if a rate hint is provided.
64+
var assetRateHint fn.Option[AssetRate]
65+
rateHint.WhenSome(func(rate rfqmath.BigIntFixedPoint) {
66+
expiry := time.Now().Add(DefaultQuoteLifetime).UTC()
67+
assetRateHint = fn.Some(NewAssetRate(rate, expiry))
68+
})
69+
6270
return &BuyRequest{
63-
Peer: peer,
64-
Version: latestBuyRequestVersion,
65-
ID: id,
66-
AssetID: assetID,
67-
AssetGroupKey: assetGroupKey,
68-
AssetAmount: assetAmount,
69-
SuggestedAssetRate: suggestedAssetRate,
71+
Peer: peer,
72+
Version: latestBuyRequestVersion,
73+
ID: id,
74+
AssetID: assetID,
75+
AssetGroupKey: assetGroupKey,
76+
AssetAmount: assetAmount,
77+
AssetRateHint: assetRateHint,
7078
}, nil
7179
}
7280

@@ -102,24 +110,30 @@ func NewBuyRequestFromWire(wireMsg WireMessage,
102110
"request")
103111
}
104112

113+
// Convert the wire message expiration time to a time.Time.
114+
if msgData.Expiry.Val > math.MaxInt64 {
115+
return nil, fmt.Errorf("expiry time exceeds maximum int64")
116+
}
117+
118+
expiry := time.Unix(int64(msgData.Expiry.Val), 0)
119+
105120
// Extract the suggested asset to BTC rate if provided.
106-
var suggestedAssetRate fn.Option[rfqmath.BigIntFixedPoint]
121+
var assetRateHint fn.Option[AssetRate]
107122
msgData.SuggestedAssetRate.WhenSome(
108123
func(rate tlv.RecordT[tlv.TlvType19, TlvFixedPoint]) {
109124
fp := rate.Val.IntoBigIntFixedPoint()
110-
suggestedAssetRate =
111-
fn.Some[rfqmath.BigIntFixedPoint](fp)
125+
assetRateHint = fn.Some(NewAssetRate(fp, expiry))
112126
},
113127
)
114128

115129
req := BuyRequest{
116-
Peer: wireMsg.Peer,
117-
Version: msgData.Version.Val,
118-
ID: msgData.ID.Val,
119-
AssetID: assetID,
120-
AssetGroupKey: assetGroupKey,
121-
AssetAmount: msgData.AssetMaxAmount.Val,
122-
SuggestedAssetRate: suggestedAssetRate,
130+
Peer: wireMsg.Peer,
131+
Version: msgData.Version.Val,
132+
ID: msgData.ID.Val,
133+
AssetID: assetID,
134+
AssetGroupKey: assetGroupKey,
135+
AssetAmount: msgData.AssetMaxAmount.Val,
136+
AssetRateHint: assetRateHint,
123137
}
124138

125139
// Perform basic sanity checks on the quote request.
@@ -148,6 +162,17 @@ func (q *BuyRequest) Validate() error {
148162
q.Version)
149163
}
150164

165+
// Ensure that the suggested asset rate has not expired.
166+
err := fn.MapOptionZ(q.AssetRateHint, func(rate AssetRate) error {
167+
if rate.Expiry.Before(time.Now()) {
168+
return fmt.Errorf("suggested asset rate has expired")
169+
}
170+
return nil
171+
})
172+
if err != nil {
173+
return err
174+
}
175+
151176
return nil
152177
}
153178

@@ -195,10 +220,19 @@ func (q *BuyRequest) String() string {
195220
groupKeyBytes = q.AssetGroupKey.SerializeCompressed()
196221
}
197222

223+
// Convert the asset rate hint to a string representation. Use empty
224+
// string if the hint is not set.
225+
assetRateHintStr := fn.MapOptionZ(
226+
q.AssetRateHint,
227+
func(rate AssetRate) string {
228+
return rate.String()
229+
},
230+
)
231+
198232
return fmt.Sprintf("BuyRequest(peer=%x, id=%x, asset_id=%s, "+
199-
"asset_group_key=%x, asset_amount=%d, "+
200-
"suggested_asset_rate=%v)", q.Peer[:], q.ID[:], q.AssetID,
201-
groupKeyBytes, q.AssetAmount, q.SuggestedAssetRate)
233+
"asset_group_key=%x, asset_amount=%d, asset_rate_hint=%s)",
234+
q.Peer[:], q.ID[:], q.AssetID, groupKeyBytes, q.AssetAmount,
235+
assetRateHintStr)
202236
}
203237

204238
// Ensure that the message type implements the OutgoingMsg interface.

rfqmsg/request.go

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -107,24 +107,13 @@ func newRequestWireMsgDataFromBuy(q BuyRequest) (requestWireMsgData, error) {
107107
version := tlv.NewRecordT[tlv.TlvType0](q.Version)
108108
id := tlv.NewRecordT[tlv.TlvType2](q.ID)
109109

110-
// Calculate the expiration unix timestamp in seconds.
111-
// TODO(ffranr): The expiry timestamp should be obtained from the
112-
// request message.
113-
expiry := tlv.NewPrimitiveRecord[tlv.TlvType6](
114-
uint64(time.Now().Add(DefaultQuoteLifetime).Unix()),
115-
)
116-
117-
assetMaxAmount := tlv.NewPrimitiveRecord[tlv.TlvType16](q.AssetAmount)
118-
119-
// Convert the suggested asset to BTC rate to a TLV record.
120-
var suggestedAssetRate requestSuggestedAssetRate
121-
q.SuggestedAssetRate.WhenSome(func(rate rfqmath.BigIntFixedPoint) {
122-
// Convert the BigIntFixedPoint to a Uint64FixedPoint.
123-
wireRate := NewTlvFixedPointFromBigInt(rate)
124-
suggestedAssetRate = tlv.SomeRecordT[tlv.TlvType19](
125-
tlv.NewRecordT[tlv.TlvType19](wireRate),
126-
)
110+
// Set the expiry to the default request lifetime unless an asset rate
111+
// hint is provided.
112+
expiry := time.Now().Add(DefaultQuoteLifetime).Unix()
113+
q.AssetRateHint.WhenSome(func(assetRate AssetRate) {
114+
expiry = assetRate.Expiry.Unix()
127115
})
116+
expiryTlv := tlv.NewPrimitiveRecord[tlv.TlvType6](uint64(expiry))
128117

129118
var inAssetID requestInAssetID
130119
if q.AssetID != nil {
@@ -151,11 +140,23 @@ func newRequestWireMsgDataFromBuy(q BuyRequest) (requestWireMsgData, error) {
151140

152141
outAssetGroupKey := requestOutAssetGroupKey{}
153142

143+
// Convert the suggested asset to BTC rate to a TLV record.
144+
var suggestedAssetRate requestSuggestedAssetRate
145+
q.AssetRateHint.WhenSome(func(assetRate AssetRate) {
146+
// Convert the BigIntFixedPoint to a TlvFixedPoint.
147+
wireRate := NewTlvFixedPointFromBigInt(assetRate.Rate)
148+
suggestedAssetRate = tlv.SomeRecordT[tlv.TlvType19](
149+
tlv.NewRecordT[tlv.TlvType19](wireRate),
150+
)
151+
})
152+
153+
assetMaxAmount := tlv.NewPrimitiveRecord[tlv.TlvType16](q.AssetAmount)
154+
154155
// Encode message data component as TLV bytes.
155156
return requestWireMsgData{
156157
Version: version,
157158
ID: id,
158-
Expiry: expiry,
159+
Expiry: expiryTlv,
159160
AssetMaxAmount: assetMaxAmount,
160161
SuggestedAssetRate: suggestedAssetRate,
161162
InAssetID: inAssetID,

0 commit comments

Comments
 (0)