• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 23864456463

01 Apr 2026 06:31PM UTC coverage: 62.104% (-0.02%) from 62.127%
23864456463

push

github

web-flow
Merge pull request #10700 from ziggie1984/invoices-cursor-pagination

invoices+sqldb/sqlc: replace offset-based pagination with cursor-based

67 of 75 new or added lines in 2 files covered. (89.33%)

102 existing lines in 25 files now uncovered.

142741 of 229843 relevant lines covered (62.1%)

19178.69 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.89
/graph/db/graph.go
1
package graphdb
2

3
import (
4
        "context"
5
        "errors"
6
        "fmt"
7
        "iter"
8
        "net"
9
        "sync"
10
        "sync/atomic"
11
        "testing"
12
        "time"
13

14
        "github.com/btcsuite/btcd/btcec/v2"
15
        "github.com/btcsuite/btcd/chaincfg/chainhash"
16
        "github.com/btcsuite/btcd/wire"
17
        "github.com/lightningnetwork/lnd/batch"
18
        "github.com/lightningnetwork/lnd/fn/v2"
19
        "github.com/lightningnetwork/lnd/graph/db/models"
20
        "github.com/lightningnetwork/lnd/lnwire"
21
        "github.com/lightningnetwork/lnd/routing/route"
22
        "github.com/stretchr/testify/require"
23
)
24

25
// ErrChanGraphShuttingDown indicates that the ChannelGraph has shutdown or is
26
// busy shutting down.
27
var ErrChanGraphShuttingDown = fmt.Errorf("ChannelGraph shutting down")
28

29
// GraphCacheStatus describes the current state of the in-memory graph cache.
30
type GraphCacheStatus uint8
31

32
const (
33
        // GraphCacheStatusDisabled indicates that the graph cache is disabled.
34
        GraphCacheStatusDisabled GraphCacheStatus = iota
35

36
        // GraphCacheStatusLoading indicates that the graph cache is still
37
        // being populated from the DB and is not yet serving reads.
38
        GraphCacheStatusLoading
39

40
        // GraphCacheStatusLoaded indicates that the graph cache has
41
        // completed its initial population and is serving reads.
42
        GraphCacheStatusLoaded
43

44
        // GraphCacheStatusFailed indicates that the initial population of
45
        // the graph cache failed. Reads fall back to the database.
46
        GraphCacheStatusFailed
47
)
48

49
// ChannelGraph is a layer above the graph's CRUD layer.
50
type ChannelGraph struct {
51
        started atomic.Bool
52
        stopped atomic.Bool
53

54
        opts *chanGraphOptions
55

56
        cache *graphCacheState
57

58
        db Store
59
        *topologyManager
60

61
        quit   chan struct{}
62
        wg     sync.WaitGroup
63
        cancel fn.Option[context.CancelFunc]
64
}
65

66
// NewChannelGraph creates a new ChannelGraph instance with the given backend.
67
func NewChannelGraph(v1Store Store,
68
        options ...ChanGraphOption) (*ChannelGraph, error) {
207✔
69

207✔
70
        opts := defaultChanGraphOptions()
207✔
71
        for _, o := range options {
505✔
72
                o(opts)
298✔
73
        }
298✔
74

75
        g := &ChannelGraph{
207✔
76
                opts:            opts,
207✔
77
                db:              v1Store,
207✔
78
                topologyManager: newTopologyManager(),
207✔
79
                quit:            make(chan struct{}),
207✔
80
        }
207✔
81

207✔
82
        // The graph cache can be turned off (e.g. for mobile users) for a
207✔
83
        // speed/memory usage tradeoff.
207✔
84
        if opts.useGraphCache {
378✔
85
                g.cache = newGraphCacheState(opts.preAllocCacheNumNodes)
171✔
86
        }
171✔
87

88
        return g, nil
207✔
89
}
90

91
// GraphCacheStatus returns the current state of the in-memory graph cache.
92
func (c *ChannelGraph) GraphCacheStatus() GraphCacheStatus {
11✔
93
        switch {
11✔
94
        case c.cache == nil:
6✔
95
                return GraphCacheStatusDisabled
6✔
96

97
        case c.cache.isLoaded():
5✔
98
                return GraphCacheStatusLoaded
5✔
99

100
        case c.cache.isFailed():
2✔
101
                return GraphCacheStatusFailed
2✔
102

103
        default:
2✔
104
                return GraphCacheStatusLoading
2✔
105
        }
106
}
107

108
// Start kicks off any goroutines required for the ChannelGraph to function.
109
// If the graph cache is enabled, then it will be populated with the contents of
110
// the database.
111
func (c *ChannelGraph) Start() error {
321✔
112
        if !c.started.CompareAndSwap(false, true) {
435✔
113
                return nil
114✔
114
        }
114✔
115
        log.Debugf("ChannelGraph starting")
207✔
116
        defer log.Debug("ChannelGraph started")
207✔
117

207✔
118
        ctx, cancel := context.WithCancel(context.Background())
207✔
119
        c.cancel = fn.Some(cancel)
207✔
120

207✔
121
        if c.opts.asyncGraphCachePopulation {
219✔
122
                c.wg.Add(1)
12✔
123
                go func() {
24✔
124
                        defer c.wg.Done()
12✔
125

12✔
126
                        if err := c.populateCache(ctx); err != nil {
15✔
127
                                log.Criticalf("Could not populate the "+
3✔
128
                                        "graph cache: %v", err)
3✔
129
                        }
3✔
130
                }()
131
        } else {
195✔
132
                if err := c.populateCache(ctx); err != nil {
195✔
133
                        return fmt.Errorf("could not populate the graph "+
×
134
                                "cache: %w", err)
×
135
                }
×
136
        }
137

138
        c.wg.Add(1)
207✔
139
        go c.handleTopologySubscriptions(ctx)
207✔
140

207✔
141
        return nil
207✔
142
}
143

144
// Stop signals any active goroutines for a graceful closure.
145
func (c *ChannelGraph) Stop() error {
322✔
146
        if !c.stopped.CompareAndSwap(false, true) {
437✔
147
                return nil
115✔
148
        }
115✔
149

150
        log.Debugf("ChannelGraph shutting down...")
207✔
151
        defer log.Debug("ChannelGraph shutdown complete")
207✔
152

207✔
153
        c.cancel.WhenSome(func(fn context.CancelFunc) { fn() })
414✔
154
        close(c.quit)
207✔
155
        c.wg.Wait()
207✔
156

207✔
157
        return nil
207✔
158
}
159

