routing: modify findPath to take into account additional edges

In this commit, we modify our path finding algorithm to take an
additional set of edges that are currently not known to us that are
used to temporarily extend our graph with during a payment session.
These edges should assist the sender of a payment in successfully
constructing a path to the destination.

These edges should usually represent private channels, as they are not
publicly advertised to the network for routing.
This commit is contained in:
Wilmer Paulino committed 2018-04-20 04:01:32 -04:00
1 parent 7965247db1
commit 4598df054e
4 files changed
+142 -92

No files matched your search

+99 -70
View File
@@ -455,6 +455,7 @@ func edgeWeight(amt lnwire.MilliSatoshi, e *channeldb.ChannelEdgePolicy) int64 {
// function returns a slice of ChannelHop structs which encoded the chosen path
// from the target to the source.
func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
additionalEdges map[Vertex][]*channeldb.ChannelEdgePolicy,
sourceNode *channeldb.LightningNode, target *btcec.PublicKey,
ignoredNodes map[Vertex]struct{}, ignoredEdges map[uint64]struct{},
amt lnwire.MilliSatoshi) ([]*ChannelHop, error) {
@@ -473,9 +474,8 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
// traversal.
var nodeHeap distanceHeap
// For each node/Vertex the graph we create an entry in the distance
// map for the node set with a distance of "infinity". We also mark
// add the node to our set of unvisited nodes.
// For each node in the graph, we create an entry in the distance
// map for the node set with a distance of "infinity".
distance := make(map[Vertex]nodeWithDist)
if err := graph.ForEachNode(tx, func(_ *bolt.Tx, node *channeldb.LightningNode) error {
// TODO(roasbeef): with larger graph can just use disk seeks
@@ -489,6 +489,86 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
return nil, err
}
// We'll also include all the nodes found within the additional edges
// that are not known to us yet in the distance map.
for vertex := range additionalEdges {
node := &channeldb.LightningNode{PubKeyBytes: vertex}
distance[vertex] = nodeWithDist{
dist: infinity,
node: node,
}
}
// We can't always assume that the end destination is publicly
// advertised to the network and included in the graph.ForEachNode call
// above, so we'll manually include the target node.
targetVertex := NewVertex(target)
targetNode := &channeldb.LightningNode{PubKeyBytes: targetVertex}
distance[targetVertex] = nodeWithDist{
dist: infinity,
node: targetNode,
}
// We'll use this map as a series of "previous" hop pointers. So to get
// to `Vertex` we'll take the edge that it's mapped to within `prev`.
prev := make(map[Vertex]edgeWithPrev)
// processEdge is a helper closure that will be used to make sure edges
// satisfy our specific requirements.
processEdge := func(edge *channeldb.ChannelEdgePolicy,
capacity btcutil.Amount, pivot Vertex) {
v := Vertex(edge.Node.PubKeyBytes)
// If the edge is currently disabled, then we'll stop here, as
// we shouldn't attempt to route through it.
edgeFlags := lnwire.ChanUpdateFlag(edge.Flags)
if edgeFlags&lnwire.ChanUpdateDisabled != 0 {
return
}
// If this vertex or edge has been black listed, then we'll skip
// exploring this edge.
if _, ok := ignoredNodes[v]; ok {
return
}
if _, ok := ignoredEdges[edge.ChannelID]; ok {
return
}
// Compute the tentative distance to this new channel/edge which
// is the distance to our pivot node plus the weight of this
// edge.
tempDist := distance[pivot].dist + edgeWeight(amt, edge)
// If this new tentative distance is better than the current
// best known distance to this node, then we record the new
// better distance, and also populate our "next hop" map with
// this edge. We'll also shave off irrelevant edges by adding
// the sufficient capacity of an edge and clearing their
// min-htlc amount to our relaxation condition.
if tempDist < distance[v].dist && capacity >= amt.ToSatoshis() &&
amt >= edge.MinHTLC && edge.TimeLockDelta != 0 {
distance[v] = nodeWithDist{
dist: tempDist,
node: edge.Node,
}
prev[v] = edgeWithPrev{
edge: &ChannelHop{
ChannelEdgePolicy: edge,
Capacity: capacity,
},
prevNode: pivot,
}
// Add this new node to our heap as we'd like to further
// explore down this edge.
heap.Push(&nodeHeap, distance[v])
}
}
// TODO(roasbeef): also add path caching
// * similar to route caching, but doesn't factor in the amount
@@ -505,11 +585,6 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
// heap.
heap.Push(&nodeHeap, distance[sourceVertex])
targetBytes := target.SerializeCompressed()
// We'll use this map as a series of "previous" hop pointers. So to get
// to `Vertex` we'll take the edge that it's mapped to within `prev`.
prev := make(map[Vertex]edgeWithPrev)
for nodeHeap.Len() != 0 {
// Fetch the node within the smallest distance from our source
// from the heap.
@@ -519,7 +594,7 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
// If we've reached our target (or we don't have any outgoing
// edges), then we're done here and can exit the graph
// traversal early.
if bytes.Equal(bestNode.PubKeyBytes[:], targetBytes) {
if bytes.Equal(bestNode.PubKeyBytes[:], targetVertex[:]) {
break
}
@@ -529,65 +604,9 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
pivot := Vertex(bestNode.PubKeyBytes)
err := bestNode.ForEachChannel(tx, func(tx *bolt.Tx,
edgeInfo *channeldb.ChannelEdgeInfo,
outEdge, inEdge *channeldb.ChannelEdgePolicy) error {
outEdge, _ *channeldb.ChannelEdgePolicy) error {
v := Vertex(outEdge.Node.PubKeyBytes)
// If the outgoing edge is currently disabled, then
// we'll stop here, as we shouldn't attempt to route
// through it.
edgeFlags := lnwire.ChanUpdateFlag(outEdge.Flags)
if edgeFlags&lnwire.ChanUpdateDisabled == lnwire.ChanUpdateDisabled {
return nil
}
// If this Vertex or edge has been black listed, then
// we'll skip exploring this edge during this
// iteration.
if _, ok := ignoredNodes[v]; ok {
return nil
}
if _, ok := ignoredEdges[outEdge.ChannelID]; ok {
return nil
}
// Compute the tentative distance to this new
// channel/edge which is the distance to our current
// pivot node plus the weight of this edge.
tempDist := distance[pivot].dist + edgeWeight(amt, outEdge)
// If this new tentative distance is better than the
// current best known distance to this node, then we
// record the new better distance, and also populate
// our "next hop" map with this edge. We'll also shave
// off irrelevant edges by adding the sufficient
// capacity of an edge and clearing their min-htlc
// amount to our relaxation condition.
if tempDist < distance[v].dist &&
edgeInfo.Capacity >= amt.ToSatoshis() &&
amt >= outEdge.MinHTLC &&
outEdge.TimeLockDelta != 0 {
distance[v] = nodeWithDist{
dist: tempDist,
node: outEdge.Node,
}
prev[v] = edgeWithPrev{
// We'll use the *incoming* edge here
// as we need to use the routing policy
// specified by the node this channel
// connects to.
edge: &ChannelHop{
ChannelEdgePolicy: outEdge,
Capacity: edgeInfo.Capacity,
},
prevNode: bestNode.PubKeyBytes,
}
// Add this new node to our heap as we'd like
// to further explore down this edge.
heap.Push(&nodeHeap, distance[v])
}
processEdge(outEdge, edgeInfo.Capacity, pivot)
// TODO(roasbeef): return min HTLC as error in end?
@@ -596,6 +615,15 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
if err != nil {
return nil, err
}
// Then, we'll examine all the additional edges from the node
// we're currently visiting. Since we don't know the capacity
// of the private channel, we'll assume it was selected as a
// routing hint due to having enough capacity for the payment
// and use the payment amount as its capacity.
for _, edge := range additionalEdges[bestNode.PubKeyBytes] {
processEdge(edge, amt.ToSatoshis(), pivot)
}
}
// If the target node isn't found in the prev hop map, then a path
@@ -669,7 +697,8 @@ func findPaths(tx *bolt.Tx, graph *channeldb.ChannelGraph,
// selfNode) to the target destination that's capable of carrying amt
// satoshis along the path before fees are calculated.
startingPath, err := findPath(
tx, graph, source, target, ignoredVertexes, ignoredEdges, amt,
tx, graph, nil, source, target, ignoredVertexes, ignoredEdges,
amt,
)
if err != nil {
log.Errorf("Unable to find path: %v", err)
@@ -742,8 +771,8 @@ func findPaths(tx *bolt.Tx, graph *channeldb.ChannelGraph,
// root path removed, we'll attempt to find another
// shortest path from the spur node to the destination.
spurPath, err := findPath(
tx, graph, spurNode, target, ignoredVertexes,
ignoredEdges, amt,
tx, graph, nil, spurNode, target,
ignoredVertexes, ignoredEdges, amt,
)
// If we weren't able to find a path, we'll continue to