graph/db: gracefully handle duplicate channel policy announcements
What changed, and why it matters
This commit fixes a bug in LND's channel graph database where applying the exact same Lightning channel policy update twice in one batch would cause an error. The fix makes the code ignore the harmless duplicate instead of failing. It is a robustness improvement rather than a clear-cut security vulnerability, but the prior failure path could have caused database errors or inconsistent graph state during normal network operation.
Treat as a routine bug-fix / hardening patch. Review whether the duplicate-policy scenario could be induced by a peer to disrupt local graph state or logging, but no urgent security response is indicated by the diff alone.
Security signals we found
Duplicate input not gracefully handled, leading to database-layer error path
Batch processing of network announcements could trigger the failure
Fix converts an error return into a silent no-op for exact duplicates
No explicit security claim made by vendor in commit message
Evidence from the diff
UpdateEdgePolicy in graph/db/sql_store.go previously returned an error when an upsert of a duplicate edge policy hit the SQL conflict check (new last_update must be greater than existing last_update). The patch treats sql.ErrNoRows as a benign duplicate and returns nil. A regression test is added in graph_test.go to verify re-adding the same policy does not error.
Changed components
graph/db/sql_store.go:UpdateEdgePolicygraph/db/graph_test.goInspect captured patch +16 / −1
diff --git a/graph/db/graph_test.go b/graph/db/graph_test.go
index 08fa36f..ee5cf8d 100644
--- a/graph/db/graph_test.go
+++ b/graph/db/graph_test.go
@@ -944,6 +944,13 @@ func TestEdgePolicyCRUD(t *testing.T) {
require.NoError(t, graph.UpdateEdgePolicy(ctx, edge1))
require.NoError(t, graph.UpdateEdgePolicy(ctx, edge2))
+ // Even though we assert at the DB level that any newer edge
+ // update has a newer timestamp, we need to still gracefully
+ // handle the case where the same exact policy is re-added since
+ // it could be possible that our batch executor has two of the
+ // same policy updates in the same batch.
+ require.NoError(t, graph.UpdateEdgePolicy(ctx, edge1))
+
// Use the ForEachChannel method to fetch the policies and
// assert that the deserialized policies match the original
// ones.
diff --git a/graph/db/sql_store.go b/graph/db/sql_store.go
index a942405..f67894e 100644
--- a/graph/db/sql_store.go
+++ b/graph/db/sql_store.go
@@ -780,7 +780,15 @@ func (s *SQLStore) UpdateEdgePolicy(ctx context.Context,
from, to, isUpdate1, err = updateChanEdgePolicy(
ctx, tx, edge,
)
- if err != nil {
+ // It is possible that two of the same policy
+ // announcements are both being processed in the same
+ // batch. This may case the UpsertEdgePolicy conflict to
+ // be hit since we require at the db layer that the
+ // new last_update is greater than the existing
+ // last_update. We need to gracefully handle this here.
+ if errors.Is(err, sql.ErrNoRows) {
+ return nil
+ } else if err != nil {
log.Errorf("UpdateEdgePolicy faild: %v", err)
}
Why this scored 37/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.