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

lightningnetwork / lnd / 16777740336

06 Aug 2025 01:04PM UTC coverage: 54.85% (-12.1%) from 66.954%
16777740336

Pull #10135

github

web-flow
Merge 429aa830c into e512770f1
Pull Request #10135: docs: move v0.19.3 items to correct file

108702 of 198181 relevant lines covered (54.85%)

22045.97 hits per line

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

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

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

12
        "github.com/btcsuite/btcd/chaincfg/chainhash"
13
        "github.com/btcsuite/btcd/wire"
14
        "github.com/lightningnetwork/lnd/batch"
15
        "github.com/lightningnetwork/lnd/graph/db/models"
16
        "github.com/lightningnetwork/lnd/lnwire"
17
        "github.com/lightningnetwork/lnd/routing/route"
18
        "github.com/stretchr/testify/require"
19
)
20

21
// ErrChanGraphShuttingDown indicates that the ChannelGraph has shutdown or is
22
// busy shutting down.
23
var ErrChanGraphShuttingDown = fmt.Errorf("ChannelGraph shutting down")
24

25
// ChannelGraph is a layer above the graph's CRUD layer.
26
//
27
// NOTE: currently, this is purely a pass-through layer directly to the backing
28
// KVStore. Upcoming commits will move the graph cache out of the KVStore and
29
// into this layer so that the KVStore is only responsible for CRUD operations.
30
type ChannelGraph struct {
31
        started atomic.Bool
32
        stopped atomic.Bool
33

34
        graphCache *GraphCache
35

36
        V1Store
37
        *topologyManager
38

39
        quit chan struct{}
40
        wg   sync.WaitGroup
41
}
42

43
// NewChannelGraph creates a new ChannelGraph instance with the given backend.
44
func NewChannelGraph(v1Store V1Store,
45
        options ...ChanGraphOption) (*ChannelGraph, error) {
46

171✔
47
        opts := defaultChanGraphOptions()
171✔
48
        for _, o := range options {
171✔
49
                o(opts)
262✔
50
        }
91✔
51

91✔
52
        g := &ChannelGraph{
53
                V1Store:         v1Store,
171✔
54
                topologyManager: newTopologyManager(),
171✔
55
                quit:            make(chan struct{}),
171✔
56
        }
171✔
57

171✔
58
        // The graph cache can be turned off (e.g. for mobile users) for a
171✔
59
        // speed/memory usage tradeoff.
171✔
60
        if opts.useGraphCache {
171✔
61
                g.graphCache = NewGraphCache(opts.preAllocCacheNumNodes)
309✔
62
        }
138✔
63

138✔
64
        return g, nil
65
}
171✔
66

67
// Start kicks off any goroutines required for the ChannelGraph to function.
68
// If the graph cache is enabled, then it will be populated with the contents of
69
// the database.
70
func (c *ChannelGraph) Start() error {
71
        if !c.started.CompareAndSwap(false, true) {
284✔
72
                return nil
397✔
73
        }
113✔
74
        log.Debugf("ChannelGraph starting")
113✔
75
        defer log.Debug("ChannelGraph started")
171✔
76

171✔
77
        if c.graphCache != nil {
171✔
78
                if err := c.populateCache(context.TODO()); err != nil {
309✔
79
                        return fmt.Errorf("could not populate the graph "+
138✔
80
                                "cache: %w", err)
×
81
                }
×
82
        }
×
83

84
        c.wg.Add(1)
85
        go c.handleTopologySubscriptions()
171✔
86

171✔
87
        return nil
171✔
88
}
171✔
89

90
// Stop signals any active goroutines for a graceful closure.
91
func (c *ChannelGraph) Stop() error {
92
        if !c.stopped.CompareAndSwap(false, true) {
284✔
93
                return nil
397✔
94
        }
113✔
95

113✔
96
        log.Debugf("ChannelGraph shutting down...")
97
        defer log.Debug("ChannelGraph shutdown complete")
171✔
98

171✔
99
        close(c.quit)
171✔
100
        c.wg.Wait()
171✔
101

171✔
102
        return nil
171✔
103
}
171✔
104