160
// handleTopologySubscriptions ensures that topology client subscriptions,
161
// subscription cancellations and topology notifications are handled
162
// synchronously.
163
//
164
// NOTE: this MUST be run in a goroutine.
165
func (c *ChannelGraph) handleTopologySubscriptions(ctx context.Context) {
207✔
166
        defer c.wg.Done()
207✔
167

207✔
168
        for {
6,903✔
169
                select {
6,696✔
170
                // A new fully validated topology update has just arrived.
171
                // We'll notify any registered clients.
172
                case update := <-c.topologyUpdate:
6,488✔
173
                        // TODO(elle): change topology handling to be handled
6,488✔
174
                        // synchronously so that we can guarantee the order of
6,488✔
175
                        // notification delivery.
6,488✔
176
                        c.wg.Add(1)
6,488✔
177
                        go c.handleTopologyUpdate(ctx, update)
6,488✔
178

179
                        // TODO(roasbeef): remove all unconnected vertexes
180
                        // after N blocks pass with no corresponding
181
                        // announcements.
182

183
                // A new notification client update has arrived. We're either
184
                // gaining a new client, or cancelling notifications for an
185
                // existing client.
186
                case ntfnUpdate := <-c.ntfnClientUpdates:
9✔
187
                        clientID := ntfnUpdate.clientID
9✔
188

9✔
189
                        if ntfnUpdate.cancel {
14✔
190
                                client, ok := c.topologyClients.LoadAndDelete(
5✔
191
                                        clientID,
5✔
192
                                )
5✔
193
                                if ok {
10✔
194
                                        close(client.exit)
5✔
195
                                        client.wg.Wait()
5✔
196

5✔
197
                                        close(client.ntfnChan)
5✔
198
                                }
5✔
199

200
                                continue
5✔
201
                        }
202

203
                        c.topologyClients.Store(clientID, &topologyClient{
8✔
204
                                ntfnChan: ntfnUpdate.ntfnChan,
8✔
205
                                exit:     make(chan struct{}),
8✔
206
                        })
8✔
207

208
                case <-ctx.Done():
171✔
209
                        return
171✔
210

211
                case <-c.quit:
36✔
212
                        return
36✔
213
                }
214
        }
215
}
216

217
// populateCache loads the entire channel graph into the in-memory graph cache.
218
func (c *ChannelGraph) populateCache(ctx context.Context) error {
207✔
219
        if c.cache == nil {
247✔
220
                log.Info("In-memory channel graph cache disabled")
40✔
221

40✔
222
                return nil
40✔
223
        }
40✔
224

225
        c.cache.beginPopulation()
171✔
226

171✔
227
        loaded := false
171✔
228
        defer func() {
342✔
229
                c.cache.finishPopulation(loaded)
171✔
230
        }()
171✔
231

232
        cache := c.cache.graphCache
171✔
233

171✔
234
        startTime := time.Now()
171✔
235
        log.Info("Populating in-memory channel graph, this might take a " +
171✔
236
                "while...")
171✔
237

171✔
238
        for _, v := range []lnwire.GossipVersion{
171✔
239
                gossipV1, gossipV2,
171✔
240
        } {
506✔
241
                // TODO(elle): If we have both v1 and v2 entries for the same
335✔
242
                // node/channel, prefer v2 when merging.
335✔
243
                err := c.db.ForEachNodeCacheable(ctx, v,
335✔
244
                        func(node route.Vertex,
335✔
245
                                features *lnwire.FeatureVector) error {
549✔
246

214✔
247
                                cache.AddNodeFeatures(node, features)
214✔
248

214✔
249
                                return nil
214✔
250
                        }, func() {},
385✔
251
                )
252
                if err != nil && !errors.Is(
335✔
253
                        err, ErrVersionNotSupportedForKVDB,
335✔
254
                ) {
335✔
255

×
256
                        return err
×
257
                }
×
258

259
                err = c.db.ForEachChannelCacheable(
335✔
260
                        ctx, v, func(info *models.CachedEdgeInfo,
335✔
261
                                policy1,
335✔
262
                                policy2 *models.CachedEdgePolicy) error {
1,034✔
263

699✔
264
                                cache.AddChannel(info, policy1, policy2)
699✔
265

699✔
266
                                return nil
699✔
267
                        }, func() {},
868✔
268
                )
269
                if err != nil &&
335✔
270
                        !errors.Is(err, ErrVersionNotSupportedForKVDB) {
338✔
271

3✔
272
                        return err
3✔
273
                }
3✔
274
        }
275

276
        loaded = true
168✔
277

168✔
278
        log.Infof("Finished populating in-memory channel graph (took %v, %s)",
168✔
279
                time.Since(startTime), cache.Stats())
168✔
280

168✔
281
        return nil
168✔
282
}
283

284
// ForEachNodeDirectedChannel iterates through all channels of a given node,
285
// executing the passed callback on the directed edge representing the channel
286
// and its incoming policy. If the callback returns an error, then the iteration
287
// is halted with the error propagated back up to the caller. If the graphCache
288
// is available, then it will be used to retrieve the node's channels instead
289
// of the database.
290
//
291
// Unknown policies are passed into the callback as nil values.
292
//
293
// NOTE: this is part of the graphdb.NodeTraverser interface.
294
func (c *ChannelGraph) ForEachNodeDirectedChannel(ctx context.Context,
295
        node route.Vertex, cb func(channel *DirectedChannel) error,
296
        reset func()) error {
22✔
297

22✔
298
        if c.cache != nil && c.cache.isLoaded() {
43✔
299
                return c.cache.graphCache.ForEachChannel(node, cb)
21✔
300
        }
21✔
301

302
        // TODO(elle): once the no-cache path needs to support
303
        // pathfinding across gossip versions, this should iterate
304
        // across all versions rather than defaulting to v1.
305
        return c.db.ForEachNodeDirectedChannel(
1✔
306
                ctx, gossipV1, node, cb, reset,
1✔
307
        )
1✔
308
}
309

310
// FetchNodeFeatures returns the features of the given node. If no features are
311
// known for the node, an empty feature vector is returned.
312
// If the graphCache is available, then it will be used to retrieve the node's
313
// features instead of the database.
314
//
315
// NOTE: this is part of the graphdb.NodeTraverser interface.
316
func (c *ChannelGraph) FetchNodeFeatures(ctx context.Context,
317
        node route.Vertex) (*lnwire.FeatureVector, error) {
×
318

×
319
        if c.cache != nil && c.cache.isLoaded() {
×
320
                return c.cache.graphCache.GetFeatures(node), nil
×
321
        }
×
322

323
        return c.db.FetchNodeFeatures(ctx, lnwire.GossipVersion1, node)
×
324
}
325

