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

lightningnetwork / lnd / 19924300449

04 Dec 2025 09:35AM UTC coverage: 53.479% (-1.9%) from 55.404%
19924300449

Pull #10419

github

web-flow
Merge f811805c6 into 20473482d
Pull Request #10419: [docs] Document use-native-sql=true for SQL migration step 2

110496 of 206616 relevant lines covered (53.48%)

21221.61 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
        "net"
8
        "sync"
9
        "sync/atomic"
10
        "testing"
11
        "time"
12

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

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

26
// ChannelGraph is a layer above the graph's CRUD layer.
27
type ChannelGraph struct {
28
        started atomic.Bool
29
        stopped atomic.Bool
30

31
        graphCache *GraphCache
32

33
        V1Store
34
        *topologyManager
35

36
        quit chan struct{}
37
        wg   sync.WaitGroup
38
}
39

40
// NewChannelGraph creates a new ChannelGraph instance with the given backend.
41
func NewChannelGraph(v1Store V1Store,
42
        options ...ChanGraphOption) (*ChannelGraph, error) {
183✔
43

183✔
44
        opts := defaultChanGraphOptions()
183✔
45
        for _, o := range options {
274✔
46
                o(opts)
91✔
47
        }
91✔
48

49
        g := &ChannelGraph{
183✔
50
                V1Store:         v1Store,
183✔
51
                topologyManager: newTopologyManager(),
183✔
52
                quit:            make(chan struct{}),
183✔
53
        }
183✔
54

183✔
55
        // The graph cache can be turned off (e.g. for mobile users) for a
183✔
56
        // speed/memory usage tradeoff.
183✔
57
        if opts.useGraphCache {
333✔
58
                g.graphCache = NewGraphCache(opts.preAllocCacheNumNodes)
150✔
59
        }
150✔
60

61
        return g, nil
183✔
62
}
63

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

183✔
74
        if c.graphCache != nil {
333✔
75
                if err := c.populateCache(context.TODO()); err != nil {
150✔
76
                        return fmt.Errorf("could not populate the graph "+
×
77
                                "cache: %w", err)
×
78
                }
×
79
        }
80

81
        c.wg.Add(1)
183✔
82
        go c.handleTopologySubscriptions()
183✔
83

183✔
84
        return nil
183✔
85
}
86

87
// Stop signals any active goroutines for a graceful closure.
88
func (c *ChannelGraph) Stop() error {
296✔
89
        if !c.stopped.CompareAndSwap(false, true) {
409✔
90
                return nil
113✔
91
        }
113✔
92

93
        log.Debugf("ChannelGraph shutting down...")
183✔
94
        defer log.Debug("ChannelGraph shutdown complete")
183✔
95

183✔
96
        close(c.quit)
183✔
97
        c.wg.Wait()
183✔
98

183✔
99
        return nil
183✔
100
}
101

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

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

121
                        // TODO(roasbeef): remove all unconnected vertexes
122
                        // after N blocks pass with no corresponding
123
                        // announcements.
124

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

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

1✔
139
                                        close(client.ntfnChan)
1✔
140
                                }
1✔
141

142
                                continue
1✔
143
                        }
144

145
                        c.topologyClients.Store(clientID, &topologyClient{
4✔
146
                                ntfnChan: ntfnUpdate.ntfnChan,
4✔
147
                                exit:     make(chan struct{}),
4✔
148
                        })
4✔
149

150
                case <-c.quit:
183✔
151
                        return
183✔
152
                }
153
        }
154
}
155

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

150✔
164
        err := c.V1Store.ForEachNodeCacheable(ctx, func(node route.Vertex,
150✔
165
                features *lnwire.FeatureVector) error {
250✔
166

100✔
167
                c.graphCache.AddNodeFeatures(node, features)
100✔
168

100✔
169
                return nil
100✔
170
        }, func() {})
250✔
171
        if err != nil {
150✔
172
                return err
×
173
        }
×
174

175
        err = c.V1Store.ForEachChannelCacheable(
150✔
176
                func(info *models.CachedEdgeInfo,
150✔
177
                        policy1, policy2 *models.CachedEdgePolicy) error {
546✔
178

396✔
179
                        c.graphCache.AddChannel(info, policy1, policy2)
396✔
180

396✔
181
                        return nil
396✔
182
                }, func() {},
546✔
183
        )
184
        if err != nil {
150✔
185
                return err
×
186
        }
×
187

