graph/db: treat empty channel signatures as missing

This commit improves handling of missing channel signatures in the
database:

- Return nil from auth proof accessors instead of empty slices so that
  missing signatures are stored as NULL in SQL.
- Update public channel checks to require signature length > 0, which
  properly handles existing empty bytea values in the database.
- Add regression test covering empty v1 and v2 channel signatures to
  prevent future issues.
This commit is contained in:
Elle Mouton committed 2026-02-13 07:21:23 +02:00
1 parent a39f94eb4a
commit 6328c4d897
5 files changed
+85 -27

No files matched your search

+57
View File
@@ -154,6 +154,10 @@ var versionedTests = []versionedTest{
name: "node is public",
test: testNodeIsPublic,
},
{
name: "node is public empty channel signature",
test: testIsPublicNodeEmptyChannelSignature,
},
}
// TestVersionedDBs runs various tests against both v1 and v2 versioned
@@ -4265,6 +4269,59 @@ func testNodeIsPublic(t *testing.T, v lnwire.GossipVersion) {
)
}
// testIsPublicNodeEmptyChannelSignature ensures empty channel signatures don't
// mark nodes as public.
func testIsPublicNodeEmptyChannelSignature(t *testing.T,
v lnwire.GossipVersion) {
t.Parallel()
ctx := t.Context()
testGraph := MakeTestGraph(t)
graph := NewVersionedGraph(testGraph, v)
// Set a source node as it's required for IsPublicNode.
sourceNode := createTestVertex(t, v)
err := graph.SetSourceNode(ctx, sourceNode)
require.NoError(t, err)
node1 := createTestVertex(t, v)
node1.LastUpdate = nextUpdateTime()
err = graph.AddNode(ctx, node1)
require.NoError(t, err)
// Create an edge between source node and node1, with
// empty signatures. This tests that empty signatures
// don't mark nodes as public.
edgeInfo, _ := createEdge(
v, 10, 0, 0, 0, sourceNode, node1,
true,
)
switch v {
case lnwire.GossipVersion1:
edgeInfo.AuthProof =
models.NewV1ChannelAuthProof(
[]byte{}, []byte{},
[]byte{}, []byte{},
)
case lnwire.GossipVersion2:
edgeInfo.AuthProof =
models.NewV2ChannelAuthProof([]byte{})
}
err = graph.AddChannelEdge(ctx, edgeInfo)
require.NoError(t, err)
// node1 should NOT be considered public because the
// channel announcement has empty signatures.
isPublic, err := graph.IsPublicNode(node1.PubKeyBytes)
require.NoError(t, err)
require.False(t, isPublic)
}
// BenchmarkIsPublicNode measures the performance of IsPublicNode when checking
// a large number of nodes.
func BenchmarkIsPublicNode(b *testing.B) {
+5 -2
View File
@@ -3377,8 +3377,11 @@ func (c *KVStore) isPublic(tx kvdb.RTx, nodePub route.Vertex,
}
// Since the edge _does_ extend to the source node, we'll also
// need to ensure that this is a public edge.
if info.AuthProof != nil {
// need to ensure that this is a public edge with valid
// signatures (not empty).
if info.AuthProof != nil && !info.AuthProof.IsEmpty() &&
len(info.AuthProof.BitcoinSig1()) > 0 {
nodeIsPublic = true
return errDone
}
+12 -14
View File
@@ -89,31 +89,29 @@ func NewV2ChannelAuthProof(signature []byte) *ChannelAuthProof {
}
}
// NodeSig1 returns the first node signature bytes, or an empty slice if not
// present.
// NodeSig1 returns the first node signature bytes, or nil if not present.
func (c *ChannelAuthProof) NodeSig1() []byte {
return c.NodeSig1Bytes.UnwrapOr([]byte{})
return c.NodeSig1Bytes.UnwrapOr(nil)
}
// NodeSig2 returns the second node signature bytes, or an empty slice if not
// present.
// NodeSig2 returns the second node signature bytes, or nil if not present.
func (c *ChannelAuthProof) NodeSig2() []byte {
return c.NodeSig2Bytes.UnwrapOr([]byte{})
return c.NodeSig2Bytes.UnwrapOr(nil)
}
// BitcoinSig1 returns the first bitcoin signature bytes, or an empty slice if
// not present.
// BitcoinSig1 returns the first bitcoin signature bytes, or nil if not
// present.
func (c *ChannelAuthProof) BitcoinSig1() []byte {
return c.BitcoinSig1Bytes.UnwrapOr([]byte{})
return c.BitcoinSig1Bytes.UnwrapOr(nil)
}
// BitcoinSig2 returns the second bitcoin signature bytes, or an empty slice if
// not present.
// BitcoinSig2 returns the second bitcoin signature bytes, or nil if not
// present.
func (c *ChannelAuthProof) BitcoinSig2() []byte {
return c.BitcoinSig2Bytes.UnwrapOr([]byte{})
return c.BitcoinSig2Bytes.UnwrapOr(nil)
}
// Sig returns the v2 signature bytes, or an empty slice if not present.
// Sig returns the v2 signature bytes, or nil if not present.
func (c *ChannelAuthProof) Sig() []byte {
return c.Signature.UnwrapOr([]byte{})
return c.Signature.UnwrapOr(nil)
}