From 4040bb356d4fb32c7841b52419b03fd4de1decd6 Mon Sep 17 00:00:00 2001 From: carla Date: Mon, 24 May 2021 08:40:13 +0200 Subject: [PATCH] loopout: refactor payInvoice to return more payment information We're going to want more information about our failures going forward, so we refactor payInvoice to return a full payment status. The primary change in this commit is that we surface both types of payment failures (result.err when we fail immediately, and lnrpc.Failure when our payment is failed back) and return them in the failure() method, rather than combining this information at a lower level. --- loopout.go | 134 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 102 insertions(+), 32 deletions(-) diff --git a/loopout.go b/loopout.go index a90365e..15b480c 100644 --- a/loopout.go +++ b/loopout.go @@ -62,8 +62,8 @@ type loopOutSwap struct { // htlcTxHash is the confirmed htlc tx id. htlcTxHash *chainhash.Hash - swapPaymentChan chan lndclient.PaymentResult - prePaymentChan chan lndclient.PaymentResult + swapPaymentChan chan paymentResult + prePaymentChan chan paymentResult wg sync.WaitGroup } @@ -340,27 +340,35 @@ func (s *loopOutSwap) executeAndFinalize(globalCtx context.Context) error { select { case result := <-s.swapPaymentChan: s.swapPaymentChan = nil - if result.Err != nil { + + err := s.handlePaymentResult(result) + if err != nil { + return err + } + + if result.failure() != nil { // Server didn't pull the swap payment. s.log.Infof("Swap payment failed: %v", - result.Err) + result.failure()) continue } - s.cost.Server += result.PaidAmt - s.cost.Offchain += result.PaidFee case result := <-s.prePaymentChan: s.prePaymentChan = nil - if result.Err != nil { + + err := s.handlePaymentResult(result) + if err != nil { + return err + } + + if result.failure() != nil { // Server didn't pull the prepayment. s.log.Infof("Prepayment failed: %v", - result.Err) + result.failure()) continue } - s.cost.Server += result.PaidAmt - s.cost.Offchain += result.PaidFee case <-globalCtx.Done(): return globalCtx.Err() @@ -379,6 +387,27 @@ func (s *loopOutSwap) executeAndFinalize(globalCtx context.Context) error { return s.persistState(globalCtx) } +func (s *loopOutSwap) handlePaymentResult(result paymentResult) error { + switch { + // If our result has a non-nil error, our status will be nil. In this + // case the payment failed so we do not need to take any action. + case result.err != nil: + return nil + + case result.status.State == lnrpc.Payment_SUCCEEDED: + s.cost.Server += result.status.Value.ToSatoshis() + s.cost.Offchain += result.status.Fee.ToSatoshis() + + return nil + + case result.status.State == lnrpc.Payment_FAILED: + return nil + + default: + return fmt.Errorf("unexpected state: %v", result.status.State) + } +} + // executeSwap executes the swap, but returns as soon as the swap outcome is // final. At that point, there may still be pending off-chain payment(s). func (s *loopOutSwap) executeSwap(globalCtx context.Context) error { @@ -510,31 +539,64 @@ func (s *loopOutSwap) payInvoices(ctx context.Context) { ) } +// paymentResult contains the response for a failed or settled payment, and +// any errors that occurred if the payment unexpectedly failed. +type paymentResult struct { + status lndclient.PaymentStatus + err error +} + +// failure returns the error we encountered trying to dispatch a payment result, +// if any. +func (p paymentResult) failure() error { + if p.err != nil { + return p.err + } + + if p.status.State == lnrpc.Payment_SUCCEEDED { + return nil + } + + return fmt.Errorf("payment failed: %v", p.status.FailureReason) +} + // payInvoice pays a single invoice. func (s *loopOutSwap) payInvoice(ctx context.Context, invoice string, maxFee btcutil.Amount, - outgoingChanIds loopdb.ChannelSet) chan lndclient.PaymentResult { + outgoingChanIds loopdb.ChannelSet) chan paymentResult { - resultChan := make(chan lndclient.PaymentResult) + resultChan := make(chan paymentResult) + sendResult := func(result paymentResult) { + select { + case resultChan <- result: + case <-ctx.Done(): + } + } go func() { - var result lndclient.PaymentResult + var result paymentResult status, err := s.payInvoiceAsync( ctx, invoice, maxFee, outgoingChanIds, ) if err != nil { - result.Err = err - } else { - result.Preimage = status.Preimage - result.PaidFee = status.Fee.ToSatoshis() - result.PaidAmt = status.Value.ToSatoshis() + result.err = err + sendResult(result) + return } - select { - case resultChan <- result: - case <-ctx.Done(): + // If our payment failed or succeeded, our status should be + // non-nil. + switch status.State { + case lnrpc.Payment_FAILED, lnrpc.Payment_SUCCEEDED: + result.status = *status + + default: + result.err = fmt.Errorf("unexpected payment state: %v", + status.State) } + + sendResult(result) }() return resultChan @@ -583,7 +645,7 @@ func (s *loopOutSwap) payInvoiceAsync(ctx context.Context, return &payState, nil case lnrpc.Payment_FAILED: - return nil, errors.New("payment failed") + return &payState, nil case lnrpc.Payment_IN_FLIGHT: // Continue waiting for final state. @@ -686,30 +748,38 @@ func (s *loopOutSwap) waitForConfirmedHtlc(globalCtx context.Context) ( // have lost the prepayment. case result := <-s.swapPaymentChan: s.swapPaymentChan = nil - if result.Err != nil { - s.state = loopdb.StateFailOffchainPayments + + err := s.handlePaymentResult(result) + if err != nil { + return nil, err + } + + if result.failure() != nil { s.log.Infof("Failed swap payment: %v", - result.Err) + result.failure()) + s.state = loopdb.StateFailOffchainPayments return nil, nil } - s.cost.Server += result.PaidAmt - s.cost.Offchain += result.PaidFee // If the prepay fails, abandon the swap. Because we // didn't reveal the preimage, the swap payment will be // canceled or time out. case result := <-s.prePaymentChan: s.prePaymentChan = nil - if result.Err != nil { - s.state = loopdb.StateFailOffchainPayments + + err := s.handlePaymentResult(result) + if err != nil { + return nil, err + } + + if result.failure() != nil { s.log.Infof("Failed prepayment: %v", - result.Err) + result.failure()) + s.state = loopdb.StateFailOffchainPayments return nil, nil } - s.cost.Server += result.PaidAmt - s.cost.Offchain += result.PaidFee // Unexpected error on the confirm channel happened, // abandon the swap.