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

lightningnetwork / lnd / 15858991938

24 Jun 2025 06:51PM UTC coverage: 55.808% (-2.4%) from 58.173%
15858991938

Pull #9148

github

web-flow
Merge 0e921d6a5 into 29ff13d83
Pull Request #9148: DynComms [2/n]: lnwire: add authenticated wire messages for Dyn*

232 of 267 new or added lines in 5 files covered. (86.89%)

24606 existing lines in 281 files now uncovered.

108380 of 194201 relevant lines covered (55.81%)

22488.12 hits per line

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

86.34
/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) {
170✔
46

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

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

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

64
        return g, nil
170✔
65
}
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 {
283✔
71
        if !c.started.CompareAndSwap(false, true) {
396✔
72
                return nil
113✔
73
        }
113✔
74
        log.Debugf("ChannelGraph starting")
170✔
75
        defer log.Debug("ChannelGraph started")
170✔
76

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

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

170✔
87
        return nil
170✔
88
}
89

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

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

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

170✔
102
        return nil
170✔
103
}
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() {
170✔
111
        defer c.wg.Done()
170✔
112

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

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:
5✔
132
                        clientID := ntfnUpdate.clientID
5✔
133

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

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

145
                                continue
1✔
146
                        }
147

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

153
                case <-c.quit:
170✔
154
                        return
170✔
155
                }
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() error {
137✔
163
        startTime := time.Now()
137✔
164
        log.Info("Populating in-memory channel graph, this might take a " +
137✔
165
                "while...")
137✔
166

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

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

100✔
172
                return nil
100✔
173
        })
100✔
174
        if err != nil {
137✔
175
                return err
×
176
        }
×
177

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

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

396✔
184
                        return nil
396✔
185
                })
396✔
186
        if err != nil {
137✔
187
                return err
×
188
        }
×
189

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

137✔
193
        return nil
137✔
194
}
195

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

503✔
209
        if c.graphCache != nil {
1,003✔
210
                return c.graphCache.ForEachChannel(node, cb)
500✔
211
        }
500✔
212

213
        return c.V1Store.ForEachNodeDirectedChannel(node, cb)
3✔
214
}
215

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

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

UNCOV
229
        return c.V1Store.FetchNodeFeatures(node)
×
230
}
231

232
// GraphSession will provide the call-back with access to a NodeTraverser
233
// instance which can be used to perform queries against the channel graph. If
234
// the graph cache is not enabled, then the call-back will be provided with
235
// access to the graph via a consistent read-only transaction.
236
func (c *ChannelGraph) GraphSession(cb func(graph NodeTraverser) error) error {
133✔
237
        if c.graphCache != nil {
212✔
238
                return cb(c)
79✔
239
        }
79✔
240

241
        return c.V1Store.GraphSession(cb)
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(cb func(node route.Vertex,
249
        chans map[uint64]*DirectedChannel) error) error {
1✔
250

1✔
251
        if c.graphCache != nil {
1✔
252
                return c.graphCache.ForEachNode(cb)
×
253
        }
×
254

255
        return c.V1Store.ForEachNodeCached(cb)
1✔
256
}
257

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

803✔
267
        err := c.V1Store.AddLightningNode(ctx, node, op...)
803✔
268
        if err != nil {
803✔
269
                return err
×
270
        }
×
271

272
        if c.graphCache != nil {
1,419✔
273
                c.graphCache.AddNodeFeatures(
616✔
274
                        node.PubKeyBytes, node.Features,
616✔
275
                )
616✔
276
        }
616✔
277

278
        select {
803✔
279
        case c.topologyUpdate <- node:
803✔
280
        case <-c.quit:
×
281
                return ErrChanGraphShuttingDown
×
282
        }
283

284
        return nil
803✔
285
}
286

287
// DeleteLightningNode starts a new database transaction to remove a vertex/node
288
// from the database according to the node's public key.
289
func (c *ChannelGraph) DeleteLightningNode(ctx context.Context,
290
        nodePub route.Vertex) error {
4✔
291

4✔
292
        err := c.V1Store.DeleteLightningNode(ctx, nodePub)
4✔
293
        if err != nil {
5✔
294
                return err
1✔
295
        }
1✔
296

297
        if c.graphCache != nil {
6✔
298
                c.graphCache.RemoveNode(nodePub)
3✔
299
        }
3✔
300

301
        return nil
3✔
302
}
303

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

1,731✔
313
        err := c.V1Store.AddChannelEdge(ctx, edge, op...)
1,731✔
314
        if err != nil {
1,968✔
315
                return err
237✔
316
        }
237✔
317

318
        if c.graphCache != nil {
2,798✔
319
                c.graphCache.AddChannel(models.NewCachedEdge(edge), nil, nil)
1,304✔
320
        }
1,304✔
321

322
        select {
1,494✔
323
        case c.topologyUpdate <- edge:
1,494✔
324
        case <-c.quit:
×
325
                return ErrChanGraphShuttingDown
×
326
        }
327

328
        return nil
1,494✔
329
}
330

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

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

348
                if len(infos) == 0 {
2✔
349
                        return nil
1✔
350
                }
1✔
351

352
                info := infos[0]
×
353

×
354
                var policy1, policy2 *models.CachedEdgePolicy
×
355
                if info.Policy1 != nil {
×
356
                        policy1 = models.NewCachedPolicy(info.Policy1)
×
357
                }