105
// handleTopologySubscriptions ensures that topology client subscriptions,
106
// subscription cancellations and topology notifications are handled
107
// synchronously.
108
//
109
// NOTE: this MUST be run in a goroutine.
110
func (c *ChannelGraph) handleTopologySubscriptions() {
111
        defer c.wg.Done()
171✔
112

171✔
113
        for {
171✔
114
                select {
5,228✔
115
                // A new fully validated topology update has just arrived.
5,057✔
116
                // We'll notify any registered clients.
117
                case update := <-c.topologyUpdate:
118
                        // TODO(elle): change topology handling to be handled
4,881✔
119
                        // synchronously so that we can guarantee the order of
4,881✔
120
                        // notification delivery.
4,881✔
121
                        c.wg.Add(1)
4,881✔
122
                        go c.handleTopologyUpdate(update)
4,881✔
123

4,881✔
124
                        // TODO(roasbeef): remove all unconnected vertexes
125
                        // after N blocks pass with no corresponding
126
                        // announcements.
127

128
                // A new notification client update has arrived. We're either
129
                // gaining a new client, or cancelling notifications for an
130
                // existing client.
131
                case ntfnUpdate := <-c.ntfnClientUpdates:
132
                        clientID := ntfnUpdate.clientID
5✔
133

5✔
134
                        if ntfnUpdate.cancel {
5✔
135
                                client, ok := c.topologyClients.LoadAndDelete(
6✔
136
                                        clientID,
1✔
137
                                )
1✔
138
                                if ok {
1✔
139
                                        close(client.exit)
2✔
140
                                        client.wg.Wait()
1✔
141

1✔
142
                                        close(client.ntfnChan)
1✔
143
                                }
1✔
144

1✔
145
                                continue
146
                        }
1✔
147

148
                        c.topologyClients.Store(clientID, &topologyClient{
149
                                ntfnChan: ntfnUpdate.ntfnChan,
4✔
150
                                exit:     make(chan struct{}),
4✔
151
                        })
4✔
152

4✔
153
                case <-c.quit:
154
                        return
171✔
155
                }
171✔
156
        }
157
}
158

159
// populateCache loads the entire channel graph into the in-memory graph cache.
160
//
161
// NOTE: This should only be called if the graphCache has been constructed.
162
func (c *ChannelGraph) populateCache(ctx context.Context) error {
163
        startTime := time.Now()
138✔
164
        log.Info("Populating in-memory channel graph, this might take a " +
138✔
165
                "while...")
138✔
166

138✔
167
        err := c.V1Store.ForEachNodeCacheable(ctx, func(node route.Vertex,
138✔
168
                features *lnwire.FeatureVector) error {
138✔
169

238✔
170
                c.graphCache.AddNodeFeatures(node, features)
100✔
171

100✔
172
                return nil
100✔
173
        }, func() {})
100✔
174
        if err != nil {
238✔
175
                return err
138✔
176
        }
×
177

×
178
        err = c.V1Store.ForEachChannelCacheable(
179
                func(info *models.CachedEdgeInfo,
138✔
180
                        policy1, policy2 *models.CachedEdgePolicy) error {
138✔
181

534✔
182
                        c.graphCache.AddChannel(info, policy1, policy2)
396✔
183

396✔
184
                        return nil
396✔
185
                }, func() {},
396✔
186
        )
534✔
187
        if err != nil {
188
                return err
138✔
189
        }
×
190

×
191
        log.Infof("Finished populating in-memory channel graph (took %v, %s)",
192
                time.Since(startTime), c.graphCache.Stats())
138✔
193

138✔
194
        return nil
138✔
195
}
138✔
196

197
// ForEachNodeDirectedChannel iterates through all channels of a given node,
198
// executing the passed callback on the directed edge representing the channel
199
// and its incoming policy. If the callback returns an error, then the iteration
200
// is halted with the error propagated back up to the caller. If the graphCache
201
// is available, then it will be used to retrieve the node's channels instead
202
// of the database.
203
//
204
// Unknown policies are passed into the callback as nil values.
205
//
206
// NOTE: this is part of the graphdb.NodeTraverser interface.
207
func (c *ChannelGraph) ForEachNodeDirectedChannel(node route.Vertex,
208
        cb func(channel *DirectedChannel) error, reset func()) error {
209

508✔
210
        if c.graphCache != nil {
508✔
211
                return c.graphCache.ForEachChannel(node, cb)
1,013✔
212
        }
505✔
213

505✔
214
        return c.V1Store.ForEachNodeDirectedChannel(node, cb, reset)
215
}
3✔
216

