diff --git a/routing/pathfind.go b/routing/pathfind.go index 428caf42..3b1c4433 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -644,6 +644,36 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, edge.ChannelID) } + switch { + + // If this edge takes us to the final hop, we'll set the node + // features to those determined above. These are either taken + // from the destination features, e.g. virtual or invoice + // features, or loaded as a fallback from the graph. The + // transitive dependencies were already validated above, so no + // need to do so now. + // + // NOTE: This may overwrite features loaded from the graph if + // destination features were provided. This is fine though, + // since our route construction does not care where the features + // are actually taken from. In the future we may wish to do + // route construction within findPath, and avoid using + // ChannelEdgePolicy altogether. + case edge.Node.PubKeyBytes == target: + edge.Node.Features = features + + // Otherwise, this is some other intermediary node. Verify the + // transitive feature dependencies for this node, and skip the + // channel if they are invalid. + default: + err := feature.ValidateDeps(edge.Node.Features) + if err != nil { + log.Tracef("Node %x has invalid features", + edge.Node.PubKeyBytes) + return + } + } + // All conditions are met and this new tentative distance is // better than the current best known distance to this node. // The new better distance is recorded, and also our "next hop" diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index bcd40cf0..d65090f1 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -1482,6 +1482,21 @@ func TestMissingFeatureDep(t *testing.T) { Features: payAddrFeatures, }, 0, ), + asymmetricTestChannel("conner", "joost", 100000, + &testChannelPolicy{ + Expiry: 144, + FeeRate: 400, + MinHTLC: 1, + MaxHTLC: 100000000, + Features: payAddrFeatures, + }, + &testChannelPolicy{ + Expiry: 144, + FeeRate: 400, + MinHTLC: 1, + MaxHTLC: 100000000, + }, 0, + ), } ctx := newPathFindingTestContext(t, testChannels, "roasbeef") @@ -1530,6 +1545,19 @@ func TestMissingFeatureDep(t *testing.T) { t.Fatalf("path should have been found: %v", err) } assertExpectedPath(t, ctx.testGraphInstance.aliasMap, path, "conner") + + // Finally, try to find a route to joost through conner. The + // destination features are set properly from the previous assertions, + // but conner's feature vector in the graph is still broken. We expect + // errNoPathFound and not the missing feature dep err above since + // intermediate hops are simply skipped if they have invalid feature + // vectors, leaving no possible route to joost. + joost := ctx.testGraphInstance.aliasMap["joost"] + + _, err = find(&restrictions, joost) + if err != errNoPathFound { + t.Fatalf("path shouldn't have been found: %v", err) + } } // TestDestPaymentAddr asserts that we properly detect when we can send a