Skip to content

Commit

Permalink
rpcserver: refactor invoice parsing for re-use
Browse files Browse the repository at this point in the history
We'll want to be able to parse the amount and expiry of an invoice in
other places, so we extract that into its own method.
  • Loading branch information
guggero committed Dec 16, 2024
1 parent f148369 commit ddd99db
Showing 1 changed file with 60 additions and 35 deletions.
95 changes: 60 additions & 35 deletions rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import (
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
Expand Down Expand Up @@ -7024,13 +7025,25 @@ func (r *rpcServer) SendPayment(req *tchrpc.SendPaymentRequest,
return fmt.Errorf("payment request and keysend custom " +
"records cannot be set at the same time")

// RFQ ID and keysend is set, which isn't a supported combination.
case req.RfqId != nil && isKeysend:
return fmt.Errorf("RFQ ID and keysend custom records " +
"cannot be set at the same time")

// A payment must either be a keysend payment or pay an invoice.
case !isKeysend && pReq.PaymentRequest == "":
return fmt.Errorf("payment request or keysend custom records " +
"must be set")

// There are custom records for the first hop set, which means RFQ
// negotiation has already happened (or this is a keysend payment and
// the correct asset amount is already encoded). So we don't need to do
// anything special and can just forward the payment to lnd.
case len(firstHopRecords) > 0:
// Continue below.

// The user specified a custom RFQ ID for a quote that should be used
// for the payment.
case req.RfqId != nil:
// Check if the provided RFQ ID matches the expected length.
if len(req.RfqId) != 32 {
Expand Down Expand Up @@ -7090,14 +7103,6 @@ func (r *rpcServer) SendPayment(req *tchrpc.SendPaymentRequest,

// The request wants to pay a specific invoice.
case pReq.PaymentRequest != "":
invoice, err := zpay32.Decode(
pReq.PaymentRequest, r.cfg.Lnd.ChainParams,
)
if err != nil {
return fmt.Errorf("error decoding payment request: %w",
err)
}

// The peer public key is optional if there is only a single
// asset channel.
var peerPubKey *route.Vertex
Expand Down Expand Up @@ -7128,35 +7133,11 @@ func (r *rpcServer) SendPayment(req *tchrpc.SendPaymentRequest,
// paymentMaxAmt is the maximum amount that the counterparty is
// expected to pay. This is the amount that the invoice is
// asking for plus the fee limit in milli-satoshis.
var paymentMaxAmt lnwire.MilliSatoshi
if invoice.MilliSat == nil {
amt, err := lnrpc.UnmarshallAmt(pReq.Amt, pReq.AmtMsat)
if err != nil {
return fmt.Errorf("error unmarshalling "+
"amount: %w", err)
}
if amt == 0 {
return errors.New("amount must be specified " +
"when paying a zero amount invoice")
}

paymentMaxAmt = amt
} else {
paymentMaxAmt = *invoice.MilliSat
}

// Calculate the fee limit that should be used for this payment.
feeLimit, err := lnrpc.UnmarshallAmt(
pReq.FeeLimitSat, pReq.FeeLimitMsat,
)
paymentMaxAmt, expiry, err := r.parseRequest(pReq)
if err != nil {
return fmt.Errorf("error unmarshalling fee limit: %w",
err)
return err
}

paymentMaxAmt += feeLimit

expiryTimestamp := invoice.Timestamp.Add(invoice.Expiry())
resp, err := r.AddAssetSellOrder(
ctx, &rfqrpc.AddAssetSellOrderRequest{
AssetSpecifier: &rfqrpc.AssetSpecifier{
Expand All @@ -7165,7 +7146,7 @@ func (r *rpcServer) SendPayment(req *tchrpc.SendPaymentRequest,
},
},
PaymentMaxAmt: uint64(paymentMaxAmt),
Expiry: uint64(expiryTimestamp.Unix()),
Expiry: uint64(expiry.Unix()),
PeerPubKey: peerPubKey[:],
TimeoutSeconds: uint32(
rfq.DefaultTimeout.Seconds(),
Expand Down Expand Up @@ -7295,6 +7276,50 @@ func (r *rpcServer) SendPayment(req *tchrpc.SendPaymentRequest,
}
}

// parseRequest parses the payment request and returns the payment maximum
// amount and the expiry time.
func (r *rpcServer) parseRequest(
req *routerrpc.SendPaymentRequest) (lnwire.MilliSatoshi, time.Time,
error) {

invoice, err := zpay32.Decode(req.PaymentRequest, r.cfg.Lnd.ChainParams)
if err != nil {
return 0, time.Time{}, fmt.Errorf("error decoding payment "+
"request: %w", err)
}

var paymentMaxAmt lnwire.MilliSatoshi
if invoice.MilliSat == nil {
amt, err := lnrpc.UnmarshallAmt(req.Amt, req.AmtMsat)
if err != nil {
return 0, time.Time{}, fmt.Errorf("error "+
"unmarshalling amount: %w", err)
}
if amt == 0 {
return 0, time.Time{}, errors.New("amount must be " +
"specified when paying a zero amount invoice")
}

paymentMaxAmt = amt
} else {
paymentMaxAmt = *invoice.MilliSat
}

// Calculate the fee limit that should be used for this payment.
feeLimit, err := lnrpc.UnmarshallAmt(
req.FeeLimitSat, req.FeeLimitMsat,
)
if err != nil {
return 0, time.Time{}, fmt.Errorf("error unmarshalling fee "+
"limit: %w", err)
}

paymentMaxAmt += feeLimit

expiry := invoice.Timestamp.Add(invoice.Expiry())
return paymentMaxAmt, expiry, nil
}

// AddInvoice is a wrapper around lnd's lnrpc.AddInvoice method with asset
// specific parameters. It allows RPC users to create invoices that correspond
// to the specified asset amount.
Expand Down

0 comments on commit ddd99db

Please sign in to comment.