routing: don't return an error from failPayment within handleSendError

In this commit, we fix a regression introduced by a recent bug fix in
this area. Before this change, we'd inspect the error returned by
`processSendError`, and then fail the payment from the PoV of mission
control using the returned error.

A recent refactoring removed `processSendError` and combined the logic
with `tryApplyChannelUpdate` in order to introduce a new
`handleSendError` method that consolidates the logic within the
`shardHandler`. Along the way, the behavior of the prior check was
replicated in the form of a new internal `failPayment` closure. However,
the new function closure ends up returning a `channeldb.FailureReason`
instance, which is actually an `error`.

In the wild, when `SendToRoute` fails due to an error at the
destination, then this new logic caused the `handleSendErorr` method to
fail with an error, returning an unstructured error back to the caller,
instead of the usual payment failure details.

We fix this by no longer checking the `handleSendErorr` for an error as
normal. The `handleSendErorr` function as is will always return an error
of type `*channeldb.FailureReason`, therefore we don't need to treat it
as a normal error. Instead, we check for the type of error returned, and
update the control tower state accordingly.

With this commit, the test added in the prior commit now passes.

Fixes #5477.
This commit is contained in:
Olaoluwa Osuntokun 2021-07-06 18:41:22 -07:00
parent b24edb2833
commit 525ef594c7
No known key found for this signature in database
GPG Key ID: 3BBD59E99B280306
2 changed files with 22 additions and 8 deletions

@ -782,10 +782,13 @@ func (p *shardHandler) handleSendError(attempt *channeldb.HTLCAttemptInfo,
if err := p.router.cfg.Control.Fail( if err := p.router.cfg.Control.Fail(
p.identifier, *reason); err != nil { p.identifier, *reason); err != nil {
return err log.Errorf("unable to report failure to control "+
"tower: %v", err)
return &internalErrorReason
} }
return *reason return reason
} }
// reportFail is a helper closure that reports the failure to the // reportFail is a helper closure that reports the failure to the

@ -2,6 +2,7 @@ package routing
import ( import (
"bytes" "bytes"
goErrors "errors"
"fmt" "fmt"
"runtime" "runtime"
"strings" "strings"
@ -2163,13 +2164,23 @@ func (r *ChannelRouter) SendToRoute(htlcHash lntypes.Hash, rt *route.Route) (
// mark the payment failed with the control tower immediately. Process // mark the payment failed with the control tower immediately. Process
// the error to check if it maps into a terminal error code, if not use // the error to check if it maps into a terminal error code, if not use
// a generic NO_ROUTE error. // a generic NO_ROUTE error.
if err := sh.handleSendError(attempt, shardError); err != nil { var failureReason *channeldb.FailureReason
return nil, err err = sh.handleSendError(attempt, shardError)
}
switch {
// If we weren't able to extract a proper failure reason (which can
// happen if the second chance logic is triggered), then we'll use the
// normal no route error.
case err == nil:
err = r.cfg.Control.Fail( err = r.cfg.Control.Fail(
paymentIdentifier, channeldb.FailureReasonNoRoute, paymentIdentifier, channeldb.FailureReasonNoRoute,
) )
// If this is a failure reason, then we'll apply the failure directly
// to the control tower, and return the normal response to the caller.
case goErrors.As(err, &failureReason):
err = r.cfg.Control.Fail(paymentIdentifier, *failureReason)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }