Skip to content

Commit

Permalink
feat: enable multi-path payments in LND (#944)
Browse files Browse the repository at this point in the history
* feat: enable multi-path payments in LND

* fix: change ldk payment timeout from 60 to 50 seconds

* feat: add mpp to keysend and change timeout to 50 seconds
  • Loading branch information
im-adithya authored Jan 9, 2025
1 parent accf5c6 commit da91caf
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 34 deletions.
4 changes: 2 additions & 2 deletions lnclient/ldk/ldk.go
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ func (ls *LDKService) SendPaymentSync(ctx context.Context, invoice string, amoun
fee := uint64(0)
preimage := ""

for start := time.Now(); time.Since(start) < time.Second*60; {
for start := time.Now(); time.Since(start) < time.Second*50; {
event := <-ldkEventSubscription

eventPaymentSuccessful, isEventPaymentSuccessfulEvent := (*event).(ldk_node.EventPaymentSuccessful)
Expand Down Expand Up @@ -563,7 +563,7 @@ func (ls *LDKService) SendKeysend(ctx context.Context, amount uint64, destinatio
}
fee := uint64(0)
paid := false
for start := time.Now(); time.Since(start) < time.Second*60; {
for start := time.Now(); time.Since(start) < time.Second*50; {
event := <-ldkEventSubscription

eventPaymentSuccessful, isEventPaymentSuccessfulEvent := (*event).(ldk_node.EventPaymentSuccessful)
Expand Down
81 changes: 51 additions & 30 deletions lnclient/lnd/lnd.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,22 @@ func (svc *LNDService) LookupInvoice(ctx context.Context, paymentHash string) (t
return transaction, nil
}

func (svc *LNDService) getPaymentResult(stream routerrpc.Router_SendPaymentV2Client) (*lnrpc.Payment, error) {
for {
payment, err := stream.Recv()
if err != nil {
return nil, err
}

if payment.Status != lnrpc.Payment_IN_FLIGHT {
return payment, nil
}
}
}

func (svc *LNDService) SendPaymentSync(ctx context.Context, payReq string, amount *uint64) (*lnclient.PayInvoiceResponse, error) {
const MAX_PARTIAL_PAYMENTS = 16
const SEND_PAYMENT_TIMEOUT = 50
paymentRequest, err := decodepay.Decodepay(payReq)
if err != nil {
logger.Logger.WithFields(logrus.Fields{
Expand All @@ -329,37 +344,38 @@ func (svc *LNDService) SendPaymentSync(ctx context.Context, payReq string, amoun
if amount != nil {
paymentAmountMsat = *amount
}
sendRequest := &lnrpc.SendRequest{PaymentRequest: payReq, FeeLimit: &lnrpc.FeeLimit{
Limit: &lnrpc.FeeLimit_FixedMsat{
FixedMsat: int64(transactions.CalculateFeeReserveMsat(paymentAmountMsat)),
},
}}
sendRequest := &routerrpc.SendPaymentRequest{
PaymentRequest: payReq,
MaxParts: MAX_PARTIAL_PAYMENTS,
TimeoutSeconds: SEND_PAYMENT_TIMEOUT,
FeeLimitMsat: int64(transactions.CalculateFeeReserveMsat(paymentAmountMsat)),
}

if amount != nil {
sendRequest.AmtMsat = int64(*amount)
}

resp, err := svc.client.SendPaymentSync(ctx, sendRequest)
payStream, err := svc.client.SendPayment(ctx, sendRequest)
if err != nil {
return nil, err
}

if resp.PaymentError != "" {
return nil, errors.New(resp.PaymentError)
resp, err := svc.getPaymentResult(payStream)
if err != nil {
return nil, err
}

if resp.PaymentPreimage == nil {
return nil, errors.New("no preimage in response")
if resp.Status != lnrpc.Payment_SUCCEEDED {
return nil, errors.New(resp.FailureReason.String())
}

var fee uint64 = 0
if resp.PaymentRoute != nil {
fee = uint64(resp.PaymentRoute.TotalFeesMsat)
if resp.PaymentPreimage == "" {
return nil, errors.New("no preimage in response")
}

return &lnclient.PayInvoiceResponse{
Preimage: hex.EncodeToString(resp.PaymentPreimage),
Fee: fee,
Preimage: resp.PaymentPreimage,
Fee: uint64(resp.FeeMsat),
}, nil
}

Expand Down Expand Up @@ -389,22 +405,22 @@ func (svc *LNDService) SendKeysend(ctx context.Context, amount uint64, destinati
}
destCustomRecords[record.Type] = decodedValue
}
const MAX_PARTIAL_PAYMENTS = 16
const SEND_PAYMENT_TIMEOUT = 50
const KEYSEND_CUSTOM_RECORD = 5482373484
destCustomRecords[KEYSEND_CUSTOM_RECORD] = preImageBytes
sendPaymentRequest := &lnrpc.SendRequest{
sendPaymentRequest := &routerrpc.SendPaymentRequest{
Dest: destBytes,
AmtMsat: int64(amount),
PaymentHash: paymentHashBytes,
DestFeatures: []lnrpc.FeatureBit{lnrpc.FeatureBit_TLV_ONION_REQ},
DestCustomRecords: destCustomRecords,
FeeLimit: &lnrpc.FeeLimit{
Limit: &lnrpc.FeeLimit_FixedMsat{
FixedMsat: int64(transactions.CalculateFeeReserveMsat(amount)),
},
},
MaxParts: MAX_PARTIAL_PAYMENTS,
TimeoutSeconds: SEND_PAYMENT_TIMEOUT,
FeeLimitMsat: int64(transactions.CalculateFeeReserveMsat(amount)),
}

resp, err := svc.client.SendPaymentSync(ctx, sendPaymentRequest)
payStream, err := svc.client.SendPayment(ctx, sendPaymentRequest)
if err != nil {
logger.Logger.WithFields(logrus.Fields{
"amount": amount,
Expand All @@ -416,26 +432,31 @@ func (svc *LNDService) SendKeysend(ctx context.Context, amount uint64, destinati
}).Errorf("Failed to send keysend payment")
return nil, err
}
if resp.PaymentError != "" {

resp, err := svc.getPaymentResult(payStream)
if err != nil {
return nil, err
}

if resp.Status != lnrpc.Payment_SUCCEEDED {
logger.Logger.WithFields(logrus.Fields{
"amount": amount,
"payeePubkey": destination,
"paymentHash": paymentHash,
"preimage": preimage,
"customRecords": custom_records,
"paymentError": resp.PaymentError,
"paymentError": resp.FailureReason.String(),
}).Errorf("Keysend payment has payment error")
return nil, errors.New(resp.PaymentError)
return nil, errors.New(resp.FailureReason.String())
}
respPreimage := hex.EncodeToString(resp.PaymentPreimage)
if respPreimage != preimage {

if resp.PaymentPreimage != preimage {
logger.Logger.WithFields(logrus.Fields{
"amount": amount,
"payeePubkey": destination,
"paymentHash": paymentHash,
"preimage": preimage,
"customRecords": custom_records,
"paymentError": resp.PaymentError,
}).Errorf("Preimage in keysend response does not match")
return nil, errors.New("preimage in keysend response does not match")
}
Expand All @@ -445,11 +466,11 @@ func (svc *LNDService) SendKeysend(ctx context.Context, amount uint64, destinati
"paymentHash": paymentHash,
"preimage": preimage,
"customRecords": custom_records,
"respPreimage": respPreimage,
"respPreimage": resp.PaymentPreimage,
}).Info("Keysend payment successful")

return &lnclient.PayKeysendResponse{
Fee: uint64(resp.PaymentRoute.TotalFeesMsat),
Fee: uint64(resp.FeeMsat),
}, nil
}

Expand Down
4 changes: 2 additions & 2 deletions lnclient/lnd/wrapper/lnd.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ func (wrapper *LNDWrapper) PendingChannels(ctx context.Context, req *lnrpc.Pendi
return wrapper.client.PendingChannels(ctx, req, options...)
}

func (wrapper *LNDWrapper) SendPaymentSync(ctx context.Context, req *lnrpc.SendRequest, options ...grpc.CallOption) (*lnrpc.SendResponse, error) {
return wrapper.client.SendPaymentSync(ctx, req, options...)
func (wrapper *LNDWrapper) SendPayment(ctx context.Context, req *routerrpc.SendPaymentRequest, options ...grpc.CallOption) (routerrpc.Router_SendPaymentV2Client, error) {
return wrapper.routerClient.SendPaymentV2(ctx, req, options...)
}

func (wrapper *LNDWrapper) ChannelBalance(ctx context.Context, req *lnrpc.ChannelBalanceRequest, options ...grpc.CallOption) (*lnrpc.ChannelBalanceResponse, error) {
Expand Down

0 comments on commit da91caf

Please sign in to comment.