graph/db: version ChannelView and add v2 queries
What changed, and why it matters
This commit extends the Lightning Network graph database so it can separately handle two different channel announcement formats (v1 and v2). It adds version-aware queries and makes the channel-view lookup respect the requested gossip version. There is no obvious security bug in the patch; it appears to be a feature/refactoring change to support dual-version gossip. The most notable security-relevant detail is that the older KV database backend now refuses v2 channel-view requests, while the newer SQL backend can serve both versions.
Treat as a normal feature/refactoring review. Verify that KVStore's ErrVersionNotSupportedForKVDB is handled by all callers of ChannelView, and that SQL version branching does not accidentally mix v1 and v2 data. Confirm the new unit test covers both v1 and v2 paths as claimed. No immediate security patch is indicated by the diff alone.
Security signals we found
API versioning change that prevents KV backend from serving v2 channel views (defensive fail-closed behavior)
New SQL queries filter by gossip version and public-channel criteria (signature presence)
ChannelView now returns version-scoped results instead of an unversioned global view
No input validation, authorization, or cryptographic changes observed
No vendor disclosure or CVE references present in commit or supplied materials
Evidence from the diff
The commit versions the ChannelView method across the Store interface, KVStore, SQLStore, and ChannelGraph/VersionedGraph wrappers. It introduces three new SQL queries (GetPublicV1ChannelsBySCID, GetPublicV2ChannelsBySCID, ListChannelsPaginatedV2) and updates FilterChannelRange and ChannelView to branch on gossip version. KVStore returns ErrVersionNotSupportedForKVDB for non-v1 ChannelView. SQLStore now computes funding scripts from stored keys for v1 and uses the stored funding_pk_script for v2. Tests are updated to use VersionedGraph wrappers and a new TestVersionedDBs/channel_view case is added.
Changed components
graph/db/interfaces.gograph/db/kv_store.gograph/db/sql_store.gograph/db/graph.gograph/builder.gosqldb/sqlc/graph.sql.gosqldb/sqlc/querier.gosqldb/sqlc/queries/graph.sqlInspect captured patch +383 / −170
diff --git a/graph/builder.go b/graph/builder.go
index d63cab5..614c110 100644
--- a/graph/builder.go
+++ b/graph/builder.go
@@ -236,7 +236,7 @@ func (b *Builder) Start() error {
// FilteredChainView instance. We do this before, as otherwise
// we may miss on-chain events as the filter hasn't properly
// been applied.
- channelView, err := b.cfg.Graph.ChannelView(context.TODO())
+ channelView, err := b.v1Graph.ChannelView(context.TODO())
if err != nil && !errors.Is(
err, graphdb.ErrGraphNoEdgesFound,
) {
diff --git a/graph/db/benchmark_test.go b/graph/db/benchmark_test.go
index 4ab0819..19d6a13 100644
--- a/graph/db/benchmark_test.go
+++ b/graph/db/benchmark_test.go
@@ -348,8 +348,9 @@ func TestPopulateDBs(t *testing.T) {
// graph.
countNodes := func(graph *ChannelGraph) int {
numNodes := 0
- err := graph.ForEachNode(
- ctx, lnwire.GossipVersion1,
+ v1Graph := NewVersionedGraph(graph, lnwire.GossipVersion1)
+ err := v1Graph.ForEachNode(
+ ctx,
func(node *models.Node) error {
numNodes++
@@ -456,8 +457,8 @@ func syncGraph(t *testing.T, src, dest *ChannelGraph) {
}
var wgNodes sync.WaitGroup
- v1 := lnwire.GossipVersion1
- err := src.ForEachNode(ctx, v1, func(node *models.Node) error {
+ v1Src := NewVersionedGraph(src, lnwire.GossipVersion1)
+ err := v1Src.ForEachNode(ctx, func(node *models.Node) error {
wgNodes.Add(1)
go func() {
defer wgNodes.Done()
diff --git a/graph/db/graph.go b/graph/db/graph.go
index 072cd9b..5e74bfc 100644
--- a/graph/db/graph.go
+++ b/graph/db/graph.go
@@ -625,14 +625,6 @@ func (c *ChannelGraph) ForEachNodeChannel(ctx context.Context,
return c.db.ForEachNodeChannel(ctx, v, nodePub, cb, reset)
}
-// ForEachNode iterates through all stored vertices/nodes in the graph.
-func (c *ChannelGraph) ForEachNode(ctx context.Context,
- v lnwire.GossipVersion, cb func(*models.Node) error,
- reset func()) error {
-
- return c.db.ForEachNode(ctx, v, cb, reset)
-}
-
// ForEachNodeCacheable iterates through all stored vertices/nodes in the graph.
func (c *ChannelGraph) ForEachNodeCacheable(ctx context.Context,
v lnwire.GossipVersion, cb func(route.Vertex,
@@ -658,14 +650,6 @@ func (c *ChannelGraph) HasV1Node(ctx context.Context,
return c.db.HasV1Node(ctx, nodePub)
}
-// IsPublicNode determines whether the node is seen as public in the graph for
-// the given gossip version.
-func (c *ChannelGraph) IsPublicNode(ctx context.Context,
- v lnwire.GossipVersion, pubKey [33]byte) (bool, error) {
-
- return c.db.IsPublicNode(ctx, v, pubKey)
-}
-
// ForEachChannel iterates through all channel edges stored within the graph.
func (c *ChannelGraph) ForEachChannel(ctx context.Context,
v lnwire.GossipVersion, cb func(*models.ChannelEdgeInfo,
@@ -772,27 +756,6 @@ func (c *ChannelGraph) FetchChannelEdgesByID(ctx context.Context,
)
}
-// ChannelView returns the verifiable edge information for each active channel.
-func (c *ChannelGraph) ChannelView(ctx context.Context) ([]EdgePoint, error) {
- return c.db.ChannelView(ctx)
-}
-
-// IsZombieEdge returns whether the edge is considered zombie for the given
-// gossip version.
-func (c *ChannelGraph) IsZombieEdge(ctx context.Context,
- v lnwire.GossipVersion, chanID uint64) (bool, [33]byte, [33]byte,
- error) {
-
- return c.db.IsZombieEdge(ctx, v, chanID)
-}
-
-// NumZombies returns the current number of zombie channels in the graph.
-func (c *ChannelGraph) NumZombies(ctx context.Context,
- v lnwire.GossipVersion) (uint64, error) {
-
- return c.db.NumZombies(ctx, v)
-}
-
// PutClosedScid stores a SCID for a closed channel in the database.
func (c *ChannelGraph) PutClosedScid(ctx context.Context,
scid lnwire.ShortChannelID) error {
@@ -906,7 +869,7 @@ func (c *VersionedGraph) NodeUpdatesInHorizon(ctx context.Context,
func (c *VersionedGraph) ChannelView(ctx context.Context) ([]EdgePoint,
error) {
- return c.db.ChannelView(ctx)
+ return c.db.ChannelView(ctx, c.v)
}
// GraphSession provides the callback with access to a NodeTraverser instance
@@ -1017,23 +980,9 @@ func (c *VersionedGraph) SourceNode(ctx context.Context) (*models.Node,
func (c *VersionedGraph) DeleteChannelEdges(ctx context.Context,
strictZombiePruning, markZombie bool, chanIDs ...uint64) error {
- infos, err := c.db.DeleteChannelEdges(
+ return c.ChannelGraph.DeleteChannelEdges(
ctx, c.v, strictZombiePruning, markZombie, chanIDs...,
)
- if err != nil {
- return err
- }
-
- if c.graphCache != nil {
- for _, info := range infos {
- c.graphCache.RemoveChannel(
- info.NodeKey1Bytes, info.NodeKey2Bytes,
- info.ChannelID,
- )
- }
- }
-
- return err
}
// HasChannelEdge returns true if the database knows of a channel edge with the
diff --git a/graph/db/graph_test.go b/graph/db/graph_test.go
index 06eba01..501ff10 100644
--- a/graph/db/graph_test.go
+++ b/graph/db/graph_test.go
@@ -204,6 +204,10 @@ var versionedTests = []versionedTest{
name: "fetch chan infos",
test: testFetchChanInfos,
},
+ {
+ name: "channel view",
+ test: testChannelView,
+ },
}
// TestVersionedDBs runs various tests against both v1 and v2 versioned
@@ -2151,12 +2155,12 @@ func assertNumChans(t *testing.T, graph *ChannelGraph, n int) {
func assertNumNodes(t *testing.T, graph *ChannelGraph, n int) {
numNodes := 0
- err := graph.ForEachNode(t.Context(), lnwire.GossipVersion1,
- func(_ *models.Node) error {
- numNodes++
+ v1Graph := NewVersionedGraph(graph, lnwire.GossipVersion1)
+ err := v1Graph.ForEachNode(t.Context(), func(_ *models.Node) error {
+ numNodes++
- return nil
- }, func() {})
+ return nil
+ }, func() {})
require.NoError(t, err)
require.Equal(t, n, numNodes)
}
@@ -2275,9 +2279,11 @@ func TestGraphPruning(t *testing.T) {
require.NoError(t, graph.UpdateEdgePolicy(ctx, edge))
}
+ v1Graph := NewVersionedGraph(graph, lnwire.GossipVersion1)
+
// With all the channel points added, we'll consult the graph to ensure
// it has the same channel view as the one we just constructed.
- channelView, err := graph.ChannelView(ctx)
+ channelView, err := v1Graph.ChannelView(ctx)
require.NoError(t, err, "unable to get graph channel view")
assertChanViewEqual(t, channelView, edgePoints)
@@ -2304,7 +2310,7 @@ func TestGraphPruning(t *testing.T) {
assertNumChans(t, graph, 2)
// Those channels should also be missing from the channel view.
- channelView, err = graph.ChannelView(ctx)
+ channelView, err = v1Graph.ChannelView(ctx)
require.NoError(t, err, "unable to get graph channel view")
assertChanViewEqualChanPoints(t, channelView, channelPoints[2:])
@@ -2353,7 +2359,7 @@ func TestGraphPruning(t *testing.T) {
// Finally, the channel view at this point in the graph should now be
// completely empty. Those channels should also be missing from the
// channel view.
- channelView, err = graph.ChannelView(ctx)
+ channelView, err = v1Graph.ChannelView(ctx)
require.NoError(t, err, "unable to get graph channel view")
require.Empty(t, channelView)
}
@@ -2956,10 +2962,9 @@ func TestFilterKnownChanIDsZombieRevival(t *testing.T) {
scid3 = lnwire.ShortChannelID{BlockHeight: 3}
)
+ v1Graph := NewVersionedGraph(graph, lnwire.GossipVersion1)
isZombie := func(scid lnwire.ShortChannelID) bool {
- zombie, _, _, err := graph.IsZombieEdge(
- ctx, lnwire.GossipVersion1, scid.ToUint64(),
- )
+ zombie, _, _, err := v1Graph.IsZombieEdge(ctx, scid.ToUint64())
require.NoError(t, err)
return zombie
@@ -3830,6 +3835,52 @@ func testFetchChanInfos(t *testing.T, v lnwire.GossipVersion) {
}
}
+// testChannelView tests that ChannelView returns the correct edge points for
+// each active channel in the graph.
+func testChannelView(t *testing.T, v lnwire.GossipVersion) {
+ t.Parallel()
+ ctx := t.Context()
+
+ graph := NewVersionedGraph(MakeTestGraph(t), v)
+
+ // Initially the channel view should be empty.
+ channelView, err := graph.ChannelView(ctx)
+ require.NoError(t, err)
+ require.Empty(t, channelView)
+
+ // Add some nodes and a set of channels between them.
+ node1 := createTestVertex(t, v)
+ require.NoError(t, graph.AddNode(ctx, node1))
+ node2 := createTestVertex(t, v)
+ require.NoError(t, graph.AddNode(ctx, node2))
+
+ const numChans = 3
+ edgePoints := make([]EdgePoint, 0, numChans)
+ for i := 0; i < numChans; i++ {
+ edge, _ := createEdge(
+ v, uint32(i+1), 0, 0, uint32(i), node1, node2,
+ )
+ require.NoError(t, graph.AddChannelEdge(ctx, edge))
+
+ pkScript, err := edge.FundingPKScript()
+ require.NoError(t, err)
+
+ edgePoints = append(edgePoints, EdgePoint{
+ FundingPkScript: pkScript,
+ OutPoint: wire.OutPoint{
+ Hash: rev,
+ Index: uint32(i),
+ },
+ })
+ }
+
+ // Fetch the channel view and ensure it matches the expected edge
+ // points.
+ channelView, err = graph.ChannelView(ctx)
+ require.NoError(t, err)
+ assertChanViewEqual(t, channelView, edgePoints)
+}
+
// testIncompleteChannelPolicies tests that a channel that only has a policy
// specified on one end is properly returned in ForEachChannel calls from
// both sides.
@@ -4385,13 +4436,12 @@ func BenchmarkIsPublicNode(b *testing.B) {
// Use deterministic random number generator for reproducible results.
rng := prand.New(prand.NewSource(42))
+ v1Graph := NewVersionedGraph(graph, lnwire.GossipVersion1)
for b.Loop() {
// Query random nodes to avoid query caching and better
// represent real-world query patterns.
nodePub := nodes[rng.Intn(len(nodes))].PubKeyBytes
- _, err := graph.IsPublicNode(
- b.Context(), lnwire.GossipVersion1, nodePub,
- )
+ _, err := v1Graph.IsPublicNode(b.Context(), nodePub)
require.NoError(b, err)
}
}
@@ -4593,7 +4643,8 @@ func putSerializedPolicy(t *testing.T, db kvdb.Backend, from []byte,
func assertNumZombies(t *testing.T, graph *ChannelGraph, expZombies uint64) {
t.Helper()
- numZombies, err := graph.NumZombies(t.Context(), lnwire.GossipVersion1)
+ v1Graph := NewVersionedGraph(graph, lnwire.GossipVersion1)
+ numZombies, err := v1Graph.NumZombies(t.Context())
require.NoError(t, err, "unable to query number of zombies")
require.Equal(t, expZombies, numZombies)
}
@@ -4620,11 +4671,11 @@ func TestGraphZombieIndex(t *testing.T) {
)
require.NoError(t, graph.AddChannelEdge(ctx, edge))
+ v1Graph := NewVersionedGraph(graph, lnwire.GossipVersion1)
+
// Since the edge is known the graph and it isn't a zombie, IsZombieEdge
// should not report the channel as a zombie.
- isZombie, _, _, err := graph.IsZombieEdge(
- ctx, lnwire.GossipVersion1, edge.ChannelID,
- )
+ isZombie, _, _, err := v1Graph.IsZombieEdge(ctx, edge.ChannelID)
require.NoError(t, err)
require.False(t, isZombie)
assertNumZombies(t, graph, 0)
@@ -4635,8 +4686,8 @@ func TestGraphZombieIndex(t *testing.T) {
ctx, lnwire.GossipVersion1, false, true, edge.ChannelID,
)
require.NoError(t, err, "unable to mark edge as zombie")
- isZombie, pubKey1, pubKey2, err := graph.IsZombieEdge(
- ctx, lnwire.GossipVersion1, edge.ChannelID,
+ isZombie, pubKey1, pubKey2, err := v1Graph.IsZombieEdge(
+ ctx, edge.ChannelID,
)
require.NoError(t, err)
require.True(t, isZombie)
@@ -4658,9 +4709,7 @@ func TestGraphZombieIndex(t *testing.T) {
ErrZombieEdgeNotFound,
)
- isZombie, _, _, err = graph.IsZombieEdge(
- ctx, lnwire.GossipVersion1, edge.ChannelID,
- )
+ isZombie, _, _, err = v1Graph.IsZombieEdge(ctx, edge.ChannelID)
require.NoError(t, err)
require.False(t, isZombie)
@@ -4674,9 +4723,7 @@ func TestGraphZombieIndex(t *testing.T) {
)
require.NoError(t, err, "unable to mark edge as zombie")
- isZombie, _, _, err = graph.IsZombieEdge(
- ctx, lnwire.GossipVersion1, edge.ChannelID,
- )
+ isZombie, _, _, err = v1Graph.IsZombieEdge(ctx, edge.ChannelID)
require.NoError(t, err)
require.True(t, isZombie)
assertNumZombies(t, graph, 1)
diff --git a/graph/db/interfaces.go b/graph/db/interfaces.go
index 4d643b4..a725cb8 100644
--- a/graph/db/interfaces.go
+++ b/graph/db/interfaces.go
@@ -322,10 +322,12 @@ type Store interface { //nolint:interfacebloat
*models.ChannelEdgePolicy, error)
// ChannelView returns the verifiable edge information for each active
- // channel within the known channel graph. The set of UTXO's (along with
- // their scripts) returned are the ones that need to be watched on chain
- // to detect channel closes on the resident blockchain.
- ChannelView(ctx context.Context) ([]EdgePoint, error)
+ // channel within the known channel graph for the given gossip version.
+ // The set of UTXO's (along with their scripts) returned are the ones
+ // that need to be watched on chain to detect channel closes on the
+ // resident blockchain.
+ ChannelView(ctx context.Context, v lnwire.GossipVersion) ([]EdgePoint,
+ error)
// MarkEdgeZombie attempts to mark a channel identified by its channel
// ID as a zombie for the given gossip version. This method is used on
diff --git a/graph/db/kv_store.go b/graph/db/kv_store.go
index 1a8fd82..2a993d7 100644
--- a/graph/db/kv_store.go
+++ b/graph/db/kv_store.go
@@ -3,7 +3,6 @@ package graphdb
import (
"bytes"
"context"
- "crypto/sha256"
"encoding/binary"
"errors"
"fmt"
@@ -18,14 +17,12 @@ import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/chaincfg/chainhash"
- "github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/walletdb"
"github.com/lightningnetwork/lnd/aliasmgr"
"github.com/lightningnetwork/lnd/batch"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/graph/db/models"
- "github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
@@ -4197,26 +4194,6 @@ func (c *KVStore) IsPublicNode(_ context.Context, v lnwire.GossipVersion,
return nodeIsPublic, nil
}
-// genMultiSigP2WSH generates the p2wsh'd multisig script for 2 of 2 pubkeys.
-func genMultiSigP2WSH(aPub, bPub []byte) ([]byte, error) {
- witnessScript, err := input.GenMultiSigScript(aPub, bPub)
- if err != nil {
- return nil, err
- }
-
- // With the witness script generated, we'll now turn it into a p2wsh
- // script:
- // * OP_0 <sha256(script)>
- bldr := txscript.NewScriptBuilder(
- txscript.WithScriptAllocSize(input.P2WSHSize),
- )
- bldr.AddOp(txscript.OP_0)
- scriptHash := sha256.Sum256(witnessScript)
- bldr.AddData(scriptHash[:])
-
- return bldr.Script()
-}
-
// EdgePoint couples the outpoint of a channel with the funding script that it
// creates. The FilteredChainView will use this to watch for spends of this
// edge point on chain. We require both of these values as depending on the
@@ -4239,7 +4216,12 @@ func (e *EdgePoint) String() string {
// within the known channel graph. The set of UTXO's (along with their scripts)
// returned are the ones that need to be watched on chain to detect channel
// closes on the resident blockchain.
-func (c *KVStore) ChannelView(_ context.Context) ([]EdgePoint, error) {
+func (c *KVStore) ChannelView(_ context.Context,
+ v lnwire.GossipVersion) ([]EdgePoint, error) {
+
+ if v != lnwire.GossipVersion1 {
+ return nil, ErrVersionNotSupportedForKVDB
+ }
var edgePoints []EdgePoint
if err := kvdb.View(c.db, func(tx kvdb.RTx) error {
// We're going to iterate over the entire channel index, so
diff --git a/graph/db/sql_store.go b/graph/db/sql_store.go
index dc59159..0e39075 100644
--- a/graph/db/sql_store.go
+++ b/graph/db/sql_store.go
@@ -105,9 +105,11 @@ type SQLQueries interface {
ListChannelsWithPoliciesPaginated(ctx context.Context, arg sqlc.ListChannelsWithPoliciesPaginatedParams) ([]sqlc.ListChannelsWithPoliciesPaginatedRow, error)
ListChannelsWithPoliciesForCachePaginated(ctx context.Context, arg sqlc.ListChannelsWithPoliciesForCachePaginatedParams) ([]sqlc.ListChannelsWithPoliciesForCachePaginatedRow, error)
ListChannelsPaginated(ctx context.Context, arg sqlc.ListChannelsPaginatedParams) ([]sqlc.ListChannelsPaginatedRow, error)
+ ListChannelsPaginatedV2(ctx context.Context, arg sqlc.ListChannelsPaginatedV2Params) ([]sqlc.ListChannelsPaginatedV2Row, error)
GetChannelsByPolicyLastUpdateRange(ctx context.Context, arg sqlc.GetChannelsByPolicyLastUpdateRangeParams) ([]sqlc.GetChannelsByPolicyLastUpdateRangeRow, error)
GetChannelByOutpointWithPolicies(ctx context.Context, arg sqlc.GetChannelByOutpointWithPoliciesParams) (sqlc.GetChannelByOutpointWithPoliciesRow, error)
GetPublicV1ChannelsBySCID(ctx context.Context, arg sqlc.GetPublicV1ChannelsBySCIDParams) ([]sqlc.GraphChannel, error)
+ GetPublicV2ChannelsBySCID(ctx context.Context, arg sqlc.GetPublicV2ChannelsBySCIDParams) ([]sqlc.GraphChannel, error)
GetSCIDByOutpoint(ctx context.Context, arg sqlc.GetSCIDByOutpointParams) ([]byte, error)
DeleteChannels(ctx context.Context, ids []int64) error
@@ -1689,29 +1691,48 @@ func (s *SQLStore) FilterChannelRange(ctx context.Context,
// and add those timestamps to the collected channel.
channelsPerBlock := make(map[uint32][]ChannelUpdateInfo)
err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error {
- // TODO(elle): replace with a version-aware query.
- dbChans, err := db.GetPublicV1ChannelsBySCID(
- ctx, sqlc.GetPublicV1ChannelsBySCIDParams{
- StartScid: chanIDStart,
- EndScid: chanIDEnd,
- },
+ var (
+ dbChans []sqlc.GraphChannel
+ chanErr error
)
- if err != nil {
+
+ switch v {
+ case gossipV1:
+ dbChans, chanErr = db.GetPublicV1ChannelsBySCID(
+ ctx, sqlc.GetPublicV1ChannelsBySCIDParams{
+ StartScid: chanIDStart,
+ EndScid: chanIDEnd,
+ },
+ )
+ case gossipV2:
+ dbChans, chanErr = db.GetPublicV2ChannelsBySCID(
+ ctx, sqlc.GetPublicV2ChannelsBySCIDParams{
+ StartScid: chanIDStart,
+ EndScid: chanIDEnd,
+ },
+ )
+ default:
+ return fmt.Errorf("unsupported gossip version: %d", v)
+ }
+ if chanErr != nil {
return fmt.Errorf("unable to fetch channel range: %w",
- err)
+ chanErr)
}
for _, dbChan := range dbChans {
- if v != lnwire.GossipVersion(dbChan.Version) {
- continue
- }
-
cid := lnwire.NewShortChanIDFromInt(
byteOrder.Uint64(dbChan.Scid),
)
- chanInfo := NewV1ChannelUpdateInfo(
- cid, time.Time{}, time.Time{},
- )
+
+ var chanInfo ChannelUpdateInfo
+ switch v {
+ case gossipV1:
+ chanInfo = NewV1ChannelUpdateInfo(
+ cid, time.Time{}, time.Time{},
+ )
+ case gossipV2:
+ chanInfo = NewV2ChannelUpdateInfo(cid, 0, 0)
+ }
if !withTimestamps {
channelsPerBlock[cid.BlockHeight] = append(
@@ -1734,9 +1755,19 @@ func (s *SQLStore) FilterChannelRange(ctx context.Context,
return fmt.Errorf("unable to fetch node1 "+
"policy: %w", err)
} else if err == nil {
- chanInfo.Node1Freshness = lnwire.UnixTimestamp(
- node1Policy.LastUpdate.Int64,
- )
+ n1Update := node1Policy.LastUpdate.Int64
+ n1Height := node1Policy.BlockHeight.Int64
+
+ switch v {
+ case gossipV1:
+ chanInfo.Node1Freshness =
+ lnwire.UnixTimestamp(n1Update)
+ case gossipV2:
+ chanInfo.Node1Freshness =
+ lnwire.BlockHeightTimestamp(
+ n1Height,
+ )
+ }
}
//nolint:ll
@@ -1751,9 +1782,19 @@ func (s *SQLStore) FilterChannelRange(ctx context.Context,
return fmt.Errorf("unable to fetch node2 "+
"policy: %w", err)
} else if err == nil {
- chanInfo.Node2Freshness = lnwire.UnixTimestamp(
- node2Policy.LastUpdate.Int64,
- )
+ n2Update := node2Policy.LastUpdate.Int64
+ n2Height := node2Policy.BlockHeight.Int64
+
+ switch v {
+ case gossipV1:
+ chanInfo.Node2Freshness =
+ lnwire.UnixTimestamp(n2Update)
+ case gossipV2:
+ chanInfo.Node2Freshness =
+ lnwire.BlockHeightTimestamp(
+ n2Height,
+ )
+ }
}
channelsPerBlock[cid.BlockHeight] = append(
@@ -3004,54 +3045,126 @@ func (s *SQLStore) deleteChannels(ctx context.Context, db SQLQueries,
// closes on the resident blockchain.
//
// NOTE: part of the Store interface.
-func (s *SQLStore) ChannelView(ctx context.Context) ([]EdgePoint, error) {
+func (s *SQLStore) ChannelView(ctx context.Context,
+ v lnwire.GossipVersion) ([]EdgePoint, error) {
+
var edgePoints []EdgePoint
err := s.db.ExecTx(ctx, sqldb.ReadTxOpt(), func(db SQLQueries) error {
- handleChannel := func(_ context.Context,
- channel sqlc.ListChannelsPaginatedRow) error {
+ switch v {
+ case gossipV1:
+ handleChannel := func(_ context.Context,
+ channel sqlc.ListChannelsPaginatedRow) error {
- // TODO(elle): update to handle V2 channels.
- pkScript, err := genMultiSigP2WSH(
- channel.BitcoinKey1, channel.BitcoinKey2,
- )
- if err != nil {
- return err
+ key1, err := route.NewVertexFromBytes(
+ channel.BitcoinKey1,
+ )
+ if err != nil {
+ return err
+ }
+
+ key2, err := route.NewVertexFromBytes(
+ channel.BitcoinKey2,
+ )
+ if err != nil {
+ return err
+ }
+
+ edge := &models.ChannelEdgeInfo{
+ Version: gossipV1,
+ BitcoinKey1Bytes: fn.Some(key1),
+ BitcoinKey2Bytes: fn.Some(key2),
+ }
+ pkScript, err := edge.FundingPKScript()
+ if err != nil {
+ return err
+ }
+
+ op, err := wire.NewOutPointFromString(
+ channel.Outpoint,
+ )
+ if err != nil {
+ return err
+ }
+
+ edgePoints = append(edgePoints, EdgePoint{
+ FundingPkScript: pkScript,
+ OutPoint: *op,
+ })
+
+ return nil
}
- op, err := wire.NewOutPointFromString(channel.Outpoint)
- if err != nil {
- return err
+ queryFunc := func(ctx context.Context, lastID int64,
+ limit int32) ([]sqlc.ListChannelsPaginatedRow,
+ error) {
+
+ return db.ListChannelsPaginated(
+ ctx, sqlc.ListChannelsPaginatedParams{
+ Version: int16(gossipV1),
+ ID: lastID,
+ Limit: limit,
+ },
+ )
}
- edgePoints = append(edgePoints, EdgePoint{
- FundingPkScript: pkScript,
- OutPoint: *op,
- })
+ extractCursor := func(
+ row sqlc.ListChannelsPaginatedRow) int64 {
- return nil
- }
+ return row.ID
+ }
- queryFunc := func(ctx context.Context, lastID int64,
- limit int32) ([]sqlc.ListChannelsPaginatedRow, error) {
+ return sqldb.ExecutePaginatedQuery(
+ ctx, s.cfg.QueryCfg, int64(-1), queryFunc,
+ extractCursor, handleChannel,
+ )
- return db.ListChannelsPaginated(
- ctx, sqlc.ListChannelsPaginatedParams{
- Version: int16(lnwire.GossipVersion1),
- ID: lastID,
- Limit: limit,
- },
+ case gossipV2:
+ handleChannel := func(_ context.Context,
+ channel sqlc.ListChannelsPaginatedV2Row) error {
+
+ op, err := wire.NewOutPointFromString(
+ channel.Outpoint,
+ )
+ if err != nil {
+ return err
+ }
+
+ pkScript := channel.FundingPkScript
+ edgePoints = append(edgePoints, EdgePoint{
+ FundingPkScript: pkScript,
+ OutPoint: *op,
+ })
+
+ return nil
+ }
+
+ queryFunc := func(ctx context.Context, lastID int64,
+ limit int32) ([]sqlc.ListChannelsPaginatedV2Row,
+ error) {
+
+ return db.ListChannelsPaginatedV2(
+ ctx, sqlc.ListChannelsPaginatedV2Params{
+ ID: lastID,
+ Limit: limit,
+ },
+ )
+ }
+
+ extractCursor := func(
+ row sqlc.ListChannelsPaginatedV2Row) int64 {
+
+ return row.ID
+ }
+
+ return sqldb.ExecutePaginatedQuery(
+ ctx, s.cfg.QueryCfg, int64(-1), queryFunc,
+ extractCursor, handleChannel,
)
- }
- extractCursor := func(row sqlc.ListChannelsPaginatedRow) int64 {
- return row.ID
+ default:
+ return fmt.Errorf("unsupported gossip version: %d", v)
}
-
- return sqldb.ExecutePaginatedQuery(
- ctx, s.cfg.QueryCfg, int64(-1), queryFunc,
- extractCursor, handleChannel,
- )
}, func() {
edgePoints = nil
})
diff --git a/sqldb/sqlc/graph.sql.go b/sqldb/sqlc/graph.sql.go
index 6293ef2..dc0a064 100644
--- a/sqldb/sqlc/graph.sql.go
+++ b/sqldb/sqlc/graph.sql.go
@@ -2271,9 +2271,11 @@ func (q *Queries) GetPruneTip(ctx context.Context) (GraphPruneLog, error) {
const getPublicV1ChannelsBySCID = `-- name: GetPublicV1ChannelsBySCID :many
SELECT id, 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, signature, funding_pk_script, merkle_root_hash
FROM graph_channels
-WHERE COALESCE(length(node_1_signature), 0) > 0
+WHERE version = 1
+ AND COALESCE(length(node_1_signature), 0) > 0
AND scid >= $1
AND scid < $2
+ORDER BY scid ASC
`
type GetPublicV1ChannelsBySCIDParams struct {
@@ -2321,6 +2323,61 @@ func (q *Queries) GetPublicV1ChannelsBySCID(ctx context.Context, arg GetPublicV1
return items, nil
}
+const getPublicV2ChannelsBySCID = `-- name: GetPublicV2ChannelsBySCID :many
+SELECT id, 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, signature, funding_pk_script, merkle_root_hash
+FROM graph_channels
+WHERE version = 2
+ AND COALESCE(length(signature), 0) > 0
+ AND scid >= $1
+ AND scid < $2
+ORDER BY scid ASC
+`
+
+type GetPublicV2ChannelsBySCIDParams struct {
+ StartScid []byte
+ EndScid []byte
+}
+
+func (q *Queries) GetPublicV2ChannelsBySCID(ctx context.Context, arg GetPublicV2ChannelsBySCIDParams) ([]GraphChannel, error) {
+ rows, err := q.db.QueryContext(ctx, getPublicV2ChannelsBySCID, arg.StartScid, arg.EndScid)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ var items []GraphChannel
+ for rows.Next() {
+ var i GraphChannel
+ if err := rows.Scan(
+ &i.ID,
+ &i.Version,
+ &i.Scid,
+ &i.NodeID1,
+ &i.NodeID2,
+ &i.Outpoint,
+ &i.Capacity,
+ &i.BitcoinKey1,
+ &i.BitcoinKey2,
+ &i.Node1Signature,
+ &i.Node2Signature,
+ &i.Bitcoin1Signature,
+ &i.Bitcoin2Signature,
+ &i.Signature,
+ &i.FundingPkScript,
+ &i.MerkleRootHash,
+ ); err != nil {
+ return nil, err
+ }
+ items = append(items, i)
+ }
+ if err := rows.Close(); err != nil {
+ return nil, err
+ }
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ return items, nil
+}
+
const getSCIDByOutpoint = `-- name: GetSCIDByOutpoint :one
SELECT scid from graph_channels
WHERE outpoint = $1 AND version = $2
@@ -3323,6 +3380,48 @@ func (q *Queries) ListChannelsPaginated(ctx context.Context, arg ListChannelsPag
return items, nil
}
+const listChannelsPaginatedV2 = `-- name: ListChannelsPaginatedV2 :many
+SELECT id, outpoint, funding_pk_script
+FROM graph_channels c
+WHERE c.version = 2 AND c.id > $1
+ORDER BY c.id
+LIMIT $2
+`
+
+type ListChannelsPaginatedV2Params struct {
+ ID int64
+ Limit int32
+}
+
+type ListChannelsPaginatedV2Row struct {
+ ID int64
+ Outpoint string
+ FundingPkScript []byte
+}
+
+func (q *Queries) ListChannelsPaginatedV2(ctx context.Context, arg ListChannelsPaginatedV2Params) ([]ListChannelsPaginatedV2Row, error) {
+ rows, err := q.db.QueryContext(ctx, listChannelsPaginatedV2, arg.ID, arg.Limit)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ var items []ListChannelsPaginatedV2Row
+ for rows.Next() {
+ var i ListChannelsPaginatedV2Row
+ if err := rows.Scan(&i.ID, &i.Outpoint, &i.FundingPkScript); err != nil {
+ return nil, err
+ }
+ items = append(items, i)
+ }
+ if err := rows.Close(); err != nil {
+ return nil, err
+ }
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ return items, nil
+}
+
const listChannelsWithPoliciesForCachePaginated = `-- name: ListChannelsWithPoliciesForCachePaginated :many
SELECT
c.id as id,
diff --git a/sqldb/sqlc/querier.go b/sqldb/sqlc/querier.go
index 40f9161..c148d66 100644
--- a/sqldb/sqlc/querier.go
+++ b/sqldb/sqlc/querier.go
@@ -133,6 +133,7 @@ type Querier interface {
GetPruneHashByHeight(ctx context.Context, blockHeight int64) ([]byte, error)
GetPruneTip(ctx context.Context) (GraphPruneLog, error)
GetPublicV1ChannelsBySCID(ctx context.Context, arg GetPublicV1ChannelsBySCIDParams) ([]GraphChannel, error)
+ GetPublicV2ChannelsBySCID(ctx context.Context, arg GetPublicV2ChannelsBySCIDParams) ([]GraphChannel, error)
GetSCIDByOutpoint(ctx context.Context, arg GetSCIDByOutpointParams) ([]byte, error)
GetSourceNodesByVersion(ctx context.Context, version int16) ([]GetSourceNodesByVersionRow, error)
// NOTE: this is V1 specific since for V1, disabled is a
@@ -204,6 +205,7 @@ type Querier interface {
ListChannelsByNodeID(ctx context.Context, arg ListChannelsByNodeIDParams) ([]ListChannelsByNodeIDRow, error)
ListChannelsForNodeIDs(ctx context.Context, arg ListChannelsForNodeIDsParams) ([]ListChannelsForNodeIDsRow, error)
ListChannelsPaginated(ctx context.Context, arg ListChannelsPaginatedParams) ([]ListChannelsPaginatedRow, error)
+ ListChannelsPaginatedV2(ctx context.Context, arg ListChannelsPaginatedV2Params) ([]ListChannelsPaginatedV2Row, error)
ListChannelsWithPoliciesForCachePaginated(ctx context.Context, arg ListChannelsWithPoliciesForCachePaginatedParams) ([]ListChannelsWithPoliciesForCachePaginatedRow, error)
ListChannelsWithPoliciesPaginated(ctx context.Context, arg ListChannelsWithPoliciesPaginatedParams) ([]ListChannelsWithPoliciesPaginatedRow, error)
ListNodeIDsAndPubKeys(ctx context.Context, arg ListNodeIDsAndPubKeysParams) ([]ListNodeIDsAndPubKeysRow, error)
diff --git a/sqldb/sqlc/queries/graph.sql b/sqldb/sqlc/queries/graph.sql
index ec44bc0..78c1ebe 100644
--- a/sqldb/sqlc/queries/graph.sql
+++ b/sqldb/sqlc/queries/graph.sql
@@ -755,9 +755,20 @@ WHERE c.version = $1
-- name: GetPublicV1ChannelsBySCID :many
SELECT *
FROM graph_channels
-WHERE COALESCE(length(node_1_signature), 0) > 0
+WHERE version = 1
+ AND COALESCE(length(node_1_signature), 0) > 0
AND scid >= @start_scid
- AND scid < @end_scid;
+ AND scid < @end_scid
+ORDER BY scid ASC;
+
+-- name: GetPublicV2ChannelsBySCID :many
+SELECT *
+FROM graph_channels
+WHERE version = 2
+ AND COALESCE(length(signature), 0) > 0
+ AND scid >= @start_scid
+ AND scid < @end_scid
+ORDER BY scid ASC;
-- name: ListChannelsPaginated :many
SELECT id, bitcoin_key_1, bitcoin_key_2, outpoint
@@ -766,6 +777,13 @@ WHERE c.version = $1 AND c.id > $2
ORDER BY c.id
LIMIT $3;
+-- name: ListChannelsPaginatedV2 :many
+SELECT id, outpoint, funding_pk_script
+FROM graph_channels c
+WHERE c.version = 2 AND c.id > $1
+ORDER BY c.id
+LIMIT $2;
+
-- name: ListChannelsWithPoliciesPaginated :many
SELECT
sqlc.embed(c),
Why this scored 32/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.