Merge pull request #5840 from guggero/bolt-pathfinding-fallback

db: allow turning off in-memory graph cache for bbolt
This commit is contained in:
Olaoluwa Osuntokun
2021-10-25 14:24:10 -07:00
committed by GitHub
18 changed files with 527 additions and 223 deletions

View File

@@ -2,6 +2,7 @@ package routing
import (
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)
@@ -25,6 +26,7 @@ type routingGraph interface {
// database.
type CachedGraph struct {
graph *channeldb.ChannelGraph
tx kvdb.RTx
source route.Vertex
}
@@ -32,27 +34,40 @@ type CachedGraph struct {
// interface.
var _ routingGraph = (*CachedGraph)(nil)
// NewCachedGraph instantiates a new db-connected routing graph. It implictly
// NewCachedGraph instantiates a new db-connected routing graph. It implicitly
// instantiates a new read transaction.
func NewCachedGraph(graph *channeldb.ChannelGraph) (*CachedGraph, error) {
sourceNode, err := graph.SourceNode()
func NewCachedGraph(sourceNode *channeldb.LightningNode,
graph *channeldb.ChannelGraph) (*CachedGraph, error) {
tx, err := graph.NewPathFindTx()
if err != nil {
return nil, err
}
return &CachedGraph{
graph: graph,
tx: tx,
source: sourceNode.PubKeyBytes,
}, nil
}
// close attempts to close the underlying db transaction. This is a no-op in
// case the underlying graph uses an in-memory cache.
func (g *CachedGraph) close() error {
if g.tx == nil {
return nil
}
return g.tx.Rollback()
}
// forEachNodeChannel calls the callback for every channel of the given node.
//
// NOTE: Part of the routingGraph interface.
func (g *CachedGraph) forEachNodeChannel(nodePub route.Vertex,
cb func(channel *channeldb.DirectedChannel) error) error {
return g.graph.ForEachNodeChannel(nodePub, cb)
return g.graph.ForEachNodeChannel(g.tx, nodePub, cb)
}
// sourceNode returns the source node of the graph.

View File

@@ -145,7 +145,7 @@ func (c *integratedRoutingContext) testPayment(maxParts uint32,
c.t.Fatal(err)
}
getBandwidthHints := func() (bandwidthHints, error) {
getBandwidthHints := func(_ routingGraph) (bandwidthHints, error) {
// Create bandwidth hints based on local channel balances.
bandwidthHints := map[uint64]lnwire.MilliSatoshi{}
for _, ch := range c.graph.nodes[c.source.pubkey].channels {
@@ -179,7 +179,11 @@ func (c *integratedRoutingContext) testPayment(maxParts uint32,
}
session, err := newPaymentSession(
&payment, getBandwidthHints, c.graph, mc, c.pathFindingCfg,
&payment, getBandwidthHints,
func() (routingGraph, func(), error) {
return c.graph, func() {}, nil
},
mc, c.pathFindingCfg,
)
if err != nil {
c.t.Fatal(err)

View File

@@ -150,7 +150,9 @@ type testChan struct {
// makeTestGraph creates a new instance of a channeldb.ChannelGraph for testing
// purposes. A callback which cleans up the created temporary directories is
// also returned and intended to be executed after the test completes.
func makeTestGraph() (*channeldb.ChannelGraph, kvdb.Backend, func(), error) {
func makeTestGraph(useCache bool) (*channeldb.ChannelGraph, kvdb.Backend,
func(), error) {
// First, create a temporary directory to be used for the duration of
// this test.
tempDirName, err := ioutil.TempDir("", "channeldb")
@@ -173,6 +175,7 @@ func makeTestGraph() (*channeldb.ChannelGraph, kvdb.Backend, func(), error) {
graph, err := channeldb.NewChannelGraph(
backend, opts.RejectCacheSize, opts.ChannelCacheSize,
opts.BatchCommitInterval, opts.PreAllocCacheNumNodes,
useCache,
)
if err != nil {
cleanUp()
@@ -184,7 +187,7 @@ func makeTestGraph() (*channeldb.ChannelGraph, kvdb.Backend, func(), error) {
// parseTestGraph returns a fully populated ChannelGraph given a path to a JSON
// file which encodes a test graph.
func parseTestGraph(path string) (*testGraphInstance, error) {
func parseTestGraph(useCache bool, path string) (*testGraphInstance, error) {
graphJSON, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
@@ -209,7 +212,7 @@ func parseTestGraph(path string) (*testGraphInstance, error) {
testAddrs = append(testAddrs, testAddr)
// Next, create a temporary graph database for usage within the test.
graph, graphBackend, cleanUp, err := makeTestGraph()
graph, graphBackend, cleanUp, err := makeTestGraph(useCache)
if err != nil {
return nil, err
}
@@ -528,8 +531,8 @@ func (g *testGraphInstance) getLink(chanID lnwire.ShortChannelID) (
// a deterministical way and added to the channel graph. A list of nodes is
// not required and derived from the channel data. The goal is to keep
// instantiating a test channel graph as light weight as possible.
func createTestGraphFromChannels(testChannels []*testChannel, source string) (
*testGraphInstance, error) {
func createTestGraphFromChannels(useCache bool, testChannels []*testChannel,
source string) (*testGraphInstance, error) {
// We'll use this fake address for the IP address of all the nodes in
// our tests. This value isn't needed for path finding so it doesn't
@@ -542,7 +545,7 @@ func createTestGraphFromChannels(testChannels []*testChannel, source string) (
testAddrs = append(testAddrs, testAddr)
// Next, create a temporary graph database for usage within the test.
graph, graphBackend, cleanUp, err := makeTestGraph()
graph, graphBackend, cleanUp, err := makeTestGraph(useCache)
if err != nil {
return nil, err
}
@@ -768,13 +771,106 @@ func createTestGraphFromChannels(testChannels []*testChannel, source string) (
}, nil
}
// TestFindLowestFeePath tests that out of two routes with identical total
// TestPathFinding tests all path finding related cases both with the in-memory
// graph cached turned on and off.
func TestPathFinding(t *testing.T) {
testCases := []struct {
name string
fn func(t *testing.T, useCache bool)
}{{
name: "lowest fee path",
fn: runFindLowestFeePath,
}, {
name: "basic graph path finding",
fn: runBasicGraphPathFinding,
}, {
name: "path finding with additional edges",
fn: runPathFindingWithAdditionalEdges,
}, {
name: "new route path too long",
fn: runNewRoutePathTooLong,
}, {
name: "path not available",
fn: runPathNotAvailable,
}, {
name: "destination tlv graph fallback",
fn: runDestTLVGraphFallback,
}, {
name: "missing feature dependency",
fn: runMissingFeatureDep,
}, {
name: "unknown required features",
fn: runUnknownRequiredFeatures,
}, {
name: "destination payment address",
fn: runDestPaymentAddr,
}, {
name: "path insufficient capacity",
fn: runPathInsufficientCapacity,
}, {
name: "route fail min HTLC",
fn: runRouteFailMinHTLC,
}, {
name: "route fail max HTLC",
fn: runRouteFailMaxHTLC,
}, {
name: "route fail disabled edge",
fn: runRouteFailDisabledEdge,
}, {
name: "path source edges bandwidth",
fn: runPathSourceEdgesBandwidth,
}, {
name: "restrict outgoing channel",
fn: runRestrictOutgoingChannel,
}, {
name: "restrict last hop",
fn: runRestrictLastHop,
}, {
name: "CLTV limit",
fn: runCltvLimit,
}, {
name: "probability routing",
fn: runProbabilityRouting,
}, {
name: "equal cost route selection",
fn: runEqualCostRouteSelection,
}, {
name: "no cycle",
fn: runNoCycle,
}, {
name: "route to self",
fn: runRouteToSelf,
}}
// Run with graph cache enabled.
for _, tc := range testCases {
tc := tc
t.Run("cache=true/"+tc.name, func(tt *testing.T) {
tt.Parallel()
tc.fn(tt, true)
})
}
// And with the DB fallback to make sure everything works the same
// still.
for _, tc := range testCases {
tc := tc
t.Run("cache=false/"+tc.name, func(tt *testing.T) {
tt.Parallel()
tc.fn(tt, false)
})
}
}
// runFindLowestFeePath tests that out of two routes with identical total
// time lock values, the route with the lowest total fee should be returned.
// The fee rates are chosen such that the test failed on the previous edge
// weight function where one of the terms was fee squared.
func TestFindLowestFeePath(t *testing.T) {
t.Parallel()
func runFindLowestFeePath(t *testing.T, useCache bool) {
// Set up a test graph with two paths from roasbeef to target. Both
// paths have equal total time locks, but the path through b has lower
// fees (700 compared to 800 for the path through a).
@@ -811,7 +907,7 @@ func TestFindLowestFeePath(t *testing.T) {
}),
}
ctx := newPathFindingTestContext(t, testChannels, "roasbeef")
ctx := newPathFindingTestContext(t, useCache, testChannels, "roasbeef")
defer ctx.cleanup()
const (
@@ -916,10 +1012,8 @@ var basicGraphPathFindingTests = []basicGraphPathFindingTestCase{
expectFailureNoPath: true,
}}
func TestBasicGraphPathFinding(t *testing.T) {
t.Parallel()
testGraphInstance, err := parseTestGraph(basicGraphFilePath)
func runBasicGraphPathFinding(t *testing.T, useCache bool) {
testGraphInstance, err := parseTestGraph(useCache, basicGraphFilePath)
if err != nil {
t.Fatalf("unable to create graph: %v", err)
}
@@ -1091,14 +1185,12 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc
}
}
// TestPathFindingWithAdditionalEdges asserts that we are able to find paths to
// runPathFindingWithAdditionalEdges asserts that we are able to find paths to
// nodes that do not exist in the graph by way of hop hints. We also test that
// the path can support custom TLV records for the receiver under the
// appropriate circumstances.
func TestPathFindingWithAdditionalEdges(t *testing.T) {
t.Parallel()
graph, err := parseTestGraph(basicGraphFilePath)
func runPathFindingWithAdditionalEdges(t *testing.T, useCache bool) {
graph, err := parseTestGraph(useCache, basicGraphFilePath)
if err != nil {
t.Fatalf("unable to create graph: %v", err)
}
@@ -1502,9 +1594,7 @@ func TestNewRoute(t *testing.T) {
}
}
func TestNewRoutePathTooLong(t *testing.T) {
t.Parallel()
func runNewRoutePathTooLong(t *testing.T, useCache bool) {
var testChannels []*testChannel
// Setup a linear network of 21 hops.
@@ -1522,7 +1612,7 @@ func TestNewRoutePathTooLong(t *testing.T) {
fromNode = toNode
}
ctx := newPathFindingTestContext(t, testChannels, "start")
ctx := newPathFindingTestContext(t, useCache, testChannels, "start")
defer ctx.cleanup()
// Assert that we can find 20 hop routes.
@@ -1552,10 +1642,8 @@ func TestNewRoutePathTooLong(t *testing.T) {
}
}
func TestPathNotAvailable(t *testing.T) {
t.Parallel()
graph, err := parseTestGraph(basicGraphFilePath)
func runPathNotAvailable(t *testing.T, useCache bool) {
graph, err := parseTestGraph(useCache, basicGraphFilePath)
if err != nil {
t.Fatalf("unable to create graph: %v", err)
}
@@ -1587,12 +1675,10 @@ func TestPathNotAvailable(t *testing.T) {
}
}
// TestDestTLVGraphFallback asserts that we properly detect when we can send TLV
// runDestTLVGraphFallback asserts that we properly detect when we can send TLV
// records to a receiver, and also that we fallback to the receiver's node
// announcement if we don't have an invoice features.
func TestDestTLVGraphFallback(t *testing.T) {
t.Parallel()
func runDestTLVGraphFallback(t *testing.T, useCache bool) {
testChannels := []*testChannel{
asymmetricTestChannel("roasbeef", "luoji", 100000,
&testChannelPolicy{
@@ -1621,7 +1707,7 @@ func TestDestTLVGraphFallback(t *testing.T) {
}, 0),
}
ctx := newPathFindingTestContext(t, testChannels, "roasbeef")
ctx := newPathFindingTestContext(t, useCache, testChannels, "roasbeef")
defer ctx.cleanup()
sourceNode, err := ctx.graph.SourceNode()
@@ -1689,12 +1775,10 @@ func TestDestTLVGraphFallback(t *testing.T) {
assertExpectedPath(t, ctx.testGraphInstance.aliasMap, path, "luoji")
}
// TestMissingFeatureDep asserts that we fail path finding when the
// runMissingFeatureDep asserts that we fail path finding when the
// destination's features are broken, in that the feature vector doesn't signal
// all transitive dependencies.
func TestMissingFeatureDep(t *testing.T) {
t.Parallel()
func runMissingFeatureDep(t *testing.T, useCache bool) {
testChannels := []*testChannel{
asymmetricTestChannel("roasbeef", "conner", 100000,
&testChannelPolicy{
@@ -1728,7 +1812,7 @@ func TestMissingFeatureDep(t *testing.T) {
),
}
ctx := newPathFindingTestContext(t, testChannels, "roasbeef")
ctx := newPathFindingTestContext(t, useCache, testChannels, "roasbeef")
defer ctx.cleanup()
// Conner's node in the graph has a broken feature vector, since it
@@ -1766,12 +1850,10 @@ func TestMissingFeatureDep(t *testing.T) {
}
}
// TestUnknownRequiredFeatures asserts that we fail path finding when the
// runUnknownRequiredFeatures asserts that we fail path finding when the
// destination requires an unknown required feature, and that we skip
// intermediaries that signal unknown required features.
func TestUnknownRequiredFeatures(t *testing.T) {
t.Parallel()
func runUnknownRequiredFeatures(t *testing.T, useCache bool) {
testChannels := []*testChannel{
asymmetricTestChannel("roasbeef", "conner", 100000,
&testChannelPolicy{
@@ -1805,7 +1887,7 @@ func TestUnknownRequiredFeatures(t *testing.T) {
),
}
ctx := newPathFindingTestContext(t, testChannels, "roasbeef")
ctx := newPathFindingTestContext(t, useCache, testChannels, "roasbeef")
defer ctx.cleanup()
conner := ctx.keyFromAlias("conner")
@@ -1832,12 +1914,10 @@ func TestUnknownRequiredFeatures(t *testing.T) {
}
}
// TestDestPaymentAddr asserts that we properly detect when we can send a
// runDestPaymentAddr asserts that we properly detect when we can send a
// payment address to a receiver, and also that we fallback to the receiver's
// node announcement if we don't have an invoice features.
func TestDestPaymentAddr(t *testing.T) {
t.Parallel()
func runDestPaymentAddr(t *testing.T, useCache bool) {
testChannels := []*testChannel{
symmetricTestChannel("roasbeef", "luoji", 100000,
&testChannelPolicy{
@@ -1849,7 +1929,7 @@ func TestDestPaymentAddr(t *testing.T) {
),
}
ctx := newPathFindingTestContext(t, testChannels, "roasbeef")
ctx := newPathFindingTestContext(t, useCache, testChannels, "roasbeef")
defer ctx.cleanup()
luoji := ctx.keyFromAlias("luoji")
@@ -1877,10 +1957,8 @@ func TestDestPaymentAddr(t *testing.T) {
assertExpectedPath(t, ctx.testGraphInstance.aliasMap, path, "luoji")
}
func TestPathInsufficientCapacity(t *testing.T) {
t.Parallel()
graph, err := parseTestGraph(basicGraphFilePath)
func runPathInsufficientCapacity(t *testing.T, useCache bool) {
graph, err := parseTestGraph(useCache, basicGraphFilePath)
if err != nil {
t.Fatalf("unable to create graph: %v", err)
}
@@ -1912,12 +1990,10 @@ func TestPathInsufficientCapacity(t *testing.T) {
}
}
// TestRouteFailMinHTLC tests that if we attempt to route an HTLC which is
// runRouteFailMinHTLC tests that if we attempt to route an HTLC which is
// smaller than the advertised minHTLC of an edge, then path finding fails.
func TestRouteFailMinHTLC(t *testing.T) {
t.Parallel()
graph, err := parseTestGraph(basicGraphFilePath)
func runRouteFailMinHTLC(t *testing.T, useCache bool) {
graph, err := parseTestGraph(useCache, basicGraphFilePath)
if err != nil {
t.Fatalf("unable to create graph: %v", err)
}
@@ -1943,11 +2019,9 @@ func TestRouteFailMinHTLC(t *testing.T) {
}
}
// TestRouteFailMaxHTLC tests that if we attempt to route an HTLC which is
// runRouteFailMaxHTLC tests that if we attempt to route an HTLC which is
// larger than the advertised max HTLC of an edge, then path finding fails.
func TestRouteFailMaxHTLC(t *testing.T) {
t.Parallel()
func runRouteFailMaxHTLC(t *testing.T, useCache bool) {
// Set up a test graph:
// roasbeef <--> firstHop <--> secondHop <--> target
// We will be adjusting the max HTLC of the edge between the first and
@@ -1974,7 +2048,7 @@ func TestRouteFailMaxHTLC(t *testing.T) {
}),
}
ctx := newPathFindingTestContext(t, testChannels, "roasbeef")
ctx := newPathFindingTestContext(t, useCache, testChannels, "roasbeef")
defer ctx.cleanup()
// First, attempt to send a payment greater than the max HTLC we are
@@ -2007,15 +2081,13 @@ func TestRouteFailMaxHTLC(t *testing.T) {
}
}
// TestRouteFailDisabledEdge tests that if we attempt to route to an edge
// runRouteFailDisabledEdge tests that if we attempt to route to an edge
// that's disabled, then that edge is disqualified, and the routing attempt
// will fail. We also test that this is true only for non-local edges, as we'll
// ignore the disable flags, with the assumption that the correct bandwidth is
// found among the bandwidth hints.
func TestRouteFailDisabledEdge(t *testing.T) {
t.Parallel()
graph, err := parseTestGraph(basicGraphFilePath)
func runRouteFailDisabledEdge(t *testing.T, useCache bool) {
graph, err := parseTestGraph(useCache, basicGraphFilePath)
if err != nil {
t.Fatalf("unable to create graph: %v", err)
}
@@ -2089,13 +2161,11 @@ func TestRouteFailDisabledEdge(t *testing.T) {
}
}
// TestPathSourceEdgesBandwidth tests that explicitly passing in a set of
// runPathSourceEdgesBandwidth tests that explicitly passing in a set of
// bandwidth hints is used by the path finding algorithm to consider whether to
// use a local channel.
func TestPathSourceEdgesBandwidth(t *testing.T) {
t.Parallel()
graph, err := parseTestGraph(basicGraphFilePath)
func runPathSourceEdgesBandwidth(t *testing.T, useCache bool) {
graph, err := parseTestGraph(useCache, basicGraphFilePath)
if err != nil {
t.Fatalf("unable to create graph: %v", err)
}
@@ -2390,11 +2460,9 @@ func TestNewRouteFromEmptyHops(t *testing.T) {
}
}
// TestRestrictOutgoingChannel asserts that a outgoing channel restriction is
// runRestrictOutgoingChannel asserts that a outgoing channel restriction is
// obeyed by the path finding algorithm.
func TestRestrictOutgoingChannel(t *testing.T) {
t.Parallel()
func runRestrictOutgoingChannel(t *testing.T, useCache bool) {
// Define channel id constants
const (
chanSourceA = 1
@@ -2430,7 +2498,7 @@ func TestRestrictOutgoingChannel(t *testing.T) {
}, chanSourceTarget),
}
ctx := newPathFindingTestContext(t, testChannels, "roasbeef")
ctx := newPathFindingTestContext(t, useCache, testChannels, "roasbeef")
defer ctx.cleanup()
const (
@@ -2473,11 +2541,9 @@ func TestRestrictOutgoingChannel(t *testing.T) {
}
}
// TestRestrictLastHop asserts that a last hop restriction is obeyed by the path
// runRestrictLastHop asserts that a last hop restriction is obeyed by the path
// finding algorithm.
func TestRestrictLastHop(t *testing.T) {
t.Parallel()
func runRestrictLastHop(t *testing.T, useCache bool) {
// Set up a test graph with three possible paths from roasbeef to
// target. The path via channel 1 and 2 is the lowest cost path.
testChannels := []*testChannel{
@@ -2497,7 +2563,7 @@ func TestRestrictLastHop(t *testing.T) {
}, 4),
}
ctx := newPathFindingTestContext(t, testChannels, "source")
ctx := newPathFindingTestContext(t, useCache, testChannels, "source")
defer ctx.cleanup()
paymentAmt := lnwire.NewMSatFromSatoshis(100)
@@ -2518,15 +2584,23 @@ func TestRestrictLastHop(t *testing.T) {
}
}
// TestCltvLimit asserts that a cltv limit is obeyed by the path finding
// runCltvLimit asserts that a cltv limit is obeyed by the path finding
// algorithm.
func TestCltvLimit(t *testing.T) {
t.Run("no limit", func(t *testing.T) { testCltvLimit(t, 2016, 1) })
t.Run("no path", func(t *testing.T) { testCltvLimit(t, 50, 0) })
t.Run("force high cost", func(t *testing.T) { testCltvLimit(t, 80, 3) })
func runCltvLimit(t *testing.T, useCache bool) {
t.Run("no limit", func(t *testing.T) {
testCltvLimit(t, useCache, 2016, 1)
})
t.Run("no path", func(t *testing.T) {
testCltvLimit(t, useCache, 50, 0)
})
t.Run("force high cost", func(t *testing.T) {
testCltvLimit(t, useCache, 80, 3)
})
}
func testCltvLimit(t *testing.T, limit uint32, expectedChannel uint64) {
func testCltvLimit(t *testing.T, useCache bool, limit uint32,
expectedChannel uint64) {
t.Parallel()
// Set up a test graph with three possible paths to the target. The path
@@ -2560,7 +2634,7 @@ func testCltvLimit(t *testing.T, limit uint32, expectedChannel uint64) {
}),
}
ctx := newPathFindingTestContext(t, testChannels, "roasbeef")
ctx := newPathFindingTestContext(t, useCache, testChannels, "roasbeef")
defer ctx.cleanup()
paymentAmt := lnwire.NewMSatFromSatoshis(100)
@@ -2603,11 +2677,9 @@ func testCltvLimit(t *testing.T, limit uint32, expectedChannel uint64) {
}
}
// TestProbabilityRouting asserts that path finding not only takes into account
// runProbabilityRouting asserts that path finding not only takes into account
// fees but also success probability.
func TestProbabilityRouting(t *testing.T) {
t.Parallel()
func runProbabilityRouting(t *testing.T, useCache bool) {
testCases := []struct {
name string
p10, p11, p20 float64
@@ -2693,15 +2765,16 @@ func TestProbabilityRouting(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
testProbabilityRouting(
t, tc.amount, tc.p10, tc.p11, tc.p20,
t, useCache, tc.amount, tc.p10, tc.p11, tc.p20,
tc.minProbability, tc.expectedChan,
)
})
}
}
func testProbabilityRouting(t *testing.T, paymentAmt btcutil.Amount,
p10, p11, p20, minProbability float64, expectedChan uint64) {
func testProbabilityRouting(t *testing.T, useCache bool,
paymentAmt btcutil.Amount, p10, p11, p20, minProbability float64,
expectedChan uint64) {
t.Parallel()
@@ -2728,7 +2801,7 @@ func testProbabilityRouting(t *testing.T, paymentAmt btcutil.Amount,
}, 20),
}
ctx := newPathFindingTestContext(t, testChannels, "roasbeef")
ctx := newPathFindingTestContext(t, useCache, testChannels, "roasbeef")
defer ctx.cleanup()
alias := ctx.testGraphInstance.aliasMap
@@ -2782,11 +2855,9 @@ func testProbabilityRouting(t *testing.T, paymentAmt btcutil.Amount,
}
}
// TestEqualCostRouteSelection asserts that route probability will be used as a
// runEqualCostRouteSelection asserts that route probability will be used as a
// tie breaker in case the path finding probabilities are equal.
func TestEqualCostRouteSelection(t *testing.T) {
t.Parallel()
func runEqualCostRouteSelection(t *testing.T, useCache bool) {
// Set up a test graph with two possible paths to the target: via a and
// via b. The routing fees and probabilities are chosen such that the
// algorithm will first explore target->a->source (backwards search).
@@ -2811,7 +2882,7 @@ func TestEqualCostRouteSelection(t *testing.T) {
}, 2),
}
ctx := newPathFindingTestContext(t, testChannels, "source")
ctx := newPathFindingTestContext(t, useCache, testChannels, "source")
defer ctx.cleanup()
alias := ctx.testGraphInstance.aliasMap
@@ -2848,11 +2919,9 @@ func TestEqualCostRouteSelection(t *testing.T) {
}
}
// TestNoCycle tries to guide the path finding algorithm into reconstructing an
// runNoCycle tries to guide the path finding algorithm into reconstructing an
// endless route. It asserts that the algorithm is able to handle this properly.
func TestNoCycle(t *testing.T) {
t.Parallel()
func runNoCycle(t *testing.T, useCache bool) {
// Set up a test graph with two paths: source->a->target and
// source->b->c->target. The fees are setup such that, searching
// backwards, the algorithm will evaluate the following end of the route
@@ -2882,7 +2951,7 @@ func TestNoCycle(t *testing.T) {
}, 5),
}
ctx := newPathFindingTestContext(t, testChannels, "source")
ctx := newPathFindingTestContext(t, useCache, testChannels, "source")
defer ctx.cleanup()
const (
@@ -2922,10 +2991,8 @@ func TestNoCycle(t *testing.T) {
}
}
// TestRouteToSelf tests that it is possible to find a route to the self node.
func TestRouteToSelf(t *testing.T) {
t.Parallel()
// runRouteToSelf tests that it is possible to find a route to the self node.
func runRouteToSelf(t *testing.T, useCache bool) {
testChannels := []*testChannel{
symmetricTestChannel("source", "a", 100000, &testChannelPolicy{
Expiry: 144,
@@ -2941,7 +3008,7 @@ func TestRouteToSelf(t *testing.T) {
}, 3),
}
ctx := newPathFindingTestContext(t, testChannels, "source")
ctx := newPathFindingTestContext(t, useCache, testChannels, "source")
defer ctx.cleanup()
paymentAmt := lnwire.NewMSatFromSatoshis(100)
@@ -2979,11 +3046,11 @@ type pathFindingTestContext struct {
source route.Vertex
}
func newPathFindingTestContext(t *testing.T, testChannels []*testChannel,
source string) *pathFindingTestContext {
func newPathFindingTestContext(t *testing.T, useCache bool,
testChannels []*testChannel, source string) *pathFindingTestContext {
testGraphInstance, err := createTestGraphFromChannels(
testChannels, source,
useCache, testChannels, source,
)
if err != nil {
t.Fatalf("unable to create graph: %v", err)
@@ -3059,11 +3126,22 @@ func dbFindPath(graph *channeldb.ChannelGraph,
source, target route.Vertex, amt lnwire.MilliSatoshi,
finalHtlcExpiry int32) ([]*channeldb.CachedEdgePolicy, error) {
routingGraph, err := NewCachedGraph(graph)
sourceNode, err := graph.SourceNode()
if err != nil {
return nil, err
}
routingGraph, err := NewCachedGraph(sourceNode, graph)
if err != nil {
return nil, err
}
defer func() {
if err := routingGraph.close(); err != nil {
log.Errorf("Error closing db tx: %v", err)
}
}()
return findPath(
&graphParams{
additionalEdges: additionalEdges,

View File

@@ -180,7 +180,7 @@ func TestRouterPaymentStateMachine(t *testing.T) {
}, 2),
}
testGraph, err := createTestGraphFromChannels(testChannels, "a")
testGraph, err := createTestGraphFromChannels(true, testChannels, "a")
if err != nil {
t.Fatalf("unable to create graph: %v", err)
}

View File

@@ -164,7 +164,7 @@ type PaymentSession interface {
type paymentSession struct {
additionalEdges map[route.Vertex][]*channeldb.CachedEdgePolicy
getBandwidthHints func() (bandwidthHints, error)
getBandwidthHints func(routingGraph) (bandwidthHints, error)
payment *LightningPayment
@@ -172,7 +172,7 @@ type paymentSession struct {
pathFinder pathFinder
routingGraph routingGraph
getRoutingGraph func() (routingGraph, func(), error)
// pathFindingConfig defines global parameters that control the
// trade-off in path finding between fees and probabiity.
@@ -192,8 +192,8 @@ type paymentSession struct {
// newPaymentSession instantiates a new payment session.
func newPaymentSession(p *LightningPayment,
getBandwidthHints func() (bandwidthHints, error),
routingGraph routingGraph,
getBandwidthHints func(routingGraph) (bandwidthHints, error),
getRoutingGraph func() (routingGraph, func(), error),
missionControl MissionController, pathFindingConfig PathFindingConfig) (
*paymentSession, error) {
@@ -209,7 +209,7 @@ func newPaymentSession(p *LightningPayment,
getBandwidthHints: getBandwidthHints,
payment: p,
pathFinder: findPath,
routingGraph: routingGraph,
getRoutingGraph: getRoutingGraph,
pathFindingConfig: pathFindingConfig,
missionControl: missionControl,
minShardAmt: DefaultShardMinAmt,
@@ -274,33 +274,42 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi,
}
for {
// Get a routing graph.
routingGraph, cleanup, err := p.getRoutingGraph()
if err != nil {
return nil, err
}
// We'll also obtain a set of bandwidthHints from the lower
// layer for each of our outbound channels. This will allow the
// path finding to skip any links that aren't active or just
// don't have enough bandwidth to carry the payment. New
// bandwidth hints are queried for every new path finding
// attempt, because concurrent payments may change balances.
bandwidthHints, err := p.getBandwidthHints()
bandwidthHints, err := p.getBandwidthHints(routingGraph)
if err != nil {
return nil, err
}
p.log.Debugf("pathfinding for amt=%v", maxAmt)
sourceVertex := p.routingGraph.sourceNode()
sourceVertex := routingGraph.sourceNode()
// Find a route for the current amount.
path, err := p.pathFinder(
&graphParams{
additionalEdges: p.additionalEdges,
bandwidthHints: bandwidthHints,
graph: p.routingGraph,
graph: routingGraph,
},
restrictions, &p.pathFindingConfig,
sourceVertex, p.payment.Target,
maxAmt, finalHtlcExpiry,
)
// Close routing graph.
cleanup()
switch {
case err == errNoPathFound:
// Don't split if this is a legacy payment without mpp

View File

@@ -17,7 +17,10 @@ var _ PaymentSessionSource = (*SessionSource)(nil)
type SessionSource struct {
// Graph is the channel graph that will be used to gather metrics from
// and also to carry out path finding queries.
Graph routingGraph
Graph *channeldb.ChannelGraph
// SourceNode is the graph's source node.
SourceNode *channeldb.LightningNode
// GetLink is a method that allows querying the lower link layer
// to determine the up to date available bandwidth at a prospective link
@@ -40,6 +43,21 @@ type SessionSource struct {
PathFindingConfig PathFindingConfig
}
// getRoutingGraph returns a routing graph and a clean-up function for
// pathfinding.
func (m *SessionSource) getRoutingGraph() (routingGraph, func(), error) {
routingTx, err := NewCachedGraph(m.SourceNode, m.Graph)
if err != nil {
return nil, nil, err
}
return routingTx, func() {
err := routingTx.close()
if err != nil {
log.Errorf("Error closing db tx: %v", err)
}
}, nil
}
// 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
@@ -47,14 +65,14 @@ type SessionSource struct {
func (m *SessionSource) NewPaymentSession(p *LightningPayment) (
PaymentSession, error) {
sourceNode := m.Graph.sourceNode()
getBandwidthHints := func() (bandwidthHints, error) {
return newBandwidthManager(m.Graph, sourceNode, m.GetLink)
getBandwidthHints := func(graph routingGraph) (bandwidthHints, error) {
return newBandwidthManager(
graph, m.SourceNode.PubKeyBytes, m.GetLink,
)
}
session, err := newPaymentSession(
p, getBandwidthHints, m.Graph,
p, getBandwidthHints, m.getRoutingGraph,
m.MissionControl, m.PathFindingConfig,
)
if err != nil {

View File

@@ -116,10 +116,12 @@ func TestUpdateAdditionalEdge(t *testing.T) {
// Create the paymentsession.
session, err := newPaymentSession(
payment,
func() (bandwidthHints, error) {
func(routingGraph) (bandwidthHints, error) {
return &mockBandwidthHints{}, nil
},
&sessionGraph{},
func() (routingGraph, func(), error) {
return &sessionGraph{}, func() {}, nil
},
&MissionControl{},
PathFindingConfig{},
)
@@ -194,10 +196,12 @@ func TestRequestRoute(t *testing.T) {
session, err := newPaymentSession(
payment,
func() (bandwidthHints, error) {
func(routingGraph) (bandwidthHints, error) {
return &mockBandwidthHints{}, nil
},
&sessionGraph{},
func() (routingGraph, func(), error) {
return &sessionGraph{}, func() {}, nil
},
&MissionControl{},
PathFindingConfig{},
)

View File

@@ -129,11 +129,11 @@ func createTestCtxFromGraphInstanceAssumeValid(t *testing.T,
)
require.NoError(t, err, "failed to create missioncontrol")
cachedGraph, err := NewCachedGraph(graphInstance.graph)
sourceNode, err := graphInstance.graph.SourceNode()
require.NoError(t, err)
sessionSource := &SessionSource{
Graph: cachedGraph,
Graph: graphInstance.graph,
SourceNode: sourceNode,
GetLink: graphInstance.getLink,
PathFindingConfig: pathFindingConfig,
MissionControl: mc,
@@ -190,7 +190,7 @@ func createTestCtxFromGraphInstanceAssumeValid(t *testing.T,
func createTestCtxSingleNode(t *testing.T,
startingHeight uint32) (*testCtx, func()) {
graph, graphBackend, cleanup, err := makeTestGraph()
graph, graphBackend, cleanup, err := makeTestGraph(true)
require.NoError(t, err, "failed to make test graph")
sourceNode, err := createTestNode()
@@ -216,7 +216,7 @@ func createTestCtxFromFile(t *testing.T,
// We'll attempt to locate and parse out the file
// that encodes the graph that our tests should be run against.
graphInstance, err := parseTestGraph(testGraph)
graphInstance, err := parseTestGraph(true, testGraph)
require.NoError(t, err, "unable to create test graph")
return createTestCtxFromGraphInstance(
@@ -387,7 +387,7 @@ func TestChannelUpdateValidation(t *testing.T) {
}, 2),
}
testGraph, err := createTestGraphFromChannels(testChannels, "a")
testGraph, err := createTestGraphFromChannels(true, testChannels, "a")
require.NoError(t, err, "unable to create graph")
defer testGraph.cleanUp()
@@ -1246,7 +1246,7 @@ func TestIgnoreChannelEdgePolicyForUnknownChannel(t *testing.T) {
// Setup an initially empty network.
testChannels := []*testChannel{}
testGraph, err := createTestGraphFromChannels(
testChannels, "roasbeef",
true, testChannels, "roasbeef",
)
if err != nil {
t.Fatalf("unable to create graph: %v", err)
@@ -2260,7 +2260,9 @@ func TestPruneChannelGraphStaleEdges(t *testing.T) {
for _, strictPruning := range []bool{true, false} {
// We'll create our test graph and router backed with these test
// channels we've created.
testGraph, err := createTestGraphFromChannels(testChannels, "a")
testGraph, err := createTestGraphFromChannels(
true, testChannels, "a",
)
if err != nil {
t.Fatalf("unable to create test graph: %v", err)
}
@@ -2390,7 +2392,9 @@ func testPruneChannelGraphDoubleDisabled(t *testing.T, assumeValid bool) {
// We'll create our test graph and router backed with these test
// channels we've created.
testGraph, err := createTestGraphFromChannels(testChannels, "self")
testGraph, err := createTestGraphFromChannels(
true, testChannels, "self",
)
if err != nil {
t.Fatalf("unable to create test graph: %v", err)
}
@@ -2760,7 +2764,7 @@ func TestUnknownErrorSource(t *testing.T) {
}, 4),
}
testGraph, err := createTestGraphFromChannels(testChannels, "a")
testGraph, err := createTestGraphFromChannels(true, testChannels, "a")
defer testGraph.cleanUp()
if err != nil {
t.Fatalf("unable to create graph: %v", err)
@@ -2896,7 +2900,7 @@ func TestSendToRouteStructuredError(t *testing.T) {
}, 2),
}
testGraph, err := createTestGraphFromChannels(testChannels, "a")
testGraph, err := createTestGraphFromChannels(true, testChannels, "a")
if err != nil {
t.Fatalf("unable to create graph: %v", err)
}
@@ -3145,7 +3149,7 @@ func TestSendToRouteMaxHops(t *testing.T) {
}, 1),
}
testGraph, err := createTestGraphFromChannels(testChannels, "a")
testGraph, err := createTestGraphFromChannels(true, testChannels, "a")
if err != nil {
t.Fatalf("unable to create graph: %v", err)
}
@@ -3256,7 +3260,7 @@ func TestBuildRoute(t *testing.T) {
}, 4),
}
testGraph, err := createTestGraphFromChannels(testChannels, "a")
testGraph, err := createTestGraphFromChannels(true, testChannels, "a")
if err != nil {
t.Fatalf("unable to create graph: %v", err)
}
@@ -3496,7 +3500,7 @@ func createDummyTestGraph(t *testing.T) *testGraphInstance {
}, 2),
}
testGraph, err := createTestGraphFromChannels(testChannels, "a")
testGraph, err := createTestGraphFromChannels(true, testChannels, "a")
require.NoError(t, err, "failed to create graph")
return testGraph
}