326
// GraphSession will provide the call-back with access to a NodeTraverser
327
// instance which can be used to perform queries against the channel graph. If
328
// the graph cache is not enabled, then the call-back will be provided with
329
// access to the graph via a consistent read-only transaction.
330
func (c *ChannelGraph) GraphSession(ctx context.Context,
331
        cb func(graph NodeTraverser) error, reset func()) error {
×
332

×
333
        if c.cache != nil && c.cache.isLoaded() {
×
334
                return cb(c)
×
335
        }
×
336

337
        return c.db.GraphSession(ctx, cb, reset)
×
338
}
339

340
// ForEachNodeCached iterates through all the stored vertices/nodes in the
341
// graph, executing the passed callback with each node encountered.
342
//
343
// NOTE: The callback contents MUST not be modified.
344
func (c *ChannelGraph) ForEachNodeCached(ctx context.Context,
345
        v lnwire.GossipVersion, withAddrs bool,
346
        cb func(ctx context.Context, node route.Vertex, addrs []net.Addr,
347
                chans map[uint64]*DirectedChannel) error, reset func()) error {
131✔
348

131✔
349
        if !withAddrs && c.cache != nil && c.cache.isLoaded() {
134✔
350
                return c.cache.graphCache.ForEachNode(
3✔
351
                        func(node route.Vertex,
3✔
352
                                channels map[uint64]*DirectedChannel) error {
303✔
353

300✔
354
                                return cb(ctx, node, nil, channels)
300✔
355
                        },
300✔
356
                )
357
        }
358

359
        return c.db.ForEachNodeCached(ctx, v, withAddrs, cb, reset)
128✔
360
}
361

362
// AddNode adds a vertex/node to the graph database. If the node is not
363
// in the database from before, this will add a new, unconnected one to the
364
// graph. If it is present from before, this will update that node's
365
// information. Note that this method is expected to only be called to update an
366
// already present node from a node announcement, or to insert a node found in a
367
// channel update.
368
func (c *ChannelGraph) AddNode(ctx context.Context,
369
        node *models.Node, op ...batch.SchedulerOption) error {
1,098✔
370

1,098✔
371
        err := c.db.AddNode(ctx, node, op...)
1,098✔
372
        if err != nil {
1,098✔
373
                return err
×
374
        }
×
375

376
        if c.cache != nil {
2,022✔
377
                c.cache.applyUpdate(func(cache *GraphCache) {
1,848✔
378
                        cache.AddNodeFeatures(
924✔
379
                                node.PubKeyBytes, node.Features,
924✔
380
                        )
924✔
381
                })
924✔
382
        }
383

384
        select {
1,098✔
385
        case c.topologyUpdate <- node:
1,098✔
UNCOV
386
        case <-c.quit:
×
UNCOV
387
                return ErrChanGraphShuttingDown
×
388
        }
389

390
        return nil
1,098✔
391
}
392

393
// AddChannelEdge adds a new (undirected, blank) edge to the graph database. An
394
// undirected edge from the two target nodes are created. The information stored
395
// denotes the static attributes of the channel, such as the channelID, the keys
396
// involved in creation of the channel, and the set of features that the channel
397
// supports. The chanPoint and chanID are used to uniquely identify the edge
398
// globally within the database.
399
func (c *ChannelGraph) AddChannelEdge(ctx context.Context,
400
        edge *models.ChannelEdgeInfo, op ...batch.SchedulerOption) error {
2,134✔
401

2,134✔
402
        err := c.db.AddChannelEdge(ctx, edge, op...)
2,134✔
403
        if err != nil {
2,371✔
404
                return err
237✔
405
        }
237✔
406

407
        if c.cache != nil {
3,509✔
408
                c.cache.applyUpdate(func(cache *GraphCache) {
3,224✔
409
                        cache.AddChannel(models.NewCachedEdge(edge), nil, nil)
1,612✔
410
                })
1,612✔
411
        }
412

413
        select {
1,897✔
414
        case c.topologyUpdate <- edge:
1,897✔
415
        case <-c.quit:
×
416
                return ErrChanGraphShuttingDown
×
417
        }
418

419
        return nil
1,897✔
420
}
421

422
// MarkEdgeLive clears an edge from our zombie index for the given gossip
423
// version, deeming it as live. If the cache is enabled, the edge will be added
424
// back to the graph cache if we still have a record of this channel in the DB.
425
func (c *ChannelGraph) MarkEdgeLive(ctx context.Context,
426
        v lnwire.GossipVersion, chanID uint64) error {
2✔
427

2✔
428
        err := c.db.MarkEdgeLive(ctx, v, chanID)
2✔
429
        if err != nil {
3✔
430
                return err
1✔
431
        }
1✔
432

433
        if c.cache != nil {
2✔
434
                // We need to add the channel back into our graph cache,
1✔
435
                // otherwise we won't use it for path finding.
1✔
436
                infos, err := c.db.FetchChanInfos(ctx, v, []uint64{chanID})
1✔
437
                if err != nil {
1✔
438
                        return err
×
439
                }
×
440

441
                if len(infos) == 0 {
2✔
442
                        return nil
1✔
443
                }
1✔
444

445
                info := infos[0]
×
446

×
447
                var policy1, policy2 *models.CachedEdgePolicy
×
448
                if info.Policy1 != nil {
×
449
                        policy1 = models.NewCachedPolicy(info.Policy1)
×
450
                }
×
451
                if info.Policy2 != nil {
×
452
                        policy2 = models.NewCachedPolicy(info.Policy2)
×
453
                }
×
454

455
                c.cache.applyUpdate(func(cache *GraphCache) {
×
456
                        cache.AddChannel(
×
457
                                models.NewCachedEdge(info.Info),
×
458
                                policy1, policy2,
×
459
                        )
×
460
                })
×
461
        }
462

463
        return nil
×
464
}
465

466
// DeleteChannelEdges removes edges with the given channel IDs from the
467
// database and marks them as zombies. This ensures that we're unable to re-add
468
// it to our database once again. If an edge does not exist within the
469
// database, then ErrEdgeNotFound will be returned. If strictZombiePruning is
470
// true, then when we mark these edges as zombies, we'll set up the keys such
471
// that we require the node that failed to send the fresh update to be the one
472
// that resurrects the channel from its zombie state. The markZombie bool
473
// denotes whether to mark the channel as a zombie.
474
func (c *ChannelGraph) DeleteChannelEdges(ctx context.Context,
475
        v lnwire.GossipVersion, strictZombiePruning, markZombie bool,
476
        chanIDs ...uint64) error {
147✔
477

147✔
478
        infos, err := c.db.DeleteChannelEdges(
147✔
479
                ctx, v, strictZombiePruning, markZombie, chanIDs...,
147✔
480
        )
147✔
481
        if err != nil {
209✔
482
                return err
62✔
483
        }
62✔
484

485
        if c.cache != nil {
170✔
486
                c.cache.applyUpdate(func(cache *GraphCache) {
170✔
487
                        for _, info := range infos {
115✔
488
                                cache.RemoveChannel(
30✔
489
                                        info.NodeKey1Bytes, info.NodeKey2Bytes,
30✔
490
                                        info.ChannelID,
30✔
491
                                )
30✔
492
                        }
30✔
493
                })
494
        }
495

496
        return err
85✔
497
}
498

