multi: use relaxed feature bit Set method for peer features

The spec says: `The origin node MUST NOT set both the optional and
mandatory bits`. and so this is why we have the SafeSet method which
prevents us from accidentally setting both optional and required bits
for any of our own feature bits. But the spec then also says `if both
the optional and the mandatory feature bits in a pair are set, the
feature should be treated as mandatory.` which means that when we read
the feature vectors of our peers or from a payment request, we should be
a bit less strict and not error out. We should just set both bits which
will result in "IsRequired" returning true.

Update the `TestExtractIntentFromSendRequest` test to show the new
behaviour.
This commit is contained in:
Elle Mouton committed 2025-06-25 13:14:31 +02:00
1 parent 9ee3bebc6a
commit b41a079901
4 files changed
+18 -32

No files matched your search

+15 -25
View File
@@ -360,10 +360,7 @@ func (r *RouterBackend) parseQueryRoutesRequest(in *lnrpc.QueryRoutesRequest) (
} }
// Parse destination feature bits. // Parse destination feature bits.
destinationFeatures, err = UnmarshalFeatures(in.DestFeatures) destinationFeatures = UnmarshalFeatures(in.DestFeatures)
if err != nil {
return nil, err
}
} }
// We need to subtract the final delta before passing it into path // We need to subtract the final delta before passing it into path
@@ -495,10 +492,7 @@ func unmarshalBlindedPayment(rpcPayment *lnrpc.BlindedPaymentPath) (
return nil, err return nil, err
} }
features, err := UnmarshalFeatures(rpcPayment.Features) features := UnmarshalFeatures(rpcPayment.Features)
if err != nil {
return nil, err
}
return &routing.BlindedPayment{ return &routing.BlindedPayment{
BlindedPath: path, BlindedPath: path,
@@ -1124,10 +1118,7 @@ func (r *RouterBackend) extractIntentFromSendRequest(
payIntent.Amount = reqAmt payIntent.Amount = reqAmt
// Parse destination feature bits. // Parse destination feature bits.
features, err := UnmarshalFeatures(rpcPayReq.DestFeatures) features := UnmarshalFeatures(rpcPayReq.DestFeatures)
if err != nil {
return nil, err
}
// Validate the features if any was specified. // Validate the features if any was specified.
if features != nil { if features != nil {
@@ -1149,10 +1140,7 @@ func (r *RouterBackend) extractIntentFromSendRequest(
lnrpc.FeatureBit_AMP_OPT, lnrpc.FeatureBit_AMP_OPT,
} }
features, err = UnmarshalFeatures(ampFeatures) features = UnmarshalFeatures(ampFeatures)
if err != nil {
return nil, err
}
} }
// First make sure the destination supports AMP. // First make sure the destination supports AMP.
@@ -1348,24 +1336,26 @@ func MarshalFeatures(feats *lnwire.FeatureVector) []lnrpc.FeatureBit {
// UnmarshalFeatures converts a list of uint32's into a valid feature vector. // UnmarshalFeatures converts a list of uint32's into a valid feature vector.
// This method checks that feature bit pairs aren't assigned together, and // This method checks that feature bit pairs aren't assigned together, and
// validates transitive dependencies. // validates transitive dependencies.
func UnmarshalFeatures( func UnmarshalFeatures(rpcFeatures []lnrpc.FeatureBit) *lnwire.FeatureVector {
rpcFeatures []lnrpc.FeatureBit) (*lnwire.FeatureVector, error) {
// If no destination features are specified we'll return nil to signal // If no destination features are specified we'll return nil to signal
// that the router should try to use the graph as a fallback. // that the router should try to use the graph as a fallback.
if rpcFeatures == nil { if rpcFeatures == nil {
return nil, nil return nil
} }
raw := lnwire.NewRawFeatureVector() raw := lnwire.NewRawFeatureVector()
for _, bit := range rpcFeatures { for _, bit := range rpcFeatures {
err := raw.SafeSet(lnwire.FeatureBit(bit)) // Even though the spec says that the writer of a feature vector
if err != nil { // should never set both the required and optional bits of a
return nil, err // feature, it also says that if we receive a vector with both
} // bits set, then we should just treat the feature as required.
// Therefore, we don't use SafeSet here when parsing a peer's
// feature bits and just set the feature no matter what so that
// if both are set then IsRequired returns true.
raw.Set(lnwire.FeatureBit(bit))
} }
return lnwire.NewFeatureVector(raw, lnwire.Features), nil return lnwire.NewFeatureVector(raw, lnwire.Features)
} }
// ValidatePayReqExpiry checks if the passed payment request has expired. In // ValidatePayReqExpiry checks if the passed payment request has expired. In
+1 -2
View File
@@ -828,8 +828,7 @@ func TestExtractIntentFromSendRequest(t *testing.T) {
lnrpc.FeatureBit_GOSSIP_QUERIES_REQ, lnrpc.FeatureBit_GOSSIP_QUERIES_REQ,
}, },
}, },
valid: false, valid: true,
expectedErrorMsg: "feature pair exists",
}, },
{ {
name: "Valid send req parameters, payment settled", name: "Valid send req parameters, payment settled",
+1 -1
View File
@@ -446,7 +446,7 @@ func (fv RawFeatureVector) Equals(other *RawFeatureVector) bool {
return true return true
} }
// Merges sets all feature bits in other on the receiver's feature vector. // Merge sets all feature bits in other on the receiver's feature vector.
func (fv *RawFeatureVector) Merge(other *RawFeatureVector) error { func (fv *RawFeatureVector) Merge(other *RawFeatureVector) error {
for bit := range other.features { for bit := range other.features {
err := fv.SafeSet(bit) err := fv.SafeSet(bit)
+1 -4
View File
@@ -5782,12 +5782,9 @@ func (r *rpcServer) extractPaymentIntent(rpcPayReq *rpcPaymentRequest) (rpcPayme
} }
// Unmarshal any custom destination features. // Unmarshal any custom destination features.
payIntent.destFeatures, err = routerrpc.UnmarshalFeatures( payIntent.destFeatures = routerrpc.UnmarshalFeatures(
rpcPayReq.DestFeatures, rpcPayReq.DestFeatures,
) )
if err != nil {
return payIntent, err
}
return payIntent, nil return payIntent, nil
} }