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

lightningnetwork / lnd / 23586606866

26 Mar 2026 09:18AM UTC coverage: 51.166% (-11.0%) from 62.126%
23586606866

Pull #10673

github

web-flow
Merge 331f53bee into 671521319
Pull Request #10673: gitignore: ignore `.worktrees`

114177 of 223149 relevant lines covered (51.17%)

19714.26 hits per line

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

87.7
/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) {
198✔
69

198✔
70
        opts := defaultChanGraphOptions()
198✔
71
        for _, o := range options {
487✔
72
                o(opts)
289✔
73
        }
289✔
74

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

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

88
        return g, nil
198✔
89
}
90

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

97
        case c.cache.isLoaded():
1✔
98
                return GraphCacheStatusLoaded
1✔
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 {
311✔
112
        if !c.started.CompareAndSwap(false, true) {
424✔
113
                return nil
113✔
114
        }
113✔
115
        log.Debugf("ChannelGraph starting")
198✔
116
        defer log.Debug("ChannelGraph started")
198✔
117

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

198✔
121
        if c.opts.asyncGraphCachePopulation {
206✔
122
                c.wg.Add(1)
8✔
123
                go func() {
16✔
124
                        defer c.wg.Done()
8✔
125

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

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

198✔
141
        return nil
198✔
142
}
143

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

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

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

198✔
157
        return nil
198✔
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) {
198✔
166
        defer c.wg.Done()
198✔
167

198✔
168
        for {
6,873✔
169
                select {
6,675✔
170
                // A new fully validated topology update has just arrived.
171
                // We'll notify any registered clients.
172
                case update := <-c.topologyUpdate:
6,472✔
173
                        // TODO(elle): change topology handling to be handled
6,472✔
174
                        // synchronously so that we can guarantee the order of
6,472✔
175
                        // notification delivery.
6,472✔
176
                        c.wg.Add(1)
6,472✔
177
                        go c.handleTopologyUpdate(ctx, update)
6,472✔
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:
5✔
187
                        clientID := ntfnUpdate.clientID
5✔
188

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

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

200
                                continue
1✔
201
                        }
202

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

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

211
                case <-c.quit:
26✔
212
                        return
26✔
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 {
198✔
219
        if c.cache == nil {
234✔
220
                log.Info("In-memory channel graph cache disabled")
36✔
221

36✔
222
                return nil
36✔
223
        }
36✔
224

225
        c.cache.beginPopulation()
162✔
226

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

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

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

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

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

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

×
256
                        return err
×
257
                }
×
258

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

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

695✔
266
                                return nil
695✔
267
                        }, func() {},
855✔
268
                )
269
                if err != nil &&
321✔
270
                        !errors.Is(err, ErrVersionNotSupportedForKVDB) {
324✔
271

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

276
        loaded = true
159✔
277

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

159✔
281
        return nil
159✔
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() {
137✔
350
                return c.cache.graphCache.ForEachNode(
6✔
351
                        func(node route.Vertex,
6✔
352
                                channels map[uint64]*DirectedChannel) error {
606✔
353

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

359
        return c.db.ForEachNodeCached(ctx, v, withAddrs, cb, reset)
125✔
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,084✔
370

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

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

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

390
        return nil
1,084✔
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,127✔
401

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

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

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

419
        return nil
1,890✔
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 {
138✔
477

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

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

496
        return err
82✔
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) {
154✔
508

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

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

525
        return edges, nil
154✔
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) {
244✔
539

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

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

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

565
        if len(edges) != 0 {
267✔
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
244✔
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 {
23✔
587
        nodes, err := c.db.PruneGraphNodes(ctx)
23✔
588
        if err != nil {
23✔
589
                return err
×
590
        }
×
591

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

600
        return nil
23✔
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) {
126✔
611

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

617
        for _, info := range knownZombies {
172✔
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
126✔
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 {
128✔
659

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

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

671
        return nil
128✔
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,480✔
683

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

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

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

703
        return nil
3,475✔
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 {
1✔
712

1✔
713
        return c.db.ForEachNodeChannel(ctx, v, nodePub, cb, reset)
1✔
714
}
1✔
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
// NodeUpdatesInHorizon returns all known lightning nodes with updates in the
725
// range.
726
func (c *ChannelGraph) NodeUpdatesInHorizon(ctx context.Context,
727
        startTime, endTime time.Time,
728
        opts ...IteratorOption) iter.Seq2[*models.Node, error] {
49✔
729

49✔
730
        return c.db.NodeUpdatesInHorizon(ctx, startTime, endTime, opts...)
49✔
731
}
49✔
732

733
// HasV1Node determines if the graph has a vertex identified by the target node
734
// in the V1 graph.
735
func (c *ChannelGraph) HasV1Node(ctx context.Context,
736
        nodePub [33]byte) (time.Time, bool, error) {
10✔
737

10✔
738
        return c.db.HasV1Node(ctx, nodePub)
10✔
739
}
10✔
740

741
// ForEachChannel iterates through all channel edges stored within the graph.
742
func (c *ChannelGraph) ForEachChannel(ctx context.Context,
743
        v lnwire.GossipVersion, cb func(*models.ChannelEdgeInfo,
744
                *models.ChannelEdgePolicy, *models.ChannelEdgePolicy) error,
745
        reset func()) error {
4✔
746

4✔
747
        return c.db.ForEachChannel(ctx, v, cb, reset)
4✔
748
}
4✔
749

750
// DisabledChannelIDs returns the channel ids of disabled channels.
751
func (c *ChannelGraph) DisabledChannelIDs(ctx context.Context,
752
        v lnwire.GossipVersion) (
753
        []uint64, error) {
2✔
754

2✔
755
        return c.db.DisabledChannelIDs(ctx, v)
2✔
756
}
2✔
757

758
// HasV1ChannelEdge returns true if the database knows of a channel edge.
759
func (c *ChannelGraph) HasV1ChannelEdge(ctx context.Context,
760
        chanID uint64) (time.Time, time.Time, bool, bool, error) {
12✔
761

12✔
762
        return c.db.HasV1ChannelEdge(ctx, chanID)
12✔
763
}
12✔
764

765
// HasChannelEdge returns true if the database knows of a channel edge.
766
func (c *ChannelGraph) HasChannelEdge(ctx context.Context,
767
        v lnwire.GossipVersion, chanID uint64) (bool, bool, error) {
134✔
768

134✔
769
        return c.db.HasChannelEdge(ctx, v, chanID)
134✔
770
}
134✔
771

772
// AddEdgeProof sets the proof of an existing edge in the graph database.
773
func (c *ChannelGraph) AddEdgeProof(ctx context.Context,
774
        chanID lnwire.ShortChannelID, proof *models.ChannelAuthProof) error {
2✔
775

2✔
776
        return c.db.AddEdgeProof(ctx, chanID, proof)
2✔
777
}
2✔
778

779
// HighestChanID returns the "highest" known channel ID in the channel graph.
780
func (c *ChannelGraph) HighestChanID(ctx context.Context,
781
        v lnwire.GossipVersion) (uint64, error) {
×
782

×
783
        return c.db.HighestChanID(ctx, v)
×
784
}
×
785

786
// ChanUpdatesInHorizon returns all known channel edges with updates in the
787
// horizon.
788
func (c *ChannelGraph) ChanUpdatesInHorizon(ctx context.Context,
789
        startTime, endTime time.Time,
790
        opts ...IteratorOption) iter.Seq2[ChannelEdge, error] {
144✔
791

144✔
792
        return c.db.ChanUpdatesInHorizon(ctx, startTime, endTime, opts...)
144✔
793
}
144✔
794

795
// FilterChannelRange returns channel IDs within the passed block height range
796
// for the given gossip version.
797
func (c *ChannelGraph) FilterChannelRange(ctx context.Context,
798
        v lnwire.GossipVersion, startHeight, endHeight uint32,
799
        withTimestamps bool) ([]BlockChannelRange, error) {
11✔
800

11✔
801
        return c.db.FilterChannelRange(
11✔
802
                ctx, v, startHeight, endHeight, withTimestamps,
11✔
803
        )
11✔
804
}
11✔
805

806
// FilterChannelRange returns channel IDs within the passed block height range
807
// for this graph's gossip version.
808
func (c *VersionedGraph) FilterChannelRange(ctx context.Context,
809
        startHeight, endHeight uint32,
810
        withTimestamps bool) ([]BlockChannelRange, error) {
×
811

×
812
        return c.db.FilterChannelRange(
×
813
                ctx, c.v, startHeight, endHeight, withTimestamps,
×
814
        )
×
815
}
×
816

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

×
821
        return c.db.FetchChanInfos(ctx, v, chanIDs)
×
822
}
×
823

824
// FetchChannelEdgesByOutpoint attempts to lookup directed edges by funding
825
// outpoint.
826
func (c *ChannelGraph) FetchChannelEdgesByOutpoint(ctx context.Context,
827
        op *wire.OutPoint) (
828
        *models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
829
        *models.ChannelEdgePolicy, error) {
9✔
830

9✔
831
        return c.db.FetchChannelEdgesByOutpoint(
9✔
832
                ctx, lnwire.GossipVersion1, op,
9✔
833
        )
9✔
834
}
9✔
835

836
// FetchChannelEdgesByID attempts to lookup directed edges by channel ID.
837
func (c *ChannelGraph) FetchChannelEdgesByID(ctx context.Context,
838
        chanID uint64) (
839
        *models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
840
        *models.ChannelEdgePolicy, error) {
3,491✔
841

3,491✔
842
        return c.db.FetchChannelEdgesByID(
3,491✔
843
                ctx, lnwire.GossipVersion1, chanID,
3,491✔
844
        )
3,491✔
845
}
3,491✔
846

847
// PutClosedScid stores a SCID for a closed channel in the database.
848
func (c *ChannelGraph) PutClosedScid(ctx context.Context,
849
        scid lnwire.ShortChannelID) error {
1✔
850

1✔
851
        return c.db.PutClosedScid(ctx, scid)
1✔
852
}
1✔
853

854
// IsClosedScid checks whether a channel identified by the scid is closed.
855
func (c *ChannelGraph) IsClosedScid(ctx context.Context,
856
        scid lnwire.ShortChannelID) (bool, error) {
2✔
857

2✔
858
        return c.db.IsClosedScid(ctx, scid)
2✔
859
}
2✔
860

861
// SetSourceNode sets the source node within the graph database.
862
func (c *ChannelGraph) SetSourceNode(ctx context.Context,
863
        node *models.Node) error {
117✔
864

117✔
865
        return c.db.SetSourceNode(ctx, node)
117✔
866
}
117✔
867

868
// PruneTip returns the block height and hash of the latest pruning block.
869
func (c *ChannelGraph) PruneTip(ctx context.Context) (*chainhash.Hash,
870
        uint32, error) {
53✔
871

53✔
872
        return c.db.PruneTip(ctx)
53✔
873
}
53✔
874

875
// VersionedGraph is a wrapper around ChannelGraph that will call underlying
876
// Store methods with a specific gossip version.
877
type VersionedGraph struct {
878
        *ChannelGraph
879
        v lnwire.GossipVersion
880
}
881

882
// NewVersionedGraph creates a new VersionedGraph.
883
func NewVersionedGraph(c *ChannelGraph,
884
        v lnwire.GossipVersion) *VersionedGraph {
186✔
885

186✔
886
        return &VersionedGraph{
186✔
887
                ChannelGraph: c,
186✔
888
                v:            v,
186✔
889
        }
186✔
890
}
186✔
891

892
// FetchNodeFeatures returns the features of the given node. If no features are
893
// known for the node, an empty feature vector is returned. If the graphCache is
894
// available, it will be used instead of the database.
895
//
896
// NOTE: This is part of the graphdb.NodeTraverser interface.
897
func (c *VersionedGraph) FetchNodeFeatures(ctx context.Context,
898
        node route.Vertex) (*lnwire.FeatureVector, error) {
463✔
899

463✔
900
        if c.cache != nil && c.cache.isLoaded() {
926✔
901
                return c.cache.graphCache.GetFeatures(node), nil
463✔
902
        }
463✔
903

904
        return c.db.FetchNodeFeatures(ctx, c.v, node)
×
905
}
906

907
// ForEachNodeDirectedChannel iterates through all channels of a given node,
908
// executing the passed callback on the directed edge representing the channel
909
// and its incoming policy. If the graphCache is available, it will be used
910
// instead of the database.
911
//
912
// NOTE: This is part of the graphdb.NodeTraverser interface.
913
func (c *VersionedGraph) ForEachNodeDirectedChannel(ctx context.Context,
914
        node route.Vertex, cb func(channel *DirectedChannel) error,
915
        reset func()) error {
505✔
916

505✔
917
        if c.cache != nil && c.cache.isLoaded() {
1,010✔
918
                return c.cache.graphCache.ForEachChannel(node, cb)
505✔
919
        }
505✔
920

921
        return c.db.ForEachNodeDirectedChannel(ctx, c.v, node, cb, reset)
×
922
}
923

924
// ForEachNodeCached iterates through all stored vertices/nodes in the graph,
925
// delegating to the embedded ChannelGraph.
926
func (c *VersionedGraph) ForEachNodeCached(ctx context.Context,
927
        withAddrs bool, cb func(ctx context.Context, node route.Vertex,
928
                addrs []net.Addr,
929
                chans map[uint64]*DirectedChannel) error,
930
        reset func()) error {
119✔
931

119✔
932
        return c.ChannelGraph.ForEachNodeCached(ctx, c.v, withAddrs, cb, reset)
119✔
933
}
119✔
934

935
// ForEachNode iterates through all stored vertices/nodes in the graph.
936
func (c *VersionedGraph) ForEachNode(ctx context.Context,
937
        cb func(*models.Node) error, reset func()) error {
5✔
938

5✔
939
        return c.db.ForEachNode(ctx, c.v, cb, reset)
5✔
940
}
5✔
941

942
// NumZombies returns the current number of zombie channels in the graph.
943
func (c *VersionedGraph) NumZombies(ctx context.Context) (uint64, error) {
4✔
944
        return c.db.NumZombies(ctx, c.v)
4✔
945
}
4✔
946

947
// NodeUpdatesInHorizon returns all known lightning nodes which have an update
948
// timestamp within the passed range.
949
func (c *VersionedGraph) NodeUpdatesInHorizon(ctx context.Context,
950
        startTime, endTime time.Time,
951
        opts ...IteratorOption) iter.Seq2[*models.Node, error] {
2✔
952

2✔
953
        return c.db.NodeUpdatesInHorizon(ctx, startTime, endTime, opts...)
2✔
954
}
2✔
955

956
// ChannelView returns the verifiable edge information for each active channel.
957
func (c *VersionedGraph) ChannelView(ctx context.Context) ([]EdgePoint,
958
        error) {
24✔
959

24✔
960
        return c.db.ChannelView(ctx, c.v)
24✔
961
}
24✔
962

963
// GraphSession provides the callback with access to a NodeTraverser instance
964
// for performing queries against the channel graph. If the graph cache is
965
// enabled, the callback receives the VersionedGraph directly (which implements
966
// NodeTraverser using the cache). Otherwise a read-only database session is
967
// used.
968
func (c *VersionedGraph) GraphSession(ctx context.Context,
969
        cb func(graph NodeTraverser) error, reset func()) error {
133✔
970

133✔
971
        if c.cache != nil && c.cache.isLoaded() {
212✔
972
                return cb(c)
79✔
973
        }
79✔
974

975
        // TODO(elle): the underlying GraphSession currently creates a
976
        // NodeTraverser that is hardcoded to GossipVersion1. This needs to be
977
        // updated to pass the version through for v2 support.
978
        return c.db.GraphSession(ctx, cb, reset)
54✔
979
}
980

981
// FetchNode attempts to look up a target node by its identity public key.
982
func (c *VersionedGraph) FetchNode(ctx context.Context,
983
        nodePub route.Vertex) (*models.Node, error) {
156✔
984

156✔
985
        return c.db.FetchNode(ctx, c.v, nodePub)
156✔
986
}
156✔
987

988
// FetchChannelEdgesByID attempts to lookup directed edges by channel ID.
989
func (c *VersionedGraph) FetchChannelEdgesByID(ctx context.Context,
990
        chanID uint64) (
991
        *models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
992
        *models.ChannelEdgePolicy, error) {
6✔
993

6✔
994
        return c.db.FetchChannelEdgesByID(ctx, c.v, chanID)
6✔
995
}
6✔
996

997
// FetchChannelEdgesByOutpoint attempts to lookup directed edges by funding
998
// outpoint.
999
func (c *VersionedGraph) FetchChannelEdgesByOutpoint(ctx context.Context,
1000
        op *wire.OutPoint) (
1001
        *models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
1002
        *models.ChannelEdgePolicy, error) {
3✔
1003

3✔
1004
        return c.db.FetchChannelEdgesByOutpoint(ctx, c.v, op)
3✔
1005
}
3✔
1006

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

14✔
1011
        return c.db.IsZombieEdge(ctx, c.v, chanID)
14✔
1012
}
14✔
1013

1014
// AddrsForNode returns all known addresses for the target node public key.
1015
func (c *VersionedGraph) AddrsForNode(ctx context.Context,
1016
        nodePub *btcec.PublicKey) (bool, []net.Addr, error) {
3✔
1017

3✔
1018
        return c.db.AddrsForNode(ctx, c.v, nodePub)
3✔
1019
}
3✔
1020

1021
// DeleteNode starts a new database transaction to remove a vertex/node
1022
// from the database according to the node's public key.
1023
func (c *VersionedGraph) DeleteNode(ctx context.Context,
1024
        nodePub route.Vertex) error {
4✔
1025

4✔
1026
        err := c.db.DeleteNode(ctx, c.v, nodePub)
4✔
1027
        if err != nil {
5✔
1028
                return err
1✔
1029
        }
1✔
1030

1031
        if c.cache != nil {
6✔
1032
                c.cache.applyUpdate(func(cache *GraphCache) {
6✔
1033
                        cache.RemoveNode(nodePub)
3✔
1034
                })
3✔
1035
        }
1036

1037
        return nil
3✔
1038
}
1039

1040
// HasNode determines if the graph has a vertex identified by the target node
1041
// in the V1 graph.
1042
func (c *VersionedGraph) HasNode(ctx context.Context, nodePub [33]byte) (bool,
1043
        error) {
7✔
1044

7✔
1045
        return c.db.HasNode(ctx, c.v, nodePub)
7✔
1046
}
7✔
1047

1048
// LookupAlias attempts to return the alias as advertised by the target node.
1049
func (c *VersionedGraph) LookupAlias(ctx context.Context,
1050
        pub *btcec.PublicKey) (string, error) {
2✔
1051

2✔
1052
        return c.db.LookupAlias(ctx, c.v, pub)
2✔
1053
}
2✔
1054

1055
// SourceNode returns the source node of the graph.
1056
func (c *VersionedGraph) SourceNode(ctx context.Context) (*models.Node,
1057
        error) {
240✔
1058

240✔
1059
        return c.db.SourceNode(ctx, c.v)
240✔
1060
}
240✔
1061

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

13✔
1073
        return c.ChannelGraph.DeleteChannelEdges(
13✔
1074
                ctx, c.v, strictZombiePruning, markZombie, chanIDs...,
13✔
1075
        )
13✔
1076
}
13✔
1077

1078
// HasChannelEdge returns true if the database knows of a channel edge with the
1079
// passed channel ID and this graph's gossip version, and false otherwise. If it
1080
// is not found, then the zombie index is checked and its result is returned as
1081
// the second boolean.
1082
func (c *VersionedGraph) HasChannelEdge(ctx context.Context,
1083
        chanID uint64) (bool, bool, error) {
67✔
1084

67✔
1085
        return c.db.HasChannelEdge(ctx, c.v, chanID)
67✔
1086
}
67✔
1087

1088
// ForEachSourceNodeChannel iterates through all channels of the source node.
1089
func (c *VersionedGraph) ForEachSourceNodeChannel(ctx context.Context,
1090
        cb func(chanPoint wire.OutPoint, havePolicy bool,
1091
                otherNode *models.Node) error, reset func()) error {
1✔
1092

1✔
1093
        return c.db.ForEachSourceNodeChannel(ctx, c.v, cb, reset)
1✔
1094
}
1✔
1095

1096
// ForEachNodeChannel iterates through all channels of the given node.
1097
func (c *VersionedGraph) ForEachNodeChannel(ctx context.Context,
1098
        nodePub route.Vertex, cb func(*models.ChannelEdgeInfo,
1099
                *models.ChannelEdgePolicy,
1100
                *models.ChannelEdgePolicy) error, reset func()) error {
5✔
1101

5✔
1102
        return c.db.ForEachNodeChannel(ctx, c.v, nodePub, cb, reset)
5✔
1103
}
5✔
1104

1105
// ForEachChannel iterates through all channel edges stored within the graph.
1106
func (c *VersionedGraph) ForEachChannel(ctx context.Context,
1107
        cb func(*models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
1108
                *models.ChannelEdgePolicy) error, reset func()) error {
3✔
1109

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

1113
// ForEachNodeCacheable iterates through all stored vertices/nodes in the graph.
1114
func (c *VersionedGraph) ForEachNodeCacheable(ctx context.Context,
1115
        cb func(route.Vertex, *lnwire.FeatureVector) error,
1116
        reset func()) error {
1✔
1117

1✔
1118
        return c.db.ForEachNodeCacheable(ctx, c.v, cb, reset)
1✔
1119
}
1✔
1120

1121
// ForEachChannelCacheable iterates through all channel edges for the cache.
1122
func (c *VersionedGraph) ForEachChannelCacheable(ctx context.Context,
1123
        cb func(*models.CachedEdgeInfo, *models.CachedEdgePolicy,
1124
                *models.CachedEdgePolicy) error, reset func()) error {
×
1125

×
1126
        return c.db.ForEachChannelCacheable(ctx, c.v, cb, reset)
×
1127
}
×
1128

1129
// DisabledChannelIDs returns the channel ids of disabled channels.
1130
func (c *VersionedGraph) DisabledChannelIDs(
1131
        ctx context.Context) ([]uint64, error) {
4✔
1132

4✔
1133
        return c.db.DisabledChannelIDs(ctx, c.v)
4✔
1134
}
4✔
1135

1136
// FetchChanInfos returns the set of channel edges for the passed channel IDs.
1137
func (c *VersionedGraph) FetchChanInfos(ctx context.Context,
1138
        chanIDs []uint64) ([]ChannelEdge, error) {
3✔
1139

3✔
1140
        return c.db.FetchChanInfos(ctx, c.v, chanIDs)
3✔
1141
}
3✔
1142

1143
// HighestChanID returns the "highest" known channel ID in the channel graph.
1144
func (c *VersionedGraph) HighestChanID(ctx context.Context) (uint64, error) {
3✔
1145
        return c.db.HighestChanID(ctx, c.v)
3✔
1146
}
3✔
1147

1148
// ChannelID attempts to lookup the 8-byte compact channel ID.
1149
func (c *VersionedGraph) ChannelID(ctx context.Context,
1150
        chanPoint *wire.OutPoint) (uint64, error) {
1✔
1151

1✔
1152
        return c.db.ChannelID(ctx, c.v, chanPoint)
1✔
1153
}
1✔
1154

1155
// IsPublicNode determines whether the node is seen as public in the graph.
1156
func (c *VersionedGraph) IsPublicNode(ctx context.Context,
1157
        pubKey [33]byte) (bool, error) {
14✔
1158

14✔
1159
        return c.db.IsPublicNode(ctx, c.v, pubKey)
14✔
1160
}
14✔
1161

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

183✔
1174
        t.Helper()
183✔
1175

183✔
1176
        store := NewTestDB(t)
183✔
1177

183✔
1178
        // Default to synchronous cache population in tests so that the
183✔
1179
        // cache is fully loaded before the test proceeds.
183✔
1180
        allOpts := append(
183✔
1181
                []ChanGraphOption{WithSyncGraphCachePopulation()}, opts...,
183✔
1182
        )
183✔
1183

183✔
1184
        graph, err := NewChannelGraph(store, allOpts...)
183✔
1185
        require.NoError(t, err)
183✔
1186
        require.NoError(t, graph.Start())
183✔
1187

183✔
1188
        t.Cleanup(func() {
366✔
1189
                require.NoError(t, graph.Stop())
183✔
1190
        })
183✔
1191

1192
        return graph
183✔
1193
}
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