217
// FetchNodeFeatures returns the features of the given node. If no features are
218
// known for the node, an empty feature vector is returned.
219
// If the graphCache is available, then it will be used to retrieve the node's
220
// features instead of the database.
221
//
222
// NOTE: this is part of the graphdb.NodeTraverser interface.
223
func (c *ChannelGraph) FetchNodeFeatures(node route.Vertex) (
224
        *lnwire.FeatureVector, error) {
225

462✔
226
        if c.graphCache != nil {
462✔
227
                return c.graphCache.GetFeatures(node), nil
924✔
228
        }
462✔
229

462✔
230
        return c.V1Store.FetchNodeFeatures(node)
231
}
×
232

233
// GraphSession will provide the call-back with access to a NodeTraverser
234
// instance which can be used to perform queries against the channel graph. If
235
// the graph cache is not enabled, then the call-back will be provided with
236
// access to the graph via a consistent read-only transaction.
237
func (c *ChannelGraph) GraphSession(cb func(graph NodeTraverser) error,
238
        reset func()) error {
239

133✔
240
        if c.graphCache != nil {
133✔
241
                return cb(c)
212✔
242
        }
79✔
243

79✔
244
        return c.V1Store.GraphSession(cb, reset)
245
}
54✔
246

247
// ForEachNodeCached iterates through all the stored vertices/nodes in the
248
// graph, executing the passed callback with each node encountered.
249
//
250
// NOTE: The callback contents MUST not be modified.
251
func (c *ChannelGraph) ForEachNodeCached(ctx context.Context,
252
        cb func(node route.Vertex, chans map[uint64]*DirectedChannel) error,
253
        reset func()) error {
254

120✔
255
        if c.graphCache != nil {
120✔
256
                return c.graphCache.ForEachNode(cb)
120✔
257
        }
×
258

×
259
        return c.V1Store.ForEachNodeCached(ctx, cb, reset)
×
260
}
×
261

×
262
// AddLightningNode adds a vertex/node to the graph database. If the node is not
×
263
// in the database from before, this will add a new, unconnected one to the
264
// graph. If it is present from before, this will update that node's
265
// information. Note that this method is expected to only be called to update an
266
// already present node from a node announcement, or to insert a node found in a
120✔
267
// channel update.
268
func (c *ChannelGraph) AddLightningNode(ctx context.Context,
269
        node *models.LightningNode, op ...batch.SchedulerOption) error {
270

271
        err := c.V1Store.AddLightningNode(ctx, node, op...)
272
        if err != nil {
273
                return err
274
        }
275

276
        if c.graphCache != nil {
712✔
277
                c.graphCache.AddNodeFeatures(
712✔
278
                        node.PubKeyBytes, node.Features,
712✔
279
                )
712✔
280
        }
×
281

×
282
        select {
283
        case c.topologyUpdate <- node:
1,270✔
284
        case <-c.quit:
558✔
285
                return ErrChanGraphShuttingDown
558✔
286
        }
558✔
287

558✔
288
        return nil
289
}
712✔
290

712✔
291
// DeleteLightningNode starts a new database transaction to remove a vertex/node
×
292
// from the database according to the node's public key.
×
293
func (c *ChannelGraph) DeleteLightningNode(ctx context.Context,
294
        nodePub route.Vertex) error {
295

712✔
296
        err := c.V1Store.DeleteLightningNode(ctx, nodePub)
297
        if err != nil {
298
                return err
299
        }
300

301
        if c.graphCache != nil {
4✔
302
                c.graphCache.RemoveNode(nodePub)
4✔
303
        }
4✔
304

5✔
305
        return nil
1✔
306
}
1✔
307

308
// AddChannelEdge adds a new (undirected, blank) edge to the graph database. An
6✔
309
// undirected edge from the two target nodes are created. The information stored
3✔
310
// denotes the static attributes of the channel, such as the channelID, the keys
3✔
311
// involved in creation of the channel, and the set of features that the channel
312
// supports. The chanPoint and chanID are used to uniquely identify the edge
3✔
313
// globally within the database.
314
func (c *ChannelGraph) AddChannelEdge(ctx context.Context,
315
        edge *models.ChannelEdgeInfo, op ...batch.SchedulerOption) error {
316

317
        err := c.V1Store.AddChannelEdge(ctx, edge, op...)
318
        if err != nil {
319
                return err
320
        }
321

322
        if c.graphCache != nil {
1,723✔
323
                c.graphCache.AddChannel(models.NewCachedEdge(edge), nil, nil)
1,723✔
324
        }
1,723✔
325

1,960✔
326
        select {
237✔
327
        case c.topologyUpdate <- edge:
237✔
328
        case <-c.quit:
329
                return ErrChanGraphShuttingDown
2,782✔
330
        }
1,296✔
331

1,296✔
332
        return nil
333
}
1,486✔
334

