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.
pull/378/head
carla 3 years ago
parent 969e300241
commit 4040bb356d
No known key found for this signature in database
GPG Key ID: 4CA7FE54A6213C91

@ -62,8 +62,8 @@ type loopOutSwap struct {
// htlcTxHash is the confirmed htlc tx id. // htlcTxHash is the confirmed htlc tx id.
htlcTxHash *chainhash.Hash htlcTxHash *chainhash.Hash
swapPaymentChan chan lndclient.PaymentResult swapPaymentChan chan paymentResult
prePaymentChan chan lndclient.PaymentResult prePaymentChan chan paymentResult
wg sync.WaitGroup wg sync.WaitGroup
} }
@ -340,27 +340,35 @@ func (s *loopOutSwap) executeAndFinalize(globalCtx context.Context) error {
select { select {
case result := <-s.swapPaymentChan: case result := <-s.swapPaymentChan:
s.swapPaymentChan = nil 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. // Server didn't pull the swap payment.
s.log.Infof("Swap payment failed: %v", s.log.Infof("Swap payment failed: %v",
result.Err) result.failure())
continue continue
} }
s.cost.Server += result.PaidAmt
s.cost.Offchain += result.PaidFee
case result := <-s.prePaymentChan: case result := <-s.prePaymentChan:
s.prePaymentChan = nil 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. // Server didn't pull the prepayment.
s.log.Infof("Prepayment failed: %v", s.log.Infof("Prepayment failed: %v",
result.Err) result.failure())
continue continue
} }
s.cost.Server += result.PaidAmt
s.cost.Offchain += result.PaidFee
case <-globalCtx.Done(): case <-globalCtx.Done():
return globalCtx.Err() return globalCtx.Err()
@ -379,6 +387,27 @@ func (s *loopOutSwap) executeAndFinalize(globalCtx context.Context) error {
return s.persistState(globalCtx) 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 // 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). // final. At that point, there may still be pending off-chain payment(s).
func (s *loopOutSwap) executeSwap(globalCtx context.Context) error { 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. // payInvoice pays a single invoice.
func (s *loopOutSwap) payInvoice(ctx context.Context, invoice string, func (s *loopOutSwap) payInvoice(ctx context.Context, invoice string,
maxFee btcutil.Amount, 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() { go func() {
var result lndclient.PaymentResult var result paymentResult
status, err := s.payInvoiceAsync( status, err := s.payInvoiceAsync(
ctx, invoice, maxFee, outgoingChanIds, ctx, invoice, maxFee, outgoingChanIds,
) )
if err != nil { if err != nil {
result.Err = err result.err = err
} else { sendResult(result)
result.Preimage = status.Preimage return
result.PaidFee = status.Fee.ToSatoshis()
result.PaidAmt = status.Value.ToSatoshis()
} }
select { // If our payment failed or succeeded, our status should be
case resultChan <- result: // non-nil.
case <-ctx.Done(): 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 return resultChan
@ -583,7 +645,7 @@ func (s *loopOutSwap) payInvoiceAsync(ctx context.Context,
return &payState, nil return &payState, nil
case lnrpc.Payment_FAILED: case lnrpc.Payment_FAILED:
return nil, errors.New("payment failed") return &payState, nil
case lnrpc.Payment_IN_FLIGHT: case lnrpc.Payment_IN_FLIGHT:
// Continue waiting for final state. // Continue waiting for final state.
@ -686,30 +748,38 @@ func (s *loopOutSwap) waitForConfirmedHtlc(globalCtx context.Context) (
// have lost the prepayment. // have lost the prepayment.
case result := <-s.swapPaymentChan: case result := <-s.swapPaymentChan:
s.swapPaymentChan = nil 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", s.log.Infof("Failed swap payment: %v",
result.Err) result.failure())
s.state = loopdb.StateFailOffchainPayments
return nil, nil return nil, nil
} }
s.cost.Server += result.PaidAmt
s.cost.Offchain += result.PaidFee
// If the prepay fails, abandon the swap. Because we // If the prepay fails, abandon the swap. Because we
// didn't reveal the preimage, the swap payment will be // didn't reveal the preimage, the swap payment will be
// canceled or time out. // canceled or time out.
case result := <-s.prePaymentChan: case result := <-s.prePaymentChan:
s.prePaymentChan = nil 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", s.log.Infof("Failed prepayment: %v",
result.Err) result.failure())
s.state = loopdb.StateFailOffchainPayments
return nil, nil return nil, nil
} }
s.cost.Server += result.PaidAmt
s.cost.Offchain += result.PaidFee
// Unexpected error on the confirm channel happened, // Unexpected error on the confirm channel happened,
// abandon the swap. // abandon the swap.

Loading…
Cancel
Save