From d6ebc5a2420b7c0100cfbc77e2bb6793bff16482 Mon Sep 17 00:00:00 2001 From: Lrifton92 Date: Tue, 9 Jun 2026 16:39:25 +0200 Subject: [PATCH 1/2] feature: allow custom feature bits already set by default When a custom feature bit configured via protocol.custom-init or protocol.custom-nodeann is already advertised in lnd's default feature vector, newManager returned a "feature bit: X already set" error and lnd failed to start. Re-advertising an already-set bit yields an identical feature vector, so this is harmless. Treat an already-set custom bit as a no-op instead of a fatal error. Genuine conflicts between the optional and required variants of a feature are still rejected by SafeSet. Signed-off-by: Lrifton92 --- feature/manager.go | 10 ++++++++-- feature/manager_internal_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/feature/manager.go b/feature/manager.go index b1fff33c67b..9869d941a18 100644 --- a/feature/manager.go +++ b/feature/manager.go @@ -239,9 +239,15 @@ func newManager(cfg Config, desc setDesc) (*Manager, error) { set, set.Maximum()) } + // If the feature bit is already advertised in lnd's + // default feature vector, configuring it as a custom + // feature produces an identical vector. Treat this as a + // no-op rather than failing startup (see #10883). + // Genuine conflicts between the optional and required + // variants of a feature are still caught by SafeSet + // below. if raw.IsSet(custom) { - return nil, fmt.Errorf("feature bit: %v "+ - "already set", custom) + continue } if err := raw.SafeSet(custom); err != nil { diff --git a/feature/manager_internal_test.go b/feature/manager_internal_test.go index c110fc7a9d9..fb56be157f1 100644 --- a/feature/manager_internal_test.go +++ b/feature/manager_internal_test.go @@ -289,3 +289,33 @@ func TestUpdateFeatureSets(t *testing.T) { }) } } + +// TestManagerCustomFeatureAlreadyDefault asserts that configuring a custom +// feature bit which lnd already advertises by default does not prevent the +// feature manager from initializing. The resulting feature vector is identical, +// so the duplicate must be treated as a no-op rather than a fatal error. +// +// See: https://github.com/lightningnetwork/lnd/issues/10883 +func TestManagerCustomFeatureAlreadyDefault(t *testing.T) { + t.Parallel() + + // TLVOnionPayloadRequired is advertised by default in the Init set per + // testSetDesc, so configuring it as a custom feature for the same set + // duplicates an already-set bit. This previously failed startup with a + // "feature bit: ... already set" error. + cfg := Config{ + CustomFeatures: map[Set][]lnwire.FeatureBit{ + SetInit: {lnwire.TLVOnionPayloadRequired}, + }, + } + + m, err := newManager(cfg, testSetDesc) + require.NoError(t, err) + + // The required bit must still be advertised, and the duplicate must not + // have introduced its optional variant (which would form an invalid + // pairing). Check the exact bits via the raw vector. + rawInit := m.GetRaw(SetInit) + require.True(t, rawInit.IsSet(lnwire.TLVOnionPayloadRequired)) + require.False(t, rawInit.IsSet(lnwire.TLVOnionPayloadOptional)) +} From c870ccf4b5961fd2f25d8ee968b6d76c3bf94f87 Mon Sep 17 00:00:00 2001 From: Lrifton92 Date: Tue, 9 Jun 2026 16:40:21 +0200 Subject: [PATCH 2/2] docs: add release note for #10888 Signed-off-by: Lrifton92 --- docs/release-notes/release-notes-0.22.0.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/release-notes/release-notes-0.22.0.md b/docs/release-notes/release-notes-0.22.0.md index 85626b68f41..cfaf166f2c3 100644 --- a/docs/release-notes/release-notes-0.22.0.md +++ b/docs/release-notes/release-notes-0.22.0.md @@ -55,6 +55,12 @@ the reported network statistics such as total network capacity, channel count and max out degree. +* [Fixed a bug](https://github.com/lightningnetwork/lnd/pull/10888) where + configuring a `protocol.custom-init` or `protocol.custom-nodeann` feature bit + that lnd already advertises by default caused startup to fail with a + `feature bit: X already set` error. Such a duplicate now yields an identical + feature vector and is treated as a no-op. + # New Features ## Functional Enhancements @@ -148,3 +154,4 @@ * Boris Nagaev * Erick Cestari * Jared Tobin +* Lrifton92