routing: perform path finding inside a single DB transaction

This commit modifies the path finding logic such that all path finding
is done inside a _single_ database transaction. With this change, we
ensure that we don’t end up possibly creating hundreds of database
transactions slowing down the path finding and payment sending process
all together.
This commit is contained in:
Olaoluwa Osuntokun 2017-10-10 21:39:54 -07:00
parent 5eb3406b19
commit 646f79f566
No known key found for this signature in database
GPG Key ID: 964EA263DD637C21
3 changed files with 45 additions and 18 deletions

@ -369,9 +369,19 @@ func edgeWeight(e *channeldb.ChannelEdgePolicy) float64 {
// time-lock+fee costs along a particular edge. If a path is found, this // time-lock+fee costs along a particular edge. If a path is found, this
// function returns a slice of ChannelHop structs which encoded the chosen path // function returns a slice of ChannelHop structs which encoded the chosen path
// from the target to the source. // from the target to the source.
func findPath(graph *channeldb.ChannelGraph, sourceNode *channeldb.LightningNode, func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
target *btcec.PublicKey, ignoredNodes map[vertex]struct{}, sourceNode *channeldb.LightningNode, target *btcec.PublicKey,
ignoredEdges map[uint64]struct{}, amt lnwire.MilliSatoshi) ([]*ChannelHop, error) { ignoredNodes map[vertex]struct{}, ignoredEdges map[uint64]struct{},
amt lnwire.MilliSatoshi) ([]*ChannelHop, error) {
var err error
if tx == nil {
tx, err = graph.Database().Begin(false)
if err != nil {
return nil, err
}
defer tx.Rollback()
}
// First we'll initialize an empty heap which'll help us to quickly // First we'll initialize an empty heap which'll help us to quickly
// locate the next edge we should visit next during our graph // locate the next edge we should visit next during our graph
@ -394,6 +404,9 @@ func findPath(graph *channeldb.ChannelGraph, sourceNode *channeldb.LightningNode
return nil, err return nil, err
} }
// TODO(roasbeef): also add path caching
// * similar to route caching, but doesn't factor in the amount
// To start, we add the source of our path finding attempt to the // To start, we add the source of our path finding attempt to the
// distance map with with a distance of 0. This indicates our starting // distance map with with a distance of 0. This indicates our starting
// point in the graph traversal. // point in the graph traversal.
@ -428,13 +441,13 @@ func findPath(graph *channeldb.ChannelGraph, sourceNode *channeldb.LightningNode
// examine all the outgoing edge (channels) from this node to // examine all the outgoing edge (channels) from this node to
// further our graph traversal. // further our graph traversal.
pivot := newVertex(bestNode.PubKey) pivot := newVertex(bestNode.PubKey)
err := bestNode.ForEachChannel(nil, func(tx *bolt.Tx, err := bestNode.ForEachChannel(tx, func(tx *bolt.Tx,
edgeInfo *channeldb.ChannelEdgeInfo, edgeInfo *channeldb.ChannelEdgeInfo,
outEdge, inEdge *channeldb.ChannelEdgePolicy) error { outEdge, inEdge *channeldb.ChannelEdgePolicy) error {
v := newVertex(outEdge.Node.PubKey) v := newVertex(outEdge.Node.PubKey)
// TODO(roasbeef): skip if disabled // TODO(roasbeef): skip if chan disabled
// If this vertex or edge has been black listed, then // If this vertex or edge has been black listed, then
// we'll skip exploring this edge during this // we'll skip exploring this edge during this
@ -494,6 +507,7 @@ func findPath(graph *channeldb.ChannelGraph, sourceNode *channeldb.LightningNode
// to further explore down this edge. // to further explore down this edge.
heap.Push(&nodeHeap, distance[v]) heap.Push(&nodeHeap, distance[v])
} }
return nil return nil
}) })
if err != nil { if err != nil {
@ -555,8 +569,11 @@ func findPath(graph *channeldb.ChannelGraph, sourceNode *channeldb.LightningNode
// make our inner path finding algorithm aware of our k-shortest paths // make our inner path finding algorithm aware of our k-shortest paths
// algorithm, rather than attempting to use an unmodified path finding // algorithm, rather than attempting to use an unmodified path finding
// algorithm in a block box manner. // algorithm in a block box manner.
func findPaths(graph *channeldb.ChannelGraph, source *channeldb.LightningNode, func findPaths(tx *bolt.Tx, graph *channeldb.ChannelGraph,
target *btcec.PublicKey, amt lnwire.MilliSatoshi) ([][]*ChannelHop, error) { source *channeldb.LightningNode, target *btcec.PublicKey,
amt lnwire.MilliSatoshi) ([][]*ChannelHop, error) {
// TODO(roasbeef): take in db tx
ignoredEdges := make(map[uint64]struct{}) ignoredEdges := make(map[uint64]struct{})
ignoredVertexes := make(map[vertex]struct{}) ignoredVertexes := make(map[vertex]struct{})
@ -571,7 +588,7 @@ func findPaths(graph *channeldb.ChannelGraph, source *channeldb.LightningNode,
// First we'll find a single shortest path from the source (our // First we'll find a single shortest path from the source (our
// selfNode) to the target destination that's capable of carrying amt // selfNode) to the target destination that's capable of carrying amt
// satoshis along the path before fees are calculated. // satoshis along the path before fees are calculated.
startingPath, err := findPath(graph, source, target, startingPath, err := findPath(tx, graph, source, target,
ignoredVertexes, ignoredEdges, amt) ignoredVertexes, ignoredEdges, amt)
if err != nil { if err != nil {
log.Errorf("Unable to find path: %v", err) log.Errorf("Unable to find path: %v", err)
@ -643,7 +660,7 @@ func findPaths(graph *channeldb.ChannelGraph, source *channeldb.LightningNode,
// the vertexes (other than the spur path) within the // the vertexes (other than the spur path) within the
// root path removed, we'll attempt to find another // root path removed, we'll attempt to find another
// shortest path from the spur node to the destination. // shortest path from the spur node to the destination.
spurPath, err := findPath(graph, spurNode, target, spurPath, err := findPath(tx, graph, spurNode, target,
ignoredVertexes, ignoredEdges, amt) ignoredVertexes, ignoredEdges, amt)
// If we weren't able to find a path, we'll continue to // If we weren't able to find a path, we'll continue to

@ -316,7 +316,7 @@ func TestBasicGraphPathFinding(t *testing.T) {
paymentAmt := lnwire.NewMSatFromSatoshis(100) paymentAmt := lnwire.NewMSatFromSatoshis(100)
target := aliases["sophon"] target := aliases["sophon"]
path, err := findPath(graph, sourceNode, target, ignoredVertexes, path, err := findPath(nil, graph, sourceNode, target, ignoredVertexes,
ignoredEdges, paymentAmt) ignoredEdges, paymentAmt)
if err != nil { if err != nil {
t.Fatalf("unable to find path: %v", err) t.Fatalf("unable to find path: %v", err)
@ -412,7 +412,7 @@ func TestBasicGraphPathFinding(t *testing.T) {
// exist two possible paths in the graph, but the shorter (1 hop) path // exist two possible paths in the graph, but the shorter (1 hop) path
// should be selected. // should be selected.
target = aliases["luoji"] target = aliases["luoji"]
path, err = findPath(graph, sourceNode, target, ignoredVertexes, path, err = findPath(nil, graph, sourceNode, target, ignoredVertexes,
ignoredEdges, paymentAmt) ignoredEdges, paymentAmt)
if err != nil { if err != nil {
t.Fatalf("unable to find route: %v", err) t.Fatalf("unable to find route: %v", err)
@ -469,7 +469,7 @@ func TestKShortestPathFinding(t *testing.T) {
paymentAmt := lnwire.NewMSatFromSatoshis(100) paymentAmt := lnwire.NewMSatFromSatoshis(100)
target := aliases["luoji"] target := aliases["luoji"]
paths, err := findPaths(graph, sourceNode, target, paymentAmt) paths, err := findPaths(nil, graph, sourceNode, target, paymentAmt)
if err != nil { if err != nil {
t.Fatalf("unable to find paths between roasbeef and "+ t.Fatalf("unable to find paths between roasbeef and "+
"luo ji: %v", err) "luo ji: %v", err)
@ -530,7 +530,7 @@ func TestNewRoutePathTooLong(t *testing.T) {
// We start by confirminig that routing a payment 20 hops away is possible. // We start by confirminig that routing a payment 20 hops away is possible.
// Alice should be able to find a valid route to ursula. // Alice should be able to find a valid route to ursula.
target := aliases["ursula"] target := aliases["ursula"]
_, err = findPath(graph, sourceNode, target, ignoredVertexes, _, err = findPath(nil, graph, sourceNode, target, ignoredVertexes,
ignoredEdges, paymentAmt) ignoredEdges, paymentAmt)
if err != nil { if err != nil {
t.Fatalf("path should have been found") t.Fatalf("path should have been found")
@ -539,7 +539,7 @@ func TestNewRoutePathTooLong(t *testing.T) {
// Vincent is 21 hops away from Alice, and thus no valid route should be // Vincent is 21 hops away from Alice, and thus no valid route should be
// presented to Alice. // presented to Alice.
target = aliases["vincent"] target = aliases["vincent"]
path, err := findPath(graph, sourceNode, target, ignoredVertexes, path, err := findPath(nil, graph, sourceNode, target, ignoredVertexes,
ignoredEdges, paymentAmt) ignoredEdges, paymentAmt)
if err == nil { if err == nil {
t.Fatalf("should not have been able to find path, supposed to be "+ t.Fatalf("should not have been able to find path, supposed to be "+
@ -579,7 +579,7 @@ func TestPathNotAvailable(t *testing.T) {
t.Fatalf("unable to parse pubkey: %v", err) t.Fatalf("unable to parse pubkey: %v", err)
} }
_, err = findPath(graph, sourceNode, unknownNode, ignoredVertexes, _, err = findPath(nil, graph, sourceNode, unknownNode, ignoredVertexes,
ignoredEdges, 100) ignoredEdges, 100)
if !IsError(err, ErrNoPathFound) { if !IsError(err, ErrNoPathFound) {
t.Fatalf("path shouldn't have been found: %v", err) t.Fatalf("path shouldn't have been found: %v", err)
@ -613,7 +613,7 @@ func TestPathInsufficientCapacity(t *testing.T) {
target := aliases["sophon"] target := aliases["sophon"]
const payAmt = btcutil.SatoshiPerBitcoin const payAmt = btcutil.SatoshiPerBitcoin
_, err = findPath(graph, sourceNode, target, ignoredVertexes, _, err = findPath(nil, graph, sourceNode, target, ignoredVertexes,
ignoredEdges, payAmt) ignoredEdges, payAmt)
if !IsError(err, ErrNoPathFound) { if !IsError(err, ErrNoPathFound) {
t.Fatalf("graph shouldn't be able to support payment: %v", err) t.Fatalf("graph shouldn't be able to support payment: %v", err)

@ -978,14 +978,24 @@ func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey,
return nil, err return nil, err
} }
tx, err := r.cfg.Graph.Database().Begin(false)
if err != nil {
tx.Rollback()
return nil, err
}
// Now that we know the destination is reachable within the graph, // Now that we know the destination is reachable within the graph,
// we'll execute our KSP algorithm to find the k-shortest paths from // we'll execute our KSP algorithm to find the k-shortest paths from
// our source to the destination. // our source to the destination.
shortestPaths, err := findPaths(r.cfg.Graph, r.selfNode, target, amt) shortestPaths, err := findPaths(tx, r.cfg.Graph, r.selfNode, target,
amt)
if err != nil { if err != nil {
tx.Rollback()
return nil, err return nil, err
} }
tx.Rollback()
// Now that we have a set of paths, we'll need to turn them into // Now that we have a set of paths, we'll need to turn them into
// *routes* by computing the required time-lock and fee information for // *routes* by computing the required time-lock and fee information for
// each path. During this process, some paths may be discarded if they // each path. During this process, some paths may be discarded if they
@ -1032,7 +1042,7 @@ func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey,
return validRoutes[i].TotalFees < validRoutes[j].TotalFees return validRoutes[i].TotalFees < validRoutes[j].TotalFees
}) })
log.Tracef("Obtained %v paths sending %v to %x: %v", len(validRoutes), go log.Tracef("Obtained %v paths sending %v to %x: %v", len(validRoutes),
amt, dest, newLogClosure(func() string { amt, dest, newLogClosure(func() string {
return spew.Sdump(validRoutes) return spew.Sdump(validRoutes)
}), }),