graph: add regression test for the fixed behaviour
What changed, and why it matters
This commit only adds a regression test for a previously fixed bug in LND's routing graph cache. The bug caused channels whose two routing policies were initially disabled to be left out of an in-memory cache. Later, if one policy became enabled, the cache could not be updated, so the channel stayed invisible for routing. The test documents the bug and verifies the fix, but does not itself change production code.
No immediate action is required for this commit because it only adds a test. Operators should ensure they are running a version of LND that includes the underlying fix the test validates. Reviewers may want to confirm the prior production-code fix is present and that this test passes in CI.
Security signals we found
Routing cache inconsistency could make channels unusable for pathfinding
Denial-of-service-like effect on channel usability after policy recovery
Regression test documents a prior bug in graph cache population/update logic
Evidence from the diff
The diff adds TestGraphCacheDisabledPoliciesRegression in graph/db/graph_cache_test.go. It simulates: a channel with both directions’ ChanUpdateDisabled flag set is added to the graph cache; the cache retains the channel structure but omits the disabled policies; a subsequent UpdatePolicy call enabling one direction succeeds and the cached policy is reflected. The test confirms that populateCache no longer skips channels solely because both policies are disabled, and that UpdatePolicy can then populate the previously missing policy.
Changed components
graph/db/graph_cache_test.gograph cache population logic (tested, not modified)UpdatePolicy / UpdateEdgePolicy cache update path (tested, not modified)Inspect captured patch +110 / −0
diff --git a/graph/db/graph_cache_test.go b/graph/db/graph_cache_test.go
index 43c3586..89e3a7e 100644
--- a/graph/db/graph_cache_test.go
+++ b/graph/db/graph_cache_test.go
@@ -139,3 +139,113 @@ func assertCachedPolicyEqual(t *testing.T, original,
require.Equal(t, original.ToNodePubKey(), cached.ToNodePubKey())
}
}
+
+// TestGraphCacheDisabledPoliciesRegression is a regression test for the bug
+// where channels with both policies disabled were not added to the graph cache
+// during population, preventing future policy updates from working.
+//
+// The bug flow was:
+// 1. Channel with both policies disabled exists in DB.
+// 2. populateCache skips adding it to graph cache entirely.
+// 3. Later, a policy update arrives enabling one direction.
+// 4. UpdateEdgePolicy updates the DB successfully.
+// 5. UpdateEdgePolicy tries to update graph cache but channel not found.
+// 6. Channel never becomes usable for routing.
+func TestGraphCacheDisabledPoliciesRegression(t *testing.T) {
+ t.Parallel()
+
+ // Create a simple cache instance.
+ cache := NewGraphCache(10)
+
+ // Simulate a channel with both policies disabled.
+ chanID := uint64(12345)
+ node1 := pubKey1
+ node2 := pubKey2
+
+ edgeInfo := &models.CachedEdgeInfo{
+ ChannelID: chanID,
+ NodeKey1Bytes: node1,
+ NodeKey2Bytes: node2,
+ Capacity: 1000000,
+ }
+
+ // Create two disabled policies.
+ disabledPolicy1 := &models.CachedEdgePolicy{
+ ChannelID: chanID,
+ ChannelFlags: lnwire.ChanUpdateDisabled,
+ }
+ disabledPolicy2 := &models.CachedEdgePolicy{
+ ChannelID: chanID,
+ ChannelFlags: lnwire.ChanUpdateDisabled |
+ lnwire.ChanUpdateDirection,
+ }
+
+ // Add the channel with both policies disabled (simulating
+ // populateCache).
+ cache.AddChannel(edgeInfo, disabledPolicy1, disabledPolicy2)
+
+ // Verify the channel structure was added to cache.
+ var foundChannels []*DirectedChannel
+ err := cache.ForEachChannel(node1, func(c *DirectedChannel) error {
+ if c.ChannelID == chanID {
+ foundChannels = append(foundChannels, c)
+ }
+
+ return nil
+ })
+ require.NoError(t, err)
+ require.Len(t, foundChannels, 1,
+ "channel structure should be in cache even when both "+
+ "policies are disabled")
+
+ // Verify policies were NOT added (both disabled).
+ require.False(t, foundChannels[0].OutPolicySet,
+ "disabled outgoing policy should not be set in cache")
+ require.Nil(t, foundChannels[0].InPolicy,
+ "disabled incoming policy should not be set in cache")
+
+ // Now simulate receiving a fresh update enabling one direction.
+ enabledPolicy1 := &models.CachedEdgePolicy{
+ ChannelID: chanID,
+ ChannelFlags: 0, // NOT disabled anymore
+ TimeLockDelta: 40,
+ MinHTLC: lnwire.MilliSatoshi(1000),
+ }
+
+ // Update the policy (simulating what UpdateEdgePolicy does).
+ cache.UpdatePolicy(enabledPolicy1, node1, node2)
+
+ // Verify the policy update succeeded. Before the fix, UpdatePolicy
+ // would log "Channel not found in graph cache" and return early,
+ // so the policy would never be added.
+ foundChannels = nil
+ err = cache.ForEachChannel(node1, func(c *DirectedChannel) error {
+ if c.ChannelID == chanID {
+ foundChannels = append(foundChannels, c)
+ }
+
+ return nil
+ })
+ require.NoError(t, err)
+ require.Len(t, foundChannels, 1)
+
+ // The policy should now be set.
+ require.True(t, foundChannels[0].OutPolicySet,
+ "REGRESSION: policy update should work even for channels that "+
+ "had both policies disabled initially")
+
+ // Verify we can also see it from node2's perspective.
+ foundChannels = nil
+ err = cache.ForEachChannel(node2, func(c *DirectedChannel) error {
+ if c.ChannelID == chanID {
+ foundChannels = append(foundChannels, c)
+ }
+
+ return nil
+ })
+ require.NoError(t, err)
+ require.Len(t, foundChannels, 1)
+ require.NotNil(t, foundChannels[0].InPolicy,
+ "incoming policy should be set after policy update")
+ require.Equal(t, uint16(40), foundChannels[0].InPolicy.TimeLockDelta)
+}
Why this scored 64/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.