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 *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.

Loading…
Cancel
Save