graph/db: refactor CachedEdgePolicy to use explicit boolean fields
What changed, and why it matters
This commit is a straightforward internal code cleanup in LND's routing graph cache. It replaces compact bit-field flags with clearly named true/false fields (e.g., 'IsDisabled', 'IsNode1', 'HasMaxHTLC') and updates the code that reads them. There is no security fix or externally reported vulnerability here; it is a readability and maintainability refactor that also prepares the code for a newer gossip protocol version.
No security action required. Review as normal code-quality refactor; verify test coverage remains equivalent for v1 and v2 gossip policy conversion.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change refactors models.CachedEdgePolicy to expose explicit booleans instead of lnwire.ChanUpdateMsgFlags and lnwire.ChanUpdateChanFlags bitfields. NewCachedPolicy now derives those booleans differently for gossip v1 (from MessageFlags/ChannelFlags bits) and v2 (from policy.SecondPeer and policy.DisableFlags.IsEnabled()). Call sites in graph_cache.go, unified_edges.go, and tests are updated from method calls (IsNode1(), IsDisabled()) and bit tests (MessageFlags.HasMaxHtlc()) to direct field access. No behavioral change is intended; the existing regression test for disabled policies is preserved and updated to the new field style.
Changed components
graph/db/models/cached_edge_policy.gograph/db/graph_cache.gorouting/unified_edges.gograph/db/graph_cache_test.gorouting/unified_edges_test.goInspect captured patch +59 / −45
diff --git a/graph/db/graph_cache.go b/graph/db/graph_cache.go
index 4a3a3b0..7cf101f 100644
--- a/graph/db/graph_cache.go
+++ b/graph/db/graph_cache.go
@@ -142,8 +142,8 @@ func (c *GraphCache) AddChannel(info *models.CachedEdgeInfo,
// Skip adding policies if both are disabled, as the channel is
// currently unusable for routing. However, we still add the channel
// structure above so that policy updates can later enable it.
- if policy1 != nil && policy1.IsDisabled() &&
- policy2 != nil && policy2.IsDisabled() {
+ if policy1 != nil && policy1.IsDisabled &&
+ policy2 != nil && policy2.IsDisabled {
log.Debugf("Skipping policies for channel %v: both "+
"policies are disabled (channel structure still "+
@@ -156,14 +156,14 @@ func (c *GraphCache) AddChannel(info *models.CachedEdgeInfo,
// of node 2 then we have the policy 1 as seen from node 1.
if policy1 != nil {
fromNode, toNode := info.NodeKey1Bytes, info.NodeKey2Bytes
- if !policy1.IsNode1() {
+ if !policy1.IsNode1 {
fromNode, toNode = toNode, fromNode
}
c.UpdatePolicy(policy1, fromNode, toNode)
}
if policy2 != nil {
fromNode, toNode := info.NodeKey2Bytes, info.NodeKey1Bytes
- if policy2.IsNode1() {
+ if policy2.IsNode1 {
fromNode, toNode = toNode, fromNode
}
c.UpdatePolicy(policy2, fromNode, toNode)
@@ -210,7 +210,7 @@ func (c *GraphCache) UpdatePolicy(policy *models.CachedEdgePolicy, fromNode,
switch {
// This is node 1, and it is edge 1, so this is the outgoing
// policy for node 1.
- case channel.IsNode1 && policy.IsNode1():
+ case channel.IsNode1 && policy.IsNode1:
channel.OutPolicySet = true
policy.InboundFee.WhenSome(func(fee lnwire.Fee) {
channel.InboundFee = fee
@@ -218,7 +218,7 @@ func (c *GraphCache) UpdatePolicy(policy *models.CachedEdgePolicy, fromNode,
// This is node 2, and it is edge 2, so this is the outgoing
// policy for node 2.
- case !channel.IsNode1 && !policy.IsNode1():
+ case !channel.IsNode1 && !policy.IsNode1:
channel.OutPolicySet = true
policy.InboundFee.WhenSome(func(fee lnwire.Fee) {
channel.InboundFee = fee
diff --git a/graph/db/graph_cache_test.go b/graph/db/graph_cache_test.go
index 89e3a7e..3d5fba8 100644
--- a/graph/db/graph_cache_test.go
+++ b/graph/db/graph_cache_test.go
@@ -33,9 +33,9 @@ func TestGraphCacheAddNode(t *testing.T) {
runTest := func(nodeA, nodeB route.Vertex) {
t.Helper()
- channelFlagA, channelFlagB := 0, 1
+ isNode1A, isNode1B := true, false
if nodeA == pubKey2 {
- channelFlagA, channelFlagB = 1, 0
+ isNode1A, isNode1B = false, true
}
inboundFee := lnwire.Fee{
@@ -44,8 +44,9 @@ func TestGraphCacheAddNode(t *testing.T) {
}
outPolicy1 := &models.CachedEdgePolicy{
- ChannelID: 1000,
- ChannelFlags: lnwire.ChanUpdateChanFlags(channelFlagA),
+ ChannelID: 1000,
+ IsNode1: isNode1A,
+ IsDisabled: false,
ToNodePubKey: func() route.Vertex {
return nodeB
},
@@ -53,8 +54,9 @@ func TestGraphCacheAddNode(t *testing.T) {
InboundFee: fn.Some(inboundFee),
}
inPolicy1 := &models.CachedEdgePolicy{
- ChannelID: 1000,
- ChannelFlags: lnwire.ChanUpdateChanFlags(channelFlagB),
+ ChannelID: 1000,
+ IsNode1: isNode1B,
+ IsDisabled: false,
ToNodePubKey: func() route.Vertex {
return nodeA
},
@@ -125,8 +127,9 @@ func assertCachedPolicyEqual(t *testing.T, original,
cached *models.CachedEdgePolicy) {
require.Equal(t, original.ChannelID, cached.ChannelID)
- require.Equal(t, original.MessageFlags, cached.MessageFlags)
- require.Equal(t, original.ChannelFlags, cached.ChannelFlags)
+ require.Equal(t, original.HasMaxHTLC, cached.HasMaxHTLC)
+ require.Equal(t, original.IsNode1, cached.IsNode1)
+ require.Equal(t, original.IsDisabled, cached.IsDisabled)
require.Equal(t, original.TimeLockDelta, cached.TimeLockDelta)
require.Equal(t, original.MinHTLC, cached.MinHTLC)
require.Equal(t, original.MaxHTLC, cached.MaxHTLC)
@@ -171,13 +174,14 @@ func TestGraphCacheDisabledPoliciesRegression(t *testing.T) {
// Create two disabled policies.
disabledPolicy1 := &models.CachedEdgePolicy{
- ChannelID: chanID,
- ChannelFlags: lnwire.ChanUpdateDisabled,
+ ChannelID: chanID,
+ IsNode1: true,
+ IsDisabled: true,
}
disabledPolicy2 := &models.CachedEdgePolicy{
- ChannelID: chanID,
- ChannelFlags: lnwire.ChanUpdateDisabled |
- lnwire.ChanUpdateDirection,
+ ChannelID: chanID,
+ IsNode1: false,
+ IsDisabled: true,
}
// Add the channel with both policies disabled (simulating
@@ -207,7 +211,8 @@ func TestGraphCacheDisabledPoliciesRegression(t *testing.T) {
// Now simulate receiving a fresh update enabling one direction.
enabledPolicy1 := &models.CachedEdgePolicy{
ChannelID: chanID,
- ChannelFlags: 0, // NOT disabled anymore
+ IsNode1: true,
+ IsDisabled: false,
TimeLockDelta: 40,
MinHTLC: lnwire.MilliSatoshi(1000),
}
diff --git a/graph/db/models/cached_edge_policy.go b/graph/db/models/cached_edge_policy.go
index 40b0d92..90c8d56 100644
--- a/graph/db/models/cached_edge_policy.go
+++ b/graph/db/models/cached_edge_policy.go
@@ -20,13 +20,15 @@ type CachedEdgePolicy struct {
// and the last 2 bytes are the output index for the channel.
ChannelID uint64
- // MessageFlags is a bitfield which indicates the presence of optional
- // fields (like max_htlc) in the policy.
- MessageFlags lnwire.ChanUpdateMsgFlags
+ // HasMaxHTLC indicates whether the policy has a max HTLC value.
+ HasMaxHTLC bool
- // ChannelFlags is a bitfield which signals the capabilities of the
- // channel as well as the directed edge this update applies to.
- ChannelFlags lnwire.ChanUpdateChanFlags
+ // IsNode1 indicates whether this policy was announced by the channel's
+ // node_1.
+ IsNode1 bool
+
+ // IsDisabled indicates whether the policy disables forwarding.
+ IsDisabled bool
// TimeLockDelta is the number of blocks this node will subtract from
// the expiry of an incoming HTLC. This value expresses the time buffer
@@ -75,24 +77,31 @@ func (c *CachedEdgePolicy) ComputeFee(
return c.FeeBaseMSat + (amt*c.FeeProportionalMillionths)/feeRateParts
}
-// IsDisabled returns true if the channel is disabled in the direction from the
-// advertising node.
-func (c *CachedEdgePolicy) IsDisabled() bool {
- return c.ChannelFlags&lnwire.ChanUpdateDisabled != 0
-}
-
-// IsNode1 returns true if this policy was announced by the channel's node_1
-// node.
-func (c *CachedEdgePolicy) IsNode1() bool {
- return c.ChannelFlags&lnwire.ChanUpdateDirection == 0
-}
-
// NewCachedPolicy turns a full policy into a minimal one that can be cached.
func NewCachedPolicy(policy *ChannelEdgePolicy) *CachedEdgePolicy {
+ if policy.Version != lnwire.GossipVersion2 {
+ return &CachedEdgePolicy{
+ ChannelID: policy.ChannelID,
+ HasMaxHTLC: policy.MessageFlags.HasMaxHtlc(),
+ IsDisabled: policy.ChannelFlags&
+ lnwire.ChanUpdateDisabled != 0,
+ IsNode1: policy.ChannelFlags&
+ lnwire.ChanUpdateDirection == 0,
+ TimeLockDelta: policy.TimeLockDelta,
+ MinHTLC: policy.MinHTLC,
+ MaxHTLC: policy.MaxHTLC,
+ FeeBaseMSat: policy.FeeBaseMSat,
+ FeeProportionalMillionths: policy.
+ FeeProportionalMillionths,
+ InboundFee: policy.InboundFee,
+ }
+ }
+
return &CachedEdgePolicy{
ChannelID: policy.ChannelID,
- MessageFlags: policy.MessageFlags,
- ChannelFlags: policy.ChannelFlags,
+ HasMaxHTLC: true,
+ IsNode1: !policy.SecondPeer,
+ IsDisabled: !policy.DisableFlags.IsEnabled(),
TimeLockDelta: policy.TimeLockDelta,
MinHTLC: policy.MinHTLC,
MaxHTLC: policy.MaxHTLC,
diff --git a/routing/unified_edges.go b/routing/unified_edges.go
index 9b8f6c5..fda06aa 100644
--- a/routing/unified_edges.go
+++ b/routing/unified_edges.go
@@ -188,7 +188,7 @@ func (u *unifiedEdge) amtInRange(amt lnwire.MilliSatoshi) bool {
}
// Skip channels for which this htlc is too large.
- if u.policy.MessageFlags.HasMaxHtlc() &&
+ if u.policy.HasMaxHTLC &&
amt > u.policy.MaxHTLC {
log.Tracef("Exceeds policy's MaxHTLC: amt=%v, MaxHTLC=%v",
@@ -376,7 +376,7 @@ func (u *edgeUnifier) getEdgeNetwork(netAmtReceived lnwire.MilliSatoshi,
}
// For network channels, skip the disabled ones.
- if edge.policy.IsDisabled() {
+ if edge.policy.IsDisabled {
log.Debugf("Skipped edge %v due to it being disabled",
edge.policy.ChannelID)
continue
@@ -385,7 +385,7 @@ func (u *edgeUnifier) getEdgeNetwork(netAmtReceived lnwire.MilliSatoshi,
// Track the maximal capacity for usable channels. If we don't
// know the capacity, we fall back to MaxHTLC.
capMsat := lnwire.NewMSatFromSatoshis(edge.capacity)
- if capMsat == 0 && edge.policy.MessageFlags.HasMaxHtlc() {
+ if capMsat == 0 && edge.policy.HasMaxHTLC {
log.Tracef("No capacity available for channel %v, "+
"using MaxHtlcMsat (%v) as a fallback.",
edge.policy.ChannelID, edge.policy.MaxHTLC)
diff --git a/routing/unified_edges_test.go b/routing/unified_edges_test.go
index 8fc7903..25c8e92 100644
--- a/routing/unified_edges_test.go
+++ b/routing/unified_edges_test.go
@@ -30,7 +30,7 @@ func TestNodeEdgeUnifier(t *testing.T) {
FeeProportionalMillionths: 100000,
FeeBaseMSat: 30,
TimeLockDelta: 60,
- MessageFlags: lnwire.ChanUpdateRequiredMaxHtlc,
+ HasMaxHTLC: true,
MaxHTLC: 5000,
MinHTLC: 100,
}
@@ -39,7 +39,7 @@ func TestNodeEdgeUnifier(t *testing.T) {
FeeProportionalMillionths: 190000,
FeeBaseMSat: 10,
TimeLockDelta: 40,
- MessageFlags: lnwire.ChanUpdateRequiredMaxHtlc,
+ HasMaxHTLC: true,
MaxHTLC: 4000,
MinHTLC: 100,
}
Why this scored 14/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.