From 03050a589d852c8d8b458bb68d83631853201d4f Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 19 Nov 2025 18:36:54 +0200 Subject: [PATCH] graph/db: add version parameter to IsZombieEdge Make IsZombieEdge version-aware and add corresponding method to VersionedGraph. Convert TestEdgeInsertionDeletion to versioned test using the createEdge helper for both v1 and v2 channel testing. --- graph/db/graph.go | 9 ++- graph/db/graph_test.go | 136 +++++++++++++++++++++++------------------ graph/db/interfaces.go | 3 +- graph/db/kv_store.go | 9 ++- graph/db/sql_store.go | 11 +++- 5 files changed, 103 insertions(+), 65 deletions(-) diff --git a/graph/db/graph.go b/graph/db/graph.go index 0f4dda661..41396aceb 100644 --- a/graph/db/graph.go +++ b/graph/db/graph.go @@ -735,7 +735,7 @@ func (c *ChannelGraph) ChannelView() ([]EdgePoint, error) { func (c *ChannelGraph) IsZombieEdge(chanID uint64) (bool, [33]byte, [33]byte, error) { - return c.db.IsZombieEdge(chanID) + return c.db.IsZombieEdge(lnwire.GossipVersion1, chanID) } // NumZombies returns the current number of zombie channels in the graph. @@ -806,6 +806,13 @@ func (c *VersionedGraph) FetchChannelEdgesByOutpoint(op *wire.OutPoint) ( return c.db.FetchChannelEdgesByOutpoint(c.v, op) } +// IsZombieEdge returns whether the edge is considered zombie for this version. +func (c *VersionedGraph) IsZombieEdge(chanID uint64) (bool, [33]byte, + [33]byte, error) { + + return c.db.IsZombieEdge(c.v, chanID) +} + // AddrsForNode returns all known addresses for the target node public key. func (c *VersionedGraph) AddrsForNode(ctx context.Context, nodePub *btcec.PublicKey) (bool, []net.Addr, error) { diff --git a/graph/db/graph_test.go b/graph/db/graph_test.go index 2f106dd63..a6dcb106d 100644 --- a/graph/db/graph_test.go +++ b/graph/db/graph_test.go @@ -142,6 +142,10 @@ var versionedTests = []versionedTest{ name: "add edge proof", test: testAddEdgeProof, }, + { + name: "edge insertion deletion", + test: testEdgeInsertionDeletion, + }, { name: "partial node", test: testPartialNode, @@ -549,88 +553,104 @@ func TestSetSourceNodeSameTimestamp(t *testing.T) { require.Equal(t, testNode.LastUpdate, updatedNode.LastUpdate) } -// TestEdgeInsertionDeletion tests the basic CRUD operations for channel edges. -func TestEdgeInsertionDeletion(t *testing.T) { +// testEdgeInsertionDeletion tests the basic CRUD operations for channel edges. +func testEdgeInsertionDeletion(t *testing.T, v lnwire.GossipVersion) { t.Parallel() ctx := t.Context() - graph := MakeTestGraph(t) + graph := NewVersionedGraph(MakeTestGraph(t), v) // We'd like to test the insertion/deletion of edges, so we create two // vertexes to connect. - node1 := createTestVertex(t, lnwire.GossipVersion1) - node2 := createTestVertex(t, lnwire.GossipVersion1) + node1 := createTestVertex(t, v) + node2 := createTestVertex(t, v) - // In addition to the fake vertexes we create some fake channel - // identifiers. - chanID := uint64(prand.Int63()) + // Create a fake channel and add it to the graph. + const ( + blockHeight = 1234 + txIndex = 1 + txPosition = 0 + outPointIndex = 9 + ) + + edgeInfo, shortChanID := createEdge( + v, blockHeight, txIndex, txPosition, outPointIndex, node1, + node2, + ) + chanID := shortChanID.ToUint64() outpoint := wire.OutPoint{ Hash: rev, - Index: 9, + Index: outPointIndex, } - // Add the new edge to the database, this should proceed without any - // errors. - node1Pub, err := node1.PubKey() - require.NoError(t, err, "unable to generate node key") - node2Pub, err := node2.PubKey() - require.NoError(t, err, "unable to generate node key") - - node1Vertex, err := route.NewVertexFromBytes( - node1Pub.SerializeCompressed(), - ) - require.NoError(t, err) - node2Vertex, err := route.NewVertexFromBytes( - node2Pub.SerializeCompressed(), - ) - require.NoError(t, err) - - btcKey1, err := route.NewVertexFromBytes( - node1Pub.SerializeCompressed(), - ) - require.NoError(t, err) - btcKey2, err := route.NewVertexFromBytes( - node2Pub.SerializeCompressed(), - ) - require.NoError(t, err) - - proof := models.NewV1ChannelAuthProof( - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - ) - - edgeInfo, err := models.NewV1Channel( - chanID, *chaincfg.MainNetParams.GenesisHash, node1Vertex, - node2Vertex, &models.ChannelV1Fields{ - BitcoinKey1Bytes: btcKey1, - BitcoinKey2Bytes: btcKey2, - }, - models.WithChanProof(proof), - models.WithChannelPoint(outpoint), - models.WithCapacity(9000), - ) - require.NoError(t, err) - require.NoError(t, graph.AddChannelEdge(ctx, edgeInfo)) - assertEdgeWithNoPoliciesInCache(t, graph, edgeInfo) + assertEdgeWithNoPoliciesInCache(t, graph.ChannelGraph, edgeInfo) // Show that trying to insert the same channel again will return the // expected error. - err = graph.AddChannelEdge(ctx, edgeInfo) + err := graph.AddChannelEdge(ctx, edgeInfo) require.ErrorIs(t, err, ErrEdgeAlreadyExist) - // Ensure that both policies are returned as unknown (nil). - _, e1, e2, err := graph.FetchChannelEdgesByID(chanID) + // Ensure that both policies are returned as unknown (nil) and that + // the edge info round-trips correctly. + dbEdge, e1, e2, err := graph.FetchChannelEdgesByID(chanID) require.NoError(t, err) require.Nil(t, e1) require.Nil(t, e2) + // Verify core fields match. + require.Equal(t, edgeInfo.ChannelID, dbEdge.ChannelID) + require.Equal(t, edgeInfo.Version, dbEdge.Version) + require.Equal(t, edgeInfo.NodeKey1Bytes, dbEdge.NodeKey1Bytes) + require.Equal(t, edgeInfo.NodeKey2Bytes, dbEdge.NodeKey2Bytes) + require.Equal(t, edgeInfo.ChainHash, dbEdge.ChainHash) + require.Equal(t, edgeInfo.ChannelPoint, dbEdge.ChannelPoint) + require.Equal(t, edgeInfo.Capacity, dbEdge.Capacity) + + // Verify auth proof round-trips. + require.NotNil(t, dbEdge.AuthProof) + require.Equal(t, edgeInfo.AuthProof.Version, dbEdge.AuthProof.Version) + + // Verify version-specific fields. + switch v { + case lnwire.GossipVersion1: + require.Equal(t, + edgeInfo.BitcoinKey1Bytes, dbEdge.BitcoinKey1Bytes, + ) + require.Equal(t, + edgeInfo.BitcoinKey2Bytes, dbEdge.BitcoinKey2Bytes, + ) + require.Equal(t, + edgeInfo.ExtraOpaqueData, dbEdge.ExtraOpaqueData, + ) + + case lnwire.GossipVersion2: + require.Equal(t, + edgeInfo.BitcoinKey1Bytes, dbEdge.BitcoinKey1Bytes, + ) + require.Equal(t, + edgeInfo.BitcoinKey2Bytes, dbEdge.BitcoinKey2Bytes, + ) + require.Equal(t, + edgeInfo.MerkleRootHash, dbEdge.MerkleRootHash, + ) + require.Equal(t, + edgeInfo.FundingScript, dbEdge.FundingScript, + ) + require.Equal(t, + edgeInfo.ExtraSignedFields, dbEdge.ExtraSignedFields, + ) + } + + // Also verify fetching by outpoint returns the same data. + dbEdge2, _, _, err := graph.FetchChannelEdgesByOutpoint(&outpoint) + require.NoError(t, err) + require.Equal(t, dbEdge.ChannelID, dbEdge2.ChannelID) + // Next, attempt to delete the edge from the database, again this // should proceed without any issues. require.NoError(t, graph.DeleteChannelEdges(false, true, chanID)) - assertNoEdge(t, graph, chanID) + assertNoEdge(t, graph.ChannelGraph, chanID) // Ensure that any query attempts to lookup the delete channel edge are // properly deleted. diff --git a/graph/db/interfaces.go b/graph/db/interfaces.go index c6848132e..24a48e4c7 100644 --- a/graph/db/interfaces.go +++ b/graph/db/interfaces.go @@ -314,7 +314,8 @@ type Store interface { //nolint:interfacebloat // IsZombieEdge returns whether the edge is considered zombie. If it is // a zombie, then the two node public keys corresponding to this edge // are also returned. - IsZombieEdge(chanID uint64) (bool, [33]byte, [33]byte, error) + IsZombieEdge(v lnwire.GossipVersion, chanID uint64) (bool, [33]byte, + [33]byte, error) // NumZombies returns the current number of zombie channels in the // graph. diff --git a/graph/db/kv_store.go b/graph/db/kv_store.go index 0887ff0d8..550ed3270 100644 --- a/graph/db/kv_store.go +++ b/graph/db/kv_store.go @@ -4257,14 +4257,19 @@ func (c *KVStore) markEdgeLiveUnsafe(tx kvdb.RwTx, chanID uint64) error { // IsZombieEdge returns whether the edge is considered zombie. If it is a // zombie, then the two node public keys corresponding to this edge are also // returned. -func (c *KVStore) IsZombieEdge(chanID uint64) (bool, [33]byte, [33]byte, - error) { +func (c *KVStore) IsZombieEdge(v lnwire.GossipVersion, + chanID uint64) (bool, [33]byte, [33]byte, error) { var ( isZombie bool pubKey1, pubKey2 [33]byte ) + if v != lnwire.GossipVersion1 { + return false, [33]byte{}, [33]byte{}, + ErrVersionNotSupportedForKVDB + } + err := kvdb.View(c.db, func(tx kvdb.RTx) error { edges := tx.ReadBucket(edgeBucket) if edges == nil { diff --git a/graph/db/sql_store.go b/graph/db/sql_store.go index 7cebedeef..c55110338 100644 --- a/graph/db/sql_store.go +++ b/graph/db/sql_store.go @@ -1824,8 +1824,8 @@ func (s *SQLStore) MarkEdgeLive(chanID uint64) error { // returned. // // NOTE: part of the Store interface. -func (s *SQLStore) IsZombieEdge(chanID uint64) (bool, [33]byte, [33]byte, - error) { +func (s *SQLStore) IsZombieEdge(v lnwire.GossipVersion, + chanID uint64) (bool, [33]byte, [33]byte, error) { var ( ctx = context.TODO() @@ -1834,11 +1834,16 @@ func (s *SQLStore) IsZombieEdge(chanID uint64) (bool, [33]byte, [33]byte, chanIDB = channelIDToBytes(chanID) ) + if !isKnownGossipVersion(v) { + return false, [33]byte{}, [33]byte{}, + fmt.Errorf("unsupported gossip version: %d", v) + } + err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error { zombie, err := db.GetZombieChannel( ctx, sqlc.GetZombieChannelParams{ Scid: chanIDB, - Version: int16(lnwire.GossipVersion1), + Version: int16(v), }, ) if errors.Is(err, sql.ErrNoRows) {