diff --git a/routing/missioncontrol.go b/routing/missioncontrol.go index 418fa0a9..750e4266 100644 --- a/routing/missioncontrol.go +++ b/routing/missioncontrol.go @@ -1,7 +1,6 @@ package routing import ( - "fmt" "sync" "time" @@ -149,33 +148,6 @@ func (m *missionControl) GraphPruneView() graphPruneView { } } -// paymentSession is used during an HTLC routings session to prune the local -// chain view in response to failures, and also report those failures back to -// missionControl. The snapshot copied for this session will only ever grow, -// and will now be pruned after a decay like the main view within mission -// control. We do this as we want to avoid the case where we continually try a -// bad edge or route multiple times in a session. This can lead to an infinite -// loop if payment attempts take long enough. An additional set of edges can -// also be provided to assist in reaching the payment's destination. -type paymentSession struct { - pruneViewSnapshot graphPruneView - - additionalEdges map[Vertex][]*channeldb.ChannelEdgePolicy - - bandwidthHints map[uint64]lnwire.MilliSatoshi - - // errFailedFeeChans is a map of the short channel IDs that were the - // source of policy related routing failures during this payment attempt. - // We'll use this map to prune out channels when the first error may not - // require pruning, but any subsequent ones do. - errFailedPolicyChans map[edgeLocator]struct{} - - mc *missionControl - - haveRoutes bool - preBuiltRoutes []*Route -} - // NewPaymentSession creates a new payment session backed by the latest prune // view from Mission Control. An optional set of routing hints can be provided // in order to populate additional edges to explore when finding a path to the @@ -299,145 +271,6 @@ func generateBandwidthHints(sourceNode *channeldb.LightningNode, return bandwidthHints, nil } -// ReportVertexFailure adds a vertex to the graph prune view after a client -// reports a routing failure localized to the vertex. The time the vertex was -// added is noted, as it'll be pruned from the shared view after a period of -// vertexDecay. However, the vertex will remain pruned for the *local* session. -// This ensures we don't retry this vertex during the payment attempt. -func (p *paymentSession) ReportVertexFailure(v Vertex) { - log.Debugf("Reporting vertex %v failure to Mission Control", v) - - // First, we'll add the failed vertex to our local prune view snapshot. - p.pruneViewSnapshot.vertexes[v] = struct{}{} - - // With the vertex added, we'll now report back to the global prune - // view, with this new piece of information so it can be utilized for - // new payment sessions. - p.mc.Lock() - p.mc.failedVertexes[v] = time.Now() - p.mc.Unlock() -} - -// ReportChannelFailure adds a channel to the graph prune view. The time the -// channel was added is noted, as it'll be pruned from the global view after a -// period of edgeDecay. However, the edge will remain pruned for the duration -// of the *local* session. This ensures that we don't flap by continually -// retrying an edge after its pruning has expired. -// -// TODO(roasbeef): also add value attempted to send and capacity of channel -func (p *paymentSession) ReportEdgeFailure(e *edgeLocator) { - log.Debugf("Reporting edge %v failure to Mission Control", e) - - // First, we'll add the failed edge to our local prune view snapshot. - p.pruneViewSnapshot.edges[*e] = struct{}{} - - // With the edge added, we'll now report back to the global prune view, - // with this new piece of information so it can be utilized for new - // payment sessions. - p.mc.Lock() - p.mc.failedEdges[*e] = time.Now() - p.mc.Unlock() -} - -// ReportChannelPolicyFailure handles a failure message that relates to a -// channel policy. For these types of failures, the policy is updated and we -// want to keep it included during path finding. This function does mark the -// edge as 'policy failed once'. The next time it fails, the whole node will be -// pruned. This is to prevent nodes from keeping us busy by continuously sending -// new channel updates. -func (p *paymentSession) ReportEdgePolicyFailure( - errSource Vertex, failedEdge *edgeLocator) { - - // Check to see if we've already reported a policy related failure for - // this channel. If so, then we'll prune out the vertex. - _, ok := p.errFailedPolicyChans[*failedEdge] - if ok { - // TODO(joostjager): is this aggresive pruning still necessary? - // Just pruning edges may also work unless there is a huge - // number of failing channels from that node? - p.ReportVertexFailure(errSource) - - return - } - - // Finally, we'll record a policy failure from this node and move on. - p.errFailedPolicyChans[*failedEdge] = struct{}{} -} - -// 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 -// the way, however as more payments are sent, mission control will start to -// build an up to date view of the network itself. With each payment a new area -// will be explored, which feeds into the recommendations made for routing. -// -// NOTE: This function is safe for concurrent access. -func (p *paymentSession) RequestRoute(payment *LightningPayment, - height uint32, finalCltvDelta uint16) (*Route, error) { - - switch { - // If we have a set of pre-built routes, then we'll just pop off the - // next route from the queue, and use it directly. - case p.haveRoutes && len(p.preBuiltRoutes) > 0: - nextRoute := p.preBuiltRoutes[0] - p.preBuiltRoutes[0] = nil // Set to nil to avoid GC leak. - p.preBuiltRoutes = p.preBuiltRoutes[1:] - - return nextRoute, nil - - // If we were instantiated with a set of pre-built routes, and we've - // run out, then we'll return a terminal error. - case p.haveRoutes && len(p.preBuiltRoutes) == 0: - return nil, fmt.Errorf("pre-built routes exhausted") - } - - // Otherwise we actually need to perform path finding, so we'll obtain - // our current prune view snapshot. This view will only ever grow - // during the duration of this payment session, never shrinking. - pruneView := p.pruneViewSnapshot - - log.Debugf("Mission Control session using prune view of %v "+ - "edges, %v vertexes", len(pruneView.edges), - len(pruneView.vertexes)) - - // TODO(roasbeef): sync logic amongst dist sys - - // Taking into account this prune view, we'll attempt to locate a path - // to our destination, respecting the recommendations from - // missionControl. - path, err := findPath( - &graphParams{ - graph: p.mc.graph, - additionalEdges: p.additionalEdges, - bandwidthHints: p.bandwidthHints, - }, - &restrictParams{ - ignoredNodes: pruneView.vertexes, - ignoredEdges: pruneView.edges, - feeLimit: payment.FeeLimit, - }, - p.mc.selfNode, payment.Target, payment.Amount, - ) - if err != nil { - return nil, err - } - - // With the next candidate path found, we'll attempt to turn this into - // a route by applying the time-lock and fee requirements. - sourceVertex := Vertex(p.mc.selfNode.PubKeyBytes) - route, err := newRoute( - payment.Amount, payment.FeeLimit, sourceVertex, path, height, - finalCltvDelta, - ) - if err != nil { - // TODO(roasbeef): return which edge/vertex didn't work - // out - return nil, err - } - - return route, err -} - // ResetHistory resets the history of missionControl returning it to a state as // if no payment attempts have been made. func (m *missionControl) ResetHistory() { diff --git a/routing/payment_session.go b/routing/payment_session.go new file mode 100644 index 00000000..9eef0343 --- /dev/null +++ b/routing/payment_session.go @@ -0,0 +1,175 @@ +package routing + +import ( + "fmt" + "time" + + "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/lnwire" +) + +// paymentSession is used during an HTLC routings session to prune the local +// chain view in response to failures, and also report those failures back to +// missionControl. The snapshot copied for this session will only ever grow, +// and will now be pruned after a decay like the main view within mission +// control. We do this as we want to avoid the case where we continually try a +// bad edge or route multiple times in a session. This can lead to an infinite +// loop if payment attempts take long enough. An additional set of edges can +// also be provided to assist in reaching the payment's destination. +type paymentSession struct { + pruneViewSnapshot graphPruneView + + additionalEdges map[Vertex][]*channeldb.ChannelEdgePolicy + + bandwidthHints map[uint64]lnwire.MilliSatoshi + + // errFailedFeeChans is a map of the short channel IDs that were the + // source of policy related routing failures during this payment attempt. + // We'll use this map to prune out channels when the first error may not + // require pruning, but any subsequent ones do. + errFailedPolicyChans map[edgeLocator]struct{} + + mc *missionControl + + haveRoutes bool + preBuiltRoutes []*Route +} + +// ReportVertexFailure adds a vertex to the graph prune view after a client +// reports a routing failure localized to the vertex. The time the vertex was +// added is noted, as it'll be pruned from the shared view after a period of +// vertexDecay. However, the vertex will remain pruned for the *local* session. +// This ensures we don't retry this vertex during the payment attempt. +func (p *paymentSession) ReportVertexFailure(v Vertex) { + log.Debugf("Reporting vertex %v failure to Mission Control", v) + + // First, we'll add the failed vertex to our local prune view snapshot. + p.pruneViewSnapshot.vertexes[v] = struct{}{} + + // With the vertex added, we'll now report back to the global prune + // view, with this new piece of information so it can be utilized for + // new payment sessions. + p.mc.Lock() + p.mc.failedVertexes[v] = time.Now() + p.mc.Unlock() +} + +// ReportChannelFailure adds a channel to the graph prune view. The time the +// channel was added is noted, as it'll be pruned from the global view after a +// period of edgeDecay. However, the edge will remain pruned for the duration +// of the *local* session. This ensures that we don't flap by continually +// retrying an edge after its pruning has expired. +// +// TODO(roasbeef): also add value attempted to send and capacity of channel +func (p *paymentSession) ReportEdgeFailure(e *edgeLocator) { + log.Debugf("Reporting edge %v failure to Mission Control", e) + + // First, we'll add the failed edge to our local prune view snapshot. + p.pruneViewSnapshot.edges[*e] = struct{}{} + + // With the edge added, we'll now report back to the global prune view, + // with this new piece of information so it can be utilized for new + // payment sessions. + p.mc.Lock() + p.mc.failedEdges[*e] = time.Now() + p.mc.Unlock() +} + +// ReportChannelPolicyFailure handles a failure message that relates to a +// channel policy. For these types of failures, the policy is updated and we +// want to keep it included during path finding. This function does mark the +// edge as 'policy failed once'. The next time it fails, the whole node will be +// pruned. This is to prevent nodes from keeping us busy by continuously sending +// new channel updates. +func (p *paymentSession) ReportEdgePolicyFailure( + errSource Vertex, failedEdge *edgeLocator) { + + // Check to see if we've already reported a policy related failure for + // this channel. If so, then we'll prune out the vertex. + _, ok := p.errFailedPolicyChans[*failedEdge] + if ok { + // TODO(joostjager): is this aggresive pruning still necessary? + // Just pruning edges may also work unless there is a huge + // number of failing channels from that node? + p.ReportVertexFailure(errSource) + + return + } + + // Finally, we'll record a policy failure from this node and move on. + p.errFailedPolicyChans[*failedEdge] = struct{}{} +} + +// 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 +// the way, however as more payments are sent, mission control will start to +// build an up to date view of the network itself. With each payment a new area +// will be explored, which feeds into the recommendations made for routing. +// +// NOTE: This function is safe for concurrent access. +func (p *paymentSession) RequestRoute(payment *LightningPayment, + height uint32, finalCltvDelta uint16) (*Route, error) { + + switch { + // If we have a set of pre-built routes, then we'll just pop off the + // next route from the queue, and use it directly. + case p.haveRoutes && len(p.preBuiltRoutes) > 0: + nextRoute := p.preBuiltRoutes[0] + p.preBuiltRoutes[0] = nil // Set to nil to avoid GC leak. + p.preBuiltRoutes = p.preBuiltRoutes[1:] + + return nextRoute, nil + + // If we were instantiated with a set of pre-built routes, and we've + // run out, then we'll return a terminal error. + case p.haveRoutes && len(p.preBuiltRoutes) == 0: + return nil, fmt.Errorf("pre-built routes exhausted") + } + + // Otherwise we actually need to perform path finding, so we'll obtain + // our current prune view snapshot. This view will only ever grow + // during the duration of this payment session, never shrinking. + pruneView := p.pruneViewSnapshot + + log.Debugf("Mission Control session using prune view of %v "+ + "edges, %v vertexes", len(pruneView.edges), + len(pruneView.vertexes)) + + // TODO(roasbeef): sync logic amongst dist sys + + // Taking into account this prune view, we'll attempt to locate a path + // to our destination, respecting the recommendations from + // missionControl. + path, err := findPath( + &graphParams{ + graph: p.mc.graph, + additionalEdges: p.additionalEdges, + bandwidthHints: p.bandwidthHints, + }, + &restrictParams{ + ignoredNodes: pruneView.vertexes, + ignoredEdges: pruneView.edges, + feeLimit: payment.FeeLimit, + }, + p.mc.selfNode, payment.Target, payment.Amount, + ) + if err != nil { + return nil, err + } + + // With the next candidate path found, we'll attempt to turn this into + // a route by applying the time-lock and fee requirements. + sourceVertex := Vertex(p.mc.selfNode.PubKeyBytes) + route, err := newRoute( + payment.Amount, payment.FeeLimit, sourceVertex, path, height, + finalCltvDelta, + ) + if err != nil { + // TODO(roasbeef): return which edge/vertex didn't work + // out + return nil, err + } + + return route, err +}