188
        log.Infof("Finished populating in-memory channel graph (took %v, %s)",
150✔
189
                time.Since(startTime), c.graphCache.Stats())
150✔
190

150✔
191
        return nil
150✔
192
}
193

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

508✔
207
        if c.graphCache != nil {
1,013✔
208
                return c.graphCache.ForEachChannel(node, cb)
505✔
209
        }
505✔
210

211
        return c.V1Store.ForEachNodeDirectedChannel(node, cb, reset)
3✔
212
}
213

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

462✔
223
        if c.graphCache != nil {
924✔
224
                return c.graphCache.GetFeatures(node), nil
462✔
225
        }
462✔
226

227
        return c.V1Store.FetchNodeFeatures(node)
×
228
}
229

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

133✔
237
        if c.graphCache != nil {
212✔
238
                return cb(c)
79✔
239
        }
79✔
240

241
        return c.V1Store.GraphSession(cb, reset)
54✔
242
}
243

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

120✔
252
        if !withAddrs && c.graphCache != nil {
120✔
253
                return c.graphCache.ForEachNode(
×
254
                        func(node route.Vertex,
×
255
                                channels map[uint64]*DirectedChannel) error {
×
256

×
257
                                return cb(ctx, node, nil, channels)
×
258
                        },
×
259
                )
260
        }
261

262
        return c.V1Store.ForEachNodeCached(ctx, withAddrs, cb, reset)
120✔
263
}
264

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

971✔
274
        err := c.V1Store.AddNode(ctx, node, op...)
971✔
275
        if err != nil {
971✔
276
                return err
×
277
        }
×
278

279
        if c.graphCache != nil {
1,788✔
280
                c.graphCache.AddNodeFeatures(
817✔
281
                        node.PubKeyBytes, node.Features,
817✔
282
                )
817✔
283
        }
817✔
284

285
        select {
971✔
286
        case c.topologyUpdate <- node:
971✔
287
        case <-c.quit:
×
288
                return ErrChanGraphShuttingDown
×
289
        }
290

291
        return nil
971✔
292
}
293

294
// DeleteNode starts a new database transaction to remove a vertex/node
295
// from the database according to the node's public key.
296
func (c *ChannelGraph) DeleteNode(ctx context.Context,
297
        nodePub route.Vertex) error {
4✔
298

4✔
299
        err := c.V1Store.DeleteNode(ctx, nodePub)
4✔
300
        if err != nil {
5✔
301
                return err
1✔
302
        }
1✔
303

304
        if c.graphCache != nil {
6✔
305
                c.graphCache.RemoveNode(nodePub)
3✔
306
        }
3✔
307

308
        return nil
3✔
309
}
310

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

1,824✔
320
        err := c.V1Store.AddChannelEdge(ctx, edge, op...)
1,824✔
321
        if err != nil {
2,061✔
322
                return err
237✔
323
        }
237✔
324

325
        if c.graphCache != nil {
2,984✔
326
                c.graphCache.AddChannel(models.NewCachedEdge(edge), nil, nil)
1,397✔
327
        }
1,397✔
328

329
        select {
1,587✔
330
        case c.topologyUpdate <- edge:
1,587✔
331
        case <-c.quit:
×
332
                return ErrChanGraphShuttingDown
×
333
        }
334

335
        return nil
1,587✔
336
}
337

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

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

355
                if len(infos) == 0 {
2✔
356
                        return nil
1✔
357
                }
1✔
358

359
                info := infos[0]
×
360

×
361
                var policy1, policy2 *models.CachedEdgePolicy
×
362
                if info.Policy1 != nil {
×
363
                        policy1 = models.NewCachedPolicy(info.Policy1)
×
364
                }
×
365
                if info.Policy2 != nil {
×
366
                        policy2 = models.NewCachedPolicy(info.Policy2)
×
367
                }
×
368

369
                c.graphCache.AddChannel(
×
370
                        models.NewCachedEdge(info.Info), policy1, policy2,
×
371
                )
×
372
        }
373

374
        return nil
×
375
}
376

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

152✔
388
        infos, err := c.V1Store.DeleteChannelEdges(
152✔
389
                strictZombiePruning, markZombie, chanIDs...,
152✔
390
        )
152✔
391
        if err != nil {
213✔
392
                return err
61✔
393
        }
61✔
394

395
        if c.graphCache != nil {
182✔
396
                for _, info := range infos {
114✔
397
                        c.graphCache.RemoveChannel(
23✔
398
                                info.NodeKey1Bytes, info.NodeKey2Bytes,
23✔
399
                                info.ChannelID,
23✔
400
                        )
23✔
401
                }
23✔
402
        }
