From 29e3dad1ab00a70a8a8edbdeaae5d2950ad86b6f Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 19 Nov 2025 07:50:55 +0200 Subject: [PATCH 01/11] graph/db: remove unused sig field from ChannelEdgePolicy Remove the cached parsed signature field and its lazy getter method from ChannelEdgePolicy. This field was unused throughout the codebase and the signature is already stored as raw bytes in SigBytes. The SetSigBytes method is updated to remove the cache invalidation logic. --- graph/db/models/channel_edge_policy.go | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/graph/db/models/channel_edge_policy.go b/graph/db/models/channel_edge_policy.go index 48d748ee0ab..b47707f20f4 100644 --- a/graph/db/models/channel_edge_policy.go +++ b/graph/db/models/channel_edge_policy.go @@ -4,7 +4,6 @@ import ( "fmt" "time" - "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lnwire" ) @@ -21,9 +20,6 @@ type ChannelEdgePolicy struct { // use SetSigBytes instead to make sure that the cache is invalidated. SigBytes []byte - // sig is a cached fully parsed signature. - sig *ecdsa.Signature - // 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. @@ -84,31 +80,10 @@ type ChannelEdgePolicy struct { ExtraOpaqueData lnwire.ExtraOpaqueData } -// Signature is a channel announcement signature, which is needed for proper -// edge policy announcement. -// -// NOTE: By having this method to access an attribute, we ensure we only need -// to fully deserialize the signature if absolutely necessary. -func (c *ChannelEdgePolicy) Signature() (*ecdsa.Signature, error) { - if c.sig != nil { - return c.sig, nil - } - - sig, err := ecdsa.ParseSignature(c.SigBytes) - if err != nil { - return nil, err - } - - c.sig = sig - - return sig, nil -} - // SetSigBytes updates the signature and invalidates the cached parsed // signature. func (c *ChannelEdgePolicy) SetSigBytes(sig []byte) { c.SigBytes = sig - c.sig = nil } // IsDisabled determines whether the edge has the disabled bit set. From 4eb4ee123612f253fc7e1a988772543df2ea6465 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 19 Nov 2025 08:07:17 +0200 Subject: [PATCH 02/11] graph/db: use route.Vertex for channel keys Replace [33]byte with route.Vertex for NodeKey1Bytes, NodeKey2Bytes, BitcoinKey1Bytes, and BitcoinKey2Bytes in ChannelEdgeInfo. Since route.Vertex is defined as [33]byte, this change is functionally equivalent but provides better type safety and consistency with the rest of the routing subsystem. OtherNodeKeyBytes is also updated to return route.Vertex. --- graph/db/kv_store.go | 6 +++--- graph/db/models/channel_edge_info.go | 13 +++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/graph/db/kv_store.go b/graph/db/kv_store.go index ccc67978589..981c3b2b9eb 100644 --- a/graph/db/kv_store.go +++ b/graph/db/kv_store.go @@ -1262,9 +1262,9 @@ func (c *KVStore) addChannelEdge(tx kvdb.RwTx, // Mark edge policies for both sides as unknown. This is to enable // efficient incoming channel lookup for a node. - keys := []*[33]byte{ - &edge.NodeKey1Bytes, - &edge.NodeKey2Bytes, + keys := []route.Vertex{ + edge.NodeKey1Bytes, + edge.NodeKey2Bytes, } for _, key := range keys { err := putChanEdgePolicyUnknown(edges, edge.ChannelID, key[:]) diff --git a/graph/db/models/channel_edge_info.go b/graph/db/models/channel_edge_info.go index b86c140bc1d..b5bb6b42b4e 100644 --- a/graph/db/models/channel_edge_info.go +++ b/graph/db/models/channel_edge_info.go @@ -10,6 +10,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" ) // ChannelEdgeInfo represents a fully authenticated channel along with all its @@ -29,18 +30,18 @@ type ChannelEdgeInfo struct { ChainHash chainhash.Hash // NodeKey1Bytes is the raw public key of the first node. - NodeKey1Bytes [33]byte + NodeKey1Bytes route.Vertex nodeKey1 *btcec.PublicKey // NodeKey2Bytes is the raw public key of the first node. - NodeKey2Bytes [33]byte + NodeKey2Bytes route.Vertex nodeKey2 *btcec.PublicKey // BitcoinKey1Bytes is the raw public key of the first node. - BitcoinKey1Bytes [33]byte + BitcoinKey1Bytes route.Vertex // BitcoinKey2Bytes is the raw public key of the first node. - BitcoinKey2Bytes [33]byte + BitcoinKey2Bytes route.Vertex // Features is the list of protocol features supported by this channel // edge. @@ -118,7 +119,7 @@ func (c *ChannelEdgeInfo) NodeKey2() (*btcec.PublicKey, error) { // OtherNodeKeyBytes returns the node key bytes of the other end of the channel. func (c *ChannelEdgeInfo) OtherNodeKeyBytes(thisNodeKey []byte) ( - [33]byte, error) { + route.Vertex, error) { switch { case bytes.Equal(c.NodeKey1Bytes[:], thisNodeKey): @@ -126,7 +127,7 @@ func (c *ChannelEdgeInfo) OtherNodeKeyBytes(thisNodeKey []byte) ( case bytes.Equal(c.NodeKey2Bytes[:], thisNodeKey): return c.NodeKey1Bytes, nil default: - return [33]byte{}, fmt.Errorf("node not participating in " + + return route.Vertex{}, fmt.Errorf("node not participating in " + "this channel") } } From fed4e9fce86ebec2a43550168c20cc5d79ec5816 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 19 Nov 2025 08:18:21 +0200 Subject: [PATCH 03/11] graph/db: expose version in maybeCreateShellNode Add a version parameter to maybeCreateShellNode to allow callers to specify which gossip protocol version should be used when creating shell nodes. Currently all callers pass GossipVersion1, but this change sets up the foundation for v2 support. Shell nodes are lightweight node entries containing only a protocol version and public key, created before full node information is available. --- graph/db/sql_store.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/graph/db/sql_store.go b/graph/db/sql_store.go index b30f0a944c7..2e55d5fc78d 100644 --- a/graph/db/sql_store.go +++ b/graph/db/sql_store.go @@ -4112,14 +4112,20 @@ func marshalExtraOpaqueData(data []byte) (map[uint64][]byte, error) { func insertChannel(ctx context.Context, db SQLQueries, edge *models.ChannelEdgeInfo) error { + v := lnwire.GossipVersion1 + // Make sure that at least a "shell" entry for each node is present in // the nodes table. - node1DBID, err := maybeCreateShellNode(ctx, db, edge.NodeKey1Bytes) + node1DBID, err := maybeCreateShellNode( + ctx, db, v, edge.NodeKey1Bytes, + ) if err != nil { return fmt.Errorf("unable to create shell node: %w", err) } - node2DBID, err := maybeCreateShellNode(ctx, db, edge.NodeKey2Bytes) + node2DBID, err := maybeCreateShellNode( + ctx, db, v, edge.NodeKey2Bytes, + ) if err != nil { return fmt.Errorf("unable to create shell node: %w", err) } @@ -4130,7 +4136,7 @@ func insertChannel(ctx context.Context, db SQLQueries, } createParams := sqlc.CreateChannelParams{ - Version: int16(lnwire.GossipVersion1), + Version: int16(v), Scid: channelIDToBytes(edge.ChannelID), NodeID1: node1DBID, NodeID2: node2DBID, @@ -4199,12 +4205,12 @@ func insertChannel(ctx context.Context, db SQLQueries, // created. The ID of the node is returned. A shell node only has a protocol // version and public key persisted. func maybeCreateShellNode(ctx context.Context, db SQLQueries, - pubKey route.Vertex) (int64, error) { + v lnwire.GossipVersion, pubKey route.Vertex) (int64, error) { dbNode, err := db.GetNodeByPubKey( ctx, sqlc.GetNodeByPubKeyParams{ PubKey: pubKey[:], - Version: int16(lnwire.GossipVersion1), + Version: int16(v), }, ) // The node exists. Return the ID. @@ -4217,7 +4223,7 @@ func maybeCreateShellNode(ctx context.Context, db SQLQueries, // Otherwise, the node does not exist, so we create a shell entry for // it. id, err := db.UpsertNode(ctx, sqlc.UpsertNodeParams{ - Version: int16(lnwire.GossipVersion1), + Version: int16(v), PubKey: pubKey[:], }) if err != nil { From f3d892a6258d40a6a4c1d07338429934b3bc3b29 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 19 Nov 2025 08:21:11 +0200 Subject: [PATCH 04/11] sqldb/sqlc: update graph CreateChannel query for v2 Add three new optional fields to the CreateChannel SQL query to support v2 channel announcements: - signature: single schnorr signature (replaces four ECDSA sigs) - funding_pk_script: the funding output script - merkle_root_hash: for taproot channels These fields are NULL for v1 channels and populated for v2 channels. --- sqldb/sqlc/graph.sql.go | 10 ++++++++-- sqldb/sqlc/queries/graph.sql | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/sqldb/sqlc/graph.sql.go b/sqldb/sqlc/graph.sql.go index 26d03c96aa4..4342eeba980 100644 --- a/sqldb/sqlc/graph.sql.go +++ b/sqldb/sqlc/graph.sql.go @@ -78,9 +78,9 @@ INSERT INTO graph_channels ( version, scid, node_id_1, node_id_2, outpoint, capacity, bitcoin_key_1, bitcoin_key_2, node_1_signature, node_2_signature, bitcoin_1_signature, - bitcoin_2_signature + bitcoin_2_signature, signature, funding_pk_script, merkle_root_hash ) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12 + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15 ) RETURNING id ` @@ -98,6 +98,9 @@ type CreateChannelParams struct { Node2Signature []byte Bitcoin1Signature []byte Bitcoin2Signature []byte + Signature []byte + FundingPkScript []byte + MerkleRootHash []byte } func (q *Queries) CreateChannel(ctx context.Context, arg CreateChannelParams) (int64, error) { @@ -114,6 +117,9 @@ func (q *Queries) CreateChannel(ctx context.Context, arg CreateChannelParams) (i arg.Node2Signature, arg.Bitcoin1Signature, arg.Bitcoin2Signature, + arg.Signature, + arg.FundingPkScript, + arg.MerkleRootHash, ) var id int64 err := row.Scan(&id) diff --git a/sqldb/sqlc/queries/graph.sql b/sqldb/sqlc/queries/graph.sql index b1e3523d86e..4f02f1775c5 100644 --- a/sqldb/sqlc/queries/graph.sql +++ b/sqldb/sqlc/queries/graph.sql @@ -266,9 +266,9 @@ INSERT INTO graph_channels ( version, scid, node_id_1, node_id_2, outpoint, capacity, bitcoin_key_1, bitcoin_key_2, node_1_signature, node_2_signature, bitcoin_1_signature, - bitcoin_2_signature + bitcoin_2_signature, signature, funding_pk_script, merkle_root_hash ) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12 + $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15 ) RETURNING id; From 3b6fc84133308767a1aec5871af9a90ce6bb580c Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 19 Nov 2025 09:40:23 +0200 Subject: [PATCH 05/11] 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 5439f02aa75..a4026d73d00 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 96a8c047ae3..e3b45cd2b85 100644 --- a/discovery/gossiper.go +++ b/discovery/gossiper.go @@ -2703,6 +2703,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 01faf5561c4..1c2b8093e46 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 ca57a44c9b2..b6fe88675ac 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 767958d9a15..04f6d03317c 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 bfe9c7bddd9..978458cc00b 100644 --- a/graph/db/graph_test.go +++ b/graph/db/graph_test.go @@ -506,6 +506,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{ @@ -581,6 +582,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{ @@ -837,6 +839,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, @@ -1716,6 +1719,7 @@ func fillTestGraph(t testing.TB, graph *ChannelGraph, numNodes, } edgeInfo := models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: chanID, ChainHash: *chaincfg.MainNetParams.GenesisHash, AuthProof: &models.ChannelAuthProof{ @@ -1898,6 +1902,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 981c3b2b9eb..602b9b304c7 100644 --- a/graph/db/kv_store.go +++ b/graph/db/kv_store.go @@ -3943,6 +3943,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, } @@ -4702,6 +4703,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 { @@ -4792,6 +4799,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 b5bb6b42b4e..58765b5d5e6 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 2e55d5fc78d..dc7fa0fe4d4 100644 --- a/graph/db/sql_store.go +++ b/graph/db/sql_store.go @@ -2022,7 +2022,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) @@ -4114,6 +4116,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( @@ -4331,6 +4339,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 e3f48712e2d..e220a0b445f 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 08f01e14704..31db5fe0a0a 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 9394ce26d4a..ceab2bec4be 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 024b779322b..1ec7cea23fa 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 38949e046e4..49f61a57ff2 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 b1d281187d1..027eee42851 100644 --- a/routing/localchans/manager.go +++ b/routing/localchans/manager.go @@ -322,6 +322,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 a2e7164b20b..18b1684b2fc 100644 --- a/routing/localchans/manager_test.go +++ b/routing/localchans/manager_test.go @@ -210,6 +210,7 @@ func TestManager(t *testing.T) { channelSet: []channel{ { edgeInfo: &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, Capacity: chanCap, ChannelPoint: chanPointValid, }, @@ -228,6 +229,7 @@ func TestManager(t *testing.T) { channelSet: []channel{ { edgeInfo: &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, Capacity: chanCap, ChannelPoint: chanPointValid, }, @@ -246,6 +248,7 @@ func TestManager(t *testing.T) { channelSet: []channel{ { edgeInfo: &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, Capacity: chanCap, ChannelPoint: chanPointValid, }, @@ -268,6 +271,7 @@ func TestManager(t *testing.T) { channelSet: []channel{ { edgeInfo: &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, Capacity: chanCap, ChannelPoint: chanPointValid, }, @@ -386,6 +390,7 @@ func TestCreateEdgeLower(t *testing.T) { }, } expectedInfo := &models.ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, ChannelID: 8, ChainHash: channel.ChainHash, Features: lnwire.EmptyFeatureVector(), @@ -474,6 +479,7 @@ func TestCreateEdgeHigher(t *testing.T) { }, } 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 132d61fb873..b7256e3a80e 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 cb4393c06ed..bb0b2f85c29 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, From d966c28a93837cf356dcfe97b09ef8a7c3e55898 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 19 Nov 2025 10:19:40 +0200 Subject: [PATCH 06/11] multi: update models.ChannelAuthProof with v2 field Also update it to more closely match the persisted version which has the v1 and v2 only fields as optional. Refactor ChannelAuthProof to support both v1 and v2 channel announcements: - Add Version field to distinguish v1 from v2 proofs - Wrap v1-specific fields (NodeSig1/2, BitcoinSig1/2) in fn.Option since v2 doesn't use them - Add optional Signature field for v2's single schnorr signature - Add constructor functions NewV1ChannelAuthProof and NewV2ChannelAuthProof to enforce correct initialization - Add getter methods (NodeSig1(), BitcoinSig1(), etc.) that safely unwrap options, returning empty slices when not present The IsEmpty() check is updated to handle both versions correctly. Both stores validate v1-only for now. --- discovery/gossiper.go | 46 ++++++----- graph/builder_test.go | 60 +++++++-------- graph/db/graph_test.go | 85 +++++++++++---------- graph/db/kv_store.go | 42 +++++++--- graph/db/models/channel_auth_proof.go | 106 +++++++++++++++++++++++--- graph/db/sql_store.go | 36 +++++---- graph/notifications_test.go | 60 +++++++-------- netann/channel_announcement.go | 8 +- netann/channel_announcement_test.go | 12 +-- routing/pathfind_test.go | 12 +-- rpcserver.go | 8 +- 11 files changed, 300 insertions(+), 175 deletions(-) diff --git a/discovery/gossiper.go b/discovery/gossiper.go index e3b45cd2b85..5a850654f87 100644 --- a/discovery/gossiper.go +++ b/discovery/gossiper.go @@ -2404,25 +2404,25 @@ func (d *AuthenticatedGossiper) updateChannel(ctx context.Context, ExtraOpaqueData: info.ExtraOpaqueData, } chanAnn.NodeSig1, err = lnwire.NewSigFromECDSARawSignature( - info.AuthProof.NodeSig1Bytes, + info.AuthProof.NodeSig1(), ) if err != nil { return nil, nil, err } chanAnn.NodeSig2, err = lnwire.NewSigFromECDSARawSignature( - info.AuthProof.NodeSig2Bytes, + info.AuthProof.NodeSig2(), ) if err != nil { return nil, nil, err } chanAnn.BitcoinSig1, err = lnwire.NewSigFromECDSARawSignature( - info.AuthProof.BitcoinSig1Bytes, + info.AuthProof.BitcoinSig1(), ) if err != nil { return nil, nil, err } chanAnn.BitcoinSig2, err = lnwire.NewSigFromECDSARawSignature( - info.AuthProof.BitcoinSig2Bytes, + info.AuthProof.BitcoinSig2(), ) if err != nil { return nil, nil, err @@ -2692,12 +2692,12 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(ctx context.Context, // If the proof checks out, then we'll save the proof itself to // the database so we can fetch it later when gossiping with // other nodes. - proof = &models.ChannelAuthProof{ - NodeSig1Bytes: ann.NodeSig1.ToSignatureBytes(), - NodeSig2Bytes: ann.NodeSig2.ToSignatureBytes(), - BitcoinSig1Bytes: ann.BitcoinSig1.ToSignatureBytes(), - BitcoinSig2Bytes: ann.BitcoinSig2.ToSignatureBytes(), - } + proof = models.NewV1ChannelAuthProof( + ann.NodeSig1.ToSignatureBytes(), + ann.NodeSig2.ToSignatureBytes(), + ann.BitcoinSig1.ToSignatureBytes(), + ann.BitcoinSig2.ToSignatureBytes(), + ) } // With the proof validated (if necessary), we can now store it within @@ -3621,21 +3621,25 @@ func (d *AuthenticatedGossiper) handleAnnSig(ctx context.Context, // We now have both halves of the channel announcement proof, then // we'll reconstruct the initial announcement so we can validate it // shortly below. - var dbProof models.ChannelAuthProof + var dbProof *models.ChannelAuthProof if isFirstNode { - dbProof.NodeSig1Bytes = ann.NodeSignature.ToSignatureBytes() - dbProof.NodeSig2Bytes = oppProof.NodeSignature.ToSignatureBytes() - dbProof.BitcoinSig1Bytes = ann.BitcoinSignature.ToSignatureBytes() - dbProof.BitcoinSig2Bytes = oppProof.BitcoinSignature.ToSignatureBytes() + dbProof = models.NewV1ChannelAuthProof( + ann.NodeSignature.ToSignatureBytes(), + oppProof.NodeSignature.ToSignatureBytes(), + ann.BitcoinSignature.ToSignatureBytes(), + oppProof.BitcoinSignature.ToSignatureBytes(), + ) } else { - dbProof.NodeSig1Bytes = oppProof.NodeSignature.ToSignatureBytes() - dbProof.NodeSig2Bytes = ann.NodeSignature.ToSignatureBytes() - dbProof.BitcoinSig1Bytes = oppProof.BitcoinSignature.ToSignatureBytes() - dbProof.BitcoinSig2Bytes = ann.BitcoinSignature.ToSignatureBytes() + dbProof = models.NewV1ChannelAuthProof( + oppProof.NodeSignature.ToSignatureBytes(), + ann.NodeSignature.ToSignatureBytes(), + oppProof.BitcoinSignature.ToSignatureBytes(), + ann.BitcoinSignature.ToSignatureBytes(), + ) } chanAnn, e1Ann, e2Ann, err := netann.CreateChanAnnouncement( - &dbProof, chanInfo, e1, e2, + dbProof, chanInfo, e1, e2, ) if err != nil { log.Error(err) @@ -3661,7 +3665,7 @@ func (d *AuthenticatedGossiper) handleAnnSig(ctx context.Context, // attest to the bitcoin keys by validating the signatures of // announcement. If proof is valid then we'll populate the channel edge // with it, so we can announce it on peer connect. - err = d.cfg.Graph.AddProof(ann.ShortChannelID, &dbProof) + err = d.cfg.Graph.AddProof(ann.ShortChannelID, dbProof) if err != nil { err := fmt.Errorf("unable add proof to the channel chanID=%v:"+ " %v", ann.ChannelID, err) diff --git a/graph/builder_test.go b/graph/builder_test.go index b6fe88675ac..f2648a0028c 100644 --- a/graph/builder_test.go +++ b/graph/builder_test.go @@ -279,12 +279,12 @@ func TestWakeUpOnStaleBranch(t *testing.T) { ChannelID: chanID1, NodeKey1Bytes: node1.PubKeyBytes, NodeKey2Bytes: node2.PubKeyBytes, - AuthProof: &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - }, + AuthProof: models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ), Features: lnwire.EmptyFeatureVector(), FundingScript: fn.Some(fundingScript1), } @@ -300,12 +300,12 @@ func TestWakeUpOnStaleBranch(t *testing.T) { ChannelID: chanID2, NodeKey1Bytes: node1.PubKeyBytes, NodeKey2Bytes: node2.PubKeyBytes, - AuthProof: &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - }, + AuthProof: models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ), Features: lnwire.EmptyFeatureVector(), FundingScript: fn.Some(fundingScript2), } @@ -493,12 +493,12 @@ func TestDisconnectedBlocks(t *testing.T) { NodeKey2Bytes: node2.PubKeyBytes, BitcoinKey1Bytes: node1.PubKeyBytes, BitcoinKey2Bytes: node2.PubKeyBytes, - AuthProof: &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - }, + AuthProof: models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ), Features: lnwire.EmptyFeatureVector(), FundingScript: fn.Some([]byte{}), } @@ -516,12 +516,12 @@ func TestDisconnectedBlocks(t *testing.T) { NodeKey2Bytes: node2.PubKeyBytes, BitcoinKey1Bytes: node1.PubKeyBytes, BitcoinKey2Bytes: node2.PubKeyBytes, - AuthProof: &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - }, + AuthProof: models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ), Features: lnwire.EmptyFeatureVector(), FundingScript: fn.Some([]byte{}), } @@ -649,12 +649,12 @@ func TestChansClosedOfflinePruneGraph(t *testing.T) { ChannelID: chanID1.ToUint64(), NodeKey1Bytes: node1.PubKeyBytes, NodeKey2Bytes: node2.PubKeyBytes, - AuthProof: &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - }, + AuthProof: models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ), ChannelPoint: *chanUTXO, Capacity: chanValue, Features: lnwire.EmptyFeatureVector(), diff --git a/graph/db/graph_test.go b/graph/db/graph_test.go index 978458cc00b..843d0bff817 100644 --- a/graph/db/graph_test.go +++ b/graph/db/graph_test.go @@ -509,12 +509,12 @@ func TestEdgeInsertionDeletion(t *testing.T) { Version: lnwire.GossipVersion1, ChannelID: chanID, ChainHash: *chaincfg.MainNetParams.GenesisHash, - AuthProof: &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - }, + AuthProof: models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ), Features: lnwire.EmptyFeatureVector(), ChannelPoint: outpoint, Capacity: 9000, @@ -585,12 +585,12 @@ func createEdge(height, txIndex uint32, txPosition uint16, outPointIndex uint32, Version: lnwire.GossipVersion1, ChannelID: shortChanID.ToUint64(), ChainHash: *chaincfg.MainNetParams.GenesisHash, - AuthProof: &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - }, + AuthProof: models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ), ChannelPoint: outpoint, Capacity: 9000, ExtraOpaqueData: make([]byte, 0), @@ -764,17 +764,20 @@ func assertEdgeInfoEqual(t *testing.T, e1 *models.ChannelEdgeInfo, } require.True(t, bytes.Equal( - e1.AuthProof.NodeSig1Bytes, e2.AuthProof.NodeSig1Bytes, + e1.AuthProof.NodeSig1(), + e2.AuthProof.NodeSig1(), )) require.True(t, bytes.Equal( - e1.AuthProof.NodeSig2Bytes, e2.AuthProof.NodeSig2Bytes, + e1.AuthProof.NodeSig2(), + e2.AuthProof.NodeSig2(), )) require.True(t, bytes.Equal( - e1.AuthProof.BitcoinSig1Bytes, - e2.AuthProof.BitcoinSig1Bytes, + e1.AuthProof.BitcoinSig1(), + e2.AuthProof.BitcoinSig1(), )) require.True(t, bytes.Equal( - e1.AuthProof.BitcoinSig2Bytes, e2.AuthProof.BitcoinSig2Bytes, + e1.AuthProof.BitcoinSig2(), + e2.AuthProof.BitcoinSig2(), )) if e1.ChannelPoint != e2.ChannelPoint { @@ -860,12 +863,12 @@ func createChannelEdge(node1, node2 *models.Node, return edgeInfo, nil, nil } - edgeInfo.AuthProof = &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - } + edgeInfo.AuthProof = models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ) edge1 := &models.ChannelEdgePolicy{ SigBytes: testSig.Serialize(), @@ -1316,12 +1319,12 @@ func TestAddEdgeProof(t *testing.T) { require.Equal(t, edge1, dbEdge) // Now, add the edge proof. - proof := &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - } + proof := models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ) // First, add the proof to the rest of the channel edge info and try // to call AddChannelEdge again - this should fail due to the channel @@ -1722,12 +1725,12 @@ func fillTestGraph(t testing.TB, graph *ChannelGraph, numNodes, Version: lnwire.GossipVersion1, ChannelID: chanID, ChainHash: *chaincfg.MainNetParams.GenesisHash, - AuthProof: &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - }, + AuthProof: models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ), Features: lnwire.EmptyFeatureVector(), ChannelPoint: op, Capacity: 1000, @@ -1905,12 +1908,12 @@ func TestGraphPruning(t *testing.T) { Version: lnwire.GossipVersion1, ChannelID: chanID, ChainHash: *chaincfg.MainNetParams.GenesisHash, - AuthProof: &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - }, + AuthProof: models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ), Features: lnwire.EmptyFeatureVector(), ChannelPoint: op, Capacity: 1000, diff --git a/graph/db/kv_store.go b/graph/db/kv_store.go index 602b9b304c7..a707ae1464b 100644 --- a/graph/db/kv_store.go +++ b/graph/db/kv_store.go @@ -1398,6 +1398,12 @@ func (c *KVStore) HasChannelEdge( func (c *KVStore) AddEdgeProof(chanID lnwire.ShortChannelID, proof *models.ChannelAuthProof) error { + // We only support v1 channel proofs in the KVStore. + if proof.Version != lnwire.GossipVersion1 { + return fmt.Errorf("only v1 channel proofs supported, got v%d", + proof.Version) + } + // Construct the channel's primary key which is the 8-byte channel ID. var chanKey [8]byte binary.BigEndian.PutUint64(chanKey[:], chanID.ToUint64()) @@ -4736,10 +4742,10 @@ func putChanEdgeInfo(edgeIndex kvdb.RwBucket, authProof := edgeInfo.AuthProof var nodeSig1, nodeSig2, bitcoinSig1, bitcoinSig2 []byte if authProof != nil { - nodeSig1 = authProof.NodeSig1Bytes - nodeSig2 = authProof.NodeSig2Bytes - bitcoinSig1 = authProof.BitcoinSig1Bytes - bitcoinSig2 = authProof.BitcoinSig2Bytes + nodeSig1 = authProof.NodeSig1() + nodeSig2 = authProof.NodeSig2() + bitcoinSig1 = authProof.BitcoinSig1() + bitcoinSig2 = authProof.BitcoinSig2() } if err := wire.WriteVarBytes(&b, 0, nodeSig1); err != nil { @@ -4828,24 +4834,42 @@ func deserializeChanEdgeInfo(r io.Reader) (*models.ChannelEdgeInfo, error) { } edgeInfo.Features = lnwire.NewFeatureVector(features, lnwire.Features) - proof := &models.ChannelAuthProof{} + proof := &models.ChannelAuthProof{ + // KV store always uses v1. + Version: lnwire.GossipVersion1, + } - proof.NodeSig1Bytes, err = wire.ReadVarBytes(r, 0, 80, "sigs") + nodeSig1, err := wire.ReadVarBytes(r, 0, 80, "sigs") if err != nil { return nil, err } - proof.NodeSig2Bytes, err = wire.ReadVarBytes(r, 0, 80, "sigs") + if len(nodeSig1) > 0 { + proof.NodeSig1Bytes = fn.Some(nodeSig1) + } + + nodeSig2, err := wire.ReadVarBytes(r, 0, 80, "sigs") if err != nil { return nil, err } - proof.BitcoinSig1Bytes, err = wire.ReadVarBytes(r, 0, 80, "sigs") + if len(nodeSig2) > 0 { + proof.NodeSig2Bytes = fn.Some(nodeSig2) + } + + bitcoinSig1, err := wire.ReadVarBytes(r, 0, 80, "sigs") if err != nil { return nil, err } - proof.BitcoinSig2Bytes, err = wire.ReadVarBytes(r, 0, 80, "sigs") + if len(bitcoinSig1) > 0 { + proof.BitcoinSig1Bytes = fn.Some(bitcoinSig1) + } + + bitcoinSig2, err := wire.ReadVarBytes(r, 0, 80, "sigs") if err != nil { return nil, err } + if len(bitcoinSig2) > 0 { + proof.BitcoinSig2Bytes = fn.Some(bitcoinSig2) + } if !proof.IsEmpty() { edgeInfo.AuthProof = proof diff --git a/graph/db/models/channel_auth_proof.go b/graph/db/models/channel_auth_proof.go index daf120b10d2..98ebd7c8fef 100644 --- a/graph/db/models/channel_auth_proof.go +++ b/graph/db/models/channel_auth_proof.go @@ -1,35 +1,119 @@ package models +import ( + "github.com/lightningnetwork/lnd/fn/v2" + "github.com/lightningnetwork/lnd/lnwire" +) + // ChannelAuthProof is the authentication proof (the signature portion) for a -// channel. Using the four signatures contained in the struct, and some +// channel. +// +// For v1 channels: +// Using the four node and bitcoin signatures contained in the struct, and some // auxiliary knowledge (the funding script, node identities, and outpoint) nodes // on the network are able to validate the authenticity and existence of a // channel. Each of these signatures signs the following digest: chanID || // nodeID1 || nodeID2 || bitcoinKey1|| bitcoinKey2 || 2-byte-feature-len || // features. +// +// For v2 channels: +// The single schnorr signature signs the tlv fields of the v2 channel +// announcement message which are in the signed range. type ChannelAuthProof struct { + // Version is the version of the channel announcement. + Version lnwire.GossipVersion + // NodeSig1Bytes are the raw bytes of the first node signature encoded // in DER format. - NodeSig1Bytes []byte + // + // NOTE: v1 channel announcements only. + NodeSig1Bytes fn.Option[[]byte] // NodeSig2Bytes are the raw bytes of the second node signature // encoded in DER format. - NodeSig2Bytes []byte + // + // NOTE: v1 channel announcements only. + NodeSig2Bytes fn.Option[[]byte] // BitcoinSig1Bytes are the raw bytes of the first bitcoin signature // encoded in DER format. - BitcoinSig1Bytes []byte + // + // NOTE: v1 channel announcements only. + BitcoinSig1Bytes fn.Option[[]byte] // BitcoinSig2Bytes are the raw bytes of the second bitcoin signature // encoded in DER format. - BitcoinSig2Bytes []byte + // + // NOTE: v1 channel announcements only. + BitcoinSig2Bytes fn.Option[[]byte] + + // Signature is the raw bytes of the single schnorr signature for v2 + // channel announcements. + // + // NOTE: v2 channel announcements only. + Signature fn.Option[[]byte] } -// IsEmpty check is the authentication proof is empty Proof is empty if at -// least one of the signatures are equal to nil. +// IsEmpty check is the authentication proof is empty Proof is empty. func (c *ChannelAuthProof) IsEmpty() bool { - return len(c.NodeSig1Bytes) == 0 || - len(c.NodeSig2Bytes) == 0 || - len(c.BitcoinSig1Bytes) == 0 || - len(c.BitcoinSig2Bytes) == 0 + // For v2 channel announcements, we only have a single signature. + if c.Signature.IsSome() { + return len(c.Signature.UnwrapOr([]byte{})) == 0 + } + + // For v1 channel announcements, we either have all four signatures or + // none. + return len(c.NodeSig1Bytes.UnwrapOr([]byte{})) == 0 +} + +// NewV1ChannelAuthProof creates a new ChannelAuthProof for a v1 channel +// announcement. +func NewV1ChannelAuthProof(nodeSig1, nodeSig2, bitcoinSig1, + bitcoinSig2 []byte) *ChannelAuthProof { + + return &ChannelAuthProof{ + Version: lnwire.GossipVersion1, + NodeSig1Bytes: fn.Some(nodeSig1), + NodeSig2Bytes: fn.Some(nodeSig2), + BitcoinSig1Bytes: fn.Some(bitcoinSig1), + BitcoinSig2Bytes: fn.Some(bitcoinSig2), + } +} + +// NewV2ChannelAuthProof creates a new ChannelAuthProof for a v2 channel +// announcement. +func NewV2ChannelAuthProof(signature []byte) *ChannelAuthProof { + return &ChannelAuthProof{ + Version: lnwire.GossipVersion2, + Signature: fn.Some(signature), + } +} + +// NodeSig1 returns the first node signature bytes, or an empty slice if not +// present. +func (c *ChannelAuthProof) NodeSig1() []byte { + return c.NodeSig1Bytes.UnwrapOr([]byte{}) +} + +// NodeSig2 returns the second node signature bytes, or an empty slice if not +// present. +func (c *ChannelAuthProof) NodeSig2() []byte { + return c.NodeSig2Bytes.UnwrapOr([]byte{}) +} + +// BitcoinSig1 returns the first bitcoin signature bytes, or an empty slice if +// not present. +func (c *ChannelAuthProof) BitcoinSig1() []byte { + return c.BitcoinSig1Bytes.UnwrapOr([]byte{}) +} + +// BitcoinSig2 returns the second bitcoin signature bytes, or an empty slice if +// not present. +func (c *ChannelAuthProof) BitcoinSig2() []byte { + return c.BitcoinSig2Bytes.UnwrapOr([]byte{}) +} + +// Sig returns the v2 signature bytes, or an empty slice if not present. +func (c *ChannelAuthProof) Sig() []byte { + return c.Signature.UnwrapOr([]byte{}) } diff --git a/graph/db/sql_store.go b/graph/db/sql_store.go index dc7fa0fe4d4..a86b16a7a10 100644 --- a/graph/db/sql_store.go +++ b/graph/db/sql_store.go @@ -2948,6 +2948,12 @@ func (s *SQLStore) DisconnectBlockAtHeight(height uint32) ( func (s *SQLStore) AddEdgeProof(scid lnwire.ShortChannelID, proof *models.ChannelAuthProof) error { + // For now, we only support v1 channel proofs. + if proof.Version != lnwire.GossipVersion1 { + return fmt.Errorf("only v1 channel proofs supported, got v%d", + proof.Version) + } + var ( ctx = context.TODO() scidBytes = channelIDToBytes(scid.ToUint64()) @@ -2957,10 +2963,10 @@ func (s *SQLStore) AddEdgeProof(scid lnwire.ShortChannelID, res, err := db.AddV1ChannelProof( ctx, sqlc.AddV1ChannelProofParams{ Scid: scidBytes, - Node1Signature: proof.NodeSig1Bytes, - Node2Signature: proof.NodeSig2Bytes, - Bitcoin1Signature: proof.BitcoinSig1Bytes, - Bitcoin2Signature: proof.BitcoinSig2Bytes, + Node1Signature: proof.NodeSig1(), + Node2Signature: proof.NodeSig2(), + Bitcoin1Signature: proof.BitcoinSig1(), + Bitcoin2Signature: proof.BitcoinSig2(), }, ) if err != nil { @@ -4157,10 +4163,10 @@ func insertChannel(ctx context.Context, db SQLQueries, if edge.AuthProof != nil { proof := edge.AuthProof - createParams.Node1Signature = proof.NodeSig1Bytes - createParams.Node2Signature = proof.NodeSig2Bytes - createParams.Bitcoin1Signature = proof.BitcoinSig1Bytes - createParams.Bitcoin2Signature = proof.BitcoinSig2Bytes + createParams.Node1Signature = proof.NodeSig1() + createParams.Node2Signature = proof.NodeSig2() + createParams.Bitcoin1Signature = proof.BitcoinSig1() + createParams.Bitcoin2Signature = proof.BitcoinSig2() } // Insert the new channel record. @@ -4356,12 +4362,16 @@ func buildEdgeInfoWithBatchData(chain chainhash.Hash, // safely check if one signature is present to determine if we have the // rest of the signatures for the auth proof. if len(dbChan.Bitcoin1Signature) > 0 { - channel.AuthProof = &models.ChannelAuthProof{ - NodeSig1Bytes: dbChan.Node1Signature, - NodeSig2Bytes: dbChan.Node2Signature, - BitcoinSig1Bytes: dbChan.Bitcoin1Signature, - BitcoinSig2Bytes: dbChan.Bitcoin2Signature, + // For v1 channels, we have four signatures. + if dbChan.Version == int16(lnwire.GossipVersion1) { + channel.AuthProof = models.NewV1ChannelAuthProof( + dbChan.Node1Signature, + dbChan.Node2Signature, + dbChan.Bitcoin1Signature, + dbChan.Bitcoin2Signature, + ) } + // TODO(elle): Add v2 support when needed. } return channel, nil diff --git a/graph/notifications_test.go b/graph/notifications_test.go index e220a0b445f..fab5b4ce6f1 100644 --- a/graph/notifications_test.go +++ b/graph/notifications_test.go @@ -68,12 +68,12 @@ var ( _ = testSScalar.SetByteSlice(testSBytes) testSig = ecdsa.NewSignature(testRScalar, testSScalar) - testAuthProof = models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - } + testAuthProof = *models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ) ) func createTestNode(t *testing.T) *models.Node { @@ -452,12 +452,12 @@ func TestEdgeUpdateNotification(t *testing.T) { ChannelID: chanID.ToUint64(), NodeKey1Bytes: node1.PubKeyBytes, NodeKey2Bytes: node2.PubKeyBytes, - AuthProof: &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - }, + AuthProof: models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ), Features: lnwire.EmptyFeatureVector(), ChannelPoint: *chanPoint, Capacity: chanValue, @@ -648,12 +648,12 @@ func TestNodeUpdateNotification(t *testing.T) { NodeKey1Bytes: node1.PubKeyBytes, NodeKey2Bytes: node2.PubKeyBytes, Features: lnwire.EmptyFeatureVector(), - AuthProof: &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - }, + AuthProof: models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ), FundingScript: fn.Some(script), } copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) @@ -834,12 +834,12 @@ func TestNotificationCancellation(t *testing.T) { ChannelID: chanID.ToUint64(), NodeKey1Bytes: node1.PubKeyBytes, NodeKey2Bytes: node2.PubKeyBytes, - AuthProof: &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - }, + AuthProof: models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ), Features: lnwire.EmptyFeatureVector(), ChannelPoint: *chanPoint, Capacity: chanValue, @@ -911,12 +911,12 @@ func TestChannelCloseNotification(t *testing.T) { ChannelID: chanID.ToUint64(), NodeKey1Bytes: node1.PubKeyBytes, NodeKey2Bytes: node2.PubKeyBytes, - AuthProof: &models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - }, + AuthProof: models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ), Features: lnwire.EmptyFeatureVector(), ChannelPoint: *chanUtxo, Capacity: chanValue, diff --git a/netann/channel_announcement.go b/netann/channel_announcement.go index 9bb21c40155..fee3c5abfa8 100644 --- a/netann/channel_announcement.go +++ b/netann/channel_announcement.go @@ -56,25 +56,25 @@ func CreateChanAnnouncement(chanProof *models.ChannelAuthProof, var err error chanAnn.BitcoinSig1, err = lnwire.NewSigFromECDSARawSignature( - chanProof.BitcoinSig1Bytes, + chanProof.BitcoinSig1(), ) if err != nil { return nil, nil, nil, err } chanAnn.BitcoinSig2, err = lnwire.NewSigFromECDSARawSignature( - chanProof.BitcoinSig2Bytes, + chanProof.BitcoinSig2(), ) if err != nil { return nil, nil, nil, err } chanAnn.NodeSig1, err = lnwire.NewSigFromECDSARawSignature( - chanProof.NodeSig1Bytes, + chanProof.NodeSig1(), ) if err != nil { return nil, nil, nil, err } chanAnn.NodeSig2, err = lnwire.NewSigFromECDSARawSignature( - chanProof.NodeSig2Bytes, + chanProof.NodeSig2(), ) if err != nil { return nil, nil, nil, err diff --git a/netann/channel_announcement_test.go b/netann/channel_announcement_test.go index 49f61a57ff2..02d1d56174d 100644 --- a/netann/channel_announcement_test.go +++ b/netann/channel_announcement_test.go @@ -40,12 +40,12 @@ func TestCreateChanAnnouncement(t *testing.T) { ExtraOpaqueData: []byte{0x1}, } - chanProof := &models.ChannelAuthProof{ - NodeSig1Bytes: expChanAnn.NodeSig1.ToSignatureBytes(), - NodeSig2Bytes: expChanAnn.NodeSig2.ToSignatureBytes(), - BitcoinSig1Bytes: expChanAnn.BitcoinSig1.ToSignatureBytes(), - BitcoinSig2Bytes: expChanAnn.BitcoinSig2.ToSignatureBytes(), - } + chanProof := models.NewV1ChannelAuthProof( + expChanAnn.NodeSig1.ToSignatureBytes(), + expChanAnn.NodeSig2.ToSignatureBytes(), + expChanAnn.BitcoinSig1.ToSignatureBytes(), + expChanAnn.BitcoinSig2.ToSignatureBytes(), + ) chanInfo := &models.ChannelEdgeInfo{ Version: lnwire.GossipVersion1, ChainHash: expChanAnn.ChainHash, diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index b7256e3a80e..bbb31fdafb4 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -99,12 +99,12 @@ var ( _ = testSScalar.SetByteSlice(testSBytes) testSig = ecdsa.NewSignature(testRScalar, testSScalar) - testAuthProof = models.ChannelAuthProof{ - NodeSig1Bytes: testSig.Serialize(), - NodeSig2Bytes: testSig.Serialize(), - BitcoinSig1Bytes: testSig.Serialize(), - BitcoinSig2Bytes: testSig.Serialize(), - } + testAuthProof = *models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ) ) // noProbabilitySource is used in testing to return the same probability 1 for diff --git a/rpcserver.go b/rpcserver.go index 3997a06eb2e..ca9343bb6e8 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -6937,10 +6937,10 @@ func marshalDBEdge(edgeInfo *models.ChannelEdgeInfo, // channel announcement. if includeAuthProof && edgeInfo.AuthProof != nil { edge.AuthProof = &lnrpc.ChannelAuthProof{ - NodeSig1: edgeInfo.AuthProof.NodeSig1Bytes, - BitcoinSig1: edgeInfo.AuthProof.BitcoinSig1Bytes, - NodeSig2: edgeInfo.AuthProof.NodeSig2Bytes, - BitcoinSig2: edgeInfo.AuthProof.BitcoinSig2Bytes, + NodeSig1: edgeInfo.AuthProof.NodeSig1(), + BitcoinSig1: edgeInfo.AuthProof.BitcoinSig1(), + NodeSig2: edgeInfo.AuthProof.NodeSig2(), + BitcoinSig2: edgeInfo.AuthProof.BitcoinSig2(), } } From 2691e137cb7d57bb5c577545dce139845985cb61 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 19 Nov 2025 12:20:21 +0200 Subject: [PATCH 07/11] multi: add and use V1 constructor for models.ChannelEdgeInfo This makes it clear what fields must/can be set for a V1 channel. Introduce NewV1Channel constructor to create v1 channel edges with proper initialization and validation. The constructor: - Takes required fields (chanID, chainHash, node keys) as parameters - Takes v1-specific fields (bitcoin keys, extra opaque data) via ChannelV1Fields struct - Accepts optional fields (capacity, channel point, features, proof) via functional options (WithCapacity, WithChannelPoint, etc.) - Validates that if an AuthProof is provided, its version matches the channel version This makes it clear which fields are required vs optional for v1 channels and prevents incorrectly initialized channel edges. All tests and production code updated to use the constructor. --- autopilot/prefattach_test.go | 27 +- discovery/gossiper.go | 37 ++- discovery/gossiper_test.go | 17 +- graph/builder_test.go | 370 +++++++++++++++------------ graph/db/channel_cache_test.go | 18 +- graph/db/graph_test.go | 328 +++++++++++++++--------- graph/db/kv_store.go | 14 +- graph/db/models/channel_edge_info.go | 86 +++++++ graph/db/sql_store.go | 63 +++-- graph/notifications_test.go | 169 +++++++----- lnrpc/devrpc/dev_server.go | 24 +- lnrpc/invoicesrpc/addinvoice_test.go | 18 +- netann/chan_status_manager_test.go | 25 +- netann/channel_announcement_test.go | 31 +-- routing/localchans/manager.go | 42 ++- routing/localchans/manager_test.go | 145 +++++++---- routing/pathfind_test.go | 71 +++-- routing/router_test.go | 45 ++-- 18 files changed, 970 insertions(+), 560 deletions(-) diff --git a/autopilot/prefattach_test.go b/autopilot/prefattach_test.go index a4026d73d00..c95aaece24a 100644 --- a/autopilot/prefattach_test.go +++ b/autopilot/prefattach_test.go @@ -12,6 +12,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" graphdb "github.com/lightningnetwork/lnd/graph/db" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/lnwire" @@ -492,16 +493,24 @@ func (d *testDBGraph) addRandChannel(node1, node2 *btcec.PublicKey, } chanID := randChanID() - edge := &models.ChannelEdgeInfo{ - Version: lnwire.GossipVersion1, - ChannelID: chanID.ToUint64(), - Capacity: capacity, - Features: lnwire.EmptyFeatureVector(), + nodeKey1 := route.NewVertex(lnNode1) + nodeKey2 := route.NewVertex(lnNode2) + btcKey1 := route.NewVertex(lnNode1) + btcKey2 := route.NewVertex(lnNode2) + edge, err := models.NewV1Channel( + chanID.ToUint64(), + chainhash.Hash{}, + nodeKey1, + nodeKey2, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, + models.WithCapacity(capacity), + ) + if err != nil { + return nil, nil, err } - copy(edge.NodeKey1Bytes[:], lnNode1.SerializeCompressed()) - copy(edge.NodeKey2Bytes[:], lnNode2.SerializeCompressed()) - copy(edge.BitcoinKey1Bytes[:], lnNode1.SerializeCompressed()) - copy(edge.BitcoinKey2Bytes[:], lnNode2.SerializeCompressed()) if err := d.db.AddChannelEdge(ctx, edge); err != nil { return nil, nil, err diff --git a/discovery/gossiper.go b/discovery/gossiper.go index 5a850654f87..a3a892c9268 100644 --- a/discovery/gossiper.go +++ b/discovery/gossiper.go @@ -2702,19 +2702,30 @@ 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, - NodeKey2Bytes: ann.NodeID2, - BitcoinKey1Bytes: ann.BitcoinKey1, - BitcoinKey2Bytes: ann.BitcoinKey2, - AuthProof: proof, - Features: lnwire.NewFeatureVector( - ann.Features, lnwire.Features, - ), - ExtraOpaqueData: ann.ExtraOpaqueData, + edge, err := models.NewV1Channel( + scid.ToUint64(), + ann.ChainHash, + ann.NodeID1, + ann.NodeID2, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: ann.BitcoinKey1, + BitcoinKey2Bytes: ann.BitcoinKey2, + ExtraOpaqueData: ann.ExtraOpaqueData, + }, + models.WithChanProof(proof), + models.WithFeatures(ann.Features), + ) + if err != nil { + key := newRejectCacheKey( + ann.GossipVersion(), + scid.ToUint64(), + sourceToPub(nMsg.source), + ) + _, _ = d.recentRejects.Put(key, &cachedReject{}) + + log.Errorf("unable to create channel edge: %v", err) + nMsg.err <- err + return nil, false } // If there were any optional message fields provided, we'll include diff --git a/discovery/gossiper_test.go b/discovery/gossiper_test.go index 1c2b8093e46..a6a6aa43eec 100644 --- a/discovery/gossiper_test.go +++ b/discovery/gossiper_test.go @@ -272,11 +272,18 @@ func (r *mockGraphSource) GetChannelByID(chanID lnwire.ShortChannelID) ( return nil, nil, nil, graphdb.ErrEdgeNotFound } - return &models.ChannelEdgeInfo{ - Version: lnwire.GossipVersion1, - NodeKey1Bytes: pubKeys[0], - NodeKey2Bytes: pubKeys[1], - }, nil, nil, graphdb.ErrZombieEdge + zombieEdge, err := models.NewV1Channel( + 0, + chainhash.Hash{}, + pubKeys[0], + pubKeys[1], + &models.ChannelV1Fields{}, + ) + if err != nil { + return nil, nil, nil, err + } + + return zombieEdge, nil, nil, graphdb.ErrZombieEdge } edges := r.edges[chanID.ToUint64()] diff --git a/graph/builder_test.go b/graph/builder_test.go index f2648a0028c..111a69fea41 100644 --- a/graph/builder_test.go +++ b/graph/builder_test.go @@ -17,6 +17,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" @@ -65,17 +66,21 @@ func TestAddProof(t *testing.T) { ctx.chain.addBlock(fundingBlock, chanID.BlockHeight, chanID.BlockHeight) // 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, - AuthProof: nil, - Features: lnwire.EmptyFeatureVector(), - FundingScript: fn.Some(script), - } - copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) - copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) + btcKey1 := route.NewVertex(bitcoinKey1) + btcKey2 := route.NewVertex(bitcoinKey2) + + edge, err := models.NewV1Channel( + chanID.ToUint64(), + *chaincfg.SimNetParams.GenesisHash, + node1.PubKeyBytes, + node2.PubKeyBytes, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, + ) + require.NoError(t, err) + edge.FundingScript = fn.Some(script) require.NoError(t, ctx.builder.AddEdge(ctxb, edge)) @@ -152,17 +157,23 @@ 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, - BitcoinKey1Bytes: pub1, - BitcoinKey2Bytes: pub2, - AuthProof: nil, - Features: lnwire.EmptyFeatureVector(), - FundingScript: fn.Some(script), - } + pub1Vertex, err := route.NewVertexFromBytes(pub1[:]) + require.NoError(t, err) + pub2Vertex, err := route.NewVertexFromBytes(pub2[:]) + require.NoError(t, err) + + edge, err := models.NewV1Channel( + chanID.ToUint64(), + *chaincfg.SimNetParams.GenesisHash, + pub1Vertex, + pub2Vertex, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: pub1Vertex, + BitcoinKey2Bytes: pub2Vertex, + }, + ) + require.NoError(t, err) + edge.FundingScript = fn.Some(script) edgePolicy := &models.ChannelEdgePolicy{ SigBytes: testSig.Serialize(), ChannelID: edge.ChannelID, @@ -274,43 +285,56 @@ func TestWakeUpOnStaleBranch(t *testing.T) { node1 := createTestNode(t) node2 := createTestNode(t) - edge1 := &models.ChannelEdgeInfo{ - Version: lnwire.GossipVersion1, - ChannelID: chanID1, - NodeKey1Bytes: node1.PubKeyBytes, - NodeKey2Bytes: node2.PubKeyBytes, - AuthProof: models.NewV1ChannelAuthProof( + btcKey1, err := route.NewVertexFromBytes( + bitcoinKey1.SerializeCompressed(), + ) + require.NoError(t, err) + btcKey2, err := route.NewVertexFromBytes( + bitcoinKey2.SerializeCompressed(), + ) + require.NoError(t, err) + + edge1, err := models.NewV1Channel( + chanID1, + *chaincfg.SimNetParams.GenesisHash, + node1.PubKeyBytes, + node2.PubKeyBytes, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, + models.WithChanProof(models.NewV1ChannelAuthProof( testSig.Serialize(), testSig.Serialize(), testSig.Serialize(), testSig.Serialize(), - ), - Features: lnwire.EmptyFeatureVector(), - FundingScript: fn.Some(fundingScript1), - } - copy(edge1.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) - copy(edge1.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) + )), + ) + require.NoError(t, err) + edge1.FundingScript = fn.Some(fundingScript1) if err := ctx.builder.AddEdge(ctxb, edge1); err != nil { t.Fatalf("unable to add edge: %v", err) } - edge2 := &models.ChannelEdgeInfo{ - Version: lnwire.GossipVersion1, - ChannelID: chanID2, - NodeKey1Bytes: node1.PubKeyBytes, - NodeKey2Bytes: node2.PubKeyBytes, - AuthProof: models.NewV1ChannelAuthProof( + edge2, err := models.NewV1Channel( + chanID2, + *chaincfg.SimNetParams.GenesisHash, + node1.PubKeyBytes, + node2.PubKeyBytes, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, + models.WithChanProof(models.NewV1ChannelAuthProof( testSig.Serialize(), testSig.Serialize(), testSig.Serialize(), testSig.Serialize(), - ), - Features: lnwire.EmptyFeatureVector(), - FundingScript: fn.Some(fundingScript2), - } - copy(edge2.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) - copy(edge2.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) + )), + ) + require.NoError(t, err) + edge2.FundingScript = fn.Some(fundingScript2) if err := ctx.builder.AddEdge(ctxb, edge2); err != nil { t.Fatalf("unable to add edge: %v", err) @@ -486,47 +510,47 @@ func TestDisconnectedBlocks(t *testing.T) { node1 := createTestNode(t) node2 := createTestNode(t) - edge1 := &models.ChannelEdgeInfo{ - Version: lnwire.GossipVersion1, - ChannelID: chanID1, - NodeKey1Bytes: node1.PubKeyBytes, - NodeKey2Bytes: node2.PubKeyBytes, - BitcoinKey1Bytes: node1.PubKeyBytes, - BitcoinKey2Bytes: node2.PubKeyBytes, - AuthProof: models.NewV1ChannelAuthProof( - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - ), - Features: lnwire.EmptyFeatureVector(), - FundingScript: fn.Some([]byte{}), - } - copy(edge1.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) - copy(edge1.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) + btcKey1 := route.NewVertex(bitcoinKey1) + btcKey2 := route.NewVertex(bitcoinKey2) + + proof := models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ) + + edge1, err := models.NewV1Channel( + chanID1, + *chaincfg.SimNetParams.GenesisHash, + node1.PubKeyBytes, + node2.PubKeyBytes, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, + models.WithChanProof(proof), + ) + require.NoError(t, err) + edge1.FundingScript = fn.Some([]byte{}) if err := ctx.builder.AddEdge(ctxb, edge1); err != nil { t.Fatalf("unable to add edge: %v", err) } - edge2 := &models.ChannelEdgeInfo{ - Version: lnwire.GossipVersion1, - ChannelID: chanID2, - NodeKey1Bytes: node1.PubKeyBytes, - NodeKey2Bytes: node2.PubKeyBytes, - BitcoinKey1Bytes: node1.PubKeyBytes, - BitcoinKey2Bytes: node2.PubKeyBytes, - AuthProof: models.NewV1ChannelAuthProof( - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - ), - Features: lnwire.EmptyFeatureVector(), - FundingScript: fn.Some([]byte{}), - } - copy(edge2.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) - copy(edge2.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) + edge2, err := models.NewV1Channel( + chanID2, + *chaincfg.SimNetParams.GenesisHash, + node1.PubKeyBytes, + node2.PubKeyBytes, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, + models.WithChanProof(proof), + ) + require.NoError(t, err) + edge2.FundingScript = fn.Some([]byte{}) if err := ctx.builder.AddEdge(ctxb, edge2); err != nil { t.Fatalf("unable to add edge: %v", err) @@ -644,24 +668,31 @@ func TestChansClosedOfflinePruneGraph(t *testing.T) { node1 := createTestNode(t) node2 := createTestNode(t) - edge1 := &models.ChannelEdgeInfo{ - Version: lnwire.GossipVersion1, - ChannelID: chanID1.ToUint64(), - NodeKey1Bytes: node1.PubKeyBytes, - NodeKey2Bytes: node2.PubKeyBytes, - AuthProof: models.NewV1ChannelAuthProof( - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - ), - ChannelPoint: *chanUTXO, - Capacity: chanValue, - Features: lnwire.EmptyFeatureVector(), - FundingScript: fn.Some(script), - } - copy(edge1.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) - copy(edge1.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) + btcKey1 := route.NewVertex(bitcoinKey1) + btcKey2 := route.NewVertex(bitcoinKey2) + + proof := models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ) + + edge1, err := models.NewV1Channel( + chanID1.ToUint64(), + *chaincfg.SimNetParams.GenesisHash, + node1.PubKeyBytes, + node2.PubKeyBytes, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, + models.WithChanProof(proof), + models.WithCapacity(chanValue), + models.WithChannelPoint(*chanUTXO), + ) + require.NoError(t, err) + edge1.FundingScript = fn.Some(script) if err := ctx.builder.AddEdge(ctxb, edge1); err != nil { t.Fatalf("unable to add edge: %v", err) } @@ -1068,17 +1099,18 @@ 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, - BitcoinKey1Bytes: pub1, - BitcoinKey2Bytes: pub2, - AuthProof: nil, - Features: lnwire.EmptyFeatureVector(), - FundingScript: fn.Some(script), - } + edge, err := models.NewV1Channel( + chanID.ToUint64(), + *chaincfg.SimNetParams.GenesisHash, + pub1, + pub2, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: pub1, + BitcoinKey2Bytes: pub2, + }, + ) + require.NoError(t, err) + edge.FundingScript = fn.Some(script) if err := ctx.builder.AddEdge(ctxb, edge); err != nil { t.Fatalf("unable to add edge: %v", err) } @@ -1149,17 +1181,18 @@ 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, - BitcoinKey1Bytes: pub1, - BitcoinKey2Bytes: pub2, - AuthProof: nil, - FundingScript: fn.Some(script), - Features: lnwire.EmptyFeatureVector(), - } + edge, err := models.NewV1Channel( + chanID.ToUint64(), + *chaincfg.SimNetParams.GenesisHash, + pub1, + pub2, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: pub1, + BitcoinKey2Bytes: pub2, + }, + ) + require.NoError(t, err) + edge.FundingScript = fn.Some(script) if err := ctx.builder.AddEdge(ctxb, edge); err != nil { t.Fatalf("unable to add edge: %v", err) } @@ -1210,17 +1243,18 @@ func TestIsStaleEdgePolicy(t *testing.T) { t.Fatalf("router failed to detect fresh edge policy") } - edge := &models.ChannelEdgeInfo{ - Version: lnwire.GossipVersion1, - ChannelID: chanID.ToUint64(), - NodeKey1Bytes: pub1, - NodeKey2Bytes: pub2, - BitcoinKey1Bytes: pub1, - BitcoinKey2Bytes: pub2, - AuthProof: nil, - Features: lnwire.EmptyFeatureVector(), - FundingScript: fn.Some(script), - } + edge, err := models.NewV1Channel( + chanID.ToUint64(), + *chaincfg.SimNetParams.GenesisHash, + pub1, + pub2, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: pub1, + BitcoinKey2Bytes: pub2, + }, + ) + require.NoError(t, err) + edge.FundingScript = fn.Some(script) if err := ctx.builder.AddEdge(ctxb, edge); err != nil { t.Fatalf("unable to add edge: %v", err) } @@ -1523,20 +1557,31 @@ 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, - Capacity: btcutil.Amount(edge.Capacity), - Features: lnwire.EmptyFeatureVector(), + var node1Vertex, node2Vertex route.Vertex + copy(node1Vertex[:], node1Bytes) + copy(node2Vertex[:], node2Bytes) + + var btcKey1, btcKey2 route.Vertex + copy(btcKey1[:], node1Bytes) + copy(btcKey2[:], node2Bytes) + + edgeInfo, err := models.NewV1Channel( + edge.ChannelID, + *chaincfg.SimNetParams.GenesisHash, + node1Vertex, + node2Vertex, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, + models.WithChanProof(&testAuthProof), + models.WithChannelPoint(fundingPoint), + models.WithCapacity(btcutil.Amount(edge.Capacity)), + ) + if err != nil { + return nil, err } - copy(edgeInfo.NodeKey1Bytes[:], node1Bytes) - copy(edgeInfo.NodeKey2Bytes[:], node2Bytes) - copy(edgeInfo.BitcoinKey1Bytes[:], node1Bytes) - copy(edgeInfo.BitcoinKey2Bytes[:], node2Bytes) - shortID := lnwire.NewShortChanIDFromInt(edge.ChannelID) links[shortID] = &mockLink{ bandwidth: lnwire.MilliSatoshi( @@ -1544,7 +1589,7 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( ), } - err = graph.AddChannelEdge(ctx, &edgeInfo) + err = graph.AddChannelEdge(ctx, edgeInfo) if err != nil && !errors.Is(err, graphdb.ErrEdgeAlreadyExist) { return nil, err } @@ -1584,12 +1629,12 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( } // We also store the channel IDs info for each of the node. - node1Vertex, err := route.NewVertexFromBytes(node1Bytes) + node1Vertex, err = route.NewVertexFromBytes(node1Bytes) if err != nil { return nil, err } - node2Vertex, err := route.NewVertexFromBytes(node2Bytes) + node2Vertex, err = route.NewVertexFromBytes(node2Bytes) if err != nil { return nil, err } @@ -1895,21 +1940,24 @@ 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, - Capacity: testChannel.Capacity, - - NodeKey1Bytes: node1Vertex, - BitcoinKey1Bytes: node1Vertex, - NodeKey2Bytes: node2Vertex, - BitcoinKey2Bytes: node2Vertex, - Features: lnwire.EmptyFeatureVector(), + edgeInfo, err := models.NewV1Channel( + channelID, + *chaincfg.SimNetParams.GenesisHash, + node1Vertex, + node2Vertex, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: node1Vertex, + BitcoinKey2Bytes: node2Vertex, + }, + models.WithChanProof(&testAuthProof), + models.WithChannelPoint(*fundingPoint), + models.WithCapacity(testChannel.Capacity), + ) + if err != nil { + return nil, err } - err = graph.AddChannelEdge(ctx, &edgeInfo) + err = graph.AddChannelEdge(ctx, edgeInfo) if err != nil && !errors.Is(err, graphdb.ErrEdgeAlreadyExist) { diff --git a/graph/db/channel_cache_test.go b/graph/db/channel_cache_test.go index 04f6d03317c..1b7b0d3025e 100644 --- a/graph/db/channel_cache_test.go +++ b/graph/db/channel_cache_test.go @@ -4,8 +4,9 @@ import ( "reflect" "testing" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/graph/db/models" - "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" ) // TestChannelCache checks the behavior of the channelCache with respect to @@ -100,10 +101,17 @@ func assertHasChanEntries(t *testing.T, c *channelCache, start, end uint64) { // channelForInt generates a unique ChannelEdge given an integer. func channelForInt(i uint64) ChannelEdge { + info, err := models.NewV1Channel( + i, + chainhash.Hash{}, + route.Vertex{}, + route.Vertex{}, + &models.ChannelV1Fields{}, + ) + if err != nil { + panic(err) + } return ChannelEdge{ - Info: &models.ChannelEdgeInfo{ - Version: lnwire.GossipVersion1, - ChannelID: i, - }, + Info: info, } } diff --git a/graph/db/graph_test.go b/graph/db/graph_test.go index 843d0bff817..ea3dec08f60 100644 --- a/graph/db/graph_test.go +++ b/graph/db/graph_test.go @@ -379,7 +379,7 @@ func TestPartialNode(t *testing.T) { // Create an edge attached to these nodes and add it to the graph. edgeInfo, _ := createEdge(140, 0, 0, 0, &node1, &node2) - require.NoError(t, graph.AddChannelEdge(ctx, &edgeInfo)) + require.NoError(t, graph.AddChannelEdge(ctx, edgeInfo)) // Both of the nodes should now be in both the graph (as partial/shell) // nodes _and_ the cache should also have an awareness of both nodes. @@ -505,31 +505,53 @@ func TestEdgeInsertionDeletion(t *testing.T) { require.NoError(t, err, "unable to generate node key") 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.NewV1ChannelAuthProof( - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - ), - Features: lnwire.EmptyFeatureVector(), - ChannelPoint: outpoint, - Capacity: 9000, - } - copy(edgeInfo.NodeKey1Bytes[:], node1Pub.SerializeCompressed()) - copy(edgeInfo.NodeKey2Bytes[:], node2Pub.SerializeCompressed()) - copy(edgeInfo.BitcoinKey1Bytes[:], node1Pub.SerializeCompressed()) - copy(edgeInfo.BitcoinKey2Bytes[:], node2Pub.SerializeCompressed()) - require.NoError(t, graph.AddChannelEdge(ctx, &edgeInfo)) - assertEdgeWithNoPoliciesInCache(t, graph, &edgeInfo) + 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) // 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). @@ -566,7 +588,7 @@ func TestEdgeInsertionDeletion(t *testing.T) { } func createEdge(height, txIndex uint32, txPosition uint16, outPointIndex uint32, - node1, node2 *models.Node) (models.ChannelEdgeInfo, + node1, node2 *models.Node) (*models.ChannelEdgeInfo, lnwire.ShortChannelID) { shortChanID := lnwire.ShortChannelID{ @@ -581,26 +603,41 @@ 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.NewV1ChannelAuthProof( - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - ), - ChannelPoint: outpoint, - Capacity: 9000, - ExtraOpaqueData: make([]byte, 0), - Features: lnwire.EmptyFeatureVector(), - } - copy(edgeInfo.NodeKey1Bytes[:], node1Pub.SerializeCompressed()) - copy(edgeInfo.NodeKey2Bytes[:], node2Pub.SerializeCompressed()) - copy(edgeInfo.BitcoinKey1Bytes[:], node1Pub.SerializeCompressed()) - copy(edgeInfo.BitcoinKey2Bytes[:], node2Pub.SerializeCompressed()) + node1Vertex, _ := route.NewVertexFromBytes( + node1Pub.SerializeCompressed(), + ) + node2Vertex, _ := route.NewVertexFromBytes( + node2Pub.SerializeCompressed(), + ) + btcKey1, _ := route.NewVertexFromBytes( + node1Pub.SerializeCompressed(), + ) + btcKey2, _ := route.NewVertexFromBytes( + node2Pub.SerializeCompressed(), + ) + + proof := models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ) + + edgeInfo, _ := models.NewV1Channel( + shortChanID.ToUint64(), + *chaincfg.MainNetParams.GenesisHash, + node1Vertex, + node2Vertex, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + ExtraOpaqueData: make([]byte, 0), + }, + models.WithChanProof(proof), + models.WithChannelPoint(outpoint), + models.WithCapacity(9000), + ) return edgeInfo, shortChanID } @@ -659,20 +696,20 @@ func TestDisconnectBlockAtHeight(t *testing.T) { edgeInfo3, _ := createEdge(height-1, 0, 0, 2, node1, node2) // Now add all these new edges to the database. - if err := graph.AddChannelEdge(ctx, &edgeInfo); err != nil { + if err := graph.AddChannelEdge(ctx, edgeInfo); err != nil { t.Fatalf("unable to create channel edge: %v", err) } - if err := graph.AddChannelEdge(ctx, &edgeInfo2); err != nil { + if err := graph.AddChannelEdge(ctx, edgeInfo2); err != nil { t.Fatalf("unable to create channel edge: %v", err) } - if err := graph.AddChannelEdge(ctx, &edgeInfo3); err != nil { + if err := graph.AddChannelEdge(ctx, edgeInfo3); err != nil { t.Fatalf("unable to create channel edge: %v", err) } - assertEdgeWithNoPoliciesInCache(t, graph, &edgeInfo) - assertEdgeWithNoPoliciesInCache(t, graph, &edgeInfo2) - assertEdgeWithNoPoliciesInCache(t, graph, &edgeInfo3) + assertEdgeWithNoPoliciesInCache(t, graph, edgeInfo) + assertEdgeWithNoPoliciesInCache(t, graph, edgeInfo2) + assertEdgeWithNoPoliciesInCache(t, graph, edgeInfo3) // Call DisconnectBlockAtHeight, which should prune every channel // that has a funding height of 'height' or greater. @@ -682,7 +719,7 @@ func TestDisconnectBlockAtHeight(t *testing.T) { } assertNoEdge(t, graph, edgeInfo.ChannelID) assertNoEdge(t, graph, edgeInfo2.ChannelID) - assertEdgeWithNoPoliciesInCache(t, graph, &edgeInfo3) + assertEdgeWithNoPoliciesInCache(t, graph, edgeInfo3) // The two edges should have been removed. if len(removed) != 2 { @@ -841,35 +878,57 @@ 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, - Capacity: 1000, - ExtraOpaqueData: []byte{ - 1, 1, 1, - 2, 2, 2, 2, - 3, 3, 3, 3, 3, - }, - Features: lnwire.EmptyFeatureVector(), + var node1Key, node2Key route.Vertex + copy(node1Key[:], firstNode[:]) + copy(node2Key[:], secondNode[:]) + + extraData := []byte{ + 1, 1, 1, + 2, 2, 2, 2, + 3, 3, 3, 3, 3, } - copy(edgeInfo.NodeKey1Bytes[:], firstNode[:]) - copy(edgeInfo.NodeKey2Bytes[:], secondNode[:]) - copy(edgeInfo.BitcoinKey1Bytes[:], firstNode[:]) - copy(edgeInfo.BitcoinKey2Bytes[:], secondNode[:]) + var edgeInfo *models.ChannelEdgeInfo if opts.skipProofs { + edgeInfo, _ = models.NewV1Channel( + chanID, + *chaincfg.MainNetParams.GenesisHash, + node1Key, + node2Key, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: node1Key, + BitcoinKey2Bytes: node2Key, + ExtraOpaqueData: extraData, + }, + models.WithChannelPoint(outpoint), + models.WithCapacity(1000), + ) + return edgeInfo, nil, nil } - edgeInfo.AuthProof = models.NewV1ChannelAuthProof( + proof := models.NewV1ChannelAuthProof( testSig.Serialize(), testSig.Serialize(), testSig.Serialize(), testSig.Serialize(), ) + edgeInfo, _ = models.NewV1Channel( + chanID, + *chaincfg.MainNetParams.GenesisHash, + node1Key, + node2Key, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: node1Key, + BitcoinKey2Bytes: node2Key, + ExtraOpaqueData: extraData, + }, + models.WithChanProof(proof), + models.WithChannelPoint(outpoint), + models.WithCapacity(1000), + ) + edge1 := &models.ChannelEdgePolicy{ SigBytes: testSig.Serialize(), ChannelID: chanID, @@ -1721,25 +1780,32 @@ func fillTestGraph(t testing.TB, graph *ChannelGraph, numNodes, Index: 0, } - edgeInfo := models.ChannelEdgeInfo{ - Version: lnwire.GossipVersion1, - ChannelID: chanID, - ChainHash: *chaincfg.MainNetParams.GenesisHash, - AuthProof: models.NewV1ChannelAuthProof( - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - ), - Features: lnwire.EmptyFeatureVector(), - ChannelPoint: op, - Capacity: 1000, - } - copy(edgeInfo.NodeKey1Bytes[:], node1.PubKeyBytes[:]) - copy(edgeInfo.NodeKey2Bytes[:], node2.PubKeyBytes[:]) - copy(edgeInfo.BitcoinKey1Bytes[:], node1.PubKeyBytes[:]) - copy(edgeInfo.BitcoinKey2Bytes[:], node2.PubKeyBytes[:]) - err := graph.AddChannelEdge(ctx, &edgeInfo) + var node1Key, node2Key route.Vertex + copy(node1Key[:], node1.PubKeyBytes[:]) + copy(node2Key[:], node2.PubKeyBytes[:]) + + proof := models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ) + + edgeInfo, err := models.NewV1Channel( + chanID, + *chaincfg.MainNetParams.GenesisHash, + node1Key, + node2Key, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: node1Key, + BitcoinKey2Bytes: node2Key, + }, + models.WithChanProof(proof), + models.WithChannelPoint(op), + models.WithCapacity(1000), + ) + require.NoError(t, err) + err = graph.AddChannelEdge(ctx, edgeInfo) require.NoError(t, err) // Create and add an edge with random data that points @@ -1904,28 +1970,34 @@ func TestGraphPruning(t *testing.T) { channelPoints = append(channelPoints, &op) - edgeInfo := models.ChannelEdgeInfo{ - Version: lnwire.GossipVersion1, - ChannelID: chanID, - ChainHash: *chaincfg.MainNetParams.GenesisHash, - AuthProof: models.NewV1ChannelAuthProof( - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - ), - Features: lnwire.EmptyFeatureVector(), - ChannelPoint: op, - Capacity: 1000, - } - copy(edgeInfo.NodeKey1Bytes[:], graphNodes[i].PubKeyBytes[:]) - copy(edgeInfo.NodeKey2Bytes[:], graphNodes[i+1].PubKeyBytes[:]) - copy(edgeInfo.BitcoinKey1Bytes[:], graphNodes[i].PubKeyBytes[:]) - copy( - edgeInfo.BitcoinKey2Bytes[:], - graphNodes[i+1].PubKeyBytes[:], + var node1Key, node2Key route.Vertex + copy(node1Key[:], graphNodes[i].PubKeyBytes[:]) + copy(node2Key[:], graphNodes[i+1].PubKeyBytes[:]) + + proof := models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ) + + edgeInfo, err := models.NewV1Channel( + chanID, + *chaincfg.MainNetParams.GenesisHash, + node1Key, + node2Key, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: node1Key, + BitcoinKey2Bytes: node2Key, + }, + models.WithChanProof(proof), + models.WithChannelPoint(op), + models.WithCapacity(1000), ) - if err := graph.AddChannelEdge(ctx, &edgeInfo); err != nil { + if err != nil { + t.Fatalf("unable to create edge: %v", err) + } + if err := graph.AddChannelEdge(ctx, edgeInfo); err != nil { t.Fatalf("unable to add node: %v", err) } @@ -2081,10 +2153,10 @@ func TestHighestChanID(t *testing.T) { edge1, _ := createEdge(10, 0, 0, 0, node1, node2) edge2, chanID2 := createEdge(100, 0, 0, 0, node1, node2) - if err := graph.AddChannelEdge(ctx, &edge1); err != nil { + if err := graph.AddChannelEdge(ctx, edge1); err != nil { t.Fatalf("unable to create channel edge: %v", err) } - if err := graph.AddChannelEdge(ctx, &edge2); err != nil { + if err := graph.AddChannelEdge(ctx, edge2); err != nil { t.Fatalf("unable to create channel edge: %v", err) } @@ -2101,7 +2173,7 @@ func TestHighestChanID(t *testing.T) { // If we add another edge, then the current best chan ID should be // updated as well. edge3, chanID3 := createEdge(1000, 0, 0, 0, node1, node2) - if err := graph.AddChannelEdge(ctx, &edge3); err != nil { + if err := graph.AddChannelEdge(ctx, edge3); err != nil { t.Fatalf("unable to create channel edge: %v", err) } bestID, err = graph.HighestChanID(ctx) @@ -2157,7 +2229,7 @@ func TestChanUpdatesInHorizon(t *testing.T) { uint32(i*10), 0, 0, 0, node1, node2, ) - if err := graph.AddChannelEdge(ctx, &channel); err != nil { + if err := graph.AddChannelEdge(ctx, channel); err != nil { t.Fatalf("unable to create channel edge: %v", err) } @@ -2186,7 +2258,7 @@ func TestChanUpdatesInHorizon(t *testing.T) { } edges = append(edges, ChannelEdge{ - Info: &channel, + Info: channel, Policy1: edge1, Policy2: edge2, }) @@ -2624,7 +2696,7 @@ func TestChanUpdatesInHorizonBoundaryConditions(t *testing.T) { uint32(i*10), 0, 0, 0, node1, node2, ) require.NoError( - t, graph.AddChannelEdge(ctx, &channel), + t, graph.AddChannelEdge(ctx, channel), ) edge1 := newEdgePolicy( @@ -2792,7 +2864,7 @@ func TestFilterKnownChanIDs(t *testing.T) { uint32(i*10), 0, 0, 0, node1, node2, ) - if err := graph.AddChannelEdge(ctx, &channel); err != nil { + if err := graph.AddChannelEdge(ctx, channel); err != nil { t.Fatalf("unable to create channel edge: %v", err) } @@ -2807,7 +2879,7 @@ func TestFilterKnownChanIDs(t *testing.T) { channel, chanID := createEdge( uint32(i*10+1), 0, 0, 0, node1, node2, ) - if err := graph.AddChannelEdge(ctx, &channel); err != nil { + if err := graph.AddChannelEdge(ctx, channel); err != nil { t.Fatalf("unable to create channel edge: %v", err) } err := graph.DeleteChannelEdges(false, true, channel.ChannelID) @@ -2968,7 +3040,7 @@ func TestStressTestChannelGraphAPI(t *testing.T) { ) newChan := &chanInfo{ - info: channel, + info: *channel, id: chanID, } chans = append(chans, newChan) @@ -3280,12 +3352,12 @@ func TestFilterChannelRange(t *testing.T) { channel1, chanID1 := createEdge( chanHeight, uint32(i+1), 0, 0, node1, node2, ) - require.NoError(t, graph.AddChannelEdge(ctx, &channel1)) + require.NoError(t, graph.AddChannelEdge(ctx, channel1)) channel2, chanID2 := createEdge( chanHeight, uint32(i+2), 0, 0, node1, node2, ) - require.NoError(t, graph.AddChannelEdge(ctx, &channel2)) + require.NoError(t, graph.AddChannelEdge(ctx, channel2)) chanInfo1 := NewChannelUpdateInfo( chanID1, time.Time{}, time.Time{}, @@ -3461,7 +3533,7 @@ func TestFetchChanInfos(t *testing.T) { uint32(i*10), 0, 0, 0, node1, node2, ) - if err := graph.AddChannelEdge(ctx, &channel); err != nil { + if err := graph.AddChannelEdge(ctx, channel); err != nil { t.Fatalf("unable to create channel edge: %v", err) } @@ -3485,7 +3557,7 @@ func TestFetchChanInfos(t *testing.T) { } edges = append(edges, ChannelEdge{ - Info: &channel, + Info: channel, Policy1: edge1, Policy2: edge2, }) @@ -3502,7 +3574,7 @@ func TestFetchChanInfos(t *testing.T) { zombieChan, zombieChanID := createEdge( 666, 0, 0, 0, node1, node2, ) - if err := graph.AddChannelEdge(ctx, &zombieChan); err != nil { + if err := graph.AddChannelEdge(ctx, zombieChan); err != nil { t.Fatalf("unable to create channel edge: %v", err) } err := graph.DeleteChannelEdges(false, true, zombieChan.ChannelID) @@ -3555,7 +3627,7 @@ func TestIncompleteChannelPolicies(t *testing.T) { uint32(0), 0, 0, 0, node1, node2, ) - if err := graph.AddChannelEdge(ctx, &channel); err != nil { + if err := graph.AddChannelEdge(ctx, channel); err != nil { t.Fatalf("unable to create channel edge: %v", err) } @@ -3659,7 +3731,7 @@ func TestChannelEdgePruningUpdateIndexDeletion(t *testing.T) { // With the two nodes created, we'll now create a random channel, as // well as two edges in the database with distinct update times. edgeInfo, chanID := createEdge(100, 0, 0, 0, node1, node2) - if err := graph.AddChannelEdge(ctx, &edgeInfo); err != nil { + if err := graph.AddChannelEdge(ctx, edgeInfo); err != nil { t.Fatalf("unable to add edge: %v", err) } @@ -3808,7 +3880,7 @@ func TestPruneGraphNodes(t *testing.T) { // We'll now add a new edge to the graph, but only actually advertise // the edge of *one* of the nodes. edgeInfo, chanID := createEdge(100, 0, 0, 0, node1, node2) - if err := graph.AddChannelEdge(ctx, &edgeInfo); err != nil { + if err := graph.AddChannelEdge(ctx, edgeInfo); err != nil { t.Fatalf("unable to add edge: %v", err) } @@ -3857,7 +3929,7 @@ func TestAddChannelEdgeShellNodes(t *testing.T) { // We'll now create an edge between the two nodes, as a result, node2 // should be inserted into the database as a shell node. edgeInfo, _ := createEdge(100, 0, 0, 0, node1, node2) - require.NoError(t, graph.AddChannelEdge(ctx, &edgeInfo)) + require.NoError(t, graph.AddChannelEdge(ctx, edgeInfo)) // Ensure that node1 was inserted as a full node, while node2 only has // a shell node present. @@ -3871,7 +3943,7 @@ func TestAddChannelEdgeShellNodes(t *testing.T) { // Show that attempting to add the channel again will result in an // error. - err = graph.AddChannelEdge(ctx, &edgeInfo) + err = graph.AddChannelEdge(ctx, edgeInfo) require.ErrorIs(t, err, ErrEdgeAlreadyExist) // Show that updating the shell node to a full node record works. @@ -3990,7 +4062,7 @@ func TestNodeIsPublic(t *testing.T) { // After creating all of our nodes and edges, we'll add them to each // participant's graph. nodes := []*models.Node{aliceNode, bobNode, carolNode} - edges := []*models.ChannelEdgeInfo{&aliceBobEdge, &bobCarolEdge} + edges := []*models.ChannelEdgeInfo{aliceBobEdge, bobCarolEdge} graphs := []*ChannelGraph{aliceGraph, bobGraph, carolGraph} for _, graph := range graphs { for _, node := range nodes { @@ -4072,7 +4144,7 @@ func TestNodeIsPublic(t *testing.T) { } bobCarolEdge.AuthProof = nil - if err := graph.AddChannelEdge(ctx, &bobCarolEdge); err != nil { + if err := graph.AddChannelEdge(ctx, bobCarolEdge); err != nil { t.Fatalf("unable to add edge: %v", err) } } @@ -4553,7 +4625,7 @@ func TestBatchedAddChannelEdge(t *testing.T) { // Create a third edge, this with a block height of 155. edgeInfo3, _ := createEdge(height-1, 0, 0, 2, node1, node2) - edges := []models.ChannelEdgeInfo{edgeInfo, edgeInfo2, edgeInfo3} + edges := []models.ChannelEdgeInfo{*edgeInfo, *edgeInfo2, *edgeInfo3} errChan := make(chan error, len(edges)) errTimeout := errors.New("timeout adding batched channel") diff --git a/graph/db/kv_store.go b/graph/db/kv_store.go index a707ae1464b..8a37f44d8fd 100644 --- a/graph/db/kv_store.go +++ b/graph/db/kv_store.go @@ -3948,11 +3948,17 @@ func (c *KVStore) FetchChannelEdgesByID(chanID uint64) ( // populate the edge info with the public keys of each // 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, + zombieEdge, err := models.NewV1Channel( + 0, + chainhash.Hash{}, + pubKey1, + pubKey2, + &models.ChannelV1Fields{}, + ) + if err != nil { + return err } + edgeInfo = zombieEdge return ErrZombieEdge } diff --git a/graph/db/models/channel_edge_info.go b/graph/db/models/channel_edge_info.go index 58765b5d5e6..5a4a84dcf80 100644 --- a/graph/db/models/channel_edge_info.go +++ b/graph/db/models/channel_edge_info.go @@ -78,6 +78,92 @@ type ChannelEdgeInfo struct { ExtraOpaqueData []byte } +// EdgeModifier is a functional option that modifies a ChannelEdgeInfo. +type EdgeModifier func(*ChannelEdgeInfo) + +// WithChannelPoint sets the channel point (funding outpoint) on the edge. +func WithChannelPoint(cp wire.OutPoint) EdgeModifier { + return func(e *ChannelEdgeInfo) { + e.ChannelPoint = cp + } +} + +// WithFeatures sets the feature vector on the edge. +func WithFeatures(f *lnwire.RawFeatureVector) EdgeModifier { + return func(e *ChannelEdgeInfo) { + e.Features = lnwire.NewFeatureVector(f, lnwire.Features) + } +} + +// WithCapacity sets the capacity on the edge. +func WithCapacity(c btcutil.Amount) EdgeModifier { + return func(e *ChannelEdgeInfo) { + e.Capacity = c + } +} + +// WithChanProof sets the authentication proof on the edge. +func WithChanProof(proof *ChannelAuthProof) EdgeModifier { + return func(e *ChannelEdgeInfo) { + e.AuthProof = proof + } +} + +// ChannelV1Fields contains the fields that are specific to v1 channel +// announcements. +type ChannelV1Fields struct { + // BitcoinKey1Bytes is the raw public key of the first node. + BitcoinKey1Bytes route.Vertex + + // BitcoinKey2Bytes is the raw public key of the first node. + BitcoinKey2Bytes route.Vertex + + // ExtraOpaqueData is the set of data that was appended to this + // message, some of which we may not actually know how to iterate or + // parse. By holding onto this data, we ensure that we're able to + // properly validate the set of signatures that cover these new fields, + // and ensure we're able to make upgrades to the network in a forwards + // compatible manner. + ExtraOpaqueData []byte +} + +// NewV1Channel creates a new ChannelEdgeInfo for a v1 channel announcement. +// It takes the required fields for all channels (chanID, chainHash, node keys) +// and v1-specific fields, along with optional modifiers for setting additional +// fields like capacity, channel point, features, and auth proof. +// +// The constructor validates that if an AuthProof is provided via modifiers, its +// version matches the channel version (v1). +func NewV1Channel(chanID uint64, chainHash chainhash.Hash, node1, + node2 route.Vertex, v1Fields *ChannelV1Fields, + opts ...EdgeModifier) (*ChannelEdgeInfo, error) { + + edge := &ChannelEdgeInfo{ + Version: lnwire.GossipVersion1, + NodeKey1Bytes: node1, + NodeKey2Bytes: node2, + BitcoinKey1Bytes: v1Fields.BitcoinKey1Bytes, + BitcoinKey2Bytes: v1Fields.BitcoinKey2Bytes, + ChannelID: chanID, + ChainHash: chainHash, + Features: lnwire.EmptyFeatureVector(), + ExtraOpaqueData: v1Fields.ExtraOpaqueData, + } + + for _, opt := range opts { + opt(edge) + } + + // Validate some fields after the options have been applied. + if edge.AuthProof != nil && edge.AuthProof.Version != edge.Version { + return nil, fmt.Errorf("channel auth proof version %d does "+ + "not match channel version %d", edge.AuthProof.Version, + edge.Version) + } + + return edge, nil +} + // NodeKey1 is the identity public key of the "first" node that was involved in // the creation of this channel. A node is considered "first" if the // lexicographical ordering the its serialized public key is "smaller" than diff --git a/graph/db/sql_store.go b/graph/db/sql_store.go index a86b16a7a10..3abf2584070 100644 --- a/graph/db/sql_store.go +++ b/graph/db/sql_store.go @@ -2022,11 +2022,25 @@ 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{ - Version: lnwire.GossipVersion1, + node1, err := route.NewVertexFromBytes(zombie.NodeKey1) + if err != nil { + return err + } + node2, err := route.NewVertexFromBytes(zombie.NodeKey2) + if err != nil { + return err } - copy(edge.NodeKey1Bytes[:], zombie.NodeKey1) - copy(edge.NodeKey2Bytes[:], zombie.NodeKey2) + zombieEdge, err := models.NewV1Channel( + 0, + chainhash.Hash{}, + node1, + node2, + &models.ChannelV1Fields{}, + ) + if err != nil { + return err + } + edge = zombieEdge return ErrZombieEdge } else if err != nil { @@ -4340,22 +4354,31 @@ func buildEdgeInfoWithBatchData(chain chainhash.Hash, recs = make([]byte, 0) } - var btcKey1, btcKey2 route.Vertex - copy(btcKey1[:], dbChan.BitcoinKey1) - copy(btcKey2[:], dbChan.BitcoinKey2) - - channel := &models.ChannelEdgeInfo{ - Version: lnwire.GossipVersion1, - ChainHash: chain, - ChannelID: byteOrder.Uint64(dbChan.Scid), - NodeKey1Bytes: node1, - NodeKey2Bytes: node2, - BitcoinKey1Bytes: btcKey1, - BitcoinKey2Bytes: btcKey2, - ChannelPoint: *op, - Capacity: btcutil.Amount(dbChan.Capacity.Int64), - Features: fv, - ExtraOpaqueData: recs, + btcKey1, err := route.NewVertexFromBytes(dbChan.BitcoinKey1) + if err != nil { + return nil, err + } + btcKey2, err := route.NewVertexFromBytes(dbChan.BitcoinKey2) + if err != nil { + return nil, err + } + + channel, err := models.NewV1Channel( + byteOrder.Uint64(dbChan.Scid), + chain, + node1, + node2, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + ExtraOpaqueData: recs, + }, + models.WithChannelPoint(*op), + models.WithCapacity(btcutil.Amount(dbChan.Capacity.Int64)), + models.WithFeatures(fv.RawFeatureVector), + ) + if err != nil { + return nil, err } // We always set all the signatures at the same time, so we can diff --git a/graph/notifications_test.go b/graph/notifications_test.go index fab5b4ce6f1..4edb49a87cd 100644 --- a/graph/notifications_test.go +++ b/graph/notifications_test.go @@ -14,6 +14,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/chainntnfs" @@ -447,24 +448,31 @@ 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, - AuthProof: models.NewV1ChannelAuthProof( - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - ), - Features: lnwire.EmptyFeatureVector(), - ChannelPoint: *chanPoint, - Capacity: chanValue, - FundingScript: fn.Some(script), - } - copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) - copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) + btcKey1 := route.NewVertex(bitcoinKey1) + btcKey2 := route.NewVertex(bitcoinKey2) + + proof := models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ) + + edge, err := models.NewV1Channel( + chanID.ToUint64(), + *chaincfg.SimNetParams.GenesisHash, + node1.PubKeyBytes, + node2.PubKeyBytes, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, + models.WithChanProof(proof), + models.WithChannelPoint(*chanPoint), + models.WithCapacity(chanValue), + ) + require.NoError(t, err) + edge.FundingScript = fn.Some(script) if err := ctx.builder.AddEdge(ctxb, edge); err != nil { t.Fatalf("unable to add edge: %v", err) @@ -642,22 +650,29 @@ func TestNodeUpdateNotification(t *testing.T) { testFeaturesBuf := new(bytes.Buffer) require.NoError(t, testFeatures.Encode(testFeaturesBuf)) - edge := &models.ChannelEdgeInfo{ - Version: lnwire.GossipVersion1, - ChannelID: chanID.ToUint64(), - NodeKey1Bytes: node1.PubKeyBytes, - NodeKey2Bytes: node2.PubKeyBytes, - Features: lnwire.EmptyFeatureVector(), - AuthProof: models.NewV1ChannelAuthProof( - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - ), - FundingScript: fn.Some(script), - } - copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) - copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) + btcKey1 := route.NewVertex(bitcoinKey1) + btcKey2 := route.NewVertex(bitcoinKey2) + + proof := models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ) + + edge, err := models.NewV1Channel( + chanID.ToUint64(), + *chaincfg.SimNetParams.GenesisHash, + node1.PubKeyBytes, + node2.PubKeyBytes, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, + models.WithChanProof(proof), + ) + require.NoError(t, err) + edge.FundingScript = fn.Some(script) // Adding the edge will add the nodes to the graph, but with no info // except the pubkey known. @@ -829,24 +844,31 @@ func TestNotificationCancellation(t *testing.T) { // to the client. ntfnClient.Cancel() - edge := &models.ChannelEdgeInfo{ - Version: lnwire.GossipVersion1, - ChannelID: chanID.ToUint64(), - NodeKey1Bytes: node1.PubKeyBytes, - NodeKey2Bytes: node2.PubKeyBytes, - AuthProof: models.NewV1ChannelAuthProof( - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - ), - Features: lnwire.EmptyFeatureVector(), - ChannelPoint: *chanPoint, - Capacity: chanValue, - FundingScript: fn.Some(script), - } - copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) - copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) + btcKey1 := route.NewVertex(bitcoinKey1) + btcKey2 := route.NewVertex(bitcoinKey2) + + proof := models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ) + + edge, err := models.NewV1Channel( + chanID.ToUint64(), + *chaincfg.SimNetParams.GenesisHash, + node1.PubKeyBytes, + node2.PubKeyBytes, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, + models.WithChanProof(proof), + models.WithChannelPoint(*chanPoint), + models.WithCapacity(chanValue), + ) + require.NoError(t, err) + edge.FundingScript = fn.Some(script) if err := ctx.builder.AddEdge(ctxb, edge); err != nil { t.Fatalf("unable to add edge: %v", err) } @@ -906,24 +928,31 @@ 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, - AuthProof: models.NewV1ChannelAuthProof( - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - testSig.Serialize(), - ), - Features: lnwire.EmptyFeatureVector(), - ChannelPoint: *chanUtxo, - Capacity: chanValue, - FundingScript: fn.Some(script), - } - copy(edge.BitcoinKey1Bytes[:], bitcoinKey1.SerializeCompressed()) - copy(edge.BitcoinKey2Bytes[:], bitcoinKey2.SerializeCompressed()) + btcKey1 := route.NewVertex(bitcoinKey1) + btcKey2 := route.NewVertex(bitcoinKey2) + + proof := models.NewV1ChannelAuthProof( + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + testSig.Serialize(), + ) + + edge, err := models.NewV1Channel( + chanID.ToUint64(), + *chaincfg.SimNetParams.GenesisHash, + node1.PubKeyBytes, + node2.PubKeyBytes, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, + models.WithChanProof(proof), + models.WithChannelPoint(*chanUtxo), + models.WithCapacity(chanValue), + ) + require.NoError(t, err) + edge.FundingScript = fn.Some(script) if err := ctx.builder.AddEdge(ctxb, edge); err != nil { t.Fatalf("unable to add edge: %v", err) } diff --git a/lnrpc/devrpc/dev_server.go b/lnrpc/devrpc/dev_server.go index 31db5fe0a0a..27b7f5b640a 100644 --- a/lnrpc/devrpc/dev_server.go +++ b/lnrpc/devrpc/dev_server.go @@ -224,7 +224,6 @@ func (s *Server) ImportGraph(ctx context.Context, // Obtain the pointer to the global singleton channel graph. graphDB := s.cfg.GraphDB - var err error for _, rpcNode := range graph.Nodes { pubKeyBytes, err := parsePubKey(rpcNode.PubKey) if err != nil { @@ -273,28 +272,33 @@ func (s *Server) ImportGraph(ctx context.Context, for _, rpcEdge := range graph.Edges { rpcEdge := rpcEdge - edge := &models.ChannelEdgeInfo{ - Version: lnwire.GossipVersion1, - ChannelID: rpcEdge.ChannelId, - ChainHash: *s.cfg.ActiveNetParams.GenesisHash, - Capacity: btcutil.Amount(rpcEdge.Capacity), + node1, err := parsePubKey(rpcEdge.Node1Pub) + if err != nil { + return nil, err } - edge.NodeKey1Bytes, err = parsePubKey(rpcEdge.Node1Pub) + node2, err := parsePubKey(rpcEdge.Node2Pub) if err != nil { return nil, err } - edge.NodeKey2Bytes, err = parsePubKey(rpcEdge.Node2Pub) + channelPoint, err := parseOutPoint(rpcEdge.ChanPoint) if err != nil { return nil, err } - channelPoint, err := parseOutPoint(rpcEdge.ChanPoint) + edge, err := models.NewV1Channel( + rpcEdge.ChannelId, + *s.cfg.ActiveNetParams.GenesisHash, + node1, + node2, + &models.ChannelV1Fields{}, + models.WithCapacity(btcutil.Amount(rpcEdge.Capacity)), + models.WithChannelPoint(*channelPoint), + ) if err != nil { return nil, err } - edge.ChannelPoint = *channelPoint if err := graphDB.AddChannelEdge(ctx, edge); err != nil { return nil, fmt.Errorf("unable to add edge %v: %w", diff --git a/lnrpc/invoicesrpc/addinvoice_test.go b/lnrpc/invoicesrpc/addinvoice_test.go index ceab2bec4be..04db2463fac 100644 --- a/lnrpc/invoicesrpc/addinvoice_test.go +++ b/lnrpc/invoicesrpc/addinvoice_test.go @@ -6,10 +6,12 @@ import ( "testing" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/zpay32" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -305,10 +307,18 @@ var shouldIncludeChannelTestCases = []struct { h.Mock.On( "FetchChannelEdgesByID", mock.Anything, ).Once().Return( - &models.ChannelEdgeInfo{ - Version: lnwire.GossipVersion1, - NodeKey1Bytes: selectedPolicy, - }, + func() *models.ChannelEdgeInfo { + edge, err := models.NewV1Channel( + 0, + chainhash.Hash{}, + selectedPolicy, + route.Vertex{}, + &models.ChannelV1Fields{}, + ) + require.NoError(h.t, err) + + return edge + }(), &models.ChannelEdgePolicy{ FeeBaseMSat: 1000, FeeProportionalMillionths: 20, diff --git a/netann/chan_status_manager_test.go b/netann/chan_status_manager_test.go index 1ec7cea23fa..06b1628e3ef 100644 --- a/netann/chan_status_manager_test.go +++ b/netann/chan_status_manager_test.go @@ -12,6 +12,7 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" graphdb "github.com/lightningnetwork/lnd/graph/db" @@ -19,6 +20,7 @@ import ( "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/netann" + "github.com/lightningnetwork/lnd/routing/route" "github.com/stretchr/testify/require" ) @@ -100,12 +102,25 @@ func createEdgePolicies(t *testing.T, channel *channeldb.OpenChannel, // bit. dir2 |= lnwire.ChanUpdateDirection - return &models.ChannelEdgeInfo{ - Version: lnwire.GossipVersion1, - ChannelPoint: channel.FundingOutpoint, - NodeKey1Bytes: pubkey1, - NodeKey2Bytes: pubkey2, + pubkey1Vertex, err := route.NewVertexFromBytes(pubkey1[:]) + require.NoError(t, err) + pubkey2Vertex, err := route.NewVertexFromBytes(pubkey2[:]) + require.NoError(t, err) + + edgeInfo, err := models.NewV1Channel( + channel.ShortChanID().ToUint64(), + chainhash.Hash{}, + pubkey1Vertex, + pubkey2Vertex, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: pubkey1Vertex, + BitcoinKey2Bytes: pubkey2Vertex, }, + models.WithChannelPoint(channel.FundingOutpoint), + ) + require.NoError(t, err) + + return edgeInfo, &models.ChannelEdgePolicy{ ChannelID: channel.ShortChanID().ToUint64(), ChannelFlags: dir1, diff --git a/netann/channel_announcement_test.go b/netann/channel_announcement_test.go index 02d1d56174d..2bf64b47e71 100644 --- a/netann/channel_announcement_test.go +++ b/netann/channel_announcement_test.go @@ -46,21 +46,22 @@ func TestCreateChanAnnouncement(t *testing.T) { expChanAnn.BitcoinSig1.ToSignatureBytes(), expChanAnn.BitcoinSig2.ToSignatureBytes(), ) - chanInfo := &models.ChannelEdgeInfo{ - Version: lnwire.GossipVersion1, - ChainHash: expChanAnn.ChainHash, - ChannelID: expChanAnn.ShortChannelID.ToUint64(), - ChannelPoint: wire.OutPoint{Index: 1}, - Capacity: btcutil.SatoshiPerBitcoin, - NodeKey1Bytes: key, - NodeKey2Bytes: key, - BitcoinKey1Bytes: key, - BitcoinKey2Bytes: key, - Features: lnwire.NewFeatureVector( - features, lnwire.Features, - ), - ExtraOpaqueData: expChanAnn.ExtraOpaqueData, - } + chanInfo, err := models.NewV1Channel( + expChanAnn.ShortChannelID.ToUint64(), + expChanAnn.ChainHash, + key, + key, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: key, + BitcoinKey2Bytes: key, + ExtraOpaqueData: expChanAnn.ExtraOpaqueData, + }, + models.WithChanProof(chanProof), + models.WithChannelPoint(wire.OutPoint{Index: 1}), + models.WithCapacity(btcutil.SatoshiPerBitcoin), + models.WithFeatures(features), + ) + require.NoError(t, err) chanAnn, _, _, err := CreateChanAnnouncement( chanProof, chanInfo, nil, nil, ) diff --git a/routing/localchans/manager.go b/routing/localchans/manager.go index 027eee42851..8f694b73406 100644 --- a/routing/localchans/manager.go +++ b/routing/localchans/manager.go @@ -17,6 +17,7 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing" + "github.com/lightningnetwork/lnd/routing/route" ) // Manager manages the node's local channels. The only operation that is @@ -321,19 +322,38 @@ func (r *Manager) createEdge(channel *channeldb.OpenChannel, shortChanID = channel.ZeroConfRealScid() } - info := &models.ChannelEdgeInfo{ - Version: lnwire.GossipVersion1, - ChannelID: shortChanID.ToUint64(), - ChainHash: channel.ChainHash, - Features: lnwire.EmptyFeatureVector(), - Capacity: channel.Capacity, - ChannelPoint: channel.FundingOutpoint, + nodeKey1, err := route.NewVertexFromBytes(nodeKey1Bytes) + if err != nil { + return nil, nil, err + } + nodeKey2, err := route.NewVertexFromBytes(nodeKey2Bytes) + if err != nil { + return nil, nil, err + } + bitcoinKey1, err := route.NewVertexFromBytes(bitcoinKey1Bytes) + if err != nil { + return nil, nil, err + } + bitcoinKey2, err := route.NewVertexFromBytes(bitcoinKey2Bytes) + if err != nil { + return nil, nil, err } - copy(info.NodeKey1Bytes[:], nodeKey1Bytes) - copy(info.NodeKey2Bytes[:], nodeKey2Bytes) - copy(info.BitcoinKey1Bytes[:], bitcoinKey1Bytes) - copy(info.BitcoinKey2Bytes[:], bitcoinKey2Bytes) + info, err := models.NewV1Channel( + shortChanID.ToUint64(), + channel.ChainHash, + nodeKey1, + nodeKey2, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: bitcoinKey1, + BitcoinKey2Bytes: bitcoinKey2, + }, + models.WithCapacity(channel.Capacity), + models.WithChannelPoint(channel.FundingOutpoint), + ) + if err != nil { + return nil, nil, err + } // Construct a dummy channel edge policy with default values that will // be updated with the new values in the call to processChan below. diff --git a/routing/localchans/manager_test.go b/routing/localchans/manager_test.go index 18b1684b2fc..571df8f65b9 100644 --- a/routing/localchans/manager_test.go +++ b/routing/localchans/manager_test.go @@ -18,6 +18,7 @@ import ( "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing" + "github.com/lightningnetwork/lnd/routing/route" "github.com/stretchr/testify/require" ) @@ -209,11 +210,21 @@ func TestManager(t *testing.T) { newPolicy: newPolicy, channelSet: []channel{ { - edgeInfo: &models.ChannelEdgeInfo{ - Version: lnwire.GossipVersion1, - Capacity: chanCap, - ChannelPoint: chanPointValid, - }, + //nolint:ll + edgeInfo: func() *models.ChannelEdgeInfo { + info, err := models.NewV1Channel( + 0, + chainhash.Hash{}, + route.Vertex{}, + route.Vertex{}, + &models.ChannelV1Fields{}, + models.WithCapacity(chanCap), + models.WithChannelPoint(chanPointValid), + ) + require.NoError(t, err) + + return info + }(), }, }, specifiedChanPoints: []wire.OutPoint{chanPointValid}, @@ -228,11 +239,21 @@ func TestManager(t *testing.T) { newPolicy: newPolicy, channelSet: []channel{ { - edgeInfo: &models.ChannelEdgeInfo{ - Version: lnwire.GossipVersion1, - Capacity: chanCap, - ChannelPoint: chanPointValid, - }, + //nolint:ll + edgeInfo: func() *models.ChannelEdgeInfo { + info, err := models.NewV1Channel( + 0, + chainhash.Hash{}, + route.Vertex{}, + route.Vertex{}, + &models.ChannelV1Fields{}, + models.WithCapacity(chanCap), + models.WithChannelPoint(chanPointValid), + ) + require.NoError(t, err) + + return info + }(), }, }, specifiedChanPoints: []wire.OutPoint{}, @@ -247,11 +268,22 @@ func TestManager(t *testing.T) { newPolicy: newPolicy, channelSet: []channel{ { - edgeInfo: &models.ChannelEdgeInfo{ - Version: lnwire.GossipVersion1, - Capacity: chanCap, - ChannelPoint: chanPointValid, - }, + //nolint:ll + edgeInfo: func() *models.ChannelEdgeInfo { + info, err := models.NewV1Channel( + 0, + chainhash.Hash{}, + route.Vertex{}, + route.Vertex{}, + &models.ChannelV1Fields{}, + models.WithCapacity(chanCap), + models.WithChannelPoint(chanPointValid), + ) + + require.NoError(t, err) + + return info + }(), }, }, specifiedChanPoints: []wire.OutPoint{chanPointMissing}, @@ -270,11 +302,22 @@ func TestManager(t *testing.T) { newPolicy: noMaxHtlcPolicy, channelSet: []channel{ { - edgeInfo: &models.ChannelEdgeInfo{ - Version: lnwire.GossipVersion1, - Capacity: chanCap, - ChannelPoint: chanPointValid, - }, + //nolint:ll + edgeInfo: func() *models.ChannelEdgeInfo { + info, err := models.NewV1Channel( + 0, + chainhash.Hash{}, + route.Vertex{}, + route.Vertex{}, + &models.ChannelV1Fields{}, + models.WithCapacity(chanCap), + models.WithChannelPoint(chanPointValid), + ) + + require.NoError(t, err) + + return info + }(), }, }, specifiedChanPoints: []wire.OutPoint{chanPointValid}, @@ -389,22 +432,21 @@ func TestCreateEdgeLower(t *testing.T) { Index: 0, }, } - expectedInfo := &models.ChannelEdgeInfo{ - Version: lnwire.GossipVersion1, - ChannelID: 8, - ChainHash: channel.ChainHash, - Features: lnwire.EmptyFeatureVector(), - Capacity: 9, - ChannelPoint: channel.FundingOutpoint, - NodeKey1Bytes: sp, - NodeKey2Bytes: rp, - BitcoinKey1Bytes: [33]byte( - localMultisigKey.SerializeCompressed()), - BitcoinKey2Bytes: [33]byte( - remoteMultisigKey.SerializeCompressed()), - AuthProof: nil, - ExtraOpaqueData: nil, - } + btcKey1 := route.NewVertex(localMultisigKey) + btcKey2 := route.NewVertex(remoteMultisigKey) + expectedInfo, err := models.NewV1Channel( + 8, + channel.ChainHash, + sp, + rp, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, + models.WithCapacity(9), + models.WithChannelPoint(channel.FundingOutpoint), + ) + require.NoError(t, err) expectedEdge := &models.ChannelEdgePolicy{ ChannelID: 8, LastUpdate: timestamp, @@ -478,22 +520,21 @@ func TestCreateEdgeHigher(t *testing.T) { Index: 0, }, } - expectedInfo := &models.ChannelEdgeInfo{ - Version: lnwire.GossipVersion1, - ChannelID: 8, - ChainHash: channel.ChainHash, - Features: lnwire.EmptyFeatureVector(), - Capacity: 9, - ChannelPoint: channel.FundingOutpoint, - NodeKey1Bytes: rp, - NodeKey2Bytes: sp, - BitcoinKey1Bytes: [33]byte( - remoteMultisigKey.SerializeCompressed()), - BitcoinKey2Bytes: [33]byte( - localMultisigKey.SerializeCompressed()), - AuthProof: nil, - ExtraOpaqueData: nil, - } + btcKey1 := route.NewVertex(remoteMultisigKey) + btcKey2 := route.NewVertex(localMultisigKey) + expectedInfo, err := models.NewV1Channel( + 8, + channel.ChainHash, + rp, + sp, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, + models.WithCapacity(9), + models.WithChannelPoint(channel.FundingOutpoint), + ) + require.NoError(t, err) expectedEdge := &models.ChannelEdgePolicy{ ChannelID: 8, LastUpdate: timestamp, diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index bbb31fdafb4..9db14e99e0b 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -19,6 +19,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" sphinx "github.com/lightningnetwork/lightning-onion" @@ -345,20 +346,31 @@ 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, - Features: lnwire.EmptyFeatureVector(), - Capacity: btcutil.Amount(edge.Capacity), + var node1Vertex, node2Vertex route.Vertex + copy(node1Vertex[:], node1Bytes) + copy(node2Vertex[:], node2Bytes) + + var btcKey1, btcKey2 route.Vertex + copy(btcKey1[:], node1Bytes) + copy(btcKey2[:], node2Bytes) + + edgeInfo, err := models.NewV1Channel( + edge.ChannelID, + *chaincfg.SimNetParams.GenesisHash, + node1Vertex, + node2Vertex, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: btcKey1, + BitcoinKey2Bytes: btcKey2, + }, + models.WithChanProof(&testAuthProof), + models.WithChannelPoint(fundingPoint), + models.WithCapacity(btcutil.Amount(edge.Capacity)), + ) + if err != nil { + return nil, err } - copy(edgeInfo.NodeKey1Bytes[:], node1Bytes) - copy(edgeInfo.NodeKey2Bytes[:], node2Bytes) - copy(edgeInfo.BitcoinKey1Bytes[:], node1Bytes) - copy(edgeInfo.BitcoinKey2Bytes[:], node2Bytes) - shortID := lnwire.NewShortChanIDFromInt(edge.ChannelID) links[shortID] = &mockLink{ bandwidth: lnwire.MilliSatoshi( @@ -366,7 +378,7 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( ), } - err = graph.AddChannelEdge(ctx, &edgeInfo) + err = graph.AddChannelEdge(ctx, edgeInfo) if err != nil && !errors.Is(err, graphdb.ErrEdgeAlreadyExist) { return nil, err } @@ -396,12 +408,12 @@ func parseTestGraph(t *testing.T, useCache bool, path string) ( } // We also store the channel IDs info for each of the node. - node1Vertex, err := route.NewVertexFromBytes(node1Bytes) + node1Vertex, err = route.NewVertexFromBytes(node1Bytes) if err != nil { return nil, err } - node2Vertex, err := route.NewVertexFromBytes(node2Bytes) + node2Vertex, err = route.NewVertexFromBytes(node2Bytes) if err != nil { return nil, err } @@ -678,21 +690,24 @@ 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, - Capacity: testChannel.Capacity, - Features: lnwire.EmptyFeatureVector(), - - NodeKey1Bytes: node1Vertex, - BitcoinKey1Bytes: node1Vertex, - NodeKey2Bytes: node2Vertex, - BitcoinKey2Bytes: node2Vertex, + edgeInfo, err := models.NewV1Channel( + channelID, + *chaincfg.SimNetParams.GenesisHash, + node1Vertex, + node2Vertex, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: node1Vertex, + BitcoinKey2Bytes: node2Vertex, + }, + models.WithChanProof(&testAuthProof), + models.WithChannelPoint(*fundingPoint), + models.WithCapacity(testChannel.Capacity), + ) + if err != nil { + return nil, err } - err = graph.AddChannelEdge(ctx, &edgeInfo) + err = graph.AddChannelEdge(ctx, edgeInfo) if err != nil && !errors.Is(err, graphdb.ErrEdgeAlreadyExist) { return nil, err } diff --git a/routing/router_test.go b/routing/router_test.go index bb0b2f85c29..09df64c26d1 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -2738,16 +2738,17 @@ 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, - BitcoinKey1Bytes: pub1, - BitcoinKey2Bytes: pub2, - Features: lnwire.EmptyFeatureVector(), - AuthProof: nil, - } + edge, err := models.NewV1Channel( + chanID.ToUint64(), + chainhash.Hash{}, + pub1, + pub2, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: pub1, + BitcoinKey2Bytes: pub2, + }, + ) + require.NoError(t, err) require.NoError(t, ctx.graph.AddChannelEdge(ctxb, edge)) // We must add the edge policy to be able to use the edge for route @@ -2819,16 +2820,20 @@ func TestAddEdgeUnknownVertexes(t *testing.T) { 10000, 510) require.NoError(t, err, "unable to create channel edge") - edge = &models.ChannelEdgeInfo{ - Version: lnwire.GossipVersion1, - ChannelID: chanID.ToUint64(), - Features: lnwire.EmptyFeatureVector(), - AuthProof: nil, - } - copy(edge.NodeKey1Bytes[:], node1Bytes) - edge.NodeKey2Bytes = node2Bytes - copy(edge.BitcoinKey1Bytes[:], node1Bytes) - edge.BitcoinKey2Bytes = node2Bytes + node1Vertex, err := route.NewVertexFromBytes(node1Bytes) + require.NoError(t, err) + + edge, err = models.NewV1Channel( + chanID.ToUint64(), + chainhash.Hash{}, + node1Vertex, + node2Bytes, + &models.ChannelV1Fields{ + BitcoinKey1Bytes: node1Vertex, + BitcoinKey2Bytes: node2Bytes, + }, + ) + require.NoError(t, err) require.NoError(t, ctx.graph.AddChannelEdge(ctxb, edge)) From 89da00c2e03bdbef383aedd5ce40c1da76a1c90a Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 19 Nov 2025 13:00:31 +0200 Subject: [PATCH 08/11] multi: add ToChannelAnnouncement helper on ChannelEdgeInfo So that we have one place that converts from our `models` struct to the `lnwire.ChannelAnnouncement` struct. The commit also refactors netann.CreateChanAnnouncement to only take a ChannelEdgeInfo and get the proof from there instead of needing the proof to be passed in separately. Add ToChannelAnnouncement() method to ChannelEdgeInfo that converts the model struct to a lnwire.ChannelAnnouncement1 message. This: - Centralizes the conversion logic in one place instead of scattered across multiple call sites - Validates that AuthProof is present (can't create announcement without proof) - Currently only supports v1 channels, returning error for v2 Refactor netann.CreateChanAnnouncement to use this helper and remove the separate chanProof parameter since proof is now accessed from within ChannelEdgeInfo. This improves encapsulation and reduces parameter count. --- discovery/chan_series.go | 6 +-- discovery/gossiper.go | 49 ++++++---------------- graph/db/models/channel_edge_info.go | 63 ++++++++++++++++++++++++++++ netann/channel_announcement.go | 40 ++---------------- netann/channel_announcement_test.go | 4 +- 5 files changed, 82 insertions(+), 80 deletions(-) diff --git a/discovery/chan_series.go b/discovery/chan_series.go index 050b82b1082..9ba9607e93e 100644 --- a/discovery/chan_series.go +++ b/discovery/chan_series.go @@ -133,8 +133,7 @@ func (c *ChanSeries) UpdatesInHorizon(chain chainhash.Hash, //nolint:ll chanAnn, edge1, edge2, err := netann.CreateChanAnnouncement( - channel.Info.AuthProof, channel.Info, - channel.Policy1, channel.Policy2, + channel.Info, channel.Policy1, channel.Policy2, ) if err != nil { if !yield(nil, err) { @@ -281,8 +280,7 @@ func (c *ChanSeries) FetchChanAnns(chain chainhash.Hash, } chanAnn, edge1, edge2, err := netann.CreateChanAnnouncement( - channel.Info.AuthProof, channel.Info, channel.Policy1, - channel.Policy2, + channel.Info, channel.Policy1, channel.Policy2, ) if err != nil { return nil, err diff --git a/discovery/gossiper.go b/discovery/gossiper.go index a3a892c9268..a77f4cb5c81 100644 --- a/discovery/gossiper.go +++ b/discovery/gossiper.go @@ -1996,10 +1996,14 @@ func (d *AuthenticatedGossiper) processRejectedEdge(_ context.Context, return nil, nil } + // Attach the proof to the channel info before creating the + // announcement. + chanInfo.AuthProof = proof + // We'll then create then validate the new fully assembled // announcement. chanAnn, e1Ann, e2Ann, err := netann.CreateChanAnnouncement( - proof, chanInfo, e1, e2, + chanInfo, e1, e2, ) if err != nil { return nil, err @@ -2392,44 +2396,13 @@ func (d *AuthenticatedGossiper) updateChannel(ctx context.Context, // have a full channel announcement for this channel. var chanAnn *lnwire.ChannelAnnouncement1 if info.AuthProof != nil { - chanID := lnwire.NewShortChanIDFromInt(info.ChannelID) - chanAnn = &lnwire.ChannelAnnouncement1{ - ShortChannelID: chanID, - NodeID1: info.NodeKey1Bytes, - NodeID2: info.NodeKey2Bytes, - ChainHash: info.ChainHash, - BitcoinKey1: info.BitcoinKey1Bytes, - Features: lnwire.NewRawFeatureVector(), - BitcoinKey2: info.BitcoinKey2Bytes, - ExtraOpaqueData: info.ExtraOpaqueData, - } - chanAnn.NodeSig1, err = lnwire.NewSigFromECDSARawSignature( - info.AuthProof.NodeSig1(), - ) - if err != nil { - return nil, nil, err - } - chanAnn.NodeSig2, err = lnwire.NewSigFromECDSARawSignature( - info.AuthProof.NodeSig2(), - ) - if err != nil { - return nil, nil, err - } - chanAnn.BitcoinSig1, err = lnwire.NewSigFromECDSARawSignature( - info.AuthProof.BitcoinSig1(), - ) - if err != nil { - return nil, nil, err - } - chanAnn.BitcoinSig2, err = lnwire.NewSigFromECDSARawSignature( - info.AuthProof.BitcoinSig2(), - ) + chanAnn, err = info.ToChannelAnnouncement() if err != nil { return nil, nil, err } } - return chanAnn, chanUpdate, err + return chanAnn, chanUpdate, nil } // SyncManager returns the gossiper's SyncManager instance. @@ -3570,7 +3543,7 @@ func (d *AuthenticatedGossiper) handleAnnSig(ctx context.Context, ann.ChannelID, peerID) ca, _, _, err := netann.CreateChanAnnouncement( - chanInfo.AuthProof, chanInfo, e1, e2, + chanInfo, e1, e2, ) if err != nil { log.Errorf("unable to gen ann: %v", @@ -3649,8 +3622,12 @@ func (d *AuthenticatedGossiper) handleAnnSig(ctx context.Context, ) } + // Attach the proof to the channel info before creating the + // announcement. + chanInfo.AuthProof = dbProof + chanAnn, e1Ann, e2Ann, err := netann.CreateChanAnnouncement( - dbProof, chanInfo, e1, e2, + chanInfo, e1, e2, ) if err != nil { log.Error(err) diff --git a/graph/db/models/channel_edge_info.go b/graph/db/models/channel_edge_info.go index 5a4a84dcf80..9c9a379e904 100644 --- a/graph/db/models/channel_edge_info.go +++ b/graph/db/models/channel_edge_info.go @@ -220,3 +220,66 @@ func (c *ChannelEdgeInfo) OtherNodeKeyBytes(thisNodeKey []byte) ( "this channel") } } + +// ToChannelAnnouncement converts the ChannelEdgeInfo to a +// lnwire.ChannelAnnouncement1 message. Returns an error if AuthProof is nil +// or if the version is not v1. +func (c *ChannelEdgeInfo) ToChannelAnnouncement() ( + *lnwire.ChannelAnnouncement1, error) { + + // We currently only support v1 channel announcements. + if c.Version != lnwire.GossipVersion1 { + return nil, fmt.Errorf("unsupported channel version: %d", + c.Version) + } + + // If there's no auth proof, we can't create a full channel + // announcement. + if c.AuthProof == nil { + return nil, fmt.Errorf("cannot create channel announcement " + + "without auth proof") + } + + chanID := lnwire.NewShortChanIDFromInt(c.ChannelID) + chanAnn := &lnwire.ChannelAnnouncement1{ + ShortChannelID: chanID, + NodeID1: c.NodeKey1Bytes, + NodeID2: c.NodeKey2Bytes, + ChainHash: c.ChainHash, + BitcoinKey1: c.BitcoinKey1Bytes, + BitcoinKey2: c.BitcoinKey2Bytes, + Features: c.Features.RawFeatureVector, + ExtraOpaqueData: c.ExtraOpaqueData, + } + + var err error + chanAnn.NodeSig1, err = lnwire.NewSigFromECDSARawSignature( + c.AuthProof.NodeSig1(), + ) + if err != nil { + return nil, err + } + + chanAnn.NodeSig2, err = lnwire.NewSigFromECDSARawSignature( + c.AuthProof.NodeSig2(), + ) + if err != nil { + return nil, err + } + + chanAnn.BitcoinSig1, err = lnwire.NewSigFromECDSARawSignature( + c.AuthProof.BitcoinSig1(), + ) + if err != nil { + return nil, err + } + + chanAnn.BitcoinSig2, err = lnwire.NewSigFromECDSARawSignature( + c.AuthProof.BitcoinSig2(), + ) + if err != nil { + return nil, err + } + + return chanAnn, nil +} diff --git a/netann/channel_announcement.go b/netann/channel_announcement.go index fee3c5abfa8..14f65c3f549 100644 --- a/netann/channel_announcement.go +++ b/netann/channel_announcement.go @@ -34,48 +34,14 @@ const ( // function is used to transform out database structs into the corresponding wire // structs for announcing new channels to other peers, or simply syncing up a // peer's initial routing table upon connect. -func CreateChanAnnouncement(chanProof *models.ChannelAuthProof, - chanInfo *models.ChannelEdgeInfo, +func CreateChanAnnouncement(chanInfo *models.ChannelEdgeInfo, e1, e2 *models.ChannelEdgePolicy) (*lnwire.ChannelAnnouncement1, *lnwire.ChannelUpdate1, *lnwire.ChannelUpdate1, error) { // First, using the parameters of the channel, along with the channel - // authentication chanProof, we'll create re-create the original + // authentication proof, we'll create re-create the original // authenticated channel announcement. - chanID := lnwire.NewShortChanIDFromInt(chanInfo.ChannelID) - chanAnn := &lnwire.ChannelAnnouncement1{ - ShortChannelID: chanID, - NodeID1: chanInfo.NodeKey1Bytes, - NodeID2: chanInfo.NodeKey2Bytes, - ChainHash: chanInfo.ChainHash, - BitcoinKey1: chanInfo.BitcoinKey1Bytes, - BitcoinKey2: chanInfo.BitcoinKey2Bytes, - Features: chanInfo.Features.RawFeatureVector, - ExtraOpaqueData: chanInfo.ExtraOpaqueData, - } - - var err error - chanAnn.BitcoinSig1, err = lnwire.NewSigFromECDSARawSignature( - chanProof.BitcoinSig1(), - ) - if err != nil { - return nil, nil, nil, err - } - chanAnn.BitcoinSig2, err = lnwire.NewSigFromECDSARawSignature( - chanProof.BitcoinSig2(), - ) - if err != nil { - return nil, nil, nil, err - } - chanAnn.NodeSig1, err = lnwire.NewSigFromECDSARawSignature( - chanProof.NodeSig1(), - ) - if err != nil { - return nil, nil, nil, err - } - chanAnn.NodeSig2, err = lnwire.NewSigFromECDSARawSignature( - chanProof.NodeSig2(), - ) + chanAnn, err := chanInfo.ToChannelAnnouncement() if err != nil { return nil, nil, nil, err } diff --git a/netann/channel_announcement_test.go b/netann/channel_announcement_test.go index 2bf64b47e71..f5f8aa0923d 100644 --- a/netann/channel_announcement_test.go +++ b/netann/channel_announcement_test.go @@ -62,9 +62,7 @@ func TestCreateChanAnnouncement(t *testing.T) { models.WithFeatures(features), ) require.NoError(t, err) - chanAnn, _, _, err := CreateChanAnnouncement( - chanProof, chanInfo, nil, nil, - ) + chanAnn, _, _, err := CreateChanAnnouncement(chanInfo, nil, nil) require.NoError(t, err, "unable to create channel announcement") assert.Equal(t, chanAnn, expChanAnn) From fbe4437ce2ffc0b7198820df29093db575bdcad5 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 19 Nov 2025 13:11:02 +0200 Subject: [PATCH 09/11] graph/db: make some ChannelEdgeInfo fields optional Since not all will be required for V2 channels. Wrap BitcoinKey1Bytes and BitcoinKey2Bytes in fn.Option since these fields are only required for v1 channel announcements. V2 channels use taproot where the node keys serve as the bitcoin keys. NewV1Channel constructor wraps the bitcoin keys with fn.Some(). All access sites updated to unwrap the options, using UnwrapOr for non-critical paths and UnwrapOrErr where the keys must be present (e.g., KV serialization, ToChannelAnnouncement). --- graph/db/graph_test.go | 13 +++++++--- graph/db/kv_store.go | 38 +++++++++++++++++++++++----- graph/db/models/channel_edge_info.go | 27 +++++++++++++++----- graph/db/sql_store.go | 22 +++++++++------- 4 files changed, 73 insertions(+), 27 deletions(-) diff --git a/graph/db/graph_test.go b/graph/db/graph_test.go index ea3dec08f60..1f8ca8ae994 100644 --- a/graph/db/graph_test.go +++ b/graph/db/graph_test.go @@ -788,10 +788,14 @@ func assertEdgeInfoEqual(t *testing.T, e1 *models.ChannelEdgeInfo, if !bytes.Equal(e1.NodeKey2Bytes[:], e2.NodeKey2Bytes[:]) { t.Fatalf("nodekey2 doesn't match") } - if !bytes.Equal(e1.BitcoinKey1Bytes[:], e2.BitcoinKey1Bytes[:]) { + btcKey1E1 := e1.BitcoinKey1Bytes.UnwrapOr(route.Vertex{}) + btcKey1E2 := e2.BitcoinKey1Bytes.UnwrapOr(route.Vertex{}) + if !bytes.Equal(btcKey1E1[:], btcKey1E2[:]) { t.Fatalf("bitcoinkey1 doesn't match") } - if !bytes.Equal(e1.BitcoinKey2Bytes[:], e2.BitcoinKey2Bytes[:]) { + btcKey2E1 := e1.BitcoinKey2Bytes.UnwrapOr(route.Vertex{}) + btcKey2E2 := e2.BitcoinKey2Bytes.UnwrapOr(route.Vertex{}) + if !bytes.Equal(btcKey2E1[:], btcKey2E2[:]) { t.Fatalf("bitcoinkey2 doesn't match") } @@ -2001,9 +2005,10 @@ func TestGraphPruning(t *testing.T) { t.Fatalf("unable to add node: %v", err) } + btcKey1 := edgeInfo.BitcoinKey1Bytes.UnwrapOr(route.Vertex{}) + btcKey2 := edgeInfo.BitcoinKey2Bytes.UnwrapOr(route.Vertex{}) pkScript, err := genMultiSigP2WSH( - edgeInfo.BitcoinKey1Bytes[:], - edgeInfo.BitcoinKey2Bytes[:], + btcKey1[:], btcKey2[:], ) if err != nil { t.Fatalf("unable to gen multi-sig p2wsh: %v", err) diff --git a/graph/db/kv_store.go b/graph/db/kv_store.go index 8a37f44d8fd..3ab38d74347 100644 --- a/graph/db/kv_store.go +++ b/graph/db/kv_store.go @@ -4113,9 +4113,14 @@ func (c *KVStore) ChannelView() ([]EdgePoint, error) { return err } + btcKey1 := edgeInfo.BitcoinKey1Bytes.UnwrapOr( + route.Vertex{}, + ) + btcKey2 := edgeInfo.BitcoinKey2Bytes.UnwrapOr( + route.Vertex{}, + ) pkScript, err := genMultiSigP2WSH( - edgeInfo.BitcoinKey1Bytes[:], - edgeInfo.BitcoinKey2Bytes[:], + btcKey1[:], btcKey2[:], ) if err != nil { return err @@ -4729,10 +4734,24 @@ func putChanEdgeInfo(edgeIndex kvdb.RwBucket, if _, err := b.Write(edgeInfo.NodeKey2Bytes[:]); err != nil { return err } - if _, err := b.Write(edgeInfo.BitcoinKey1Bytes[:]); err != nil { + + btc1Key, err := edgeInfo.BitcoinKey1Bytes.UnwrapOrErr( + fmt.Errorf("edge missing bitcoin key 1"), + ) + if err != nil { + return err + } + btc2Key, err := edgeInfo.BitcoinKey2Bytes.UnwrapOrErr( + fmt.Errorf("edge missing bitcoin key 2"), + ) + if err != nil { return err } - if _, err := b.Write(edgeInfo.BitcoinKey2Bytes[:]); err != nil { + + if _, err := b.Write(btc1Key[:]); err != nil { + return err + } + if _, err := b.Write(btc2Key[:]); err != nil { return err } @@ -4770,7 +4789,7 @@ func putChanEdgeInfo(edgeIndex kvdb.RwBucket, if err := WriteOutpoint(&b, &edgeInfo.ChannelPoint); err != nil { return err } - err := binary.Write(&b, byteOrder, uint64(edgeInfo.Capacity)) + err = binary.Write(&b, byteOrder, uint64(edgeInfo.Capacity)) if err != nil { return err } @@ -4820,12 +4839,17 @@ func deserializeChanEdgeInfo(r io.Reader) (*models.ChannelEdgeInfo, error) { if _, err := io.ReadFull(r, edgeInfo.NodeKey2Bytes[:]); err != nil { return nil, err } - if _, err := io.ReadFull(r, edgeInfo.BitcoinKey1Bytes[:]); err != nil { + + var btcKey1, btcKey2 route.Vertex + if _, err := io.ReadFull(r, btcKey1[:]); err != nil { return nil, err } - if _, err := io.ReadFull(r, edgeInfo.BitcoinKey2Bytes[:]); err != nil { + edgeInfo.BitcoinKey1Bytes = fn.Some(btcKey1) + + if _, err := io.ReadFull(r, btcKey2[:]); err != nil { return nil, err } + edgeInfo.BitcoinKey2Bytes = fn.Some(btcKey2) featureBytes, err := wire.ReadVarBytes(r, 0, 900, "features") if err != nil { diff --git a/graph/db/models/channel_edge_info.go b/graph/db/models/channel_edge_info.go index 9c9a379e904..ad0bf708044 100644 --- a/graph/db/models/channel_edge_info.go +++ b/graph/db/models/channel_edge_info.go @@ -41,10 +41,10 @@ type ChannelEdgeInfo struct { nodeKey2 *btcec.PublicKey // BitcoinKey1Bytes is the raw public key of the first node. - BitcoinKey1Bytes route.Vertex + BitcoinKey1Bytes fn.Option[route.Vertex] // BitcoinKey2Bytes is the raw public key of the first node. - BitcoinKey2Bytes route.Vertex + BitcoinKey2Bytes fn.Option[route.Vertex] // Features is the list of protocol features supported by this channel // edge. @@ -142,8 +142,8 @@ func NewV1Channel(chanID uint64, chainHash chainhash.Hash, node1, Version: lnwire.GossipVersion1, NodeKey1Bytes: node1, NodeKey2Bytes: node2, - BitcoinKey1Bytes: v1Fields.BitcoinKey1Bytes, - BitcoinKey2Bytes: v1Fields.BitcoinKey2Bytes, + BitcoinKey1Bytes: fn.Some(v1Fields.BitcoinKey1Bytes), + BitcoinKey2Bytes: fn.Some(v1Fields.BitcoinKey2Bytes), ChannelID: chanID, ChainHash: chainHash, Features: lnwire.EmptyFeatureVector(), @@ -240,19 +240,32 @@ func (c *ChannelEdgeInfo) ToChannelAnnouncement() ( "without auth proof") } + btc1, err := c.BitcoinKey1Bytes.UnwrapOrErr( + fmt.Errorf("bitcoin key 1 missing for v1 channel announcement"), + ) + if err != nil { + return nil, err + } + + btc2, err := c.BitcoinKey2Bytes.UnwrapOrErr( + fmt.Errorf("bitcoin key 2 missing for v1 channel announcement"), + ) + if err != nil { + return nil, err + } + chanID := lnwire.NewShortChanIDFromInt(c.ChannelID) chanAnn := &lnwire.ChannelAnnouncement1{ ShortChannelID: chanID, NodeID1: c.NodeKey1Bytes, NodeID2: c.NodeKey2Bytes, ChainHash: c.ChainHash, - BitcoinKey1: c.BitcoinKey1Bytes, - BitcoinKey2: c.BitcoinKey2Bytes, + BitcoinKey1: btc1, + BitcoinKey2: btc2, Features: c.Features.RawFeatureVector, ExtraOpaqueData: c.ExtraOpaqueData, } - var err error chanAnn.NodeSig1, err = lnwire.NewSigFromECDSARawSignature( c.AuthProof.NodeSig1(), ) diff --git a/graph/db/sql_store.go b/graph/db/sql_store.go index 3abf2584070..7baf78d2412 100644 --- a/graph/db/sql_store.go +++ b/graph/db/sql_store.go @@ -4164,15 +4164,19 @@ func insertChannel(ctx context.Context, db SQLQueries, } createParams := sqlc.CreateChannelParams{ - Version: int16(v), - Scid: channelIDToBytes(edge.ChannelID), - NodeID1: node1DBID, - NodeID2: node2DBID, - Outpoint: edge.ChannelPoint.String(), - Capacity: capacity, - BitcoinKey1: edge.BitcoinKey1Bytes[:], - BitcoinKey2: edge.BitcoinKey2Bytes[:], - } + Version: int16(v), + Scid: channelIDToBytes(edge.ChannelID), + NodeID1: node1DBID, + NodeID2: node2DBID, + Outpoint: edge.ChannelPoint.String(), + Capacity: capacity, + } + edge.BitcoinKey1Bytes.WhenSome(func(vertex route.Vertex) { + createParams.BitcoinKey1 = vertex[:] + }) + edge.BitcoinKey2Bytes.WhenSome(func(vertex route.Vertex) { + createParams.BitcoinKey2 = vertex[:] + }) if edge.AuthProof != nil { proof := edge.AuthProof From 690f1a0abeb9cb15d00eee524fb9e99723a9c0fa Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 19 Nov 2025 13:15:19 +0200 Subject: [PATCH 10/11] graph/db: add FundingPKScript method on ChannelEdgeInfo Add FundingPKScript() method that returns the funding output's pkScript for the channel. The implementation is version-aware: - V1: generates a 2-of-2 multisig P2WSH script from the two bitcoin keys - V2: will use taproot script (to be implemented) This encapsulates the script generation logic and makes it clear which bitcoin keys are being used. Replaces direct calls to genMultiSigP2WSH with the cleaner method call. --- graph/db/graph_test.go | 11 +++------- graph/db/kv_store.go | 10 +-------- graph/db/models/channel_edge_info.go | 33 ++++++++++++++++++++++++++++ graph/db/sql_store.go | 1 + 4 files changed, 38 insertions(+), 17 deletions(-) diff --git a/graph/db/graph_test.go b/graph/db/graph_test.go index 1f8ca8ae994..72b200d554c 100644 --- a/graph/db/graph_test.go +++ b/graph/db/graph_test.go @@ -2005,14 +2005,9 @@ func TestGraphPruning(t *testing.T) { t.Fatalf("unable to add node: %v", err) } - btcKey1 := edgeInfo.BitcoinKey1Bytes.UnwrapOr(route.Vertex{}) - btcKey2 := edgeInfo.BitcoinKey2Bytes.UnwrapOr(route.Vertex{}) - pkScript, err := genMultiSigP2WSH( - btcKey1[:], btcKey2[:], - ) - if err != nil { - t.Fatalf("unable to gen multi-sig p2wsh: %v", err) - } + pkScript, err := edgeInfo.FundingPKScript() + require.NoError(t, err) + edgePoints = append(edgePoints, EdgePoint{ FundingPkScript: pkScript, OutPoint: op, diff --git a/graph/db/kv_store.go b/graph/db/kv_store.go index 3ab38d74347..b801d6780b1 100644 --- a/graph/db/kv_store.go +++ b/graph/db/kv_store.go @@ -4113,15 +4113,7 @@ func (c *KVStore) ChannelView() ([]EdgePoint, error) { return err } - btcKey1 := edgeInfo.BitcoinKey1Bytes.UnwrapOr( - route.Vertex{}, - ) - btcKey2 := edgeInfo.BitcoinKey2Bytes.UnwrapOr( - route.Vertex{}, - ) - pkScript, err := genMultiSigP2WSH( - btcKey1[:], btcKey2[:], - ) + pkScript, err := edgeInfo.FundingPKScript() if err != nil { return err } diff --git a/graph/db/models/channel_edge_info.go b/graph/db/models/channel_edge_info.go index ad0bf708044..05573fb1d1a 100644 --- a/graph/db/models/channel_edge_info.go +++ b/graph/db/models/channel_edge_info.go @@ -9,6 +9,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/fn/v2" + "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" ) @@ -221,6 +222,38 @@ func (c *ChannelEdgeInfo) OtherNodeKeyBytes(thisNodeKey []byte) ( } } +// FundingPKScript returns the funding output's pkScript for the channel. +func (c *ChannelEdgeInfo) FundingPKScript() ([]byte, error) { + switch c.Version { + case lnwire.GossipVersion1: + btc1Key, err := c.BitcoinKey1Bytes.UnwrapOrErr( + fmt.Errorf("missing bitcoin key 1"), + ) + if err != nil { + return nil, err + } + btc2Key, err := c.BitcoinKey2Bytes.UnwrapOrErr( + fmt.Errorf("missing bitcoin key 2"), + ) + if err != nil { + return nil, err + } + + witnessScript, err := input.GenMultiSigScript( + btc1Key[:], btc2Key[:], + ) + if err != nil { + return nil, err + } + + return input.WitnessScriptHash(witnessScript) + + default: + return nil, fmt.Errorf("unsupported channel version: %d", + c.Version) + } +} + // ToChannelAnnouncement converts the ChannelEdgeInfo to a // lnwire.ChannelAnnouncement1 message. Returns an error if AuthProof is nil // or if the version is not v1. diff --git a/graph/db/sql_store.go b/graph/db/sql_store.go index 7baf78d2412..9f0b3f7e1e4 100644 --- a/graph/db/sql_store.go +++ b/graph/db/sql_store.go @@ -2755,6 +2755,7 @@ func (s *SQLStore) ChannelView() ([]EdgePoint, error) { handleChannel := func(_ context.Context, channel sqlc.ListChannelsPaginatedRow) error { + // TODO(elle): update to handle V2 channels. pkScript, err := genMultiSigP2WSH( channel.BitcoinKey1, channel.BitcoinKey2, ) From 908c2ccb43cbef6081f235ebfe32e8b19443ca0d Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 19 Nov 2025 19:04:28 +0200 Subject: [PATCH 11/11] docs: update release notes Update the Database section to reference both PRs that prepare the graph DB for gossip v2 support: - PR 10339: node handling - PR 10379: channel handling (this PR) --- docs/release-notes/release-notes-0.21.0.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/release-notes-0.21.0.md b/docs/release-notes/release-notes-0.21.0.md index f26ca933d97..9d24b36dc1e 100644 --- a/docs/release-notes/release-notes-0.21.0.md +++ b/docs/release-notes/release-notes-0.21.0.md @@ -68,8 +68,9 @@ * Freeze the [graph SQL migration code](https://github.com/lightningnetwork/lnd/pull/10338) to prevent the need for maintenance as the sqlc code evolves. -* Prepare the graph DB for handling gossip [V2 - nodes](https://github.com/lightningnetwork/lnd/pull/10339). +* Prepare the graph DB for handling gossip V2 + nodes and channels [1](https://github.com/lightningnetwork/lnd/pull/10339) + [2](https://github.com/lightningnetwork/lnd/pull/10379). ## Code Health