499
// DisconnectBlockAtHeight is used to indicate that the block specified
500
// by the passed height has been disconnected from the main chain. This
501
// will "rewind" the graph back to the height below, deleting channels
502
// that are no longer confirmed from the graph. The prune log will be
503
// set to the last prune height valid for the remaining chain.
504
// Channels that were removed from the graph resulting from the
505
// disconnected block are returned.
506
func (c *ChannelGraph) DisconnectBlockAtHeight(ctx context.Context,
507
        height uint32) ([]*models.ChannelEdgeInfo, error) {
159✔
508

159✔
509
        edges, err := c.db.DisconnectBlockAtHeight(ctx, height)
159✔
510
        if err != nil {
159✔
511
                return nil, err
×
512
        }
×
513

514
        if c.cache != nil {
318✔
515
                c.cache.applyUpdate(func(cache *GraphCache) {
318✔
516
                        for _, edge := range edges {
245✔
517
                                cache.RemoveChannel(
86✔
518
                                        edge.NodeKey1Bytes, edge.NodeKey2Bytes,
86✔
519
                                        edge.ChannelID,
86✔
520
                                )
86✔
521
                        }
86✔
522
                })
523
        }
524

525
        return edges, nil
159✔
526
}
527

528
// PruneGraph prunes newly closed channels from the channel graph in response
529
// to a new block being solved on the network. Any transactions which spend the
530
// funding output of any known channels within he graph will be deleted.
531
// Additionally, the "prune tip", or the last block which has been used to
532
// prune the graph is stored so callers can ensure the graph is fully in sync
533
// with the current UTXO state. A slice of channels that have been closed by
534
// the target block are returned if the function succeeds without error.
535
func (c *ChannelGraph) PruneGraph(ctx context.Context,
536
        spentOutputs []*wire.OutPoint,
537
        blockHash *chainhash.Hash, blockHeight uint32) (
538
        []*models.ChannelEdgeInfo, error) {
247✔
539

247✔
540
        edges, nodes, err := c.db.PruneGraph(
247✔
541
                ctx, spentOutputs, blockHash, blockHeight,
247✔
542
        )
247✔
543
        if err != nil {
247✔
544
                return nil, err
×
545
        }
×
546

547
        if c.cache != nil {
494✔
548
                c.cache.applyUpdate(func(cache *GraphCache) {
494✔
549
                        for _, edge := range edges {
274✔
550
                                cache.RemoveChannel(
27✔
551
                                        edge.NodeKey1Bytes, edge.NodeKey2Bytes,
27✔
552
                                        edge.ChannelID,
27✔
553
                                )
27✔
554
                        }
27✔
555
                        for _, node := range nodes {
304✔
556
                                cache.RemoveNode(node)
57✔
557
                        }
57✔
558
                })
559

560
                if stats, ok := c.cache.stats(); ok {
494✔
561
                        log.Debugf("Pruned graph, cache now has %s", stats)
247✔
562
                }
247✔
563
        }
564

565
        if len(edges) != 0 {
270✔
566
                // Notify all currently registered clients of the newly closed
23✔
567
                // channels.
23✔
568
                closeSummaries := createCloseSummaries(
23✔
569
                        blockHeight, edges...,
23✔
570
                )
23✔
571

23✔
572
                select {
23✔
573
                case c.topologyUpdate <- closeSummaries:
23✔
574
                case <-c.quit:
×
575
                        return nil, ErrChanGraphShuttingDown
×
576
                }
577
        }
578

579
        return edges, nil
247✔
580
}
581

582
// PruneGraphNodes is a garbage collection method which attempts to prune out
583
// any nodes from the channel graph that are currently unconnected. This ensure
584
// that we only maintain a graph of reachable nodes. In the event that a pruned
585
// node gains more channels, it will be re-added back to the graph.
586
func (c *ChannelGraph) PruneGraphNodes(ctx context.Context) error {
27✔
587
        nodes, err := c.db.PruneGraphNodes(ctx)
27✔
588
        if err != nil {
27✔
589
                return err
×
590
        }
×
591

592
        if c.cache != nil {
54✔
593
                c.cache.applyUpdate(func(cache *GraphCache) {
54✔
594
                        for _, node := range nodes {
34✔
595
                                cache.RemoveNode(node)
7✔
596
                        }
7✔
597
                })
598
        }
599

600
        return nil
27✔
601
}
602

603
// FilterKnownChanIDs takes a set of channel IDs and return the subset of chan
604
// ID's that we don't know and are not known zombies of the passed set. In other
605
// words, we perform a set difference of our set of chan ID's and the ones
606
// passed in. This method can be used by callers to determine the set of
607
// channels another peer knows of that we don't.
608
func (c *ChannelGraph) FilterKnownChanIDs(ctx context.Context,
609
        chansInfo []ChannelUpdateInfo,
610
        isZombieChan func(ChannelUpdateInfo) bool) ([]uint64, error) {
132✔
611

132✔
612
        unknown, knownZombies, err := c.db.FilterKnownChanIDs(ctx, chansInfo)
132✔
613
        if err != nil {
132✔
614
                return nil, err
×
615
        }
×
616

617
        for _, info := range knownZombies {
178✔
618
                // TODO(ziggie): Make sure that for the strict pruning case we
46✔
619
                // compare the pubkeys and whether the right timestamp is not
46✔
620
                // older than the `ChannelPruneExpiry`.
46✔
621
                //
46✔
622
                // NOTE: The timestamp data has no verification attached to it
46✔
623
                // in the `ReplyChannelRange` msg so we are trusting this data
46✔
624
                // at this point. However it is not critical because we are just
46✔
625
                // removing the channel from the db when the timestamps are more
46✔
626
                // recent. During the querying of the gossip msg verification
46✔
627
                // happens as usual. However we should start punishing peers
46✔
628
                // when they don't provide us honest data ?
46✔
629
                if isZombieChan(info) {
72✔
630
                        continue
26✔
631
                }
632

633
                // If we have marked it as a zombie but the latest update
634
                // info could bring it back from the dead, then we mark it
635
                // alive, and we let it be added to the set of IDs to query our
636
                // peer for.
637
                err := c.db.MarkEdgeLive(
20✔
638
                        ctx, info.Version,
20✔
639
                        info.ShortChannelID.ToUint64(),
20✔
640
                )
20✔
641
                // Since there is a chance that the edge could have been marked
20✔
642
                // as "live" between the FilterKnownChanIDs call and the
20✔
643
                // MarkEdgeLive call, we ignore the error if the edge is already
20✔
644
                // marked as live.
20✔
645
                if err != nil && !errors.Is(err, ErrZombieEdgeNotFound) {
20✔
646
                        return nil, err
×
647
                }
×
648
        }
649

650
        return unknown, nil
132✔
651
}
652