1,486✔
335
// MarkEdgeLive clears an edge from our zombie index, deeming it as live.
×
336
// If the cache is enabled, the edge will be added back to the graph cache if
×
337
// we still have a record of this channel in the DB.
338
func (c *ChannelGraph) MarkEdgeLive(chanID uint64) error {
339
        err := c.V1Store.MarkEdgeLive(chanID)
1,486✔
340
        if err != nil {
341
                return err
342
        }
343

344
        if c.graphCache != nil {
345
                // We need to add the channel back into our graph cache,
2✔
346
                // otherwise we won't use it for path finding.
2✔
347
                infos, err := c.V1Store.FetchChanInfos([]uint64{chanID})
3✔
348
                if err != nil {
1✔
349
                        return err
1✔
350
                }
351

2✔
352
                if len(infos) == 0 {
1✔
353
                        return nil
1✔
354
                }
1✔
355

1✔
356
                info := infos[0]
×
357

×
358
                var policy1, policy2 *models.CachedEdgePolicy
359
                if info.Policy1 != nil {
2✔
360
                        policy1 = models.NewCachedPolicy(info.Policy1)
1✔
361
                }
1✔
362
                if info.Policy2 != nil {
363
                        policy2 = models.NewCachedPolicy(info.Policy2)
×
364
                }
×
365

×
366
                c.graphCache.AddChannel(
×
367
                        models.NewCachedEdge(info.Info), policy1, policy2,
×
368
                )
×
369
        }
×
370

×
371
        return nil
×
372
}
373

×
374
// DeleteChannelEdges removes edges with the given channel IDs from the
×
375
// database and marks them as zombies. This ensures that we're unable to re-add
×
376
// it to our database once again. If an edge does not exist within the
377
// database, then ErrEdgeNotFound will be returned. If strictZombiePruning is
378
// true, then when we mark these edges as zombies, we'll set up the keys such
×
379
// that we require the node that failed to send the fresh update to be the one
380
// that resurrects the channel from its zombie state. The markZombie bool
381
// denotes whether to mark the channel as a zombie.
382
func (c *ChannelGraph) DeleteChannelEdges(strictZombiePruning, markZombie bool,
383
        chanIDs ...uint64) error {
384

385
        infos, err := c.V1Store.DeleteChannelEdges(
386
                strictZombiePruning, markZombie, chanIDs...,
387
        )
388
        if err != nil {
389
                return err
390
        }
142✔
391

142✔
392
        if c.graphCache != nil {
142✔
393
                for _, info := range infos {
142✔
394
                        c.graphCache.RemoveChannel(
142✔
395
                                info.NodeKey1Bytes, info.NodeKey2Bytes,
200✔
396
                                info.ChannelID,
58✔
397
                        )
58✔
398
                }
399
        }
168✔
400

109✔
401
        return err
25✔
402
}
25✔
403

25✔
404
// DisconnectBlockAtHeight is used to indicate that the block specified
25✔
405
// by the passed height has been disconnected from the main chain. This
25✔
406
// will "rewind" the graph back to the height below, deleting channels
407
// that are no longer confirmed from the graph. The prune log will be
408
// set to the last prune height valid for the remaining chain.
84✔
409
// Channels that were removed from the graph resulting from the
410
// disconnected block are returned.
411
func (c *ChannelGraph) DisconnectBlockAtHeight(height uint32) (
412
        []*models.ChannelEdgeInfo, error) {
413

414
        edges, err := c.V1Store.DisconnectBlockAtHeight(height)
415
        if err != nil {
416
                return nil, err
417
        }
418

419
        if c.graphCache != nil {
154✔
420
                for _, edge := range edges {
154✔
421
                        c.graphCache.RemoveChannel(
154✔
422
                                edge.NodeKey1Bytes, edge.NodeKey2Bytes,
154✔
423
                                edge.ChannelID,
×
424
                        )
×
425
                }
426
        }
308✔
427

248✔
428
        return edges, nil
94✔
429
}
94✔
430

