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
// 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

@ -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)

@ -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)
}),