653
// MarkEdgeZombie attempts to mark a channel identified by its channel ID as a
654
// zombie for the given gossip version. This method is used on an ad-hoc basis,
655
// when channels need to be marked as zombies outside the normal pruning cycle.
656
func (c *ChannelGraph) MarkEdgeZombie(ctx context.Context,
657
        v lnwire.GossipVersion, chanID uint64,
658
        pubKey1, pubKey2 [33]byte) error {
127✔
659

127✔
660
        err := c.db.MarkEdgeZombie(ctx, v, chanID, pubKey1, pubKey2)
127✔
661
        if err != nil {
127✔
662
                return err
×
663
        }
×
664

665
        if c.cache != nil {
254✔
666
                c.cache.applyUpdate(func(cache *GraphCache) {
254✔
667
                        cache.RemoveChannel(pubKey1, pubKey2, chanID)
127✔
668
                })
127✔
669
        }
670

671
        return nil
127✔
672
}
673

674
// UpdateEdgePolicy updates the edge routing policy for a single directed edge
675
// within the database for the referenced channel. The `flags` attribute within
676
// the ChannelEdgePolicy determines which of the directed edges are being
677
// updated. If the flag is 1, then the first node's information is being
678
// updated, otherwise it's the second node's information. The node ordering is
679
// determined by the lexicographical ordering of the identity public keys of the
680
// nodes on either side of the channel.
681
func (c *ChannelGraph) UpdateEdgePolicy(ctx context.Context,
682
        edge *models.ChannelEdgePolicy, op ...batch.SchedulerOption) error {
3,487✔
683

3,487✔
684
        from, to, err := c.db.UpdateEdgePolicy(ctx, edge, op...)
3,487✔
685
        if err != nil {
3,492✔
686
                return err
5✔
687
        }
5✔
688

689
        if c.cache != nil {
6,389✔
690
                c.cache.applyUpdate(func(cache *GraphCache) {
5,814✔
691
                        cache.UpdatePolicy(
2,907✔
692
                                models.NewCachedPolicy(edge), from, to,
2,907✔
693
                        )
2,907✔
694
                })
2,907✔
695
        }
696

697
        select {
3,482✔
698
        case c.topologyUpdate <- edge:
3,482✔
699
        case <-c.quit:
×
700
                return ErrChanGraphShuttingDown
×
701
        }
702

703
        return nil
3,482✔
704
}
705

706
// ForEachNodeChannel iterates through all channels of the given node.
707
func (c *ChannelGraph) ForEachNodeChannel(ctx context.Context,
708
        v lnwire.GossipVersion, nodePub route.Vertex,
709
        cb func(*models.ChannelEdgeInfo,
710
                *models.ChannelEdgePolicy,
711
                *models.ChannelEdgePolicy) error, reset func()) error {
5✔
712

5✔
713
        return c.db.ForEachNodeChannel(ctx, v, nodePub, cb, reset)
5✔
714
}
5✔
715

716
// ForEachNodeCacheable iterates through all stored vertices/nodes in the graph.
717
func (c *ChannelGraph) ForEachNodeCacheable(ctx context.Context,
718
        v lnwire.GossipVersion, cb func(route.Vertex,
719
                *lnwire.FeatureVector) error, reset func()) error {
5✔
720

5✔
721
        return c.db.ForEachNodeCacheable(ctx, v, cb, reset)
5✔
722
}
5✔
723

724
// HasV1Node determines if the graph has a vertex identified by the target node
725
// in the V1 graph.
726
func (c *ChannelGraph) HasV1Node(ctx context.Context,
727
        nodePub [33]byte) (time.Time, bool, error) {
14✔
728

14✔
729
        return c.db.HasV1Node(ctx, nodePub)
14✔
730
}
14✔
731

