From 4480352d599b2ca33df02f93356903a3965db113 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 19 Nov 2025 09:40:23 +0200 Subject: [PATCH] multi: add version to models.ChannelEdgeInfo And set it to V1 version everywhere. Add a Version field to ChannelEdgeInfo to distinguish between v1 and v2 channel announcements. Set it to GossipVersion1 for all existing channels. Both KV and SQL stores now validate that only v1 channels are currently supported, returning an error for v2 channels. The KV store automatically sets version to v1 when deserializing (since all persisted channels in KV format are v1). This versioning is essential for handling the different field requirements and validation logic between v1 and v2 channels. --- autopilot/prefattach_test.go | 1 + discovery/gossiper.go | 1 + discovery/gossiper_test.go | 1 + graph/builder_test.go | 12 ++++++++++++ graph/db/channel_cache_test.go | 2 ++ graph/db/graph_test.go | 5 +++++ graph/db/kv_store.go | 10 ++++++++++ graph/db/models/channel_edge_info.go | 3 +++ graph/db/sql_store.go | 11 ++++++++++- graph/notifications_test.go | 4 ++++ lnrpc/devrpc/dev_server.go | 1 + lnrpc/invoicesrpc/addinvoice_test.go | 1 + netann/chan_status_manager_test.go | 1 + netann/channel_announcement_test.go | 1 + routing/localchans/manager.go | 1 + routing/localchans/manager_test.go | 6 ++++++ routing/pathfind_test.go | 2 ++ routing/router_test.go | 2 ++ 18 files changed, 64 insertions(+), 1 deletion(-) diff --git a/autopilot/prefattach_test.go b/autopilot/prefattach_test.go index 5439f02aa..a4026d73d 100644 --- a/autopilot/prefattach_test.go +++ b/autopilot/prefattach_test.go @@ -493,6 +493,7 @@ func (d *testDBGraph) addRandChannel(node1, node2 *btcec.PublicKey, chanID := randChanID() edge := &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: chanID.ToUint64(), Capacity: capacity, Features: lnwire.EmptyFeatureVector(), diff --git a/discovery/gossiper.go b/discovery/gossiper.go index ee83d5048..9d0338c22 100644 --- a/discovery/gossiper.go +++ b/discovery/gossiper.go @@ -2819,6 +2819,7 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(ctx context.Context, // With the proof validated (if necessary), we can now store it within // the database for our path finding and syncing needs. edge := &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: scid.ToUint64(), ChainHash: ann.ChainHash, NodeKey1Bytes: ann.NodeID1, diff --git a/discovery/gossiper_test.go b/discovery/gossiper_test.go index efcedb0e9..efb7307da 100644 --- a/discovery/gossiper_test.go +++ b/discovery/gossiper_test.go @@ -273,6 +273,7 @@ func (r *mockGraphSource) GetChannelByID(chanID lnwire.ShortChannelID) ( } return &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, NodeKey1Bytes: pubKeys[0], NodeKey2Bytes: pubKeys[1], }, nil, nil, graphdb.ErrZombieEdge diff --git a/graph/builder_test.go b/graph/builder_test.go index ca57a44c9..b6fe88675 100644 --- a/graph/builder_test.go +++ b/graph/builder_test.go @@ -66,6 +66,7 @@ func TestAddProof(t *testing.T) { // After utxo was recreated adding the edge without the proof. edge := &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: chanID.ToUint64(), NodeKey1Bytes: node1.PubKeyBytes, NodeKey2Bytes: node2.PubKeyBytes, @@ -152,6 +153,7 @@ func TestIgnoreChannelEdgePolicyForUnknownChannel(t *testing.T) { ctx.chain.addBlock(fundingBlock, chanID.BlockHeight, chanID.BlockHeight) edge := &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: chanID.ToUint64(), NodeKey1Bytes: pub1, NodeKey2Bytes: pub2, @@ -273,6 +275,7 @@ func TestWakeUpOnStaleBranch(t *testing.T) { node2 := createTestNode(t) edge1 := &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: chanID1, NodeKey1Bytes: node1.PubKeyBytes, NodeKey2Bytes: node2.PubKeyBytes, @@ -293,6 +296,7 @@ func TestWakeUpOnStaleBranch(t *testing.T) { } edge2 := &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: chanID2, NodeKey1Bytes: node1.PubKeyBytes, NodeKey2Bytes: node2.PubKeyBytes, @@ -483,6 +487,7 @@ func TestDisconnectedBlocks(t *testing.T) { node2 := createTestNode(t) edge1 := &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: chanID1, NodeKey1Bytes: node1.PubKeyBytes, NodeKey2Bytes: node2.PubKeyBytes, @@ -505,6 +510,7 @@ func TestDisconnectedBlocks(t *testing.T) { } edge2 := &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: chanID2, NodeKey1Bytes: node1.PubKeyBytes, NodeKey2Bytes: node2.PubKeyBytes, @@ -639,6 +645,7 @@ func TestChansClosedOfflinePruneGraph(t *testing.T) { node2 := createTestNode(t) edge1 := &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: chanID1.ToUint64(), NodeKey1Bytes: node1.PubKeyBytes, NodeKey2Bytes: node2.PubKeyBytes, @@ -1062,6 +1069,7 @@ func TestIsStaleNode(t *testing.T) { ctx.chain.addBlock(fundingBlock, chanID.BlockHeight, chanID.BlockHeight) edge := &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: chanID.ToUint64(), NodeKey1Bytes: pub1, NodeKey2Bytes: pub2, @@ -1142,6 +1150,7 @@ func TestIsKnownEdge(t *testing.T) { ctx.chain.addBlock(fundingBlock, chanID.BlockHeight, chanID.BlockHeight) edge := &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: chanID.ToUint64(), NodeKey1Bytes: pub1, NodeKey2Bytes: pub2, @@ -1202,6 +1211,7 @@ func TestIsStaleEdgePolicy(t *testing.T) { } edge := &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: chanID.ToUint64(), NodeKey1Bytes: pub1, NodeKey2Bytes: pub2, @@ -1514,6 +1524,7 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( // We first insert the existence of the edge between the two // nodes. edgeInfo := models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: edge.ChannelID, AuthProof: &testAuthProof, ChannelPoint: fundingPoint, @@ -1885,6 +1896,7 @@ func createTestGraphFromChannels(t *testing.T, useCache bool, // We first insert the existence of the edge between the two // nodes. edgeInfo := models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: channelID, AuthProof: &testAuthProof, ChannelPoint: *fundingPoint, diff --git a/graph/db/channel_cache_test.go b/graph/db/channel_cache_test.go index 767958d9a..04f6d0331 100644 --- a/graph/db/channel_cache_test.go +++ b/graph/db/channel_cache_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/lightningnetwork/lnd/graph/db/models" + "github.com/lightningnetwork/lnd/lnwire" ) // TestChannelCache checks the behavior of the channelCache with respect to @@ -101,6 +102,7 @@ func assertHasChanEntries(t *testing.T, c *channelCache, start, end uint64) { func channelForInt(i uint64) ChannelEdge { return ChannelEdge{ Info: &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: i, }, } diff --git a/graph/db/graph_test.go b/graph/db/graph_test.go index 33d1b2bbe..7c4255bcb 100644 --- a/graph/db/graph_test.go +++ b/graph/db/graph_test.go @@ -563,6 +563,7 @@ func TestEdgeInsertionDeletion(t *testing.T) { node2Pub, err := node2.PubKey() require.NoError(t, err, "unable to generate node key") edgeInfo := models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: chanID, ChainHash: *chaincfg.MainNetParams.GenesisHash, AuthProof: &models.ChannelAuthProof{ @@ -638,6 +639,7 @@ func createEdge(height, txIndex uint32, txPosition uint16, outPointIndex uint32, node1Pub, _ := node1.PubKey() node2Pub, _ := node2.PubKey() edgeInfo := models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: shortChanID.ToUint64(), ChainHash: *chaincfg.MainNetParams.GenesisHash, AuthProof: &models.ChannelAuthProof{ @@ -894,6 +896,7 @@ func createChannelEdge(node1, node2 *models.Node, // Add the new edge to the database, this should proceed without any // errors. edgeInfo := &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: chanID, ChainHash: *chaincfg.MainNetParams.GenesisHash, ChannelPoint: outpoint, @@ -1775,6 +1778,7 @@ func fillTestGraph(t testing.TB, graph *ChannelGraph, numNodes, } edgeInfo := models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: chanID, ChainHash: *chaincfg.MainNetParams.GenesisHash, AuthProof: &models.ChannelAuthProof{ @@ -1957,6 +1961,7 @@ func TestGraphPruning(t *testing.T) { channelPoints = append(channelPoints, &op) edgeInfo := models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: chanID, ChainHash: *chaincfg.MainNetParams.GenesisHash, AuthProof: &models.ChannelAuthProof{ diff --git a/graph/db/kv_store.go b/graph/db/kv_store.go index 85b93caed..74072781b 100644 --- a/graph/db/kv_store.go +++ b/graph/db/kv_store.go @@ -3945,6 +3945,7 @@ func (c *KVStore) FetchChannelEdgesByID(chanID uint64) ( // party as this is the only information we have about // it and return an error signaling so. edgeInfo = &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, NodeKey1Bytes: pubKey1, NodeKey2Bytes: pubKey2, } @@ -4704,6 +4705,12 @@ func deserializeLightningNode(r io.Reader) (*models.Node, error) { func putChanEdgeInfo(edgeIndex kvdb.RwBucket, edgeInfo *models.ChannelEdgeInfo, chanID [8]byte) error { + // We only support V1 channel edges in the KV store. + if edgeInfo.Version != lnwire.GossipVersion1 { + return fmt.Errorf("only V1 channel edges supported, got V%d", + edgeInfo.Version) + } + var b bytes.Buffer if _, err := b.Write(edgeInfo.NodeKey1Bytes[:]); err != nil { @@ -4866,6 +4873,9 @@ func deserializeChanEdgeInfo(r io.Reader) (*models.ChannelEdgeInfo, error) { edgeInfo models.ChannelEdgeInfo ) + // All channel edges in the KV store are V1. + edgeInfo.Version = lnwire.GossipVersion1 + if _, err := io.ReadFull(r, edgeInfo.NodeKey1Bytes[:]); err != nil { return nil, err } diff --git a/graph/db/models/channel_edge_info.go b/graph/db/models/channel_edge_info.go index cfa7e1e6a..4c783a6a9 100644 --- a/graph/db/models/channel_edge_info.go +++ b/graph/db/models/channel_edge_info.go @@ -20,6 +20,9 @@ import ( // policy of a channel are stored within a ChannelEdgePolicy for each direction // of the channel. type ChannelEdgeInfo struct { + // Version is the gossip version that this channel was advertised on. + Version lnwire.GossipVersion + // ChannelID is the unique channel ID for the channel. The first 3 // bytes are the block height, the next 3 the index within the block, // and the last 2 bytes are the output index for the channel. diff --git a/graph/db/sql_store.go b/graph/db/sql_store.go index a1592eacf..aeeeb378d 100644 --- a/graph/db/sql_store.go +++ b/graph/db/sql_store.go @@ -2038,7 +2038,9 @@ func (s *SQLStore) FetchChannelEdgesByID(chanID uint64) ( // populate the edge info with the public keys of each // party as this is the only information we have about // it. - edge = &models.ChannelEdgeInfo{} + edge = &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, + } copy(edge.NodeKey1Bytes[:], zombie.NodeKey1) copy(edge.NodeKey2Bytes[:], zombie.NodeKey2) @@ -4241,6 +4243,12 @@ func insertChannel(ctx context.Context, db SQLQueries, v := lnwire.GossipVersion1 + // For now, we only support V1 channel edges in the SQL store. + if edge.Version != v { + return fmt.Errorf("only V1 channel edges supported, got V%d", + edge.Version) + } + // Make sure that at least a "shell" entry for each node is present in // the nodes table. node1DBID, err := maybeCreateShellNode( @@ -4458,6 +4466,7 @@ func buildEdgeInfoWithBatchData(chain chainhash.Hash, copy(btcKey2[:], dbChan.BitcoinKey2) channel := &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChainHash: chain, ChannelID: byteOrder.Uint64(dbChan.Scid), NodeKey1Bytes: node1, diff --git a/graph/notifications_test.go b/graph/notifications_test.go index e3f48712e..e220a0b44 100644 --- a/graph/notifications_test.go +++ b/graph/notifications_test.go @@ -448,6 +448,7 @@ func TestEdgeUpdateNotification(t *testing.T) { // Finally, to conclude our test set up, we'll create a channel // update to announce the created channel between the two nodes. edge := &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: chanID.ToUint64(), NodeKey1Bytes: node1.PubKeyBytes, NodeKey2Bytes: node2.PubKeyBytes, @@ -642,6 +643,7 @@ func TestNodeUpdateNotification(t *testing.T) { require.NoError(t, testFeatures.Encode(testFeaturesBuf)) edge := &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: chanID.ToUint64(), NodeKey1Bytes: node1.PubKeyBytes, NodeKey2Bytes: node2.PubKeyBytes, @@ -828,6 +830,7 @@ func TestNotificationCancellation(t *testing.T) { ntfnClient.Cancel() edge := &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: chanID.ToUint64(), NodeKey1Bytes: node1.PubKeyBytes, NodeKey2Bytes: node2.PubKeyBytes, @@ -904,6 +907,7 @@ func TestChannelCloseNotification(t *testing.T) { // Finally, to conclude our test set up, we'll create a channel // announcement to announce the created channel between the two nodes. edge := &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: chanID.ToUint64(), NodeKey1Bytes: node1.PubKeyBytes, NodeKey2Bytes: node2.PubKeyBytes, diff --git a/lnrpc/devrpc/dev_server.go b/lnrpc/devrpc/dev_server.go index 08f01e147..31db5fe0a 100644 --- a/lnrpc/devrpc/dev_server.go +++ b/lnrpc/devrpc/dev_server.go @@ -274,6 +274,7 @@ func (s *Server) ImportGraph(ctx context.Context, rpcEdge := rpcEdge edge := &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: rpcEdge.ChannelId, ChainHash: *s.cfg.ActiveNetParams.GenesisHash, Capacity: btcutil.Amount(rpcEdge.Capacity), diff --git a/lnrpc/invoicesrpc/addinvoice_test.go b/lnrpc/invoicesrpc/addinvoice_test.go index 9394ce26d..ceab2bec4 100644 --- a/lnrpc/invoicesrpc/addinvoice_test.go +++ b/lnrpc/invoicesrpc/addinvoice_test.go @@ -306,6 +306,7 @@ var shouldIncludeChannelTestCases = []struct { "FetchChannelEdgesByID", mock.Anything, ).Once().Return( &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, NodeKey1Bytes: selectedPolicy, }, &models.ChannelEdgePolicy{ diff --git a/netann/chan_status_manager_test.go b/netann/chan_status_manager_test.go index 024b77932..1ec7cea23 100644 --- a/netann/chan_status_manager_test.go +++ b/netann/chan_status_manager_test.go @@ -101,6 +101,7 @@ func createEdgePolicies(t *testing.T, channel *channeldb.OpenChannel, dir2 |= lnwire.ChanUpdateDirection return &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelPoint: channel.FundingOutpoint, NodeKey1Bytes: pubkey1, NodeKey2Bytes: pubkey2, diff --git a/netann/channel_announcement_test.go b/netann/channel_announcement_test.go index 38949e046..49f61a57f 100644 --- a/netann/channel_announcement_test.go +++ b/netann/channel_announcement_test.go @@ -47,6 +47,7 @@ func TestCreateChanAnnouncement(t *testing.T) { BitcoinSig2Bytes: expChanAnn.BitcoinSig2.ToSignatureBytes(), } chanInfo := &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChainHash: expChanAnn.ChainHash, ChannelID: expChanAnn.ShortChannelID.ToUint64(), ChannelPoint: wire.OutPoint{Index: 1}, diff --git a/routing/localchans/manager.go b/routing/localchans/manager.go index 1a7c1d585..f47e34a16 100644 --- a/routing/localchans/manager.go +++ b/routing/localchans/manager.go @@ -329,6 +329,7 @@ func (r *Manager) createEdge(channel *channeldb.OpenChannel, } info := &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: shortChanID.ToUint64(), ChainHash: channel.ChainHash, Features: lnwire.EmptyFeatureVector(), diff --git a/routing/localchans/manager_test.go b/routing/localchans/manager_test.go index 5df344bba..108fe0a7a 100644 --- a/routing/localchans/manager_test.go +++ b/routing/localchans/manager_test.go @@ -212,6 +212,7 @@ func TestManager(t *testing.T) { channelSet: []channel{ { edgeInfo: &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, Capacity: chanCap, ChannelPoint: chanPointValid, }, @@ -230,6 +231,7 @@ func TestManager(t *testing.T) { channelSet: []channel{ { edgeInfo: &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, Capacity: chanCap, ChannelPoint: chanPointValid, }, @@ -248,6 +250,7 @@ func TestManager(t *testing.T) { channelSet: []channel{ { edgeInfo: &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, Capacity: chanCap, ChannelPoint: chanPointValid, }, @@ -270,6 +273,7 @@ func TestManager(t *testing.T) { channelSet: []channel{ { edgeInfo: &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, Capacity: chanCap, ChannelPoint: chanPointValid, }, @@ -392,6 +396,7 @@ func TestCreateEdgeLower(t *testing.T) { require.NoError(t, err) expectedInfo := &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: 8, ChainHash: channel.ChainHash, Features: lnwire.EmptyFeatureVector(), @@ -485,6 +490,7 @@ func TestCreateEdgeHigher(t *testing.T) { require.NoError(t, err) expectedInfo := &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: 8, ChainHash: channel.ChainHash, Features: lnwire.EmptyFeatureVector(), diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index 132d61fb8..b7256e3a8 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -346,6 +346,7 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( // We first insert the existence of the edge between the two // nodes. edgeInfo := models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: edge.ChannelID, AuthProof: &testAuthProof, ChannelPoint: fundingPoint, @@ -678,6 +679,7 @@ func createTestGraphFromChannels(t *testing.T, useCache bool, // We first insert the existence of the edge between the two // nodes. edgeInfo := models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: channelID, AuthProof: &testAuthProof, ChannelPoint: *fundingPoint, diff --git a/routing/router_test.go b/routing/router_test.go index cb4393c06..bb0b2f85c 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -2739,6 +2739,7 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { require.NoError(t, err, "unable to create channel edge") edge := &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: chanID.ToUint64(), NodeKey1Bytes: pub1, NodeKey2Bytes: pub2, @@ -2819,6 +2820,7 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { require.NoError(t, err, "unable to create channel edge") edge = &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: chanID.ToUint64(), Features: lnwire.EmptyFeatureVector(), AuthProof: nil,