From 4039ba9b696418c52ef743b8017c2a004b4f36dd Mon Sep 17 00:00:00 2001 From: Joost Jager Date: Tue, 14 Apr 2020 11:07:06 +0200 Subject: [PATCH] loopout: use routerrpc to send payments --- loopout.go | 112 +++++++++++++++++++++++++++++++++++++++++++++++- test/context.go | 19 ++++---- 2 files changed, 120 insertions(+), 11 deletions(-) diff --git a/loopout.go b/loopout.go index 82b0f92..a4dad22 100644 --- a/loopout.go +++ b/loopout.go @@ -4,6 +4,7 @@ import ( "context" "crypto/rand" "crypto/sha256" + "errors" "fmt" "time" @@ -14,6 +15,8 @@ import ( "github.com/lightninglabs/loop/swap" "github.com/lightninglabs/loop/sweep" "github.com/lightningnetwork/lnd/chainntnfs" + "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntypes" ) @@ -32,6 +35,10 @@ var ( // // TODO(wilmer): tune? DefaultSweepConfTargetDelta = DefaultSweepConfTarget * 2 + + // paymentTimeout is the timeout for the loop out payment loop as + // communicated to lnd. + paymentTimeout = time.Minute ) // loopOutSwap contains all the in-memory state related to a pending loop out @@ -384,19 +391,120 @@ func (s *loopOutSwap) persistState(ctx context.Context) error { func (s *loopOutSwap) payInvoices(ctx context.Context) { // Pay the swap invoice. s.log.Infof("Sending swap payment %v", s.SwapInvoice) - s.swapPaymentChan = s.lnd.Client.PayInvoice( + s.swapPaymentChan = s.payInvoice( ctx, s.SwapInvoice, s.MaxSwapRoutingFee, s.LoopOutContract.UnchargeChannel, ) // Pay the prepay invoice. s.log.Infof("Sending prepayment %v", s.PrepayInvoice) - s.prePaymentChan = s.lnd.Client.PayInvoice( + s.prePaymentChan = s.payInvoice( ctx, s.PrepayInvoice, s.MaxPrepayRoutingFee, nil, ) } +// payInvoice pays a single invoice. +func (s *loopOutSwap) payInvoice(ctx context.Context, invoice string, + maxFee btcutil.Amount, + outgoingChannel *uint64) chan lndclient.PaymentResult { + + resultChan := make(chan lndclient.PaymentResult) + + go func() { + var result lndclient.PaymentResult + + status, err := s.payInvoiceAsync( + ctx, invoice, maxFee, outgoingChannel, + ) + if err != nil { + result.Err = err + } else { + result.Preimage = status.Preimage + result.PaidFee = status.Fee.ToSatoshis() + result.PaidAmt = status.Value.ToSatoshis() + } + + select { + case resultChan <- result: + case <-ctx.Done(): + } + }() + + return resultChan +} + +// payInvoiceAsync is the asynchronously executed part of paying an invoice. +func (s *loopOutSwap) payInvoiceAsync(ctx context.Context, + invoice string, maxFee btcutil.Amount, outgoingChannel *uint64) ( + *lndclient.PaymentStatus, error) { + + // Extract hash from payment request. Unfortunately the request + // components aren't available directly. + chainParams := s.lnd.ChainParams + hash, _, err := swap.DecodeInvoice(chainParams, invoice) + if err != nil { + return nil, err + } + + req := lndclient.SendPaymentRequest{ + MaxFee: maxFee, + Invoice: invoice, + OutgoingChannel: outgoingChannel, + Timeout: paymentTimeout, + } + + // Lookup state of the swap payment. + paymentStateCtx, cancel := context.WithCancel(ctx) + defer cancel() + + payStatusChan, payErrChan, err := s.lnd.Router.SendPayment( + paymentStateCtx, req, + ) + if err != nil { + return nil, err + } + + for { + select { + // Payment advanced to the next state. + case payState := <-payStatusChan: + s.log.Infof("Payment %v: state=%v", + hash, payState.State) + + switch payState.State { + case lnrpc.Payment_SUCCEEDED: + return &payState, nil + + case lnrpc.Payment_FAILED: + return nil, errors.New("payment failed") + + case lnrpc.Payment_IN_FLIGHT: + // Continue waiting for final state. + + default: + return nil, errors.New("unknown payment state") + } + + // Abort the swap in case of an error. An unknown payment error + // from TrackPayment is no longer expected here. + case err := <-payErrChan: + if err != channeldb.ErrAlreadyPaid { + return nil, err + } + + payStatusChan, payErrChan, err = + s.lnd.Router.TrackPayment(paymentStateCtx, hash) + if err != nil { + return nil, err + } + + case <-ctx.Done(): + return nil, ctx.Err() + } + } +} + // waitForConfirmedHtlc waits for a confirmed htlc to appear on the chain. In // case we haven't revealed the preimage yet, it also monitors block height and // off-chain payment failure. diff --git a/test/context.go b/test/context.go index 96f7d2b..2ee09f9 100644 --- a/test/context.go +++ b/test/context.go @@ -9,6 +9,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/lightninglabs/loop/lndclient" "github.com/lightningnetwork/lnd/chainntnfs" + "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/zpay32" ) @@ -124,15 +125,15 @@ func (ctx *Context) AssertPaid( // Assert that client pays swap invoice. for { - var swapPayment PaymentChannelMessage + var swapPayment RouterPaymentChannelMessage select { - case swapPayment = <-ctx.Lnd.SendPaymentChannel: + case swapPayment = <-ctx.Lnd.RouterSendPaymentChannel: case <-time.After(Timeout): ctx.T.Fatalf("no payment sent for invoice: %v", expectedMemo) } - payReq := ctx.DecodeInvoice(swapPayment.PaymentRequest) + payReq := ctx.DecodeInvoice(swapPayment.Invoice) if _, ok := ctx.PaidInvoices[*payReq.Description]; ok { ctx.T.Fatalf("duplicate invoice paid: %v", @@ -140,12 +141,12 @@ func (ctx *Context) AssertPaid( } done := func(result error) { - select { - case swapPayment.Done <- lndclient.PaymentResult{ - Err: result, - }: - case <-time.After(Timeout): - ctx.T.Fatalf("payment result not consumed") + if result != nil { + swapPayment.Errors <- result + return + } + swapPayment.Updates <- lndclient.PaymentStatus{ + State: lnrpc.Payment_SUCCEEDED, } }