routing/pathfind: ignore unknown required features

This commit brings us inline with recent modifications to the spec, that
say we shouldn't pay nodes whose feature vectors signal unknown required
features, and also that we shouldn't route through nodes signaling
unknown required features.

Currently we assert that invoices don't have such features during
decoding, but now that users can specify feature vectors via the rpc
interface, it makes sense to perform this check deeper in call stack.
This will also allow us to remove the check from decoding entirely,
making decodepayreq more useful for debugging.
This commit is contained in:
Conner Fromknecht 2020-01-08 12:25:00 -08:00
parent f9a1acfbe4
commit c7a241fc59
No known key found for this signature in database
GPG Key ID: E7D737B67FA592C7
2 changed files with 95 additions and 5 deletions

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

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