multi: add version to models.ChannelEdgeInfo
What changed, and why it matters
This commit adds a version field to Lightning Network channel records and makes the code reject version 2 channels for now. It is a forward-looking structural change, not a fix for an active security bug. The new validation prevents the node from accidentally storing or relaying channel announcements it does not yet understand, which reduces the risk of future misbehavior but does not by itself stop any known current attack.
Treat as a routine protocol-evolution commit. Review follow-up work that will implement v2 channel announcement parsing and validation, since the current code simply rejects v2 edges. No urgent action is required unless this commit is part of a larger undisclosed security release.
Security signals we found
New input-validation gate added: KV and SQL stores reject non-V1 channel edges
Structural data-model change to support future gossip protocol versions
No demonstrated vulnerability or bug fixed in the diff
No CVE, advisory, or researcher attribution present in commit or references
Evidence from the diff
The patch introduces a Version field (lnwire.GossipVersion) to models.ChannelEdgeInfo and sets it to GossipVersion1 everywhere existing channel data is constructed. Both the KV and SQL graph stores now reject any ChannelEdgeInfo whose Version is not GossipVersion1. The KV deserializer hard-codes V1 because all previously persisted KV records are V1. This is preparatory work for distinguishing v1 and v2 channel announcements; it does not implement v2 handling or fix a demonstrated vulnerability in v1 handling.
Changed components
graph/db/models/channel_edge_info.gograph/db/kv_store.gograph/db/sql_store.godiscovery/gossiper.gorouting/localchans/manager.golnrpc/devrpc/dev_server.goInspect captured patch +64 / −1
diff --git a/autopilot/prefattach_test.go b/autopilot/prefattach_test.go
index 5439f02..a4026d7 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 ee83d50..9d0338c 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 efcedb0..efb7307 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 ca57a44..b6fe886 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 767958d..04f6d03 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 33d1b2b..7c4255b 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 85b93ca..7407278 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 cfa7e1e..4c783a6 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 a1592ea..aeeeb37 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 e3f4871..e220a0b 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 08f01e1..31db5fe 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 9394ce2..ceab2be 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 024b779..1ec7cea 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 38949e0..49f61a5 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 1a7c1d5..f47e34a 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 5df344b..108fe0a 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 132d61f..b7256e3 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 cb4393c..bb0b2f8 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,
Why this scored 19/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.