Skip to content

Commit

Permalink
rpcserver+taprpc: reject small invoice payments
Browse files Browse the repository at this point in the history
With this commit we reject the payment of small invoices by using
assets, if the total amount of sending 1 asset unit plus the
protocol-dictated 354 satoshi for a non-dust HTLC is higher than the
total invoice amount attempting to be paid.
The user can override this decision with a new flag if they wish to
proceed with the uneconomical payment.
  • Loading branch information
guggero committed Dec 13, 2024
1 parent ae58049 commit 93a2306
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 71 deletions.
63 changes: 62 additions & 1 deletion rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -7074,8 +7074,24 @@ func (r *rpcServer) SendPayment(req *tchrpc.SendPaymentRequest,
// amount based on the asset-to-BTC conversion rate.
sellOrder := taprpc.MarshalAcceptedSellQuote(*quote)

// 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.
paymentMaxAmt, _, err := r.parseRequest(pReq)
if err != nil {
return err
}

// Check if the payment is economical based on the quote.
err = checkEconomicalPayment(
sellOrder, paymentMaxAmt, req.AllowUneconomical,
)
if err != nil {
return err
}

// Send out the information about the quote on the stream.
err := stream.Send(&tchrpc.SendPaymentResponse{
err = stream.Send(&tchrpc.SendPaymentResponse{
Result: &tchrpc.SendPaymentResponse_AcceptedSellOrder{
AcceptedSellOrder: sellOrder,
},
Expand Down Expand Up @@ -7177,6 +7193,14 @@ func (r *rpcServer) SendPayment(req *tchrpc.SendPaymentRequest,
return fmt.Errorf("unexpected response type: %T", r)
}

// Check if the payment is economical based on the quote.
err = checkEconomicalPayment(
acceptedQuote, paymentMaxAmt, req.AllowUneconomical,
)
if err != nil {
return err
}

// Send out the information about the quote on the stream.
err = stream.Send(&tchrpc.SendPaymentResponse{
Result: &tchrpc.SendPaymentResponse_AcceptedSellOrder{
Expand Down Expand Up @@ -7320,6 +7344,43 @@ func (r *rpcServer) parseRequest(
return paymentMaxAmt, expiry, nil
}

// checkEconomicalPayment checks if paying a certain invoice amount is
// economical to pay using assets, given the rate from the accepted quote and
// the minimum non-dust HTLC amount dictated by the protocol.
func checkEconomicalPayment(quote *rfqrpc.PeerAcceptedSellQuote,
paymentAmount lnwire.MilliSatoshi, allowUnEconomical bool) error {

rateFP, err := rfqrpc.UnmarshalFixedPoint(quote.BidAssetRate)
if err != nil {
return fmt.Errorf("error unmarshalling asset rate: %w", err)
}

srvrLog.Debugf("Checking if payment is economical (min transportable "+
"mSat: %d, paymentAmount: %d, allowUnEconomical=%v)",
quote.MinTransportableMsat, paymentAmount, allowUnEconomical)

// If the override flag is set, we ignore this check and return early.
if allowUnEconomical {
return nil
}

// If the payment amount is less than the minimal transportable amount
// dictated by the quote, we'll return an error to inform the user. They
// can still override this check if they want to proceed anyway.
if lnwire.MilliSatoshi(quote.MinTransportableMsat) > paymentAmount {
return fmt.Errorf("rejecting payment of %v (invoice "+
"amount + user-defined routing fee limit), minimum "+
"amount for an asset payment is %v mSAT with the "+
"current rate of %v units/BTC; override this check "+
"by specifying the allow_uneconomical flag",
paymentAmount, quote.MinTransportableMsat,
rateFP.String())
}

// The amount checks out, we can proceed with the payment.
return 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
155 changes: 85 additions & 70 deletions taprpc/tapchannelrpc/tapchannel.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions taprpc/tapchannelrpc/tapchannel.proto
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@ message SendPaymentRequest {
// payment will immediately be dispatched, skipping the rfq negotiation
// phase, and using the following rfq id instead.
bytes rfq_id = 5;

// If a small invoice should be paid that is below the amount that always
// needs to be sent out to carry a single asset unit, then by default the
// payment is rejected. If this flag is set, then the payment will be
// allowed to proceed, even if it is uneconomical.
bool allow_uneconomical = 6;
}

message SendPaymentResponse {
Expand Down
Loading

0 comments on commit 93a2306

Please sign in to comment.