What changed, and why it matters
This commit updates LND's internal graph database code so it can recognize and store a new type of Lightning node announcement (called 'v2 nodes'). It does not add any user-facing feature or change network behavior yet; it is plumbing work to support a future protocol version. There is no obvious security bug in the change itself.
Treat as routine feature plumbing. Review the follow-up commits that actually enable v2 node CRUD and network handling, since security-relevant behavior (validation of v2 signatures, feature bits, alias/color handling, etc.) will likely appear there. No immediate patch or mitigation is indicated by this commit alone.
Security signals we found
Version check broadened from single hard-coded value to an allow-list (isKnownGossipVersion)
New v2 node fields (BlockHeight, ExtraSignedFields) introduced in DB read/write paths
Source-node upsert now keyed by node.Version rather than always GossipVersion1
Commit message notes this is preparatory work; no v2 node creation logic is enabled yet
Evidence from the diff
The patch modifies graph/db/sql_store.go to generalize node serialization/deserialization from hard-coded GossipVersion1 to also accept GossipVersion2. Key changes: SetSourceNode now uses node.Version instead of a constant; buildNodeWithBatchData validates versions via isKnownGossipVersion, parses pubkeys with route.NewVertexFromBytes, and handles v2-specific BlockHeight/ExtraSignedFields; upsertNodeAncillaryData and populateNodeParams branch on version; upsertNode rejects unknown versions. The commit message explicitly states no logic currently adds v2 nodes and that follow-up commits will enable CRUD testing.
Changed components
graph/db/sql_store.goSQL node upsert/read helpers (buildNode, buildNodeUpsertParams, buildSourceNodeUpsertParams, populateNodeParams, upsertNodeAncillaryData)Source node setting (SetSourceNode)Inspect captured patch +76 / −37
diff --git a/graph/db/sql_store.go b/graph/db/sql_store.go
index 3e99483..55efabf 100644
--- a/graph/db/sql_store.go
+++ b/graph/db/sql_store.go
@@ -538,7 +538,7 @@ func (s *SQLStore) SetSourceNode(ctx context.Context,
// Make sure that if a source node for this version is already
// set, then the ID is the same as the one we are about to set.
dbSourceNodeID, _, err := s.getSourceNode(
- ctx, db, lnwire.GossipVersion1,
+ ctx, db, node.Version,
)
if err != nil && !errors.Is(err, ErrSourceNodeNotSet) {
return fmt.Errorf("unable to fetch source node: %w",
@@ -3489,6 +3489,19 @@ func buildNode(ctx context.Context, cfg *sqldb.QueryConfig, db SQLQueries,
return buildNodeWithBatchData(dbNode, data)
}
+// isKnownGossipVersion checks whether the provided gossip version is known
+// and supported.
+func isKnownGossipVersion(v lnwire.GossipVersion) bool {
+ switch v {
+ case lnwire.GossipVersion1:
+ return true
+ case lnwire.GossipVersion2:
+ return true
+ default:
+ return false
+ }
+}
+
// buildNodeWithBatchData builds a models.Node instance
// from the provided sqlc.GraphNode and batchNodeData. If the node does have
// features/addresses/extra fields, then the corresponding fields are expected
@@ -3496,15 +3509,18 @@ func buildNode(ctx context.Context, cfg *sqldb.QueryConfig, db SQLQueries,
func buildNodeWithBatchData(dbNode sqlc.GraphNode,
batchData *batchNodeData) (*models.Node, error) {
- if dbNode.Version != int16(lnwire.GossipVersion1) {
- return nil, fmt.Errorf("unsupported node version: %d",
- dbNode.Version)
+ v := lnwire.GossipVersion(dbNode.Version)
+
+ if !isKnownGossipVersion(v) {
+ return nil, fmt.Errorf("unknown node version: %d", v)
}
- var pub [33]byte
- copy(pub[:], dbNode.PubKey)
+ pub, err := route.NewVertexFromBytes(dbNode.PubKey)
+ if err != nil {
+ return nil, fmt.Errorf("unable to parse pubkey: %w", err)
+ }
- node := models.NewV1ShellNode(pub)
+ node := models.NewShellNode(v, pub)
if len(dbNode.Signature) == 0 {
return node, nil
@@ -3518,8 +3534,10 @@ func buildNodeWithBatchData(dbNode sqlc.GraphNode,
if dbNode.LastUpdate.Valid {
node.LastUpdate = time.Unix(dbNode.LastUpdate.Int64, 0)
}
+ if dbNode.BlockHeight.Valid {
+ node.LastBlockHeight = uint32(dbNode.BlockHeight.Int64)
+ }
- var err error
if dbNode.Color.Valid {
nodeColor, err := DecodeHexColor(dbNode.Color.String)
if err != nil {
@@ -3551,13 +3569,19 @@ func buildNodeWithBatchData(dbNode sqlc.GraphNode,
// Use preloaded extra fields.
if extraFields, exists := batchData.extraFields[dbNode.ID]; exists {
- recs, err := lnwire.CustomRecords(extraFields).Serialize()
- if err != nil {
- return nil, fmt.Errorf("unable to serialize extra "+
- "signed fields: %w", err)
- }
- if len(recs) != 0 {
- node.ExtraOpaqueData = recs
+ if v == lnwire.GossipVersion1 {
+ records := lnwire.CustomRecords(extraFields)
+ recs, err := records.Serialize()
+ if err != nil {
+ return nil, fmt.Errorf("unable to serialize "+
+ "extra signed fields: %w", err)
+ }
+
+ if len(recs) != 0 {
+ node.ExtraOpaqueData = recs
+ }
+ } else if len(extraFields) > 0 {
+ node.ExtraSignedFields = extraFields
}
}
@@ -3637,10 +3661,13 @@ func upsertNodeAncillaryData(ctx context.Context, db SQLQueries,
// Convert the flat extra opaque data into a map of TLV types to
// values.
- extra, err := marshalExtraOpaqueData(node.ExtraOpaqueData)
- if err != nil {
- return fmt.Errorf("unable to marshal extra opaque data: %w",
- err)
+ extra := node.ExtraSignedFields
+ if node.Version == lnwire.GossipVersion1 {
+ extra, err = marshalExtraOpaqueData(node.ExtraOpaqueData)
+ if err != nil {
+ return fmt.Errorf("unable to marshal extra opaque "+
+ "data: %w", err)
+ }
}
// Update the node's extra signed fields.
@@ -3655,34 +3682,39 @@ func upsertNodeAncillaryData(ctx context.Context, db SQLQueries,
// populateNodeParams populates the common node parameters from a models.Node.
// This is a helper for building UpsertNodeParams and UpsertSourceNodeParams.
func populateNodeParams(node *models.Node,
- setParams func(lastUpdate sql.NullInt64, alias,
+ setParams func(lastUpdate, lastBlockHeight sql.NullInt64, alias,
colorStr sql.NullString, signature []byte)) error {
if !node.HaveAnnouncement() {
return nil
}
+ var (
+ alias, colorStr sql.NullString
+ lastUpdate, lastBlockHeight sql.NullInt64
+ )
+ node.Color.WhenSome(func(rgba color.RGBA) {
+ colorStr = sqldb.SQLStrValid(EncodeHexColor(rgba))
+ })
+ node.Alias.WhenSome(func(s string) {
+ alias = sqldb.SQLStrValid(s)
+ })
+
switch node.Version {
case lnwire.GossipVersion1:
- lastUpdate := sqldb.SQLInt64(node.LastUpdate.Unix())
- var alias, colorStr sql.NullString
-
- node.Color.WhenSome(func(rgba color.RGBA) {
- colorStr = sqldb.SQLStrValid(EncodeHexColor(rgba))
- })
- node.Alias.WhenSome(func(s string) {
- alias = sqldb.SQLStrValid(s)
- })
-
- setParams(lastUpdate, alias, colorStr, node.AuthSigBytes)
+ lastUpdate = sqldb.SQLInt64(node.LastUpdate.Unix())
case lnwire.GossipVersion2:
- // No-op for now.
+ lastBlockHeight = sqldb.SQLInt64(int64(node.LastBlockHeight))
default:
return fmt.Errorf("unknown gossip version: %d", node.Version)
}
+ setParams(
+ lastUpdate, lastBlockHeight, alias, colorStr, node.AuthSigBytes,
+ )
+
return nil
}
@@ -3690,20 +3722,22 @@ func populateNodeParams(node *models.Node,
// strict UpsertNode query (requires timestamp to be increasing).
func buildNodeUpsertParams(node *models.Node) (sqlc.UpsertNodeParams, error) {
params := sqlc.UpsertNodeParams{
- Version: int16(lnwire.GossipVersion1),
+ Version: int16(node.Version),
PubKey: node.PubKeyBytes[:],
}
err := populateNodeParams(
- node, func(lastUpdate sql.NullInt64, alias,
+ node, func(lastUpdate, lastBlockHeight sql.NullInt64, alias,
colorStr sql.NullString,
signature []byte) {
params.LastUpdate = lastUpdate
+ params.BlockHeight = lastBlockHeight
params.Alias = alias
params.Color = colorStr
params.Signature = signature
- })
+ },
+ )
return params, err
}
@@ -3714,14 +3748,15 @@ func buildSourceNodeUpsertParams(node *models.Node) (
sqlc.UpsertSourceNodeParams, error) {
params := sqlc.UpsertSourceNodeParams{
- Version: int16(lnwire.GossipVersion1),
+ Version: int16(node.Version),
PubKey: node.PubKeyBytes[:],
}
err := populateNodeParams(
- node, func(lastUpdate sql.NullInt64, alias,
+ node, func(lastUpdate, lastBlock sql.NullInt64, alias,
colorStr sql.NullString, signature []byte) {
+ params.BlockHeight = lastBlock
params.LastUpdate = lastUpdate
params.Alias = alias
params.Color = colorStr
@@ -3772,6 +3807,10 @@ func upsertSourceNode(ctx context.Context, db SQLQueries,
func upsertNode(ctx context.Context, db SQLQueries,
node *models.Node) (int64, error) {
+ if !isKnownGossipVersion(node.Version) {
+ return 0, fmt.Errorf("unknown gossip version: %d", node.Version)
+ }
+
params, err := buildNodeUpsertParams(node)
if err != nil {
return 0, err
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.