Skip to content

Commit 55c55c9

Browse files
authored
Merge pull request #1175 from lightninglabs/buyorder-refactor
Refactor RFQ BuyOrder struct
2 parents ab90081 + 7f95ca1 commit 55c55c9

File tree

9 files changed

+364
-315
lines changed

9 files changed

+364
-315
lines changed

itest/rfq_test.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,8 @@ func testRfqAssetBuyHtlcIntercept(t *harnessTest) {
9494
AssetId: mintedAssetId,
9595
},
9696
},
97-
MinAssetAmount: purchaseAssetAmt,
98-
MaxBid: bidAmt,
99-
Expiry: buyOrderExpiry,
97+
AssetMaxAmt: purchaseAssetAmt,
98+
Expiry: buyOrderExpiry,
10099

101100
// Here we explicitly specify Bob as the destination
102101
// peer for the buy order. This will prompt Carol's tapd

rfq/manager.go

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -688,28 +688,42 @@ func (m *Manager) UpsertAssetBuyOffer(offer BuyOffer) error {
688688
return nil
689689
}
690690

691-
// BuyOrder is a struct that represents a buy order.
691+
// BuyOrder instructs the RFQ (Request For Quote) system to request a quote from
692+
// a peer for the acquisition of an asset.
693+
//
694+
// The normal use of a buy order is as follows:
695+
// 1. Alice, operating a wallet node, wants to receive a Tap asset as payment
696+
// by issuing a Lightning invoice.
697+
// 2. Alice has an asset channel established with Bob's edge node.
698+
// 3. Before issuing the invoice, Alice needs to agree on an exchange rate with
699+
// Bob, who will facilitate the asset transfer.
700+
// 4. To obtain the best exchange rate, Alice creates a buy order specifying
701+
// the desired asset.
702+
// 5. Alice's RFQ subsystem processes the buy order and sends buy requests to
703+
// relevant peers to find the best rate. In this example, Bob is the only
704+
// available peer.
705+
// 6. Once Bob provides a satisfactory quote, Alice accepts it.
706+
// 7. Alice issues the Lightning invoice, which Charlie will pay.
707+
// 8. Instead of paying Alice directly, Charlie pays Bob.
708+
// 9. Bob then forwards the agreed amount of the Tap asset to Alice over their
709+
// asset channel.
692710
type BuyOrder struct {
693-
// AssetID is the ID of the asset that the buyer is interested in.
694-
AssetID *asset.ID
695-
696-
// AssetGroupKey is the public key of the asset group that the buyer is
697-
// interested in.
698-
AssetGroupKey *btcec.PublicKey
711+
// AssetSpecifier is the asset that the buyer is interested in.
712+
AssetSpecifier asset.Specifier
699713

700-
// MinAssetAmount is the minimum amount of the asset that the buyer is
701-
// willing to accept.
702-
MinAssetAmount uint64
703-
704-
// MaxBid is the maximum bid price that the buyer is willing to pay.
705-
MaxBid lnwire.MilliSatoshi
714+
// AssetMaxAmt is the maximum amount of the asset that the provider must
715+
// be willing to offer.
716+
AssetMaxAmt uint64
706717

707718
// Expiry is the unix timestamp at which the buy order expires.
708719
Expiry uint64
709720

710721
// Peer is the peer that the buy order is intended for. This field is
711722
// optional.
712-
Peer *route.Vertex
723+
//
724+
// TODO(ffranr): Currently, this field must be specified. In the future,
725+
// the negotiator should be able to determine the optimal peer.
726+
Peer fn.Option[route.Vertex]
713727
}
714728

