Skip to content

Commit

Permalink
lnd: use the SendPaymentV2 RPC and set payment_request
Browse files Browse the repository at this point in the history
Use the SendPaymentV2 RPC and set payment_request in the request.
This will ensure that the payments are
linked to the peerswap invoices that were paid.
I check that the remote pubkey of the channel
and the destination of the invoice match.

Also added a test case to ensure that payreq is included in the payment.
  • Loading branch information
YusukeShimizu committed Feb 8, 2024
1 parent aef7098 commit d437ac5
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 30 deletions.
60 changes: 30 additions & 30 deletions lnd/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"strings"
"sync"
"time"

"github.com/elementsproject/peerswap/log"

Expand Down Expand Up @@ -289,46 +290,45 @@ func (l *Client) PayInvoiceViaChannel(payreq, scid string) (preimage string, err
if err != nil {
return "", err
}

channel, err := l.CheckChannel(scid, uint64(decoded.NumSatoshis))
if err != nil {
return "", err
}

v, err := route.NewVertexFromStr(channel.GetRemotePubkey())
if err != nil {
return "", err
}
route, err := l.routerClient.BuildRoute(context.Background(), &routerrpc.BuildRouteRequest{
AmtMsat: decoded.NumMsat,
FinalCltvDelta: int32(decoded.CltvExpiry),
OutgoingChanId: channel.GetChanId(),
HopPubkeys: [][]byte{v[:]},
//Ensures that the invoice is paied via the direct channel to the peer.
if decoded.GetDestination() != channel.RemotePubkey {
return "", fmt.Errorf(`destination pubkey in invoice does not match remote pubkey of channel.
destination pubkey in invoice: %s, remote pubkey of channel: %s `, decoded.GetDestination(), channel.RemotePubkey)
}
paymentStream, err := l.routerClient.SendPaymentV2(l.ctx, &routerrpc.SendPaymentRequest{
PaymentRequest: payreq,
TimeoutSeconds: 30,
CltvLimit: int32(decoded.GetCltvExpiry()),
OutgoingChanIds: []uint64{channel.ChanId},
MaxParts: 1,
})

if err != nil {
return "", err
}
if decoded.GetPaymentAddr() != nil {
route.GetRoute().GetHops()[0].MppRecord = &lnrpc.MPPRecord{
PaymentAddr: decoded.GetPaymentAddr(),
TotalAmtMsat: decoded.NumMsat,
for {
res, err := paymentStream.Recv()
if err != nil {
return "", err
}
switch res.Status {
case lnrpc.Payment_UNKNOWN:
log.Debugf("PayInvoiceViaChannel: payment is unknown")
case lnrpc.Payment_SUCCEEDED:
return res.PaymentPreimage, nil
case lnrpc.Payment_IN_FLIGHT:
log.Debugf("PayInvoiceViaChannel: payment still in flight")
case lnrpc.Payment_FAILED:
return "", fmt.Errorf("payment failure %s", res.FailureReason)
default:
log.Debugf("PayInvoiceViaChannel: got unexpected payment status %d", res.Status)
}
time.Sleep(time.Second)
}
rHash, err := hex.DecodeString(decoded.PaymentHash)
if err != nil {
return "", err
}
res, err := l.lndClient.SendToRouteSync(context.Background(), &lnrpc.SendToRouteRequest{
PaymentHash: rHash,
Route: route.GetRoute(),
})
if err != nil {
return "", err
}
if res.PaymentError != "" {
return "", fmt.Errorf("received payment error: %v", res.PaymentError)
}
return hex.EncodeToString(res.PaymentPreimage), nil
}

func (l *Client) RebalancePayment(payreq string, channelId string) (preimage string, err error) {
Expand Down
3 changes: 3 additions & 0 deletions test/testcases.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@ func preimageClaimTest(t *testing.T, params *testParams) {
expectedMemo,
memo,
)
payreq, err := params.takerNode.GetLatestPayReqOfPayment()
require.NoError(err)
require.Equal(bolt11, payreq)
}

func csvClaimTest(t *testing.T, params *testParams) {
Expand Down
11 changes: 11 additions & 0 deletions testframework/clightning.go
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,17 @@ func (n *CLightningNode) GetMemoFromPayreq(bolt11 string) (string, error) {
return r.Description, nil
}

func (n *CLightningNode) GetLatestPayReqOfPayment() (string, error) {
ps, err := n.Rpc.ListPays()
if err != nil {
return "", err
}
if len(ps) > 0 {
return ps[len(ps)-1].Bolt11, nil
}
return "", fmt.Errorf("payments list is nil")
}

func (n *CLightningNode) GetFeeInvoiceAmtSat() (sat uint64, err error) {
rx := regexp.MustCompile(`^peerswap .* fee .*`)
var feeInvoiceAmt uint64
Expand Down
1 change: 1 addition & 0 deletions testframework/lightning.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ type LightningNode interface {
// invoices.
GetLatestInvoice() (payreq string, err error)
GetMemoFromPayreq(payreq string) (memo string, err error)
GetLatestPayReqOfPayment() (payreq string, err error)
GetFeeInvoiceAmtSat() (sat uint64, err error)

Run(waitForReady, swaitForBitcoinSynced bool) error
Expand Down
11 changes: 11 additions & 0 deletions testframework/lnd.go
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,17 @@ func (n *LndNode) GetMemoFromPayreq(bolt11 string) (string, error) {
return r.Description, nil
}

func (n *LndNode) GetLatestPayReqOfPayment() (string, error) {
ps, err := n.Rpc.ListPayments(context.Background(), &lnrpc.ListPaymentsRequest{})
if err != nil {
return "", err
}
if ps.GetPayments() != nil {
return ps.GetPayments()[len(ps.GetPayments())-1].PaymentRequest, nil
}
return "", fmt.Errorf("payments list is nil")
}

func ScidFromLndChanId(id uint64) string {
lndScid := lnwire.NewShortChanIDFromInt(id)
return fmt.Sprintf("%dx%dx%d", lndScid.BlockHeight, lndScid.TxIndex, lndScid.TxPosition)
Expand Down

0 comments on commit d437ac5

Please sign in to comment.