From 646f79f566ece3f647acda565a6841375960e84c Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Tue, 10 Oct 2017 21:39:54 -0700 Subject: [PATCH] routing: perform path finding inside a single DB transaction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- routing/pathfind.go | 35 ++++++++++++++++++++++++++--------- routing/pathfind_test.go | 14 +++++++------- routing/router.go | 14 ++++++++++++-- 3 files changed, 45 insertions(+), 18 deletions(-) diff --git a/routing/pathfind.go b/routing/pathfind.go index 48b189468..ad55e1635 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -369,9 +369,19 @@ func edgeWeight(e *channeldb.ChannelEdgePolicy) float64 { // 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 // from the target to the source. -func findPath(graph *channeldb.ChannelGraph, sourceNode *channeldb.LightningNode, - target *btcec.PublicKey, ignoredNodes map[vertex]struct{}, - ignoredEdges map[uint64]struct{}, amt lnwire.MilliSatoshi) ([]*ChannelHop, error) { +func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph, + sourceNode *channeldb.LightningNode, target *btcec.PublicKey, + 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 // 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 } + // 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 // distance map with with a distance of 0. This indicates our starting // 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 // further our graph traversal. pivot := newVertex(bestNode.PubKey) - err := bestNode.ForEachChannel(nil, func(tx *bolt.Tx, + err := bestNode.ForEachChannel(tx, func(tx *bolt.Tx, edgeInfo *channeldb.ChannelEdgeInfo, outEdge, inEdge *channeldb.ChannelEdgePolicy) error { 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 // 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. heap.Push(&nodeHeap, distance[v]) } + return 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 // algorithm, rather than attempting to use an unmodified path finding // algorithm in a block box manner. -func findPaths(graph *channeldb.ChannelGraph, source *channeldb.LightningNode, - target *btcec.PublicKey, amt lnwire.MilliSatoshi) ([][]*ChannelHop, error) { +func findPaths(tx *bolt.Tx, graph *channeldb.ChannelGraph, + source *channeldb.LightningNode, target *btcec.PublicKey, + amt lnwire.MilliSatoshi) ([][]*ChannelHop, error) { + + // TODO(roasbeef): take in db tx ignoredEdges := make(map[uint64]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 // selfNode) to the target destination that's capable of carrying amt // satoshis along the path before fees are calculated. - startingPath, err := findPath(graph, source, target, + startingPath, err := findPath(tx, graph, source, target, ignoredVertexes, ignoredEdges, amt) if err != nil { 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 // root path removed, we'll attempt to find another // 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) // If we weren't able to find a path, we'll continue to diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index 707ce54fa..dcbc177e8 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -316,7 +316,7 @@ func TestBasicGraphPathFinding(t *testing.T) { paymentAmt := lnwire.NewMSatFromSatoshis(100) target := aliases["sophon"] - path, err := findPath(graph, sourceNode, target, ignoredVertexes, + path, err := findPath(nil, graph, sourceNode, target, ignoredVertexes, ignoredEdges, paymentAmt) if err != nil { 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 // should be selected. target = aliases["luoji"] - path, err = findPath(graph, sourceNode, target, ignoredVertexes, + path, err = findPath(nil, graph, sourceNode, target, ignoredVertexes, ignoredEdges, paymentAmt) if err != nil { t.Fatalf("unable to find route: %v", err) @@ -469,7 +469,7 @@ func TestKShortestPathFinding(t *testing.T) { paymentAmt := lnwire.NewMSatFromSatoshis(100) target := aliases["luoji"] - paths, err := findPaths(graph, sourceNode, target, paymentAmt) + paths, err := findPaths(nil, graph, sourceNode, target, paymentAmt) if err != nil { t.Fatalf("unable to find paths between roasbeef and "+ "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. // Alice should be able to find a valid route to ursula. target := aliases["ursula"] - _, err = findPath(graph, sourceNode, target, ignoredVertexes, + _, err = findPath(nil, graph, sourceNode, target, ignoredVertexes, ignoredEdges, paymentAmt) if err != nil { 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 // presented to Alice. target = aliases["vincent"] - path, err := findPath(graph, sourceNode, target, ignoredVertexes, + path, err := findPath(nil, graph, sourceNode, target, ignoredVertexes, ignoredEdges, paymentAmt) if err == nil { 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) } - _, err = findPath(graph, sourceNode, unknownNode, ignoredVertexes, + _, err = findPath(nil, graph, sourceNode, unknownNode, ignoredVertexes, ignoredEdges, 100) if !IsError(err, ErrNoPathFound) { t.Fatalf("path shouldn't have been found: %v", err) @@ -613,7 +613,7 @@ func TestPathInsufficientCapacity(t *testing.T) { target := aliases["sophon"] const payAmt = btcutil.SatoshiPerBitcoin - _, err = findPath(graph, sourceNode, target, ignoredVertexes, + _, err = findPath(nil, graph, sourceNode, target, ignoredVertexes, ignoredEdges, payAmt) if !IsError(err, ErrNoPathFound) { t.Fatalf("graph shouldn't be able to support payment: %v", err) diff --git a/routing/router.go b/routing/router.go index 5b5f4fdb7..a8a7a01af 100644 --- a/routing/router.go +++ b/routing/router.go @@ -978,14 +978,24 @@ func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey, 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, // we'll execute our KSP algorithm to find the k-shortest paths from // 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 { + tx.Rollback() return nil, err } + tx.Rollback() + // 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 // 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 }) - 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 { return spew.Sdump(validRoutes) }),