403

404
        return err
91✔
405
}
406

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

154✔
417
        edges, err := c.V1Store.DisconnectBlockAtHeight(height)
154✔
418
        if err != nil {
154✔
419
                return nil, err
×
420
        }
×
421

422
        if c.graphCache != nil {
308✔
423
                for _, edge := range edges {
251✔
424
                        c.graphCache.RemoveChannel(
97✔
425
                                edge.NodeKey1Bytes, edge.NodeKey2Bytes,
97✔
426
                                edge.ChannelID,
97✔
427
                        )
97✔
428
                }
97✔
429
        }
430

431
        return edges, nil
154✔
432
}
433

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

240✔
445
        edges, nodes, err := c.V1Store.PruneGraph(
240✔
446
                spentOutputs, blockHash, blockHeight,
240✔
447
        )
240✔
448
        if err != nil {
240✔
449
                return nil, err
×
450
        }
×
451

452
        if c.graphCache != nil {
480✔
453
                for _, edge := range edges {
257✔
454
                        c.graphCache.RemoveChannel(
17✔
455
                                edge.NodeKey1Bytes, edge.NodeKey2Bytes,
17✔
456
                                edge.ChannelID,
17✔
457
                        )
17✔
458
                }
17✔
459

460
                for _, node := range nodes {
295✔
461
                        c.graphCache.RemoveNode(node)
55✔
462
                }
55✔
463

464
                log.Debugf("Pruned graph, cache now has %s",
240✔
465
                        c.graphCache.Stats())
240✔
466
        }
467

468
        if len(edges) != 0 {
254✔
469
                // Notify all currently registered clients of the newly closed
14✔
470
                // channels.
14✔
471
                closeSummaries := createCloseSummaries(
14✔
472
                        blockHeight, edges...,
14✔
473
                )
14✔
474

14✔
475
                select {
14✔
476
                case c.topologyUpdate <- closeSummaries:
14✔
477
                case <-c.quit:
×
478
                        return nil, ErrChanGraphShuttingDown
×
479
                }
480
        }
481

482
        return edges, nil
240✔
483
}
484

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

495
        if c.graphCache != nil {
46✔
496
                for _, node := range nodes {
30✔
497
                        c.graphCache.RemoveNode(node)
7✔
498
                }
7✔
499
        }
500

501
        return nil
23✔
502
}
503

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

122✔
512
        unknown, knownZombies, err := c.V1Store.FilterKnownChanIDs(chansInfo)
122✔
513
        if err != nil {
122✔
514
                return nil, err
×
515
        }
×
516

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

44✔
533
                if isStillZombie {
69✔
534
                        continue
25✔
535
                }
536

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

553
        return unknown, nil
122✔
554
}
555

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

123✔
562
        err := c.V1Store.MarkEdgeZombie(chanID, pubKey1, pubKey2)
123✔
563
        if err != nil {
123✔
564
                return err
×
565
        }
×
566

567
        if c.graphCache != nil {
246✔
568
                c.graphCache.RemoveChannel(pubKey1, pubKey2, chanID)
123✔
569
        }
123✔
570

571
        return nil
123✔
572
}
573

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

2,875✔
584
        from, to, err := c.V1Store.UpdateEdgePolicy(ctx, edge, op...)
2,875✔
585
        if err != nil {
2,880✔
586
                return err
5✔
587
        }
5✔
588

589
        if c.graphCache != nil {
5,355✔
590
                c.graphCache.UpdatePolicy(
2,485✔
591
                        models.NewCachedPolicy(edge), from, to,
2,485✔
592
                )
2,485✔
593
        }
2,485✔
594

595
        select {
2,870✔
596
        case c.topologyUpdate <- edge:
2,870✔
597
        case <-c.quit:
×
598
                return ErrChanGraphShuttingDown
×
599
        }
600

601
        return nil
2,870✔
602
}
603

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

181✔
616
        t.Helper()
181✔
617

181✔
618
        store := NewTestDB(t)
181✔
619

181✔
620
        graph, err := NewChannelGraph(store, opts...)
181✔
621
        require.NoError(t, err)
181✔
622
        require.NoError(t, graph.Start())
181✔
623

181✔
624
        t.Cleanup(func() {
362✔
625
                require.NoError(t, graph.Stop())
181✔
626
        })
181✔
627

628
        return graph
181✔
629
}
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