715729
// UpsertAssetBuyOrder upserts an asset buy order for management.
@@ -718,7 +732,7 @@ func (m *Manager) UpsertAssetBuyOrder(order BuyOrder) error {
718732
//
719733
// TODO(ffranr): Add support for peerless buy orders. The negotiator
720734
// should be able to determine the optimal peer.
721-
if order.Peer == nil {
735+
if order.Peer.IsNone() {
722736
return fmt.Errorf("buy order peer must be specified")
723737
}
724738

rfq/negotiator.go

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -159,31 +159,27 @@ func (n *Negotiator) HandleOutgoingBuyOrder(buyOrder BuyOrder) error {
159159
go func() {
160160
defer n.Wg.Done()
161161

162+
// Unwrap the peer from the buy order. For now, we can assume
163+
// that the peer is always specified.
164+
peer, err := buyOrder.Peer.UnwrapOrErr(
165+
fmt.Errorf("buy order peer must be specified"),
166+
)
167+
if err != nil {
168+
n.cfg.ErrChan <- err
169+
}
170+
162171
// We calculate a proposed bid price for our peer's
163172
// consideration. If a price oracle is not specified we will
164173
// skip this step.
165174
var assetRateHint fn.Option[rfqmsg.AssetRate]
166175

167-
// Construct an asset specifier from the order.
168-
// TODO(ffranr): The order should have an asset specifier field
169-
// rather than an asset ID and group key.
170-
assetSpecifier, err := asset.NewSpecifier(
171-
buyOrder.AssetID, buyOrder.AssetGroupKey, nil,
172-
true,
173-
)
174-
if err != nil {
175-
log.Warnf("failed to construct asset "+
176-
"specifier from buy order: %v", err)
177-
}
176+
if n.cfg.PriceOracle != nil &&
177+
buyOrder.AssetSpecifier.IsSome() {
178178

179-
if n.cfg.PriceOracle != nil && assetSpecifier.IsSome() {
180179
// Query the price oracle for a bid price.
181-
//
182-
// TODO(ffranr): Add assetMaxAmt to BuyOrder and use as
183-
// arg here.
184180
assetRate, err := n.queryBidFromPriceOracle(
185-
*buyOrder.Peer, assetSpecifier,
186-
fn.None[uint64](),
181+
peer, buyOrder.AssetSpecifier,
182+
fn.Some(buyOrder.AssetMaxAmt),
187183
fn.None[lnwire.MilliSatoshi](),
188184
fn.None[rfqmsg.AssetRate](),
189185
)
@@ -199,10 +195,10 @@ func (n *Negotiator) HandleOutgoingBuyOrder(buyOrder BuyOrder) error {
199195
assetRateHint = fn.Some[rfqmsg.AssetRate](*assetRate)
200196
}
201197

198+
// Construct a new buy request to send to the peer.
202199
request, err := rfqmsg.NewBuyRequest(
203-
*buyOrder.Peer, buyOrder.AssetID,
204-
buyOrder.AssetGroupKey, buyOrder.MinAssetAmount,
205-
assetRateHint,
200+
peer, buyOrder.AssetSpecifier,
201+
buyOrder.AssetMaxAmt, assetRateHint,
206202
)
207203
if err != nil {
208204
err := fmt.Errorf("unable to create buy request "+

rfqmsg/buy_request.go

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,24 +45,16 @@ type BuyRequest struct {
4545
}
4646

4747
// NewBuyRequest creates a new asset buy quote request.
48-
func NewBuyRequest(peer route.Vertex, assetID *asset.ID,
49-
assetGroupKey *btcec.PublicKey, assetMaxAmt uint64,
50-
assetRateHint fn.Option[AssetRate]) (*BuyRequest, error) {
48+
func NewBuyRequest(peer route.Vertex, assetSpecifier asset.Specifier,
49+
assetMaxAmt uint64, assetRateHint fn.Option[AssetRate]) (*BuyRequest,
50+
error) {
5151

5252
id, err := NewID()
5353
if err != nil {
5454
return nil, fmt.Errorf("unable to generate random "+
5555
"quote request id: %w", err)
5656
}
5757

58-
assetSpecifier, err := asset.NewSpecifier(
59-
assetID, assetGroupKey, nil, true,
60-
)
61-
if err != nil {
62-
return nil, fmt.Errorf("unable to create asset specifier: %w",
63-
err)
64-
}
65-
6658
return &BuyRequest{
6759
Peer: peer,
6860
Version: latestBuyRequestVersion,

rpcserver.go

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6302,13 +6302,20 @@ func unmarshalAssetBuyOrder(
63026302
peer = &pv
63036303
}
63046304

6305+
// Construct an asset specifier from the asset ID and/or group key.
6306+
assetSpecifier, err := asset.NewSpecifier(
6307+
assetId, assetGroupKey, nil, true,
6308+
)
6309+
if err != nil {
6310+
return nil, fmt.Errorf("error creating asset specifier: %w",
6311+
err)
6312+
}
6313+
63056314
return &rfq.BuyOrder{
6306-
AssetID: assetId,
6307-
AssetGroupKey: assetGroupKey,
6308-
MinAssetAmount: req.MinAssetAmount,
6309-
MaxBid: lnwire.MilliSatoshi(req.MaxBid),
6315+
AssetSpecifier: assetSpecifier,
6316+
AssetMaxAmt: req.AssetMaxAmt,
63106317
Expiry: req.Expiry,
6311-
Peer: peer,
6318+
Peer: fn.MaybeSome(peer),
63126319
}, nil
63136320
}
63146321

@@ -6328,12 +6335,13 @@ func (r *rpcServer) AddAssetBuyOrder(_ context.Context,
63286335
return nil, fmt.Errorf("error unmarshalling buy order: %w", err)
63296336
}
63306337

6331-
var peer string
6332-
if buyOrder.Peer != nil {
6333-
peer = buyOrder.Peer.String()
6334-
}
6338+
peerStr := fn.MapOptionZ(
6339+
buyOrder.Peer, func(peerVertex route.Vertex) string {
6340+
return peerVertex.String()
6341+
},
6342+
)
63356343
rpcsLog.Debugf("[AddAssetBuyOrder]: upserting buy order "+
6336-
"(dest_peer=%s)", peer)
6344+
"(dest_peer=%s)", peerStr)
63376345

63386346
// Register an event listener before actually inserting the order, so we
63396347
// definitely don't miss any responses.
@@ -6375,7 +6383,7 @@ func (r *rpcServer) AddAssetBuyOrder(_ context.Context,
63756383

63766384
case <-timeout:
63776385
return nil, fmt.Errorf("timeout waiting for response "+
6378-
"from peer %x", buyOrder.Peer[:])
6386+
"(peer=%s)", peerStr)
63796387
}
63806388
}
63816389
}
@@ -7148,9 +7156,9 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
71487156
AssetId: assetID[:],
71497157
},
71507158
},
7151-
MinAssetAmount: req.AssetAmount,
7152-
Expiry: uint64(expiryTimestamp.Unix()),
7153-
PeerPubKey: peerPubKey[:],
7159+
AssetMaxAmt: req.AssetAmount,
7160+
Expiry: uint64(expiryTimestamp.Unix()),
7161+
PeerPubKey: peerPubKey[:],
71547162
TimeoutSeconds: uint32(
71557163
rfq.DefaultTimeout.Seconds(),
71567164
),

0 commit comments

Comments
 (0)