graph/db: fetch policy version in cache paginated query
What changed, and why it matters
This commit fixes a bug in LND's channel graph cache where a database query forgot to fetch the 'version' field for routing policies. The code was then hardcoding an older policy version (GossipVersion1) for those cached policies. This could cause newer, extended routing policy fields to be misinterpreted or ignored when the cache is used, potentially leading to stale or incorrect routing information.
Treat as a bug fix with possible routing-reliability implications. Review whether cached graph state could have diverged from persisted state and whether any gossip propagation or pathfinding behavior was affected. No immediate emergency response is indicated, but operators should update to include this fix.
Security signals we found
Incorrect policy version used for cached graph data
Potential mismatch between stored and interpreted routing policy fields
Graph cache could serve stale or malformed channel policies
No explicit security framing in commit message
Evidence from the diff
The ListChannelsWithPoliciesForCachePaginated SQL query omitted the cp1.version and cp2.version columns. In extractChannelPolicies, the row type for this paginated query hardcoded lnwire.GossipVersion1 for both policies. The patch adds the version columns to the SQL query, regenerates the sqlc code to include Policy1Version/Policy2Version fields, and uses those fetched values instead of the constant. This is a correctness fix for the graph cache’s policy deserialization.
Changed components
graph/db/sql_store.gosqldb/sqlc/graph.sql.gosqldb/sqlc/queries/graph.sqlListChannelsWithPoliciesForCachePaginated queryextractChannelPolicies functionInspect captured patch +10 / −2
diff --git a/graph/db/sql_store.go b/graph/db/sql_store.go
index 2841b96..a04a70a 100644
--- a/graph/db/sql_store.go
+++ b/graph/db/sql_store.go
@@ -5082,7 +5082,7 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy,
case sqlc.ListChannelsWithPoliciesForCachePaginatedRow:
if r.Policy1Timelock.Valid {
policy1 = &sqlc.GraphChannelPolicy{
- Version: int16(lnwire.GossipVersion1),
+ Version: r.Policy1Version.Int16,
Timelock: r.Policy1Timelock.Int32,
FeePpm: r.Policy1FeePpm.Int64,
BaseFeeMsat: r.Policy1BaseFeeMsat.Int64,
@@ -5099,7 +5099,7 @@ func extractChannelPolicies(row any) (*sqlc.GraphChannelPolicy,
}
if r.Policy2Timelock.Valid {
policy2 = &sqlc.GraphChannelPolicy{
- Version: int16(lnwire.GossipVersion1),
+ Version: r.Policy2Version.Int16,
Timelock: r.Policy2Timelock.Int32,
FeePpm: r.Policy2FeePpm.Int64,
BaseFeeMsat: r.Policy2BaseFeeMsat.Int64,
diff --git a/sqldb/sqlc/graph.sql.go b/sqldb/sqlc/graph.sql.go
index b0d1f78..6293ef2 100644
--- a/sqldb/sqlc/graph.sql.go
+++ b/sqldb/sqlc/graph.sql.go
@@ -3334,6 +3334,7 @@ SELECT
n2.pub_key AS node2_pubkey,
-- Node 1 policy
+ cp1.version AS policy1_version,
cp1.timelock AS policy_1_timelock,
cp1.fee_ppm AS policy_1_fee_ppm,
cp1.base_fee_msat AS policy_1_base_fee_msat,
@@ -3348,6 +3349,7 @@ SELECT
cp1.disable_flags AS policy1_disable_flags,
-- Node 2 policy
+ cp2.version AS policy2_version,
cp2.timelock AS policy_2_timelock,
cp2.fee_ppm AS policy_2_fee_ppm,
cp2.base_fee_msat AS policy_2_base_fee_msat,
@@ -3385,6 +3387,7 @@ type ListChannelsWithPoliciesForCachePaginatedRow struct {
Capacity sql.NullInt64
Node1Pubkey []byte
Node2Pubkey []byte
+ Policy1Version sql.NullInt16
Policy1Timelock sql.NullInt32
Policy1FeePpm sql.NullInt64
Policy1BaseFeeMsat sql.NullInt64
@@ -3397,6 +3400,7 @@ type ListChannelsWithPoliciesForCachePaginatedRow struct {
Policy1ChannelFlags sql.NullInt16
Policy1BlockHeight sql.NullInt64
Policy1DisableFlags sql.NullInt16
+ Policy2Version sql.NullInt16
Policy2Timelock sql.NullInt32
Policy2FeePpm sql.NullInt64
Policy2BaseFeeMsat sql.NullInt64
@@ -3426,6 +3430,7 @@ func (q *Queries) ListChannelsWithPoliciesForCachePaginated(ctx context.Context,
&i.Capacity,
&i.Node1Pubkey,
&i.Node2Pubkey,
+ &i.Policy1Version,
&i.Policy1Timelock,
&i.Policy1FeePpm,
&i.Policy1BaseFeeMsat,
@@ -3438,6 +3443,7 @@ func (q *Queries) ListChannelsWithPoliciesForCachePaginated(ctx context.Context,
&i.Policy1ChannelFlags,
&i.Policy1BlockHeight,
&i.Policy1DisableFlags,
+ &i.Policy2Version,
&i.Policy2Timelock,
&i.Policy2FeePpm,
&i.Policy2BaseFeeMsat,
diff --git a/sqldb/sqlc/queries/graph.sql b/sqldb/sqlc/queries/graph.sql
index 2cc2b22..ec44bc0 100644
--- a/sqldb/sqlc/queries/graph.sql
+++ b/sqldb/sqlc/queries/graph.sql
@@ -834,6 +834,7 @@ SELECT
n2.pub_key AS node2_pubkey,
-- Node 1 policy
+ cp1.version AS policy1_version,
cp1.timelock AS policy_1_timelock,
cp1.fee_ppm AS policy_1_fee_ppm,
cp1.base_fee_msat AS policy_1_base_fee_msat,
@@ -848,6 +849,7 @@ SELECT
cp1.disable_flags AS policy1_disable_flags,
-- Node 2 policy
+ cp2.version AS policy2_version,
cp2.timelock AS policy_2_timelock,
cp2.fee_ppm AS policy_2_fee_ppm,
cp2.base_fee_msat AS policy_2_base_fee_msat,
Why this scored 34/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.