diff --git a/routing/payment_session.go b/routing/payment_session.go index 0afdf822f..b21d40998 100644 --- a/routing/payment_session.go +++ b/routing/payment_session.go @@ -6,6 +6,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btclog/v2" "github.com/lightningnetwork/lnd/channeldb" + graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/lnutils" "github.com/lightningnetwork/lnd/lnwire" @@ -235,6 +236,17 @@ func newPaymentSession(p *LightningPayment, selfNode route.Vertex, }, nil } +// pathFindingError is a wrapper error type that is used to distinguish path +// finding errors from other errors in path finding loop. +type pathFindingError struct { + error +} + +// Unwrap returns the underlying error. +func (e *pathFindingError) Unwrap() error { + return e.error +} + // RequestRoute returns a route which is likely to be capable for successfully // routing the specified HTLC payment to the target node. Initially the first // set of paths returned from this method may encounter routing failure along @@ -295,13 +307,8 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi, maxAmt = *p.payment.MaxShardAmt } - for { - // Get a routing graph session. - graph, closeGraph, err := p.graphSessFactory.NewGraphSession() - if err != nil { - return nil, err - } - + var path []*unifiedEdge + findPath := func(graph graphdb.NodeTraverser) error { // We'll also obtain a set of bandwidthHints from the lower // layer for each of our outbound channels. This will allow the // path finding to skip any links that aren't active or just @@ -310,19 +317,13 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi, // attempt, because concurrent payments may change balances. bandwidthHints, err := p.getBandwidthHints(graph) if err != nil { - // Close routing graph session. - if graphErr := closeGraph(); graphErr != nil { - log.Errorf("could not close graph session: %v", - graphErr) - } - - return nil, err + return err } p.log.Debugf("pathfinding for amt=%v", maxAmt) // Find a route for the current amount. - path, _, err := p.pathFinder( + path, _, err = p.pathFinder( &graphParams{ additionalEdges: p.additionalEdges, bandwidthHints: bandwidthHints, @@ -332,12 +333,42 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi, p.selfNode, p.selfNode, p.payment.Target, maxAmt, p.payment.TimePref, finalHtlcExpiry, ) - - // Close routing graph session. - if err := closeGraph(); err != nil { - log.Errorf("could not close graph session: %v", err) + if err != nil { + // Wrap the error to distinguish path finding errors + // from other errors in this closure. + return &pathFindingError{err} } + return nil + } + + for { + // Get a routing graph session. + graph, closeGraph, err := p.graphSessFactory.NewGraphSession() + if err != nil { + return nil, err + } + + err = findPath(graph) + // First, close routing graph session. + // NOTE: this will be removed in an upcoming commit. + if graphErr := closeGraph(); graphErr != nil { + log.Errorf("could not close graph session: %v", + graphErr) + } + // If there is an error, and it is not a path finding error, we + // return it immediately. + if err != nil && !lnutils.ErrorAs[*pathFindingError](err) { + return nil, err + } else if err != nil { + // If the error is a path finding error, we'll unwrap it + // to check the underlying error. + // + //nolint:errorlint + err = err.(*pathFindingError).Unwrap() + } + + // Otherwise, we'll switch on the path finding error. switch { case err == errNoPathFound: // Don't split if this is a legacy payment without mpp