@@ -7712,24 +7712,6 @@ func (r *rpcServer) SendPayment(req *tchrpc.SendPaymentRequest,
7712
7712
"use: %w" , err )
7713
7713
}
7714
7714
7715
- // TODO(george): temporary as multi-rfq send is not supported
7716
- // yet
7717
- if peerPubKey == nil && len (chanMap ) > 1 {
7718
- return fmt .Errorf ("multiple valid peers found, need " +
7719
- "specify peer pub key" )
7720
- }
7721
-
7722
- // Even if the user didn't specify the peer public key before,
7723
- // we definitely know it now. So let's make sure it's always
7724
- // set.
7725
- //
7726
- // TODO(george): we just grab the first value, this is temporary
7727
- // until multi-rfq send is implemented.
7728
- for _ , v := range chanMap {
7729
- peerPubKey = & v [0 ].ChannelInfo .PubKeyBytes
7730
- break
7731
- }
7732
-
7733
7715
// paymentMaxAmt is the maximum amount that the counterparty is
7734
7716
// expected to pay. This is the amount that the invoice is
7735
7717
// asking for plus the fee limit in milli-satoshis.
@@ -7738,77 +7720,86 @@ func (r *rpcServer) SendPayment(req *tchrpc.SendPaymentRequest,
7738
7720
return err
7739
7721
}
7740
7722
7741
- resp , err := r .AddAssetSellOrder (
7742
- ctx , & rfqrpc.AddAssetSellOrderRequest {
7743
- AssetSpecifier : & rpcSpecifier ,
7744
- PaymentMaxAmt : uint64 (paymentMaxAmt ),
7745
- Expiry : uint64 (expiry .Unix ()),
7746
- PeerPubKey : peerPubKey [:],
7747
- TimeoutSeconds : uint32 (
7748
- rfq .DefaultTimeout .Seconds (),
7749
- ),
7750
- },
7751
- )
7752
- if err != nil {
7753
- return fmt .Errorf ("error adding sell order: %w" , err )
7754
- }
7723
+ // Since we don't have any pre-negotiated rfq IDs available, we
7724
+ // need to initiate the negotiation procedure for this payment.
7725
+ // We'll store here all the quotes we acquired successfully.
7726
+ var acquiredQuotes []rfqmsg.ID
7755
7727
7756
- var acceptedQuote * rfqrpc.PeerAcceptedSellQuote
7757
- switch r := resp .Response .(type ) {
7758
- case * rfqrpc.AddAssetSellOrderResponse_AcceptedQuote :
7759
- acceptedQuote = r .AcceptedQuote
7728
+ var quoteErr , overpaymentErr error
7760
7729
7761
- case * rfqrpc.AddAssetSellOrderResponse_InvalidQuote :
7762
- return fmt .Errorf ("peer %v sent back an invalid " +
7763
- "quote, status: %v" , r .InvalidQuote .Peer ,
7764
- r .InvalidQuote .Status .String ())
7730
+ // For each peer that satisfies our query above, we'll try and
7731
+ // establish an RFQ quote for the asset specifier and max amount
7732
+ // of this payment.
7733
+ for peer := range chanMap {
7734
+ quote , err := r .acquireSellOrder (
7735
+ ctx , & rpcSpecifier , paymentMaxAmt , expiry ,
7736
+ & peer ,
7737
+ )
7738
+ if err != nil {
7739
+ quoteErr = err
7740
+ rpcsLog .Errorf ("error while trying to acquire " +
7741
+ "a sell order for payment: %v" , err )
7742
+ continue
7743
+ }
7765
7744
7766
- case * rfqrpc.AddAssetSellOrderResponse_RejectedQuote :
7767
- return fmt .Errorf ("peer %v rejected the quote, code: " +
7768
- "%v, error message: %v" , r .RejectedQuote .Peer ,
7769
- r .RejectedQuote .ErrorCode ,
7770
- r .RejectedQuote .ErrorMessage )
7745
+ err = checkOverpayment (
7746
+ quote , paymentMaxAmt , req .AllowOverpay ,
7747
+ )
7748
+ if err != nil {
7749
+ overpaymentErr = err
7750
+ rpcsLog .Error (err )
7751
+ }
7771
7752
7772
- default :
7773
- return fmt .Errorf ("unexpected response type: %T" , r )
7774
- }
7753
+ // Send out the information about the quote on the
7754
+ // stream.
7755
+ //
7756
+ // nolint:lll
7757
+ err = stream .Send (& tchrpc.SendPaymentResponse {
7758
+ Result : & tchrpc.SendPaymentResponse_AcceptedSellOrder {
7759
+ AcceptedSellOrder : quote ,
7760
+ },
7761
+ })
7762
+ if err != nil {
7763
+ return err
7764
+ }
7775
7765
7776
- // Check if the payment requires overpayment based on the quote.
7777
- err = checkOverpayment (
7778
- acceptedQuote , paymentMaxAmt , req .AllowOverpay ,
7779
- )
7780
- if err != nil {
7781
- return err
7782
- }
7766
+ // Unmarshall the accepted quote's asset rate.
7767
+ assetRate , err := rpcutils .UnmarshalRfqFixedPoint (
7768
+ quote .BidAssetRate ,
7769
+ )
7770
+ if err != nil {
7771
+ return fmt .Errorf ("error unmarshalling asset " +
7772
+ "rate: %w" , err )
7773
+ }
7783
7774
7784
- // Send out the information about the quote on the stream.
7785
- err = stream .Send (& tchrpc.SendPaymentResponse {
7786
- Result : & tchrpc.SendPaymentResponse_AcceptedSellOrder {
7787
- AcceptedSellOrder : acceptedQuote ,
7788
- },
7789
- })
7790
- if err != nil {
7791
- return err
7792
- }
7775
+ rpcsLog .Infof ("Got quote for %v asset units at %v " +
7776
+ "asset/BTC from peer %x with SCID %d" ,
7777
+ quote .AssetAmount , assetRate , peerPubKey ,
7778
+ quote .Scid )
7793
7779
7794
- // Unmarshall the accepted quote's asset rate.
7795
- assetRate , err := rpcutils .UnmarshalRfqFixedPoint (
7796
- acceptedQuote .BidAssetRate ,
7797
- )
7798
- if err != nil {
7799
- return fmt .Errorf ("error unmarshalling asset rate: %w" ,
7800
- err )
7801
- }
7780
+ var rfqID rfqmsg.ID
7781
+ copy (rfqID [:], quote .Id )
7802
7782
7803
- rpcsLog .Infof ("Got quote for %v asset units at %v asset/BTC " +
7804
- "from peer %x with SCID %d" , acceptedQuote .AssetAmount ,
7805
- assetRate , peerPubKey , acceptedQuote .Scid )
7783
+ acquiredQuotes = append (acquiredQuotes , rfqID )
7806
7784
7807
- var rfqID rfqmsg.ID
7808
- copy (rfqID [:], acceptedQuote .Id )
7785
+ // If we reached the limit of quotes we can establish,
7786
+ // break and continue.
7787
+ if len (acquiredQuotes ) >= rfqmsg .MaxSendPaymentQuotes {
7788
+ break
7789
+ }
7790
+ }
7791
+
7792
+ if len (acquiredQuotes ) == 0 {
7793
+ joinErr := errors .Join (quoteErr , overpaymentErr )
7794
+ return fmt .Errorf ("failed to acquire any quotes for " +
7795
+ "payment: %v" , joinErr )
7796
+ }
7809
7797
7798
+ // We now create the HTLC with all the available rfq IDs. This
7799
+ // will be used by LND later to query the bandwidth of various
7800
+ // channels based on the established quotes.
7810
7801
htlc := rfqmsg .NewHtlc (
7811
- nil , fn .Some ( rfqID ), fn . None [[] rfqmsg.ID ](),
7802
+ nil , fn .None [rfqmsg.ID ](), fn . Some ( acquiredQuotes ),
7812
7803
)
7813
7804
7814
7805
// We'll now map the HTLC struct into a set of TLV records,
@@ -8498,6 +8489,48 @@ func (r *rpcServer) acquireBuyOrder(ctx context.Context,
8498
8489
return quote , nil
8499
8490
}
8500
8491
8492
+ // acquireSellOrder performs an RFQ negotiation with the target peer and quote
8493
+ // parameters and returns the quote if the negotiation was successful.
8494
+ func (r * rpcServer ) acquireSellOrder (ctx context.Context ,
8495
+ rpcSpecifier * rfqrpc.AssetSpecifier , paymentMaxAmt lnwire.MilliSatoshi ,
8496
+ expiry time.Time ,
8497
+ peerPubKey * route.Vertex ) (* rfqrpc.PeerAcceptedSellQuote , error ) {
8498
+
8499
+ resp , err := r .AddAssetSellOrder (
8500
+ ctx , & rfqrpc.AddAssetSellOrderRequest {
8501
+ AssetSpecifier : rpcSpecifier ,
8502
+ PaymentMaxAmt : uint64 (paymentMaxAmt ),
8503
+ Expiry : uint64 (expiry .Unix ()),
8504
+ PeerPubKey : peerPubKey [:],
8505
+ TimeoutSeconds : uint32 (
8506
+ rfq .DefaultTimeout .Seconds (),
8507
+ ),
8508
+ },
8509
+ )
8510
+ if err != nil {
8511
+ return nil , fmt .Errorf ("error adding sell order: %w" , err )
8512
+ }
8513
+
8514
+ switch r := resp .Response .(type ) {
8515
+ case * rfqrpc.AddAssetSellOrderResponse_AcceptedQuote :
8516
+ return r .AcceptedQuote , nil
8517
+
8518
+ case * rfqrpc.AddAssetSellOrderResponse_InvalidQuote :
8519
+ return nil , fmt .Errorf ("peer %v sent back an invalid " +
8520
+ "quote, status: %v" , r .InvalidQuote .Peer ,
8521
+ r .InvalidQuote .Status .String ())
8522
+
8523
+ case * rfqrpc.AddAssetSellOrderResponse_RejectedQuote :
8524
+ return nil , fmt .Errorf ("peer %v rejected the quote, code: " +
8525
+ "%v, error message: %v" , r .RejectedQuote .Peer ,
8526
+ r .RejectedQuote .ErrorCode ,
8527
+ r .RejectedQuote .ErrorMessage )
8528
+
8529
+ default :
8530
+ return nil , fmt .Errorf ("unexpected response type: %T" , r )
8531
+ }
8532
+ }
8533
+
8501
8534
// DeclareScriptKey declares a new script key to the wallet. This is useful
8502
8535
// when the script key contains scripts, which would mean it wouldn't be
8503
8536
// recognized by the wallet automatically. Declaring a script key will make any
0 commit comments