94✔
431
// PruneGraph prunes newly closed channels from the channel graph in response
94✔
432
// to a new block being solved on the network. Any transactions which spend the
94✔
433
// funding output of any known channels within he graph will be deleted.
434
// Additionally, the "prune tip", or the last block which has been used to
435
// prune the graph is stored so callers can ensure the graph is fully in sync
154✔
436
// with the current UTXO state. A slice of channels that have been closed by
437
// the target block are returned if the function succeeds without error.
438
func (c *ChannelGraph) PruneGraph(spentOutputs []*wire.OutPoint,
439
        blockHash *chainhash.Hash, blockHeight uint32) (
440
        []*models.ChannelEdgeInfo, error) {
441

442
        edges, nodes, err := c.V1Store.PruneGraph(
443
                spentOutputs, blockHash, blockHeight,
444
        )
445
        if err != nil {
446
                return nil, err
447
        }
231✔
448

231✔
449
        if c.graphCache != nil {
231✔
450
                for _, edge := range edges {
231✔
451
                        c.graphCache.RemoveChannel(
231✔
452
                                edge.NodeKey1Bytes, edge.NodeKey2Bytes,
231✔
453
                                edge.ChannelID,
×
454
                        )
×
455
                }
456

462✔
457
                for _, node := range nodes {
250✔
458
                        c.graphCache.RemoveNode(node)
19✔
459
                }
19✔
460

19✔
461
                log.Debugf("Pruned graph, cache now has %s",
19✔
462
                        c.graphCache.Stats())
19✔
463
        }
464

285✔
465
        if len(edges) != 0 {
54✔
466
                // Notify all currently registered clients of the newly closed
54✔
467
                // channels.
468
                closeSummaries := createCloseSummaries(
231✔
469
                        blockHeight, edges...,
231✔
470
                )
471

472
                select {
247✔
473
                case c.topologyUpdate <- closeSummaries:
16✔
474
                case <-c.quit:
16✔
475
                        return nil, ErrChanGraphShuttingDown
16✔
476
                }
16✔
477
        }
16✔
478

16✔
479
        return edges, nil
16✔
480
}
16✔
481

×
482
// PruneGraphNodes is a garbage collection method which attempts to prune out
×
483
// any nodes from the channel graph that are currently unconnected. This ensure
484
// that we only maintain a graph of reachable nodes. In the event that a pruned
485
// node gains more channels, it will be re-added back to the graph.
486
func (c *ChannelGraph) PruneGraphNodes() error {
231✔
487
        nodes, err := c.V1Store.PruneGraphNodes()
488
        if err != nil {
489
                return err
490
        }
491

492
        if c.graphCache != nil {
493
                for _, node := range nodes {
23✔
494
                        c.graphCache.RemoveNode(node)
23✔
495
                }
23✔
496
        }
×
497

×
498
        return nil
499
}
46✔
500

30✔
501
// FilterKnownChanIDs takes a set of channel IDs and return the subset of chan
7✔
502
// ID's that we don't know and are not known zombies of the passed set. In other
7✔
503
// words, we perform a set difference of our set of chan ID's and the ones
504
// passed in. This method can be used by callers to determine the set of
505
// channels another peer knows of that we don't.
23✔
506
func (c *ChannelGraph) FilterKnownChanIDs(chansInfo []ChannelUpdateInfo,
507
        isZombieChan func(time.Time, time.Time) bool) ([]uint64, error) {
508

509
        unknown, knownZombies, err := c.V1Store.FilterKnownChanIDs(chansInfo)
510
        if err != nil {
511
                return nil, err
512
        }
513

514
        for _, info := range knownZombies {
124✔
515
                // TODO(ziggie): Make sure that for the strict pruning case we
124✔
516
                // compare the pubkeys and whether the right timestamp is not
124✔
517
                // older than the `ChannelPruneExpiry`.
124✔
518
                //
×
519
                // NOTE: The timestamp data has no verification attached to it
×
520
                // in the `ReplyChannelRange` msg so we are trusting this data
521
                // at this point. However it is not critical because we are just
165✔
522
                // removing the channel from the db when the timestamps are more
41✔
523
                // recent. During the querying of the gossip msg verification
41✔
524
                // happens as usual. However we should start punishing peers
41✔
525
                // when they don't provide us honest data ?
41✔
526
                isStillZombie := isZombieChan(
41✔
527
                        info.Node1UpdateTimestamp, info.Node2UpdateTimestamp,
41✔
528
                )
41✔
529

41✔
530
                if isStillZombie {
41✔
531
                        continue
41✔
532
                }
41✔
533

41✔
534
                // If we have marked it as a zombie but the latest update
41✔
535
                // timestamps could bring it back from the dead, then we mark it
41✔
536
                // alive, and we let it be added to the set of IDs to query our
41✔
537
                // peer for.
64✔
538
                err := c.V1Store.MarkEdgeLive(
23✔
539
                        info.ShortChannelID.ToUint64(),
540
                )
541
                // Since there is a chance that the edge could have been marked
542
                // as "live" between the FilterKnownChanIDs call and the
543
                // MarkEdgeLive call, we ignore the error if the edge is already
544
                // marked as live.
545
                if err != nil && !errors.Is(err, ErrZombieEdgeNotFound) {
18✔
546
                        return nil, err
18✔
547
                }
18✔
548
        }
18✔
549

18✔
550
        return unknown, nil
18✔
551
}
18✔
552

