diff --git a/routing/mock_test.go b/routing/mock_test.go index f47a9420..9645f73b 100644 --- a/routing/mock_test.go +++ b/routing/mock_test.go @@ -141,7 +141,7 @@ func (m *mockPaymentSession) RequestRoute(_, _ lnwire.MilliSatoshi, _, height uint32) (*route.Route, error) { if len(m.routes) == 0 { - return nil, fmt.Errorf("no routes") + return nil, errNoPathFound } r := m.routes[0] diff --git a/routing/pathfind.go b/routing/pathfind.go index 549d3416..1365958c 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -58,24 +58,6 @@ var ( // DefaultAprioriHopProbability is the default a priori probability for // a hop. DefaultAprioriHopProbability = float64(0.6) - - // errNoTlvPayload is returned when the destination hop does not support - // a tlv payload. - errNoTlvPayload = errors.New("destination hop doesn't " + - "understand new TLV payloads") - - // errNoPaymentAddr is returned when the destination hop does not - // support payment addresses. - errNoPaymentAddr = errors.New("destination hop doesn't " + - "understand payment addresses") - - // errNoPathFound is returned when a path to the target destination does - // not exist in the graph. - errNoPathFound = errors.New("unable to find a path to destination") - - // errInsufficientLocalBalance is returned when none of the local - // channels have enough balance for the payment. - errInsufficientBalance = errors.New("insufficient local balance") ) // edgePolicyWithSource is a helper struct to keep track of the source node diff --git a/routing/payment_lifecycle.go b/routing/payment_lifecycle.go index 3a0eb75b..6ac79400 100644 --- a/routing/payment_lifecycle.go +++ b/routing/payment_lifecycle.go @@ -210,11 +210,16 @@ func (p *paymentLifecycle) resumePayment() ([32]byte, *route.Route, error) { log.Warnf("Failed to find route for payment %x: %v", p.paymentHash, err) + routeErr, ok := err.(noRouteError) + if !ok { + return [32]byte{}, nil, err + } + // There is no route to try, and we have no active // shards. This means that there is no way for us to // send the payment, so mark it failed with no route. if state.numShardsInFlight == 0 { - failureCode := errorToPaymentFailure(err) + failureCode := routeErr.FailureReason() log.Debugf("Marking payment %v permanently "+ "failed with no route: %v", p.paymentHash, failureCode) @@ -570,25 +575,6 @@ func (p *shardHandler) collectResult(attempt *channeldb.HTLCAttemptInfo) ( }, nil } -// errorToPaymentFailure takes a path finding error and converts it into a -// payment-level failure. -func errorToPaymentFailure(err error) channeldb.FailureReason { - switch err { - case - errNoTlvPayload, - errNoPaymentAddr, - errNoPathFound, - errEmptyPaySession: - - return channeldb.FailureReasonNoRoute - - case errInsufficientBalance: - return channeldb.FailureReasonInsufficientBalance - } - - return channeldb.FailureReasonError -} - // createNewPaymentAttempt creates a new payment attempt from the given route. func (p *shardHandler) createNewPaymentAttempt(rt *route.Route) ( lnwire.ShortChannelID, *lnwire.UpdateAddHTLC, diff --git a/routing/payment_session.go b/routing/payment_session.go index f8c29755..6a597ff0 100644 --- a/routing/payment_session.go +++ b/routing/payment_session.go @@ -1,8 +1,6 @@ package routing import ( - "errors" - "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" @@ -12,12 +10,73 @@ import ( // to prevent an HTLC being failed if some blocks are mined while it's in-flight. const BlockPadding uint16 = 3 -var ( +// noRouteError encodes a non-critical error encountered during path finding. +type noRouteError uint8 + +const ( + // errNoTlvPayload is returned when the destination hop does not support + // a tlv payload. + errNoTlvPayload noRouteError = iota + + // errNoPaymentAddr is returned when the destination hop does not + // support payment addresses. + errNoPaymentAddr + + // errNoPathFound is returned when a path to the target destination does + // not exist in the graph. + errNoPathFound + + // errInsufficientLocalBalance is returned when none of the local + // channels have enough balance for the payment. + errInsufficientBalance + // errEmptyPaySession is returned when the empty payment session is // queried for a route. - errEmptyPaySession = errors.New("empty payment session") + errEmptyPaySession ) +// Error returns the string representation of the noRouteError +func (e noRouteError) Error() string { + switch e { + case errNoTlvPayload: + return "destination hop doesn't understand new TLV payloads" + + case errNoPaymentAddr: + return "destination hop doesn't understand payment addresses" + + case errNoPathFound: + return "unable to find a path to destination" + + case errEmptyPaySession: + return "empty payment session" + + case errInsufficientBalance: + return "insufficient local balance" + + default: + return "unknown no-route error" + } +} + +// FailureReason converts a path finding error into a payment-level failure. +func (e noRouteError) FailureReason() channeldb.FailureReason { + switch e { + case + errNoTlvPayload, + errNoPaymentAddr, + errNoPathFound, + errEmptyPaySession: + + return channeldb.FailureReasonNoRoute + + case errInsufficientBalance: + return channeldb.FailureReasonInsufficientBalance + + default: + return channeldb.FailureReasonError + } +} + // PaymentSession is used during SendPayment attempts to provide routes to // attempt. It also defines methods to give the PaymentSession additional // information learned during the previous attempts. @@ -29,6 +88,9 @@ type PaymentSession interface { // argument should be set to instruct the payment session about the // number of in flight HTLCS for the payment, such that it can choose // splitting strategy accordingly. + // + // A noRouteError is returned if a non-critical error is encountered + // during path finding. RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi, activeShards, height uint32) (*route.Route, error) }