Skip to content

Commit 2bb2369

Browse files
committed
rpcserver: SendPayment uses multiple RFQ quotes
The RPC method now uses all of the introduced features. Instead of acquiring just one quote we now extract that logic into a helper and call it once for each valid peer. We then encode the array of available RFQ IDs into the first hop records and hand it over to LND.
1 parent 8dc4da4 commit 2bb2369

File tree

1 file changed

+105
-78
lines changed

1 file changed

+105
-78
lines changed

rpcserver.go

Lines changed: 105 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -7677,24 +7677,6 @@ func (r *rpcServer) SendPayment(req *tchrpc.SendPaymentRequest,
76777677
"use: %w", err)
76787678
}
76797679

7680-
// TODO(george): temporary as multi-rfq send is not supported
7681-
// yet
7682-
if peerPubKey == nil && len(chanMap) > 1 {
7683-
return fmt.Errorf("multiple valid peers found, need " +
7684-
"specify peer pub key")
7685-
}
7686-
7687-
// Even if the user didn't specify the peer public key before,
7688-
// we definitely know it now. So let's make sure it's always
7689-
// set.
7690-
//
7691-
// TODO(george): we just grab the first value, this is temporary
7692-
// until multi-rfq send is implemented.
7693-
for _, v := range chanMap {
7694-
peerPubKey = &v[0].ChannelInfo.PubKeyBytes
7695-
break
7696-
}
7697-
76987680
// paymentMaxAmt is the maximum amount that the counterparty is
76997681
// expected to pay. This is the amount that the invoice is
77007682
// asking for plus the fee limit in milli-satoshis.
@@ -7703,77 +7685,76 @@ func (r *rpcServer) SendPayment(req *tchrpc.SendPaymentRequest,
77037685
return err
77047686
}
77057687

7706-
resp, err := r.AddAssetSellOrder(
7707-
ctx, &rfqrpc.AddAssetSellOrderRequest{
7708-
AssetSpecifier: &rpcSpecifier,
7709-
PaymentMaxAmt: uint64(paymentMaxAmt),
7710-
Expiry: uint64(expiry.Unix()),
7711-
PeerPubKey: peerPubKey[:],
7712-
TimeoutSeconds: uint32(
7713-
rfq.DefaultTimeout.Seconds(),
7714-
),
7715-
},
7716-
)
7717-
if err != nil {
7718-
return fmt.Errorf("error adding sell order: %w", err)
7719-
}
7688+
// Since we don't have any pre-negotiated rfq IDs available, we
7689+
// need to initiate the negotiation procedure for this payment.
77207690

7721-
var acceptedQuote *rfqrpc.PeerAcceptedSellQuote
7722-
switch r := resp.Response.(type) {
7723-
case *rfqrpc.AddAssetSellOrderResponse_AcceptedQuote:
7724-
acceptedQuote = r.AcceptedQuote
7691+
// We'll store here all the quotes we acquired successfully.
7692+
var acquiredQuotes []rfqmsg.ID
77257693

7726-
case *rfqrpc.AddAssetSellOrderResponse_InvalidQuote:
7727-
return fmt.Errorf("peer %v sent back an invalid "+
7728-
"quote, status: %v", r.InvalidQuote.Peer,
7729-
r.InvalidQuote.Status.String())
7694+
// For each peer that satisfies our query above, we'll try and
7695+
// establish an RFQ quote for the asset specifier and max amount
7696+
// of this payment.
7697+
for peer := range chanMap {
7698+
quote, err := r.acquireSellOrder(
7699+
ctx, &rpcSpecifier, paymentMaxAmt, expiry,
7700+
&peer,
7701+
)
7702+
if err != nil {
7703+
rpcsLog.Errorf("error while trying to acquire "+
7704+
"a sell order for payment: %v", err)
7705+
continue
7706+
}
77307707

7731-
case *rfqrpc.AddAssetSellOrderResponse_RejectedQuote:
7732-
return fmt.Errorf("peer %v rejected the quote, code: "+
7733-
"%v, error message: %v", r.RejectedQuote.Peer,
7734-
r.RejectedQuote.ErrorCode,
7735-
r.RejectedQuote.ErrorMessage)
7708+
err = checkOverpayment(
7709+
quote, paymentMaxAmt, req.AllowOverpay,
7710+
)
7711+
if err != nil {
7712+
return err
7713+
}
77367714

7737-
default:
7738-
return fmt.Errorf("unexpected response type: %T", r)
7739-
}
7715+
// Send out the information about the quote on the
7716+
// stream.
7717+
//
7718+
// nolint:ll
7719+
err = stream.Send(&tchrpc.SendPaymentResponse{
7720+
Result: &tchrpc.SendPaymentResponse_AcceptedSellOrder{
7721+
AcceptedSellOrder: quote,
7722+
},
7723+
})
7724+
if err != nil {
7725+
return err
7726+
}
77407727

7741-
// Check if the payment requires overpayment based on the quote.
7742-
err = checkOverpayment(
7743-
acceptedQuote, paymentMaxAmt, req.AllowOverpay,
7744-
)
7745-
if err != nil {
7746-
return err
7747-
}
7728+
// Unmarshall the accepted quote's asset rate.
7729+
assetRate, err := rpcutils.UnmarshalRfqFixedPoint(
7730+
quote.BidAssetRate,
7731+
)
7732+
if err != nil {
7733+
return fmt.Errorf("error unmarshalling asset "+
7734+
"rate: %w", err)
7735+
}
77487736

7749-
// Send out the information about the quote on the stream.
7750-
err = stream.Send(&tchrpc.SendPaymentResponse{
7751-
Result: &tchrpc.SendPaymentResponse_AcceptedSellOrder{
7752-
AcceptedSellOrder: acceptedQuote,
7753-
},
7754-
})
7755-
if err != nil {
7756-
return err
7757-
}
7737+
rpcsLog.Infof("Got quote for %v asset units at %v "+
7738+
"asset/BTC from peer %x with SCID %d",
7739+
quote.AssetAmount, assetRate, peerPubKey,
7740+
quote.Scid)
77587741

7759-
// Unmarshall the accepted quote's asset rate.
7760-
assetRate, err := rpcutils.UnmarshalRfqFixedPoint(
7761-
acceptedQuote.BidAssetRate,
7762-
)
7763-
if err != nil {
7764-
return fmt.Errorf("error unmarshalling asset rate: %w",
7765-
err)
7766-
}
7742+
var rfqID rfqmsg.ID
7743+
copy(rfqID[:], quote.Id)
77677744

7768-
rpcsLog.Infof("Got quote for %v asset units at %v asset/BTC "+
7769-
"from peer %x with SCID %d", acceptedQuote.AssetAmount,
7770-
assetRate, peerPubKey, acceptedQuote.Scid)
7745+
acquiredQuotes = append(acquiredQuotes, rfqID)
7746+
}
77717747

7772-
var rfqID rfqmsg.ID
7773-
copy(rfqID[:], acceptedQuote.Id)
7748+
if len(acquiredQuotes) == 0 {
7749+
return fmt.Errorf("failed to acquire any quotes for " +
7750+
"payment")
7751+
}
77747752

7753+
// We now create the HTLC with all the available rfq IDs. This
7754+
// will be used by LND later to query the bandwidth of various
7755+
// channels based on the established quotes.
77757756
htlc := rfqmsg.NewHtlc(
7776-
nil, fn.Some(rfqID), fn.None[[]rfqmsg.ID](),
7757+
nil, fn.None[rfqmsg.ID](), fn.Some(acquiredQuotes),
77777758
)
77787759

77797760
// We'll now map the HTLC struct into a set of TLV records,
@@ -8463,6 +8444,52 @@ func (r *rpcServer) acquireBuyOrder(ctx context.Context,
84638444
return quote, nil
84648445
}
84658446

8447+
// acquireSellOrder performs an RFQ negotiation with the target peer and quote
8448+
// parameters and returns the quote if the negotiation was successful.
8449+
func (r *rpcServer) acquireSellOrder(ctx context.Context,
8450+
rpcSpecifier *rfqrpc.AssetSpecifier, paymentMaxAmt lnwire.MilliSatoshi,
8451+
expiry time.Time,
8452+
peerPubKey *route.Vertex) (*rfqrpc.PeerAcceptedSellQuote, error) {
8453+
8454+
var quote *rfqrpc.PeerAcceptedSellQuote
8455+
8456+
resp, err := r.AddAssetSellOrder(
8457+
ctx, &rfqrpc.AddAssetSellOrderRequest{
8458+
AssetSpecifier: rpcSpecifier,
8459+
PaymentMaxAmt: uint64(paymentMaxAmt),
8460+
Expiry: uint64(expiry.Unix()),
8461+
PeerPubKey: peerPubKey[:],
8462+
TimeoutSeconds: uint32(
8463+
rfq.DefaultTimeout.Seconds(),
8464+
),
8465+
},
8466+
)
8467+
if err != nil {
8468+
return nil, fmt.Errorf("error adding sell order: %w", err)
8469+
}
8470+
8471+
switch r := resp.Response.(type) {
8472+
case *rfqrpc.AddAssetSellOrderResponse_AcceptedQuote:
8473+
quote = r.AcceptedQuote
8474+
8475+
case *rfqrpc.AddAssetSellOrderResponse_InvalidQuote:
8476+
return nil, fmt.Errorf("peer %v sent back an invalid "+
8477+
"quote, status: %v", r.InvalidQuote.Peer,
8478+
r.InvalidQuote.Status.String())
8479+
8480+
case *rfqrpc.AddAssetSellOrderResponse_RejectedQuote:
8481+
return nil, fmt.Errorf("peer %v rejected the quote, code: "+
8482+
"%v, error message: %v", r.RejectedQuote.Peer,
8483+
r.RejectedQuote.ErrorCode,
8484+
r.RejectedQuote.ErrorMessage)
8485+
8486+
default:
8487+
return nil, fmt.Errorf("unexpected response type: %T", r)
8488+
}
8489+
8490+
return quote, nil
8491+
}
8492+
84668493
// DeclareScriptKey declares a new script key to the wallet. This is useful
84678494
// when the script key contains scripts, which would mean it wouldn't be
84688495
// recognized by the wallet automatically. Declaring a script key will make any

0 commit comments

Comments
 (0)