732
// ForEachChannel iterates through all channel edges stored within the graph.
733
func (c *ChannelGraph) ForEachChannel(ctx context.Context,
734
        v lnwire.GossipVersion, cb func(*models.ChannelEdgeInfo,
735
                *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error,
736
        reset func()) error {
4✔
737

4✔
738
        return c.db.ForEachChannel(ctx, v, cb, reset)
4✔
739
}
4✔
740

741
// DisabledChannelIDs returns the channel ids of disabled channels.
742
func (c *ChannelGraph) DisabledChannelIDs(ctx context.Context,
743
        v lnwire.GossipVersion) (
744
        []uint64, error) {
2✔
745

2✔
746
        return c.db.DisabledChannelIDs(ctx, v)
2✔
747
}
2✔
748

749
// HasV1ChannelEdge returns true if the database knows of a channel edge.
750
func (c *ChannelGraph) HasV1ChannelEdge(ctx context.Context,
751
        chanID uint64) (time.Time, time.Time, bool, bool, error) {
16✔
752

16✔
753
        return c.db.HasV1ChannelEdge(ctx, chanID)
16✔
754
}
16✔
755

756
// HasChannelEdge returns true if the database knows of a channel edge.
757
func (c *ChannelGraph) HasChannelEdge(ctx context.Context,
758
        v lnwire.GossipVersion, chanID uint64) (bool, bool, error) {
22✔
759

22✔
760
        return c.db.HasChannelEdge(ctx, v, chanID)
22✔
761
}
22✔
762

763
// AddEdgeProof sets the proof of an existing edge in the graph database.
764
func (c *ChannelGraph) AddEdgeProof(ctx context.Context,
765
        chanID lnwire.ShortChannelID, proof *models.ChannelAuthProof) error {
6✔
766

6✔
767
        return c.db.AddEdgeProof(ctx, chanID, proof)
6✔
768
}
6✔
769

770
// HighestChanID returns the "highest" known channel ID in the channel graph.
771
func (c *ChannelGraph) HighestChanID(ctx context.Context,
772
        v lnwire.GossipVersion) (uint64, error) {
×
773

×
774
        return c.db.HighestChanID(ctx, v)
×
775
}
×
776

777
// FilterChannelRange returns channel IDs within the passed block height range
778
// for the given gossip version.
779
func (c *ChannelGraph) FilterChannelRange(ctx context.Context,
780
        v lnwire.GossipVersion, startHeight, endHeight uint32,
781
        withTimestamps bool) ([]BlockChannelRange, error) {
11✔
782

11✔
783
        return c.db.FilterChannelRange(
11✔
784
                ctx, v, startHeight, endHeight, withTimestamps,
11✔
785
        )
11✔
786
}
11✔
787

788
// FilterChannelRange returns channel IDs within the passed block height range
789
// for this graph's gossip version.
790
func (c *VersionedGraph) FilterChannelRange(ctx context.Context,
791
        startHeight, endHeight uint32,
792
        withTimestamps bool) ([]BlockChannelRange, error) {
4✔
793

4✔
794
        return c.db.FilterChannelRange(
4✔
795
                ctx, c.v, startHeight, endHeight, withTimestamps,
4✔
796
        )
4✔
797
}
4✔
798

799
// FetchChanInfos returns the set of channel edges for the passed channel IDs.
800
func (c *ChannelGraph) FetchChanInfos(ctx context.Context,
801
        v lnwire.GossipVersion, chanIDs []uint64) ([]ChannelEdge, error) {
×
802

×
803
        return c.db.FetchChanInfos(ctx, v, chanIDs)
×
804
}
×
805

806
// FetchChannelEdgesByOutpoint attempts to lookup directed edges by funding
807
// outpoint.
808
func (c *ChannelGraph) FetchChannelEdgesByOutpoint(ctx context.Context,
809
        op *wire.OutPoint) (
810
        *models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
811
        *models.ChannelEdgePolicy, error) {
13✔
812

13✔
813
        return c.db.FetchChannelEdgesByOutpoint(
13✔
814
                ctx, lnwire.GossipVersion1, op,
13✔
815
        )
13✔
816
}
13✔
817

818
// FetchChannelEdgesByID attempts to lookup directed edges by channel ID.
819
func (c *ChannelGraph) FetchChannelEdgesByID(ctx context.Context,
820
        chanID uint64) (
821
        *models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
822
        *models.ChannelEdgePolicy, error) {
3,498✔
823

3,498✔
824
        return c.db.FetchChannelEdgesByID(
3,498✔
825
                ctx, lnwire.GossipVersion1, chanID,
3,498✔
826
        )
3,498✔
827
}
3,498✔
828

829
// PutClosedScid stores a SCID for a closed channel in the database.
830
func (c *ChannelGraph) PutClosedScid(ctx context.Context,
831
        scid lnwire.ShortChannelID) error {
1✔
832

1✔
833
        return c.db.PutClosedScid(ctx, scid)
1✔
834
}
1✔
835

836
// IsClosedScid checks whether a channel identified by the scid is closed.
837
func (c *ChannelGraph) IsClosedScid(ctx context.Context,
838
        scid lnwire.ShortChannelID) (bool, error) {
6✔
839

6✔
840
        return c.db.IsClosedScid(ctx, scid)
6✔
841
}
6✔
842

843
// SetSourceNode sets the source node within the graph database.
844
func (c *ChannelGraph) SetSourceNode(ctx context.Context,
845
        node *models.Node) error {
122✔
846

122✔
847
        return c.db.SetSourceNode(ctx, node)
122✔
848
}
122✔
849

850
// PruneTip returns the block height and hash of the latest pruning block.
851
func (c *ChannelGraph) PruneTip(ctx context.Context) (*chainhash.Hash,
852
        uint32, error) {
57✔
853

57✔
854
        return c.db.PruneTip(ctx)
57✔
855
}
57✔
856

857
// VersionedGraph is a wrapper around ChannelGraph that will call underlying
858
// Store methods with a specific gossip version.
859
type VersionedGraph struct {
860
        *ChannelGraph
861
        v lnwire.GossipVersion
862
}
863

864
// NewVersionedGraph creates a new VersionedGraph.
865
func NewVersionedGraph(c *ChannelGraph,
866
        v lnwire.GossipVersion) *VersionedGraph {
208✔
867

208✔
868
        return &VersionedGraph{
208✔
869
                ChannelGraph: c,
208✔
870
                v:            v,
208✔
871
        }
208✔
872
}
208✔
873

874
// FetchNodeFeatures returns the features of the given node. If no features are
875
// known for the node, an empty feature vector is returned. If the graphCache is
876
// available, it will be used instead of the database.
877
//
878
// NOTE: This is part of the graphdb.NodeTraverser interface.
879
func (c *VersionedGraph) FetchNodeFeatures(ctx context.Context,
880
        node route.Vertex) (*lnwire.FeatureVector, error) {
467✔
881

467✔
882
        if c.cache != nil && c.cache.isLoaded() {
934✔
883
                return c.cache.graphCache.GetFeatures(node), nil
467✔
884
        }
467✔
885

886
        return c.db.FetchNodeFeatures(ctx, c.v, node)
4✔
887
}
888

889
// ForEachNodeDirectedChannel iterates through all channels of a given node,
890
// executing the passed callback on the directed edge representing the channel
891
// and its incoming policy. If the graphCache is available, it will be used
892
// instead of the database.
893
//
894
// NOTE: This is part of the graphdb.NodeTraverser interface.
895
func (c *VersionedGraph) ForEachNodeDirectedChannel(ctx context.Context,
896
        node route.Vertex, cb func(channel *DirectedChannel) error,
897
        reset func()) error {
509✔
898

509✔
899
        if c.cache != nil && c.cache.isLoaded() {
1,018✔
900
                return c.cache.graphCache.ForEachChannel(node, cb)
509✔
901
        }
509✔
902

903
        return c.db.ForEachNodeDirectedChannel(ctx, c.v, node, cb, reset)
4✔
904
}
905

906
// ForEachNodeCached iterates through all stored vertices/nodes in the graph,
907
// delegating to the embedded ChannelGraph.
908
func (c *VersionedGraph) ForEachNodeCached(ctx context.Context,
909
        withAddrs bool, cb func(ctx context.Context, node route.Vertex,
910
                addrs []net.Addr,
911
                chans map[uint64]*DirectedChannel) error,
912
        reset func()) error {
119✔
913

119✔
914
        return c.ChannelGraph.ForEachNodeCached(ctx, c.v, withAddrs, cb, reset)
119✔
915
}
119✔
916

917
// ForEachNode iterates through all stored vertices/nodes in the graph.
918
func (c *VersionedGraph) ForEachNode(ctx context.Context,
919
        cb func(*models.Node) error, reset func()) error {
9✔
920

9✔
921
        return c.db.ForEachNode(ctx, c.v, cb, reset)
9✔
922
}
9✔
923

924
// NumZombies returns the current number of zombie channels in the graph.
925
func (c *VersionedGraph) NumZombies(ctx context.Context) (uint64, error) {
4✔
926
        return c.db.NumZombies(ctx, c.v)
4✔
927
}
4✔
928

929
// NodeUpdatesInHorizon returns all known lightning nodes with updates within
930
// the passed range. The version is supplied by the embedded field.
931
func (c *VersionedGraph) NodeUpdatesInHorizon(ctx context.Context,
932
        r NodeUpdateRange,
933
        opts ...IteratorOption) iter.Seq2[*models.Node, error] {
62✔
934

62✔
935
        return c.db.NodeUpdatesInHorizon(ctx, c.v, r, opts...)
62✔
936
}
62✔
937

938
// ChanUpdatesInHorizon returns all known channel edges with at least one
939
// policy update within the specified range. The version is supplied by the
940
// embedded field.
941
func (c *VersionedGraph) ChanUpdatesInHorizon(ctx context.Context,
942
        r ChanUpdateRange,
943
        opts ...IteratorOption) iter.Seq2[ChannelEdge, error] {
153✔
944

153✔
945
        return c.db.ChanUpdatesInHorizon(ctx, c.v, r, opts...)
153✔
946
}
153✔
947

948
// ChannelView returns the verifiable edge information for each active channel.
949
func (c *VersionedGraph) ChannelView(ctx context.Context) ([]EdgePoint,
950
        error) {
29✔
951

29✔
952
        return c.db.ChannelView(ctx, c.v)
29✔
953
}
29✔
954

955
// GraphSession provides the callback with access to a NodeTraverser instance
956
// for performing queries against the channel graph. If the graph cache is
957
// enabled, the callback receives the VersionedGraph directly (which implements
958
// NodeTraverser using the cache). Otherwise a read-only database session is
959
// used.
960
func (c *VersionedGraph) GraphSession(ctx context.Context,
961
        cb func(graph NodeTraverser) error, reset func()) error {
137✔
962

137✔
963
        if c.cache != nil && c.cache.isLoaded() {
220✔
964
                return cb(c)
83✔
965
        }
83✔
966

967
        // TODO(elle): the underlying GraphSession currently creates a
968
        // NodeTraverser that is hardcoded to GossipVersion1. This needs to be
969
        // updated to pass the version through for v2 support.
970
        return c.db.GraphSession(ctx, cb, reset)
54✔
971
}
972

973
// FetchNode attempts to look up a target node by its identity public key.
974
func (c *VersionedGraph) FetchNode(ctx context.Context,
975
        nodePub route.Vertex) (*models.Node, error) {
160✔
976

160✔
977
        return c.db.FetchNode(ctx, c.v, nodePub)
160✔
978
}
160✔
979

980
// FetchChannelEdgesByID attempts to lookup directed edges by channel ID.
981
func (c *VersionedGraph) FetchChannelEdgesByID(ctx context.Context,
982
        chanID uint64) (
983
        *models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
984
        *models.ChannelEdgePolicy, error) {
10✔
985

10✔
986
        return c.db.FetchChannelEdgesByID(ctx, c.v, chanID)
10✔
987
}
10✔
988

989
// FetchChannelEdgesByOutpoint attempts to lookup directed edges by funding
990
// outpoint.
991
func (c *VersionedGraph) FetchChannelEdgesByOutpoint(ctx context.Context,
992
        op *wire.OutPoint) (
993
        *models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
994
        *models.ChannelEdgePolicy, error) {
3✔
995

3✔
996
        return c.db.FetchChannelEdgesByOutpoint(ctx, c.v, op)
3✔
997
}
3✔
998

999
// IsZombieEdge returns whether the edge is considered zombie for this version.
1000
func (c *VersionedGraph) IsZombieEdge(ctx context.Context,
1001
        chanID uint64) (bool, [33]byte, [33]byte, error) {
14✔
1002

14✔
1003
        return c.db.IsZombieEdge(ctx, c.v, chanID)
14✔
1004
}
14✔
1005

1006
// AddrsForNode returns all known addresses for the target node public key.
1007
func (c *VersionedGraph) AddrsForNode(ctx context.Context,
1008
        nodePub *btcec.PublicKey) (bool, []net.Addr, error) {
7✔
1009

7✔
1010
        return c.db.AddrsForNode(ctx, c.v, nodePub)
7✔
1011
}
7✔
1012

1013
// DeleteNode starts a new database transaction to remove a vertex/node
1014
// from the database according to the node's public key.
1015
func (c *VersionedGraph) DeleteNode(ctx context.Context,
1016
        nodePub route.Vertex) error {
4✔
1017

4✔
1018
        err := c.db.DeleteNode(ctx, c.v, nodePub)
4✔
1019
        if err != nil {
5✔
1020
                return err
1✔
1021
        }
1✔
1022

1023
        if c.cache != nil {
6✔
1024
                c.cache.applyUpdate(func(cache *GraphCache) {
6✔
1025
                        cache.RemoveNode(nodePub)
3✔
1026
                })
3✔
1027
        }
1028

1029
        return nil
3✔
1030
}
1031

1032
// HasNode determines if the graph has a vertex identified by the target node
1033
// in the V1 graph.
1034
func (c *VersionedGraph) HasNode(ctx context.Context, nodePub [33]byte) (bool,
1035
        error) {
11✔
1036

11✔
1037
        return c.db.HasNode(ctx, c.v, nodePub)
11✔
1038
}
11✔
1039

1040
// LookupAlias attempts to return the alias as advertised by the target node.
1041
func (c *VersionedGraph) LookupAlias(ctx context.Context,
1042
        pub *btcec.PublicKey) (string, error) {
6✔
1043

6✔
1044
        return c.db.LookupAlias(ctx, c.v, pub)
6✔
1045
}
6✔
1046

1047
// SourceNode returns the source node of the graph.
1048
func (c *VersionedGraph) SourceNode(ctx context.Context) (*models.Node,
1049
        error) {
244✔
1050

244✔
1051
        return c.db.SourceNode(ctx, c.v)
244✔
1052
}
244✔
1053

1054
// DeleteChannelEdges removes edges with the given channel IDs from the
1055
// database and marks them as zombies. This ensures that we're unable to re-add
1056
// it to our database once again. If an edge does not exist within the
1057
// database, then ErrEdgeNotFound will be returned. If strictZombiePruning is
1058
// true, then when we mark these edges as zombies, we'll set up the keys such
1059
// that we require the node that failed to send the fresh update to be the one
1060
// that resurrects the channel from its zombie state. The markZombie bool
1061
// denotes whether to mark the channel as a zombie.
1062
func (c *VersionedGraph) DeleteChannelEdges(ctx context.Context,
1063
        strictZombiePruning, markZombie bool, chanIDs ...uint64) error {
141✔
1064

141✔
1065
        return c.ChannelGraph.DeleteChannelEdges(
141✔
1066
                ctx, c.v, strictZombiePruning, markZombie, chanIDs...,
141✔
1067
        )
141✔
1068
}
141✔
1069

1070
// HasChannelEdge returns true if the database knows of a channel edge with the
1071
// passed channel ID and this graph's gossip version, and false otherwise. If it
1072
// is not found, then the zombie index is checked and its result is returned as
1073
// the second boolean.
1074
func (c *VersionedGraph) HasChannelEdge(ctx context.Context,
1075
        chanID uint64) (bool, bool, error) {
182✔
1076

182✔
1077
        return c.db.HasChannelEdge(ctx, c.v, chanID)
182✔
1078
}
182✔
1079

1080
// ForEachSourceNodeChannel iterates through all channels of the source node.
1081
func (c *VersionedGraph) ForEachSourceNodeChannel(ctx context.Context,
1082
        cb func(chanPoint wire.OutPoint, havePolicy bool,
1083
                otherNode *models.Node) error, reset func()) error {
5✔
1084

5✔
1085
        return c.db.ForEachSourceNodeChannel(ctx, c.v, cb, reset)
5✔
1086
}
5✔
1087

1088
// ForEachNodeChannel iterates through all channels of the given node.
1089
func (c *VersionedGraph) ForEachNodeChannel(ctx context.Context,
1090
        nodePub route.Vertex, cb func(*models.ChannelEdgeInfo,
1091
                *models.ChannelEdgePolicy,
1092
                *models.ChannelEdgePolicy) error, reset func()) error {
9✔
1093

9✔
1094
        return c.db.ForEachNodeChannel(ctx, c.v, nodePub, cb, reset)
9✔
1095
}
9✔
1096

1097
// ForEachChannel iterates through all channel edges stored within the graph.
1098
func (c *VersionedGraph) ForEachChannel(ctx context.Context,
1099
        cb func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
1100
                *models.ChannelEdgePolicy) error, reset func()) error {
7✔
1101

7✔
1102
        return c.db.ForEachChannel(ctx, c.v, cb, reset)
7✔
1103
}
7✔
1104

