@@ -7677,24 +7677,6 @@ func (r *rpcServer) SendPayment(req *tchrpc.SendPaymentRequest,
7677
7677
"use: %w" , err )
7678
7678
}
7679
7679
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
-
7698
7680
// paymentMaxAmt is the maximum amount that the counterparty is
7699
7681
// expected to pay. This is the amount that the invoice is
7700
7682
// asking for plus the fee limit in milli-satoshis.
@@ -7703,77 +7685,76 @@ func (r *rpcServer) SendPayment(req *tchrpc.SendPaymentRequest,
7703
7685
return err
7704
7686
}
7705
7687
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.
7720
7690
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
7725
7693
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
+ }
7730
7707
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
+ }
7736
7714
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
+ }
7740
7727
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
+ }
7748
7736
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 )
7758
7741
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 )
7767
7744
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
+ }
7771
7747
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
+ }
7774
7752
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.
7775
7756
htlc := rfqmsg .NewHtlc (
7776
- nil , fn .Some ( rfqID ), fn . None [[] rfqmsg.ID ](),
7757
+ nil , fn .None [rfqmsg.ID ](), fn . Some ( acquiredQuotes ),
7777
7758
)
7778
7759
7779
7760
// We'll now map the HTLC struct into a set of TLV records,
@@ -8463,6 +8444,52 @@ func (r *rpcServer) acquireBuyOrder(ctx context.Context,
8463
8444
return quote , nil
8464
8445
}
8465
8446
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
+
8466
8493
// DeclareScriptKey declares a new script key to the wallet. This is useful
8467
8494
// when the script key contains scripts, which would mean it wouldn't be
8468
8495
// recognized by the wallet automatically. Declaring a script key will make any
0 commit comments