×
358
                if info.Policy2 != nil {
×
359
                        policy2 = models.NewCachedPolicy(info.Policy2)
×
360
                }
×
361

362
                c.graphCache.AddChannel(
×
363
                        models.NewCachedEdge(info.Info), policy1, policy2,
×
364
                )
×
365
        }
366

367
        return nil
×
368
}
369

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

142✔
381
        infos, err := c.V1Store.DeleteChannelEdges(
142✔
382
                strictZombiePruning, markZombie, chanIDs...,
142✔
383
        )
142✔
384
        if err != nil {
204✔
385
                return err
62✔
386
        }
62✔
387

388
        if c.graphCache != nil {
160✔
389
                for _, info := range infos {
103✔
390
                        c.graphCache.RemoveChannel(
23✔
391
                                info.NodeKey1Bytes, info.NodeKey2Bytes,
23✔
392
                                info.ChannelID,
23✔
393
                        )
23✔
394
                }
23✔
395
        }
396

397
        return err
80✔
398
}
399

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

154✔
410
        edges, err := c.V1Store.DisconnectBlockAtHeight(height)
154✔
411
        if err != nil {
154✔
412
                return nil, err
×
413
        }
×
414

415
        if c.graphCache != nil {
308✔
416
                for _, edge := range edges {
260✔
417
                        c.graphCache.RemoveChannel(
106✔
418
                                edge.NodeKey1Bytes, edge.NodeKey2Bytes,
106✔
419
                                edge.ChannelID,
106✔
420
                        )
106✔
421
                }
106✔
422
        }
423

424
        return edges, nil
154✔
425
}
426

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

238✔
438
        edges, nodes, err := c.V1Store.PruneGraph(
238✔
439
                spentOutputs, blockHash, blockHeight,
238✔
440
        )
238✔
441
        if err != nil {
238✔
442
                return nil, err
×
443
        }
×
444

445
        if c.graphCache != nil {
476✔
446
                for _, edge := range edges {
256✔
447
                        c.graphCache.RemoveChannel(
18✔
448
                                edge.NodeKey1Bytes, edge.NodeKey2Bytes,
18✔
449
                                edge.ChannelID,
18✔
450
                        )
18✔
451
                }
18✔
452

453
                for _, node := range nodes {
294✔
454
                        c.graphCache.RemoveNode(node)
56✔
455
                }
56✔
456

457
                log.Debugf("Pruned graph, cache now has %s",
238✔
458
                        c.graphCache.Stats())
238✔
459
        }
460

461
        if len(edges) != 0 {
253✔
462
                // Notify all currently registered clients of the newly closed
15✔
463
                // channels.
15✔
464
                closeSummaries := createCloseSummaries(
15✔
465
                        blockHeight, edges...,
15✔
466
                )
15✔
467

15✔
468
                select {
15✔
469
                case c.topologyUpdate <- closeSummaries:
15✔
470
                case <-c.quit:
×
471
                        return nil, ErrChanGraphShuttingDown
×
472
                }
473
        }
474

475
        return edges, nil
238✔
476
}
477

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

488
        if c.graphCache != nil {
46✔
489
                for _, node := range nodes {
30✔
490
                        c.graphCache.RemoveNode(node)
7✔
491
                }
7✔
492
        }
493

494
        return nil
23✔
495
}
496

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

126✔
505
        unknown, knownZombies, err := c.V1Store.FilterKnownChanIDs(chansInfo)
126✔
506
        if err != nil {
126✔
507
                return nil, err
×
508
        }
×
509

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

47✔
526
                if isStillZombie {
71✔
527
                        continue
24✔
528
                }
529

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

546
        return unknown, nil
126✔
547
}
548

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

125✔
555
        err := c.V1Store.MarkEdgeZombie(chanID, pubKey1, pubKey2)
125✔
556
        if err != nil {
125✔
557
                return err
×
558
        }
×
559

560
        if c.graphCache != nil {
250✔
561
                c.graphCache.RemoveChannel(pubKey1, pubKey2, chanID)
125✔
562
        }
125✔
563

564
        return nil
125✔
565
}
566

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

2,665✔
577
        from, to, err := c.V1Store.UpdateEdgePolicy(ctx, edge, op...)
2,665✔
578
        if err != nil {
2,669✔
579
                return err
4✔
580
        }
4✔
581

582
        if c.graphCache != nil {
4,937✔
583
                c.graphCache.UpdatePolicy(
2,276✔
584
                        models.NewCachedPolicy(edge), from, to,
2,276✔
585
                )
2,276✔
586
        }
2,276✔
587

588
        select {
2,661✔
589
        case c.topologyUpdate <- edge:
2,661✔
590
        case <-c.quit:
×
591
                return ErrChanGraphShuttingDown
×
592
        }
593

594
        return nil
2,661✔
595
}
596

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

19✔
609
        t.Helper()
19✔
610

19✔
611
        store := NewTestDB(t)
19✔
612

19✔
613
        graph, err := NewChannelGraph(store, opts...)
19✔
614
        require.NoError(t, err)
19✔
615
        require.NoError(t, graph.Start())
19✔
616

19✔
617
        t.Cleanup(func() {
38✔
618
                require.NoError(t, graph.Stop())
19✔
619
        })
19✔
620

621
        return graph
19✔
622
}
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