@@ -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