1105
// ForEachNodeCacheable iterates through all stored vertices/nodes in the graph.
1106
func (c *VersionedGraph) ForEachNodeCacheable(ctx context.Context,
1107
        cb func(route.Vertex, *lnwire.FeatureVector) error,
1108
        reset func()) error {
1✔
1109

1✔
1110
        return c.db.ForEachNodeCacheable(ctx, c.v, cb, reset)
1✔
1111
}
1✔
1112

1113
// ForEachChannelCacheable iterates through all channel edges for the cache.
1114
func (c *VersionedGraph) ForEachChannelCacheable(ctx context.Context,
1115
        cb func(*models.CachedEdgeInfo, *models.CachedEdgePolicy,
1116
                *models.CachedEdgePolicy) error, reset func()) error {
×
1117

×
1118
        return c.db.ForEachChannelCacheable(ctx, c.v, cb, reset)
×
1119
}
×
1120

1121
// DisabledChannelIDs returns the channel ids of disabled channels.
1122
func (c *VersionedGraph) DisabledChannelIDs(
1123
        ctx context.Context) ([]uint64, error) {
4✔
1124

4✔
1125
        return c.db.DisabledChannelIDs(ctx, c.v)
4✔
1126
}
4✔
1127

1128
// FetchChanInfos returns the set of channel edges for the passed channel IDs.
1129
func (c *VersionedGraph) FetchChanInfos(ctx context.Context,
1130
        chanIDs []uint64) ([]ChannelEdge, error) {
7✔
1131

7✔
1132
        return c.db.FetchChanInfos(ctx, c.v, chanIDs)
7✔
1133
}
7✔
1134

