Skip to content

Commit df8ce52

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 dc7e7bd commit df8ce52

File tree

1 file changed

+111
-78
lines changed

1 file changed

+111
-78
lines changed

rpcserver.go

Lines changed: 111 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -7712,24 +7712,6 @@ func (r *rpcServer) SendPayment(req *tchrpc.SendPaymentRequest,
77127712
"use: %w", err)
77137713
}
77147714

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-
77337715
// paymentMaxAmt is the maximum amount that the counterparty is
77347716
// expected to pay. This is the amount that the invoice is
77357717
// asking for plus the fee limit in milli-satoshis.
@@ -7738,77 +7720,86 @@ func (r *rpcServer) SendPayment(req *tchrpc.SendPaymentRequest,
77387720
return err
77397721
}
77407722

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
77557727

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
77607729

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+
}
77657744

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+
}
77717752

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+
}
77757765

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+
}
77837774

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)
77937779

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)
78027782

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)
78067784

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+
}
78097797

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.
78107801
htlc := rfqmsg.NewHtlc(
7811-
nil, fn.Some(rfqID), fn.None[[]rfqmsg.ID](),
7802+
nil, fn.None[rfqmsg.ID](), fn.Some(acquiredQuotes),
78127803
)
78137804

78147805
// We'll now map the HTLC struct into a set of TLV records,
@@ -8498,6 +8489,48 @@ func (r *rpcServer) acquireBuyOrder(ctx context.Context,
84988489
return quote, nil
84998490
}
85008491

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+
85018534
// DeclareScriptKey declares a new script key to the wallet. This is useful
85028535
// when the script key contains scripts, which would mean it wouldn't be
85038536
// recognized by the wallet automatically. Declaring a script key will make any

0 commit comments

Comments
 (0)