18✔
553
// MarkEdgeZombie attempts to mark a channel identified by its channel ID as a
×
554
// zombie. This method is used on an ad-hoc basis, when channels need to be
×
555
// marked as zombies outside the normal pruning cycle.
556
func (c *ChannelGraph) MarkEdgeZombie(chanID uint64,
557
        pubKey1, pubKey2 [33]byte) error {
124✔
558

559
        err := c.V1Store.MarkEdgeZombie(chanID, pubKey1, pubKey2)
560
        if err != nil {
561
                return err
562
        }
563

564
        if c.graphCache != nil {
127✔
565
                c.graphCache.RemoveChannel(pubKey1, pubKey2, chanID)
127✔
566
        }
127✔
567

127✔
568
        return nil
×
569
}
×
570

571
// UpdateEdgePolicy updates the edge routing policy for a single directed edge
254✔
572
// within the database for the referenced channel. The `flags` attribute within
127✔
573
// the ChannelEdgePolicy determines which of the directed edges are being
127✔
574
// updated. If the flag is 1, then the first node's information is being
575
// updated, otherwise it's the second node's information. The node ordering is
127✔
576
// determined by the lexicographical ordering of the identity public keys of the
577
// nodes on either side of the channel.
578
func (c *ChannelGraph) UpdateEdgePolicy(ctx context.Context,
579
        edge *models.ChannelEdgePolicy, op ...batch.SchedulerOption) error {
580

581
        from, to, err := c.V1Store.UpdateEdgePolicy(ctx, edge, op...)
582
        if err != nil {
583
                return err
584
        }
585

586
        if c.graphCache != nil {
2,672✔
587
                c.graphCache.UpdatePolicy(
2,672✔
588
                        models.NewCachedPolicy(edge), from, to,
2,672✔
589
                )
2,677✔
590
        }
5✔
591

5✔
592
        select {
593
        case c.topologyUpdate <- edge:
4,949✔
594
        case <-c.quit:
2,282✔
595
                return ErrChanGraphShuttingDown
2,282✔
596
        }
2,282✔
597

2,282✔
598
        return nil
599
}
2,667✔
600

2,667✔
601
// MakeTestGraph creates a new instance of the ChannelGraph for testing
×
602
// purposes. The backing V1Store implementation depends on the version of
×
603
// NewTestDB included in the current build.
604
//
605
// NOTE: this is currently unused, but is left here for future use to show how
2,667✔
606
// NewTestDB can be used. As the SQL implementation of the V1Store is
607
// implemented, unit tests will be switched to use this function instead of
608
// the existing MakeTestGraph helper. Once only this function is used, the
609
// existing MakeTestGraph function will be removed and this one will be renamed.
610
func MakeTestGraph(t testing.TB,
611
        opts ...ChanGraphOption) *ChannelGraph {
612

613
        t.Helper()
614

615
        store := NewTestDB(t)
616

617
        graph, err := NewChannelGraph(store, opts...)
618
        require.NoError(t, err)
169✔
619
        require.NoError(t, graph.Start())
169✔
620

169✔
621
        t.Cleanup(func() {
169✔
622
                require.NoError(t, graph.Stop())
169✔
623
        })
169✔
624

169✔
625
        return graph
169✔
626
}
169✔
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

© 2025 Coveralls, Inc