1135
// HighestChanID returns the "highest" known channel ID in the channel graph.
1136
func (c *VersionedGraph) HighestChanID(ctx context.Context) (uint64, error) {
7✔
1137
        return c.db.HighestChanID(ctx, c.v)
7✔
1138
}
7✔
1139

1140
// ChannelID attempts to lookup the 8-byte compact channel ID.
1141
func (c *VersionedGraph) ChannelID(ctx context.Context,
1142
        chanPoint *wire.OutPoint) (uint64, error) {
5✔
1143

5✔
1144
        return c.db.ChannelID(ctx, c.v, chanPoint)
5✔
1145
}
5✔
1146

1147
// IsPublicNode determines whether the node is seen as public in the graph.
1148
func (c *VersionedGraph) IsPublicNode(ctx context.Context,
1149
        pubKey [33]byte) (bool, error) {
18✔
1150

18✔
1151
        return c.db.IsPublicNode(ctx, c.v, pubKey)
18✔
1152
}
18✔
1153

1154
// MakeTestGraph creates a new instance of the ChannelGraph for testing
1155
// purposes. The backing Store implementation depends on the version of
1156
// NewTestDB included in the current build.
1157
//
1158
// NOTE: this is currently unused, but is left here for future use to show how
1159
// NewTestDB can be used. As the SQL implementation of the Store is
1160
// implemented, unit tests will be switched to use this function instead of
1161
// the existing MakeTestGraph helper. Once only this function is used, the
1162
// existing MakeTestGraph function will be removed and this one will be renamed.
1163
func MakeTestGraph(t testing.TB,
1164
        opts ...ChanGraphOption) *ChannelGraph {
188✔
1165

188✔
1166
        t.Helper()
188✔
1167

188✔
1168
        store := NewTestDB(t)
188✔
1169

188✔
1170
        // Default to synchronous cache population in tests so that the
188✔
1171
        // cache is fully loaded before the test proceeds.
188✔
1172
        allOpts := append(
188✔
1173
                []ChanGraphOption{WithSyncGraphCachePopulation()}, opts...,
188✔
1174
        )
188✔
1175

188✔
1176
        graph, err := NewChannelGraph(store, allOpts...)
188✔
1177
        require.NoError(t, err)
188✔
1178
        require.NoError(t, graph.Start())
188✔
1179

188✔
1180
        t.Cleanup(func() {
376✔
1181
                require.NoError(t, graph.Stop())
188✔
1182
        })
188✔
1183

1184
        return graph
188✔
1185
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc