diff --git a/routing/pathfind.go b/routing/pathfind.go index a6bd58785..23bc1f9e9 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -456,8 +456,14 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, } } - // With the destination's feature vector selected, ensure that all - // transitive depdencies are set. + // Ensure that the destination's features don't include unknown + // required features. + err = feature.ValidateRequired(features) + if err != nil { + return nil, err + } + + // Ensure that all transitive dependencies are set. err = feature.ValidateDeps(features) if err != nil { return nil, err @@ -752,11 +758,24 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, // If the node exists and has valid features, use them. case err == nil: - err := feature.ValidateDeps(targetNode.Features) - if err == nil { - fromFeatures = targetNode.Features + nodeFeatures := targetNode.Features + + // Don't route through nodes that contain + // unknown required features. + err = feature.ValidateRequired(nodeFeatures) + if err != nil { + break } + // Don't route through nodes that don't properly + // set all transitive feature dependencies. + err = feature.ValidateDeps(nodeFeatures) + if err != nil { + break + } + + fromFeatures = nodeFeatures + // If an error other than the node not existing is hit, // abort. case err != channeldb.ErrGraphNodeNotFound: diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index c638d8512..03b3160a4 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -80,6 +80,10 @@ var ( lnwire.PaymentAddrOptional, ), lnwire.Features, ) + + unknownRequiredFeatures = lnwire.NewFeatureVector( + lnwire.NewRawFeatureVector(100), lnwire.Features, + ) ) var ( @@ -1645,6 +1649,73 @@ func TestMissingFeatureDep(t *testing.T) { } } +// TestUnknownRequiredFeatures 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() + + testChannels := []*testChannel{ + asymmetricTestChannel("roasbeef", "conner", 100000, + &testChannelPolicy{ + Expiry: 144, + FeeRate: 400, + MinHTLC: 1, + MaxHTLC: 100000000, + }, + &testChannelPolicy{ + Expiry: 144, + FeeRate: 400, + MinHTLC: 1, + MaxHTLC: 100000000, + Features: unknownRequiredFeatures, + }, 0, + ), + asymmetricTestChannel("conner", "joost", 100000, + &testChannelPolicy{ + Expiry: 144, + FeeRate: 400, + MinHTLC: 1, + MaxHTLC: 100000000, + Features: unknownRequiredFeatures, + }, + &testChannelPolicy{ + Expiry: 144, + FeeRate: 400, + MinHTLC: 1, + MaxHTLC: 100000000, + }, 0, + ), + } + + ctx := newPathFindingTestContext(t, testChannels, "roasbeef") + defer ctx.cleanup() + + conner := ctx.keyFromAlias("conner") + joost := ctx.keyFromAlias("joost") + + // Conner's node in the graph has an unknown required feature (100). + // Pathfinding should fail since we check the destination's features for + // unknown required features before beginning pathfinding. + expErr := feature.NewErrUnknownRequired([]lnwire.FeatureBit{100}) + _, err := ctx.findPath(conner, 100) + if !reflect.DeepEqual(err, expErr) { + t.Fatalf("path shouldn't have been found: %v", err) + } + + // Now, try to find a route to joost through conner. The destination + // features are valid, but conner's feature vector in the graph still + // requires feature 100. We expect errNoPathFound and not the error + // above since intermediate hops are simply skipped if they have invalid + // feature vectors, leaving no possible route to joost. This asserts + // that we don't try to route _through_ nodes with unknown required + // features. + _, err = ctx.findPath(joost, 100) + if err != errNoPathFound { + t.Fatalf("path shouldn't have been found: %v", err) + } +} + // TestDestPaymentAddr 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.