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

lightningnetwork / lnd / 12312390362

13 Dec 2024 08:44AM UTC coverage: 57.458% (+8.5%) from 48.92%
12312390362

Pull #9343

github

ellemouton
fn: rework the ContextGuard and add tests

In this commit, the ContextGuard struct is re-worked such that the
context that its new main WithCtx method provides is cancelled in sync
with a parent context being cancelled or with it's quit channel being
cancelled. Tests are added to assert the behaviour. In order for the
close of the quit channel to be consistent with the cancelling of the
derived context, the quit channel _must_ be contained internal to the
ContextGuard so that callers are only able to close the channel via the
exposed Quit method which will then take care to first cancel any
derived context that depend on the quit channel before returning.
Pull Request #9343: fn: expand the ContextGuard and add tests

101853 of 177264 relevant lines covered (57.46%)

24972.93 hits per line

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

77.11
/htlcswitch/circuit_map.go
1
package htlcswitch
2

3
import (
4
        "bytes"
5
        "fmt"
6
        "sync"
7

8
        "github.com/go-errors/errors"
9
        "github.com/lightningnetwork/lnd/channeldb"
10
        "github.com/lightningnetwork/lnd/htlcswitch/hop"
11
        "github.com/lightningnetwork/lnd/kvdb"
12
        "github.com/lightningnetwork/lnd/lnutils"
13
        "github.com/lightningnetwork/lnd/lnwire"
14
)
15

16
var (
17
        // ErrCorruptedCircuitMap indicates that the on-disk bucketing structure
18
        // has altered since the circuit map instance was initialized.
19
        ErrCorruptedCircuitMap = errors.New("circuit map has been corrupted")
20

21
        // ErrCircuitNotInHashIndex indicates that a particular circuit did not
22
        // appear in the in-memory hash index.
23
        ErrCircuitNotInHashIndex = errors.New("payment circuit not found in " +
24
                "hash index")
25

26
        // ErrUnknownCircuit signals that circuit could not be removed from the
27
        // map because it was not found.
28
        ErrUnknownCircuit = errors.New("unknown payment circuit")
29

30
        // ErrCircuitClosing signals that an htlc has already closed this
31
        // circuit in-memory.
32
        ErrCircuitClosing = errors.New("circuit has already been closed")
33

34
        // ErrDuplicateCircuit signals that this circuit was previously
35
        // added.
36
        ErrDuplicateCircuit = errors.New("duplicate circuit add")
37

38
        // ErrUnknownKeystone signals that no circuit was found using the
39
        // outgoing circuit key.
40
        ErrUnknownKeystone = errors.New("unknown circuit keystone")
41

42
        // ErrDuplicateKeystone signals that this circuit was previously
43
        // assigned a keystone.
44
        ErrDuplicateKeystone = errors.New("cannot add duplicate keystone")
45
)
46

47
// CircuitModifier is a common interface used by channel links to modify the
48
// contents of the circuit map maintained by the switch.
49
type CircuitModifier interface {
50
        // OpenCircuits preemptively records a batch keystones that will mark
51
        // currently pending circuits as open. These changes can be rolled back
52
        // on restart if the outgoing Adds do not make it into a commitment
53
        // txn.
54
        OpenCircuits(...Keystone) error
55

56
        // TrimOpenCircuits removes a channel's open channels with htlc indexes
57
        // above `start`.
58
        TrimOpenCircuits(chanID lnwire.ShortChannelID, start uint64) error
59

60
        // DeleteCircuits removes the incoming circuit key to remove all
61
        // persistent references to a circuit. Returns a ErrUnknownCircuit if
62
        // any of the incoming keys are not known.
63
        DeleteCircuits(inKeys ...CircuitKey) error
64
}
65

66
// CircuitLookup is a common interface used to lookup information that is stored
67
// in the circuit map.
68
type CircuitLookup interface {
69
        // LookupCircuit queries the circuit map for the circuit identified by
70
        // inKey.
71
        LookupCircuit(inKey CircuitKey) *PaymentCircuit
72

73
        // LookupOpenCircuit queries the circuit map for a circuit identified
74
        // by its outgoing circuit key.
75
        LookupOpenCircuit(outKey CircuitKey) *PaymentCircuit
76
}
77

78
// CircuitFwdActions represents the forwarding decision made by the circuit
79
// map, and is returned from CommitCircuits. The sequence of circuits provided
80
// to CommitCircuits is split into three sub-sequences, allowing the caller to
81
// do an in-order scan, comparing the head of each subsequence, to determine
82
// the decision made by the circuit map.
83
type CircuitFwdActions struct {
84
        // Adds is the subsequence of circuits that were successfully committed
85
        // in the circuit map.
86
        Adds []*PaymentCircuit
87

88
        // Drops is the subsequence of circuits for which no action should be
89
        // done.
90
        Drops []*PaymentCircuit
91

92
        // Fails is the subsequence of circuits that should be failed back by
93
        // the calling link.
94
        Fails []*PaymentCircuit
95
}
96

97
// CircuitMap is an interface for managing the construction and teardown of
98
// payment circuits used by the switch.
99
type CircuitMap interface {
100
        CircuitModifier
101

102
        CircuitLookup
103

104
        // CommitCircuits attempts to add the given circuits to the circuit
105
        // map. The list of circuits is split into three distinct
106
        // sub-sequences, corresponding to adds, drops, and fails. Adds should
107
        // be forwarded to the switch, while fails should be failed back
108
        // locally within the calling link.
109
        CommitCircuits(circuit ...*PaymentCircuit) (*CircuitFwdActions, error)
110

111
        // CloseCircuit marks the circuit identified by `outKey` as closing
112
        // in-memory, which prevents duplicate settles/fails from completing an
113
        // open circuit twice.
114
        CloseCircuit(outKey CircuitKey) (*PaymentCircuit, error)
115

116
        // FailCircuit is used by locally failed HTLCs to mark the circuit
117
        // identified by `inKey` as closing in-memory, which prevents duplicate
118
        // settles/fails from being accepted for the same circuit.
119
        FailCircuit(inKey CircuitKey) (*PaymentCircuit, error)
120

121
        // LookupByPaymentHash queries the circuit map and returns all open
122
        // circuits that use the given payment hash.
123
        LookupByPaymentHash(hash [32]byte) []*PaymentCircuit
124

125
        // NumPending returns the total number of active circuits added by
126
        // CommitCircuits.
127
        NumPending() int
128

129
        // NumOpen returns the number of circuits with HTLCs that have been
130
        // forwarded via an outgoing link.
131
        NumOpen() int
132
}
133

134
var (
135
        // circuitAddKey is the key used to retrieve the bucket containing
136
        // payment circuits. A circuit records information about how to return
137
        // a packet to the source link, potentially including an error
138
        // encrypter for applying this hop's encryption to the payload in the
139
        // reverse direction.
140
        //
141
        // Bucket hierarchy:
142
        //
143
        // circuitAddKey(root-bucket)
144
        //             |
145
        //             |-- <incoming-circuit-key>: <encoded bytes of PaymentCircuit>
146
        //             |-- <incoming-circuit-key>: <encoded bytes of PaymentCircuit>
147
        //             |
148
        //             ...
149
        //
150
        circuitAddKey = []byte("circuit-adds")
151

152
        // circuitKeystoneKey is used to retrieve the bucket containing circuit
153
        // keystones, which are set in place once a forwarded packet is
154
        // assigned an index on an outgoing commitment txn.
155
        //
156
        // Bucket hierarchy:
157
        //
158
        // circuitKeystoneKey(root-bucket)
159
        //             |
160
        //             |-- <outgoing-circuit-key>: <incoming-circuit-key>
161
        //             |-- <outgoing-circuit-key>: <incoming-circuit-key>
162
        //             |
163
        //             ...
164
        //
165
        circuitKeystoneKey = []byte("circuit-keystones")
166
)
167

168
// circuitMap is a data structure that implements thread safe, persistent
169
// storage of circuit routing information. The switch consults a circuit map to
170
// determine where to forward returning HTLC update messages. Circuits are
171
// always identifiable by their incoming CircuitKey, in addition to their
172
// outgoing CircuitKey if the circuit is fully-opened.
173
type circuitMap struct {
174
        cfg *CircuitMapConfig
175

176
        mtx sync.RWMutex
177

178
        // pending is an in-memory mapping of all half payment circuits, and is
179
        // kept in sync with the on-disk contents of the circuit map.
180
        pending map[CircuitKey]*PaymentCircuit
181

182
        // opened is an in-memory mapping of all full payment circuits, which
183
        // is also synchronized with the persistent state of the circuit map.
184
        opened map[CircuitKey]*PaymentCircuit
185

186
        // closed is an in-memory set of circuits for which the switch has
187
        // received a settle or fail. This precedes the actual deletion of a
188
        // circuit from disk.
189
        closed map[CircuitKey]struct{}
190

191
        // hashIndex is a volatile index that facilitates fast queries by
192
        // payment hash against the contents of circuits. This index can be
193
        // reconstructed entirely from the set of persisted full circuits on
194
        // startup.
195
        hashIndex map[[32]byte]map[CircuitKey]struct{}
196
}
197

198
// CircuitMapConfig houses the critical interfaces and references necessary to
199
// parameterize an instance of circuitMap.
200
type CircuitMapConfig struct {
201
        // DB provides the persistent storage engine for the circuit map.
202
        DB kvdb.Backend
203

204
        // FetchAllOpenChannels is a function that fetches all currently open
205
        // channels from the channel database.
206
        FetchAllOpenChannels func() ([]*channeldb.OpenChannel, error)
207

208
        // FetchClosedChannels is a function that fetches all closed channels
209
        // from the channel database.
210
        FetchClosedChannels func(
211
                pendingOnly bool) ([]*channeldb.ChannelCloseSummary, error)
212

213
        // ExtractErrorEncrypter derives the shared secret used to encrypt
214
        // errors from the obfuscator's ephemeral public key.
215
        ExtractErrorEncrypter hop.ErrorEncrypterExtracter
216

217
        // CheckResolutionMsg checks whether a given resolution message exists
218
        // for the passed CircuitKey.
219
        CheckResolutionMsg func(outKey *CircuitKey) error
220
}
221

222
// NewCircuitMap creates a new instance of the circuitMap.
223
func NewCircuitMap(cfg *CircuitMapConfig) (CircuitMap, error) {
388✔
224
        cm := &circuitMap{
388✔
225
                cfg: cfg,
388✔
226
        }
388✔
227

388✔
228
        // Initialize the on-disk buckets used by the circuit map.
388✔
229
        if err := cm.initBuckets(); err != nil {
388✔
230
                return nil, err
×
231
        }
×
232

233
        // Delete old circuits and keystones of closed channels.
234
        if err := cm.cleanClosedChannels(); err != nil {
388✔
235
                return nil, err
×
236
        }
×
237

238
        // Load any previously persisted circuit into back into memory.
239
        if err := cm.restoreMemState(); err != nil {
388✔
240
                return nil, err
×
241
        }
×
242

243
        // Trim any keystones that were not committed in an outgoing commit txn.
244
        //
245
        // NOTE: This operation will be applied to the persistent state of all
246
        // active channels. Therefore, it must be called before any links are
247
        // created to avoid interfering with normal operation.
248
        if err := cm.trimAllOpenCircuits(); err != nil {
388✔
249
                return nil, err
×
250
        }
×
251

252
        return cm, nil
388✔
253
}
254

255
// initBuckets ensures that the primary buckets used by the circuit are
256
// initialized so that we can assume their existence after startup.
257
func (cm *circuitMap) initBuckets() error {
388✔
258
        return kvdb.Update(cm.cfg.DB, func(tx kvdb.RwTx) error {
776✔
259
                _, err := tx.CreateTopLevelBucket(circuitKeystoneKey)
388✔
260
                if err != nil {
388✔
261
                        return err
×
262
                }
×
263

264
                _, err = tx.CreateTopLevelBucket(circuitAddKey)
388✔
265
                return err
388✔
266
        }, func() {})
388✔
267
}
268

269
// cleanClosedChannels deletes all circuits and keystones related to closed
270
// channels. It first reads all the closed channels and caches the ShortChanIDs
271
// into a map for fast lookup. Then it iterates the circuit bucket and keystone
272
// bucket and deletes items whose ChanID matches the ShortChanID.
273
//
274
// NOTE: this operation can also be built into restoreMemState since the latter
275
// already opens and iterates the two root buckets, circuitAddKey and
276
// circuitKeystoneKey. Depending on the size of the buckets, this marginal gain
277
// may be worth investigating. Atm, for clarity, this operation is wrapped into
278
// its own function.
279
func (cm *circuitMap) cleanClosedChannels() error {
388✔
280
        log.Infof("Cleaning circuits from disk for closed channels")
388✔
281

388✔
282
        // closedChanIDSet stores the short channel IDs for closed channels.
388✔
283
        closedChanIDSet := make(map[lnwire.ShortChannelID]struct{})
388✔
284

388✔
285
        // circuitKeySet stores the incoming circuit keys of the payment
388✔
286
        // circuits that need to be deleted.
388✔
287
        circuitKeySet := make(map[CircuitKey]struct{})
388✔
288

388✔
289
        // keystoneKeySet stores the outgoing keys of the keystones that need
388✔
290
        // to be deleted.
388✔
291
        keystoneKeySet := make(map[CircuitKey]struct{})
388✔
292

388✔
293
        // isClosedChannel is a helper closure that returns a bool indicating
388✔
294
        // the chanID belongs to a closed channel.
388✔
295
        isClosedChannel := func(chanID lnwire.ShortChannelID) bool {
432✔
296
                // Skip if the channel ID is zero value. This has the effect
44✔
297
                // that a zero value incoming or outgoing key will never be
44✔
298
                // matched and its corresponding circuits or keystones are not
44✔
299
                // deleted.
44✔
300
                if chanID.ToUint64() == 0 {
47✔
301
                        return false
3✔
302
                }
3✔
303

304
                _, ok := closedChanIDSet[chanID]
41✔
305
                return ok
41✔
306
        }
307

308
        // Find closed channels and cache their ShortChannelIDs into a map.
309
        // This map will be used for looking up relative circuits and keystones.
310
        closedChannels, err := cm.cfg.FetchClosedChannels(false)
388✔
311
        if err != nil {
388✔
312
                return err
×
313
        }
×
314

315
        for _, closedChannel := range closedChannels {
396✔
316
                // Skip if the channel close is pending.
8✔
317
                if closedChannel.IsPending {
9✔
318
                        continue
1✔
319
                }
320

321
                closedChanIDSet[closedChannel.ShortChanID] = struct{}{}
7✔
322
        }
323

324
        log.Debugf("Found %v closed channels", len(closedChanIDSet))
388✔
325

388✔
326
        // Exit early if there are no closed channels.
388✔
327
        if len(closedChanIDSet) == 0 {
769✔
328
                log.Infof("Finished cleaning: no closed channels found, " +
381✔
329
                        "no actions taken.",
381✔
330
                )
381✔
331
                return nil
381✔
332
        }
381✔
333

334
        // Find the payment circuits and keystones that need to be deleted.
335
        if err := kvdb.View(cm.cfg.DB, func(tx kvdb.RTx) error {
14✔
336
                circuitBkt := tx.ReadBucket(circuitAddKey)
7✔
337
                if circuitBkt == nil {
7✔
338
                        return ErrCorruptedCircuitMap
×
339
                }
×
340
                keystoneBkt := tx.ReadBucket(circuitKeystoneKey)
7✔
341
                if keystoneBkt == nil {
7✔
342
                        return ErrCorruptedCircuitMap
×
343
                }
×
344

345
                // If a circuit's incoming/outgoing key prefix matches the
346
                // ShortChanID, it will be deleted. However, if the ShortChanID
347
                // of the incoming key is zero, the circuit will be kept as it
348
                // indicates a locally initiated payment.
349
                if err := circuitBkt.ForEach(func(_, v []byte) error {
26✔
350
                        circuit, err := cm.decodeCircuit(v)
19✔
351
                        if err != nil {
19✔
352
                                return err
×
353
                        }
×
354

355
                        // Check if the incoming channel ID can be found in the
356
                        // closed channel ID map.
357
                        if !isClosedChannel(circuit.Incoming.ChanID) {
29✔
358
                                return nil
10✔
359
                        }
10✔
360

361
                        circuitKeySet[circuit.Incoming] = struct{}{}
9✔
362

9✔
363
                        return nil
9✔
364
                }); err != nil {
×
365
                        return err
×
366
                }
×
367

368
                // If a keystone's InKey or OutKey matches the short channel id
369
                // in the closed channel ID map, it will be deleted.
370
                err := keystoneBkt.ForEach(func(k, v []byte) error {
23✔
371
                        var (
16✔
372
                                inKey  CircuitKey
16✔
373
                                outKey CircuitKey
16✔
374
                        )
16✔
375

16✔
376
                        // Decode the incoming and outgoing circuit keys.
16✔
377
                        if err := inKey.SetBytes(v); err != nil {
16✔
378
                                return err
×
379
                        }
×
380
                        if err := outKey.SetBytes(k); err != nil {
16✔
381
                                return err
×
382
                        }
×
383

384
                        // Check if the incoming channel ID can be found in the
385
                        // closed channel ID map.
386
                        if isClosedChannel(inKey.ChanID) {
23✔
387
                                // If the incoming channel is closed, we can
7✔
388
                                // skip checking on outgoing channel ID because
7✔
389
                                // this keystone will be deleted.
7✔
390
                                keystoneKeySet[outKey] = struct{}{}
7✔
391

7✔
392
                                // Technically the incoming keys found in
7✔
393
                                // keystone bucket should be a subset of
7✔
394
                                // circuit bucket. So a previous loop should
7✔
395
                                // have this inKey put inside circuitAddKey map
7✔
396
                                // already. We do this again to be sure the
7✔
397
                                // circuits are properly cleaned. Even this
7✔
398
                                // inKey doesn't exist in circuit bucket, we
7✔
399
                                // are fine as db deletion is a noop.
7✔
400
                                circuitKeySet[inKey] = struct{}{}
7✔
401
                                return nil
7✔
402
                        }
7✔
403

404
                        // Check if the outgoing channel ID can be found in the
405
                        // closed channel ID map. Notice that we need to store
406
                        // the outgoing key because it's used for db query.
407
                        //
408
                        // NOTE: We skip this if a resolution message can be
409
                        // found under the outKey. This means that there is an
410
                        // existing resolution message(s) that need to get to
411
                        // the incoming links.
412
                        if isClosedChannel(outKey.ChanID) {
14✔
413
                                // Check the resolution message store. A return
5✔
414
                                // value of nil means we need to skip deleting
5✔
415
                                // these circuits.
5✔
416
                                if cm.cfg.CheckResolutionMsg(&outKey) == nil {
6✔
417
                                        return nil
1✔
418
                                }
1✔
419

420
                                keystoneKeySet[outKey] = struct{}{}
4✔
421

4✔
422
                                // Also update circuitKeySet to mark the
4✔
423
                                // payment circuit needs to be deleted.
4✔
424
                                circuitKeySet[inKey] = struct{}{}
4✔
425
                        }
426

427
                        return nil
8✔
428
                })
429
                return err
7✔
430
        }, func() {
7✔
431
                // Reset the sets.
7✔
432
                circuitKeySet = make(map[CircuitKey]struct{})
7✔
433
                keystoneKeySet = make(map[CircuitKey]struct{})
7✔
434
        }); err != nil {
7✔
435
                return err
×
436
        }
×
437

438
        log.Debugf("To be deleted: num_circuits=%v, num_keystones=%v",
7✔
439
                len(circuitKeySet), len(keystoneKeySet),
7✔
440
        )
7✔
441

7✔
442
        numCircuitsDeleted := 0
7✔
443
        numKeystonesDeleted := 0
7✔
444

7✔
445
        // Delete all the circuits and keystones for closed channels.
7✔
446
        if err := kvdb.Update(cm.cfg.DB, func(tx kvdb.RwTx) error {
14✔
447
                circuitBkt := tx.ReadWriteBucket(circuitAddKey)
7✔
448
                if circuitBkt == nil {
7✔
449
                        return ErrCorruptedCircuitMap
×
450
                }
×
451
                keystoneBkt := tx.ReadWriteBucket(circuitKeystoneKey)
7✔
452
                if keystoneBkt == nil {
7✔
453
                        return ErrCorruptedCircuitMap
×
454
                }
×
455

456
                // Delete the circuit.
457
                for inKey := range circuitKeySet {
20✔
458
                        if err := circuitBkt.Delete(inKey.Bytes()); err != nil {
13✔
459
                                return err
×
460
                        }
×
461

462
                        numCircuitsDeleted++
13✔
463
                }
464

465
                // Delete the keystone using the outgoing key.
466
                for outKey := range keystoneKeySet {
18✔
467
                        err := keystoneBkt.Delete(outKey.Bytes())
11✔
468
                        if err != nil {
11✔
469
                                return err
×
470
                        }
×
471

472
                        numKeystonesDeleted++
11✔
473
                }
474

475
                return nil
7✔
476
        }, func() {}); err != nil {
7✔
477
                numCircuitsDeleted = 0
×
478
                numKeystonesDeleted = 0
×
479
                return err
×
480
        }
×
481

482
        log.Infof("Finished cleaning: num_closed_channel=%v, "+
7✔
483
                "num_circuits=%v, num_keystone=%v",
7✔
484
                len(closedChannels), numCircuitsDeleted, numKeystonesDeleted,
7✔
485
        )
7✔
486

7✔
487
        return nil
7✔
488
}
489

490
// restoreMemState loads the contents of the half circuit and full circuit
491
// buckets from disk and reconstructs the in-memory representation of the
492
// circuit map. Afterwards, the state of the hash index is reconstructed using
493
// the recovered set of full circuits. This method will also remove any stray
494
// keystones, which are those that appear fully-opened, but have no pending
495
// circuit related to the intended incoming link.
496
func (cm *circuitMap) restoreMemState() error {
388✔
497
        log.Infof("Restoring in-memory circuit state from disk")
388✔
498

388✔
499
        var (
388✔
500
                opened  map[CircuitKey]*PaymentCircuit
388✔
501
                pending map[CircuitKey]*PaymentCircuit
388✔
502
        )
388✔
503

388✔
504
        if err := kvdb.Update(cm.cfg.DB, func(tx kvdb.RwTx) error {
776✔
505
                // Restore any of the circuits persisted in the circuit bucket
388✔
506
                // back into memory.
388✔
507
                circuitBkt := tx.ReadWriteBucket(circuitAddKey)
388✔
508
                if circuitBkt == nil {
388✔
509
                        return ErrCorruptedCircuitMap
×
510
                }
×
511

512
                if err := circuitBkt.ForEach(func(_, v []byte) error {
468✔
513
                        circuit, err := cm.decodeCircuit(v)
80✔
514
                        if err != nil {
80✔
515
                                return err
×
516
                        }
×
517

518
                        circuit.LoadedFromDisk = true
80✔
519
                        pending[circuit.Incoming] = circuit
80✔
520

80✔
521
                        return nil
80✔
522
                }); err != nil {
×
523
                        return err
×
524
                }
×
525

526
                // Furthermore, load the keystone bucket and resurrect the
527
                // keystones used in any open circuits.
528
                keystoneBkt := tx.ReadWriteBucket(circuitKeystoneKey)
388✔
529
                if keystoneBkt == nil {
388✔
530
                        return ErrCorruptedCircuitMap
×
531
                }
×
532

533
                var strayKeystones []Keystone
388✔
534
                if err := keystoneBkt.ForEach(func(k, v []byte) error {
446✔
535
                        var (
58✔
536
                                inKey  CircuitKey
58✔
537
                                outKey = &CircuitKey{}
58✔
538
                        )
58✔
539

58✔
540
                        // Decode the incoming and outgoing circuit keys.
58✔
541
                        if err := inKey.SetBytes(v); err != nil {
58✔
542
                                return err
×
543
                        }
×
544
                        if err := outKey.SetBytes(k); err != nil {
58✔
545
                                return err
×
546
                        }
×
547

548
                        // Retrieve the pending circuit, set its keystone, then
549
                        // add it to the opened map.
550
                        circuit, ok := pending[inKey]
58✔
551
                        if ok {
116✔
552
                                circuit.Outgoing = outKey
58✔
553
                                opened[*outKey] = circuit
58✔
554
                        } else {
58✔
555
                                strayKeystones = append(strayKeystones, Keystone{
×
556
                                        InKey:  inKey,
×
557
                                        OutKey: *outKey,
×
558
                                })
×
559
                        }
×
560

561
                        return nil
58✔
562
                }); err != nil {
×
563
                        return err
×
564
                }
×
565

566
                // If any stray keystones were found, we'll proceed to prune
567
                // them from the circuit map's persistent storage. This may
568
                // manifest on older nodes that had updated channels before
569
                // their short channel id was set properly. We believe this
570
                // issue has been fixed, though this will allow older nodes to
571
                // recover without additional intervention.
572
                for _, strayKeystone := range strayKeystones {
388✔
573
                        // As a precaution, we will only cleanup keystones
×
574
                        // related to locally-initiated payments. If a
×
575
                        // documented case of stray keystones emerges for
×
576
                        // forwarded payments, this check should be removed, but
×
577
                        // with extreme caution.
×
578
                        if strayKeystone.OutKey.ChanID != hop.Source {
×
579
                                continue
×
580
                        }
581

582
                        log.Infof("Removing stray keystone: %v", strayKeystone)
×
583
                        err := keystoneBkt.Delete(strayKeystone.OutKey.Bytes())
×
584
                        if err != nil {
×
585
                                return err
×
586
                        }
×
587
                }
588

589
                return nil
388✔
590

591
        }, func() {
388✔
592
                opened = make(map[CircuitKey]*PaymentCircuit)
388✔
593
                pending = make(map[CircuitKey]*PaymentCircuit)
388✔
594
        }); err != nil {
388✔
595
                return err
×
596
        }
×
597

598
        cm.pending = pending
388✔
599
        cm.opened = opened
388✔
600
        cm.closed = make(map[CircuitKey]struct{})
388✔
601

388✔
602
        log.Infof("Payment circuits loaded: num_pending=%v, num_open=%v",
388✔
603
                len(pending), len(opened))
388✔
604

388✔
605
        // Finally, reconstruct the hash index by running through our set of
388✔
606
        // open circuits.
388✔
607
        cm.hashIndex = make(map[[32]byte]map[CircuitKey]struct{})
388✔
608
        for _, circuit := range opened {
446✔
609
                cm.addCircuitToHashIndex(circuit)
58✔
610
        }
58✔
611

612
        return nil
388✔
613
}
614

615
// decodeCircuit reconstructs an in-memory payment circuit from a byte slice.
616
// The byte slice is assumed to have been generated by the circuit's Encode
617
// method. If the decoding is successful, the onion obfuscator will be
618
// reextracted, since it is not stored in plaintext on disk.
619
func (cm *circuitMap) decodeCircuit(v []byte) (*PaymentCircuit, error) {
99✔
620
        var circuit = &PaymentCircuit{}
99✔
621

99✔
622
        circuitReader := bytes.NewReader(v)
99✔
623
        if err := circuit.Decode(circuitReader); err != nil {
99✔
624
                return nil, err
×
625
        }
×
626

627
        // If the error encrypter is nil, this is locally-source payment so
628
        // there is no encrypter.
629
        if circuit.ErrorEncrypter == nil {
102✔
630
                return circuit, nil
3✔
631
        }
3✔
632

633
        // Otherwise, we need to reextract the encrypter, so that the shared
634
        // secret is rederived from what was decoded.
635
        err := circuit.ErrorEncrypter.Reextract(
96✔
636
                cm.cfg.ExtractErrorEncrypter,
96✔
637
        )
96✔
638
        if err != nil {
96✔
639
                return nil, err
×
640
        }
×
641

642
        return circuit, nil
96✔
643
}
644

645
// trimAllOpenCircuits reads the set of active channels from disk and trims
646
// keystones for any non-pending channels using the next unallocated htlc index.
647
// This method is intended to be called on startup. Each link will also trim
648
// it's own circuits upon startup.
649
//
650
// NOTE: This operation will be applied to the persistent state of all active
651
// channels. Therefore, it must be called before any links are created to avoid
652
// interfering with normal operation.
653
func (cm *circuitMap) trimAllOpenCircuits() error {
388✔
654
        activeChannels, err := cm.cfg.FetchAllOpenChannels()
388✔
655
        if err != nil {
388✔
656
                return err
×
657
        }
×
658

659
        for _, activeChannel := range activeChannels {
554✔
660
                if activeChannel.IsPending {
166✔
661
                        continue
×
662
                }
663

664
                // First, skip any channels that have not been assigned their
665
                // final channel identifier, otherwise we would try to trim
666
                // htlcs belonging to the all-zero, hop.Source ID.
667
                chanID := activeChannel.ShortChanID()
166✔
668
                if chanID == hop.Source {
166✔
669
                        continue
×
670
                }
671

672
                // Next, retrieve the next unallocated htlc index, which bounds
673
                // the cutoff of confirmed htlc indexes.
674
                start, err := activeChannel.NextLocalHtlcIndex()
166✔
675
                if err != nil {
166✔
676
                        return err
×
677
                }
×
678

679
                // Finally, remove all pending circuits above at or above the
680
                // next unallocated local htlc indexes. This has the effect of
681
                // reverting any circuits that have either not been locked in,
682
                // or had not been included in a pending commitment.
683
                err = cm.TrimOpenCircuits(chanID, start)
166✔
684
                if err != nil {
166✔
685
                        return err
×
686
                }
×
687
        }
688

689
        return nil
388✔
690
}
691

692
// TrimOpenCircuits removes a channel's keystones above the short chan id's
693
// highest committed htlc index. This has the effect of returning those
694
// circuits to a half-open state. Since opening of circuits is done in advance
695
// of actually committing the Add htlcs into a commitment txn, this allows
696
// circuits to be opened preemptively, since we can roll them back after any
697
// failures.
698
func (cm *circuitMap) TrimOpenCircuits(chanID lnwire.ShortChannelID,
699
        start uint64) error {
381✔
700

381✔
701
        log.Infof("Trimming open circuits for chan_id=%v, start_htlc_id=%v",
381✔
702
                chanID, start)
381✔
703

381✔
704
        var trimmedOutKeys []CircuitKey
381✔
705

381✔
706
        // Scan forward from the last unacked htlc id, stopping as soon as we
381✔
707
        // don't find any more. Outgoing htlc id's must be assigned in order,
381✔
708
        // so there should never be disjoint segments of keystones to trim.
381✔
709
        cm.mtx.Lock()
381✔
710
        for i := start; ; i++ {
781✔
711
                outKey := CircuitKey{
400✔
712
                        ChanID: chanID,
400✔
713
                        HtlcID: i,
400✔
714
                }
400✔
715

400✔
716
                circuit, ok := cm.opened[outKey]
400✔
717
                if !ok {
781✔
718
                        break
381✔
719
                }
720

721
                circuit.Outgoing = nil
19✔
722
                delete(cm.opened, outKey)
19✔
723
                trimmedOutKeys = append(trimmedOutKeys, outKey)
19✔
724
                cm.removeCircuitFromHashIndex(circuit)
19✔
725
        }
726
        cm.mtx.Unlock()
381✔
727

381✔
728
        if len(trimmedOutKeys) == 0 {
754✔
729
                return nil
373✔
730
        }
373✔
731

732
        return kvdb.Update(cm.cfg.DB, func(tx kvdb.RwTx) error {
16✔
733
                keystoneBkt := tx.ReadWriteBucket(circuitKeystoneKey)
8✔
734
                if keystoneBkt == nil {
8✔
735
                        return ErrCorruptedCircuitMap
×
736
                }
×
737

738
                for _, outKey := range trimmedOutKeys {
27✔
739
                        err := keystoneBkt.Delete(outKey.Bytes())
19✔
740
                        if err != nil {
19✔
741
                                return err
×
742
                        }
×
743
                }
744

745
                return nil
8✔
746
        }, func() {})
8✔
747
}
748

749
// LookupCircuit queries the circuit map for the circuit identified by its
750
// incoming circuit key. Returns nil if there is no such circuit.
751
func (cm *circuitMap) LookupCircuit(inKey CircuitKey) *PaymentCircuit {
370✔
752
        cm.mtx.RLock()
370✔
753
        defer cm.mtx.RUnlock()
370✔
754

370✔
755
        return cm.pending[inKey]
370✔
756
}
370✔
757

758
// LookupOpenCircuit searches for the circuit identified by its outgoing circuit
759
// key.
760
func (cm *circuitMap) LookupOpenCircuit(outKey CircuitKey) *PaymentCircuit {
96✔
761
        cm.mtx.RLock()
96✔
762
        defer cm.mtx.RUnlock()
96✔
763

96✔
764
        return cm.opened[outKey]
96✔
765
}
96✔
766

767
// LookupByPaymentHash looks up and returns any payment circuits with a given
768
// payment hash.
769
func (cm *circuitMap) LookupByPaymentHash(hash [32]byte) []*PaymentCircuit {
44✔
770
        cm.mtx.RLock()
44✔
771
        defer cm.mtx.RUnlock()
44✔
772

44✔
773
        var circuits []*PaymentCircuit
44✔
774
        if circuitSet, ok := cm.hashIndex[hash]; ok {
78✔
775
                // Iterate over the outgoing circuit keys found with this hash,
34✔
776
                // and retrieve the circuit from the opened map.
34✔
777
                circuits = make([]*PaymentCircuit, 0, len(circuitSet))
34✔
778
                for key := range circuitSet {
74✔
779
                        if circuit, ok := cm.opened[key]; ok {
80✔
780
                                circuits = append(circuits, circuit)
40✔
781
                        }
40✔
782
                }
783
        }
784

785
        return circuits
44✔
786
}
787

788
// CommitCircuits accepts any number of circuits and persistently adds them to
789
// the switch's circuit map. The method returns a list of circuits that had not
790
// been seen prior by the switch. A link should only forward HTLCs corresponding
791
// to the returned circuits to the switch.
792
//
793
// NOTE: This method uses batched writes to improve performance, gains will only
794
// be realized if it is called concurrently from separate goroutines.
795
func (cm *circuitMap) CommitCircuits(circuits ...*PaymentCircuit) (
796
        *CircuitFwdActions, error) {
582✔
797

582✔
798
        inKeys := make([]CircuitKey, 0, len(circuits))
582✔
799
        for _, circuit := range circuits {
1,193✔
800
                inKeys = append(inKeys, circuit.Incoming)
611✔
801
        }
611✔
802

803
        log.Tracef("Committing fresh circuits: %v", lnutils.SpewLogClosure(
582✔
804
                inKeys))
582✔
805

582✔
806
        actions := &CircuitFwdActions{}
582✔
807

582✔
808
        // If an empty list was passed, return early to avoid grabbing the lock.
582✔
809
        if len(circuits) == 0 {
582✔
810
                return actions, nil
×
811
        }
×
812

813
        // First, we reconcile the provided circuits with our set of pending
814
        // circuits to construct a set of new circuits that need to be written
815
        // to disk. The circuit's pointer is stored so that we only permit this
816
        // exact circuit to be forwarded through the switch. If a circuit is
817
        // already pending, the htlc will be reforwarded by the switch.
818
        //
819
        // NOTE: We track an additional addFails subsequence, which permits us
820
        // to fail back all packets that weren't dropped if we encounter an
821
        // error when committing the circuits.
822
        cm.mtx.Lock()
582✔
823
        var adds, drops, fails, addFails []*PaymentCircuit
582✔
824
        for _, circuit := range circuits {
1,193✔
825
                inKey := circuit.InKey()
611✔
826
                if foundCircuit, ok := cm.pending[inKey]; ok {
642✔
827
                        switch {
31✔
828

829
                        // This circuit has a keystone, it's waiting for a
830
                        // response from the remote peer on the outgoing link.
831
                        // Drop it like it's hot, ensure duplicates get caught.
832
                        case foundCircuit.HasKeystone():
11✔
833
                                drops = append(drops, circuit)
11✔
834

835
                        // If no keystone is set and the switch has not been
836
                        // restarted, the corresponding packet should still be
837
                        // in the outgoing link's mailbox. It will be delivered
838
                        // if it comes online before the switch goes down.
839
                        //
840
                        // NOTE: Dropping here prevents a flapping, incoming
841
                        // link from failing a duplicate add while it is still
842
                        // in the server's memory mailboxes.
843
                        case !foundCircuit.LoadedFromDisk:
8✔
844
                                drops = append(drops, circuit)
8✔
845

846
                        // Otherwise, the in-mem packet has been lost due to a
847
                        // restart. It is now safe to send back a failure along
848
                        // the incoming link. The incoming link should be able
849
                        // detect and ignore duplicate packets of this type.
850
                        default:
12✔
851
                                fails = append(fails, circuit)
12✔
852
                                addFails = append(addFails, circuit)
12✔
853
                        }
854

855
                        continue
31✔
856
                }
857

858
                cm.pending[inKey] = circuit
580✔
859
                adds = append(adds, circuit)
580✔
860
                addFails = append(addFails, circuit)
580✔
861
        }
862
        cm.mtx.Unlock()
582✔
863

582✔
864
        // If all circuits are dropped or failed, we are done.
582✔
865
        if len(adds) == 0 {
598✔
866
                actions.Drops = drops
16✔
867
                actions.Fails = fails
16✔
868
                return actions, nil
16✔
869
        }
16✔
870

871
        // Now, optimistically serialize the circuits to add.
872
        var bs = make([]bytes.Buffer, len(adds))
566✔
873
        for i, circuit := range adds {
1,146✔
874
                if err := circuit.Encode(&bs[i]); err != nil {
580✔
875
                        actions.Drops = drops
×
876
                        actions.Fails = addFails
×
877
                        return actions, err
×
878
                }
×
879
        }
880

881
        // Write the entire batch of circuits to the persistent circuit bucket
882
        // using bolt's Batch write. This method must be called from multiple,
883
        // distinct goroutines to have any impact on performance.
884
        err := kvdb.Batch(cm.cfg.DB, func(tx kvdb.RwTx) error {
1,132✔
885
                circuitBkt := tx.ReadWriteBucket(circuitAddKey)
566✔
886
                if circuitBkt == nil {
566✔
887
                        return ErrCorruptedCircuitMap
×
888
                }
×
889

890
                for i, circuit := range adds {
1,146✔
891
                        inKeyBytes := circuit.InKey().Bytes()
580✔
892
                        circuitBytes := bs[i].Bytes()
580✔
893

580✔
894
                        err := circuitBkt.Put(inKeyBytes, circuitBytes)
580✔
895
                        if err != nil {
580✔
896
                                return err
×
897
                        }
×
898
                }
899

900
                return nil
566✔
901
        })
902

903
        // Return if the write succeeded.
904
        if err == nil {
1,132✔
905
                actions.Adds = adds
566✔
906
                actions.Drops = drops
566✔
907
                actions.Fails = fails
566✔
908
                return actions, nil
566✔
909
        }
566✔
910

911
        // Otherwise, rollback the circuits added to the pending set if the
912
        // write failed.
913
        cm.mtx.Lock()
×
914
        for _, circuit := range adds {
×
915
                delete(cm.pending, circuit.InKey())
×
916
        }
×
917
        cm.mtx.Unlock()
×
918

×
919
        // Since our write failed, we will return the dropped packets and mark
×
920
        // all other circuits as failed.
×
921
        actions.Drops = drops
×
922
        actions.Fails = addFails
×
923

×
924
        return actions, err
×
925
}
926

927
// Keystone is a tuple binding an incoming and outgoing CircuitKey. Keystones
928
// are preemptively written by an outgoing link before signing a new commitment
929
// state, and cements which HTLCs we are awaiting a response from a remote
930
// peer.
931
type Keystone struct {
932
        InKey  CircuitKey
933
        OutKey CircuitKey
934
}
935

936
// String returns a human readable description of the Keystone.
937
func (k Keystone) String() string {
×
938
        return fmt.Sprintf("%s --> %s", k.InKey, k.OutKey)
×
939
}
×
940

941
// OpenCircuits sets the outgoing circuit key for the circuit identified by
942
// inKey, persistently marking the circuit as opened. After the changes have
943
// been persisted, the circuit map's in-memory indexes are updated so that this
944
// circuit can be queried using LookupByKeystone or LookupByPaymentHash.
945
func (cm *circuitMap) OpenCircuits(keystones ...Keystone) error {
1,316✔
946
        if len(keystones) == 0 {
2,244✔
947
                return nil
928✔
948
        }
928✔
949

950
        log.Tracef("Opening finalized circuits: %v", lnutils.SpewLogClosure(
388✔
951
                keystones))
388✔
952

388✔
953
        // Check that all keystones correspond to committed-but-unopened
388✔
954
        // circuits.
388✔
955
        cm.mtx.RLock()
388✔
956
        openedCircuits := make([]*PaymentCircuit, 0, len(keystones))
388✔
957
        for _, ks := range keystones {
916✔
958
                if _, ok := cm.opened[ks.OutKey]; ok {
531✔
959
                        cm.mtx.RUnlock()
3✔
960
                        return ErrDuplicateKeystone
3✔
961
                }
3✔
962

963
                circuit, ok := cm.pending[ks.InKey]
525✔
964
                if !ok {
525✔
965
                        cm.mtx.RUnlock()
×
966
                        return ErrUnknownCircuit
×
967
                }
×
968

969
                openedCircuits = append(openedCircuits, circuit)
525✔
970
        }
971
        cm.mtx.RUnlock()
385✔
972

385✔
973
        err := kvdb.Update(cm.cfg.DB, func(tx kvdb.RwTx) error {
770✔
974
                // Now, load the circuit bucket to which we will write the
385✔
975
                // already serialized circuit.
385✔
976
                keystoneBkt := tx.ReadWriteBucket(circuitKeystoneKey)
385✔
977
                if keystoneBkt == nil {
385✔
978
                        return ErrCorruptedCircuitMap
×
979
                }
×
980

981
                for _, ks := range keystones {
910✔
982
                        outBytes := ks.OutKey.Bytes()
525✔
983
                        inBytes := ks.InKey.Bytes()
525✔
984
                        err := keystoneBkt.Put(outBytes, inBytes)
525✔
985
                        if err != nil {
525✔
986
                                return err
×
987
                        }
×
988
                }
989

990
                return nil
385✔
991
        }, func() {})
385✔
992

993
        if err != nil {
385✔
994
                return err
×
995
        }
×
996

997
        cm.mtx.Lock()
385✔
998
        for i, circuit := range openedCircuits {
910✔
999
                ks := keystones[i]
525✔
1000

525✔
1001
                // Since our persistent operation was successful, we can now
525✔
1002
                // modify the in memory representations. Set the outgoing
525✔
1003
                // circuit key on our pending circuit, add the same circuit to
525✔
1004
                // set of opened circuits, and add this circuit to the hash
525✔
1005
                // index.
525✔
1006
                circuit.Outgoing = &CircuitKey{}
525✔
1007
                *circuit.Outgoing = ks.OutKey
525✔
1008

525✔
1009
                cm.opened[ks.OutKey] = circuit
525✔
1010
                cm.addCircuitToHashIndex(circuit)
525✔
1011
        }
525✔
1012
        cm.mtx.Unlock()
385✔
1013

385✔
1014
        return nil
385✔
1015
}
1016

1017
// addCirciutToHashIndex inserts a circuit into the circuit map's hash index, so
1018
// that it can be queried using LookupByPaymentHash.
1019
func (cm *circuitMap) addCircuitToHashIndex(c *PaymentCircuit) {
583✔
1020
        if _, ok := cm.hashIndex[c.PaymentHash]; !ok {
1,116✔
1021
                cm.hashIndex[c.PaymentHash] = make(map[CircuitKey]struct{})
533✔
1022
        }
533✔
1023
        cm.hashIndex[c.PaymentHash][c.OutKey()] = struct{}{}
583✔
1024
}
1025

1026
// FailCircuit marks the circuit identified by `inKey` as closing in-memory,
1027
// which prevents duplicate settles/fails from completing an open circuit twice.
1028
func (cm *circuitMap) FailCircuit(inKey CircuitKey) (*PaymentCircuit, error) {
21✔
1029

21✔
1030
        cm.mtx.Lock()
21✔
1031
        defer cm.mtx.Unlock()
21✔
1032

21✔
1033
        circuit, ok := cm.pending[inKey]
21✔
1034
        if !ok {
21✔
1035
                return nil, ErrUnknownCircuit
×
1036
        }
×
1037

1038
        _, ok = cm.closed[inKey]
21✔
1039
        if ok {
25✔
1040
                return nil, ErrCircuitClosing
4✔
1041
        }
4✔
1042

1043
        cm.closed[inKey] = struct{}{}
17✔
1044

17✔
1045
        return circuit, nil
17✔
1046
}
1047

1048
// CloseCircuit marks the circuit identified by `outKey` as closing in-memory,
1049
// which prevents duplicate settles/fails from completing an open
1050
// circuit twice.
1051
func (cm *circuitMap) CloseCircuit(outKey CircuitKey) (*PaymentCircuit, error) {
526✔
1052

526✔
1053
        cm.mtx.Lock()
526✔
1054
        defer cm.mtx.Unlock()
526✔
1055

526✔
1056
        circuit, ok := cm.opened[outKey]
526✔
1057
        if !ok {
716✔
1058
                return nil, ErrUnknownCircuit
190✔
1059
        }
190✔
1060

1061
        _, ok = cm.closed[circuit.Incoming]
336✔
1062
        if ok {
336✔
1063
                return nil, ErrCircuitClosing
×
1064
        }
×
1065

1066
        cm.closed[circuit.Incoming] = struct{}{}
336✔
1067

336✔
1068
        return circuit, nil
336✔
1069
}
1070

1071
// DeleteCircuits destroys the target circuits by removing them from the circuit
1072
// map, additionally removing the circuits' keystones if any HTLCs were
1073
// forwarded through an outgoing link. The circuits should be identified by its
1074
// incoming circuit key. If a given circuit is not found in the circuit map, it
1075
// will be ignored from the query. This would typically indicate that the
1076
// circuit was already cleaned up at a different point in time.
1077
func (cm *circuitMap) DeleteCircuits(inKeys ...CircuitKey) error {
1,689✔
1078

1,689✔
1079
        log.Tracef("Deleting resolved circuits: %v", lnutils.SpewLogClosure(
1,689✔
1080
                inKeys))
1,689✔
1081

1,689✔
1082
        var (
1,689✔
1083
                closingCircuits = make(map[CircuitKey]struct{})
1,689✔
1084
                removedCircuits = make(map[CircuitKey]*PaymentCircuit)
1,689✔
1085
        )
1,689✔
1086

1,689✔
1087
        cm.mtx.Lock()
1,689✔
1088
        // Remove any references to the circuits from memory, keeping track of
1,689✔
1089
        // which circuits were removed, and which ones had been marked closed.
1,689✔
1090
        // This can be used to restore these entries later if the persistent
1,689✔
1091
        // removal fails.
1,689✔
1092
        for _, inKey := range inKeys {
2,054✔
1093
                circuit, ok := cm.pending[inKey]
365✔
1094
                if !ok {
372✔
1095
                        continue
7✔
1096
                }
1097
                delete(cm.pending, inKey)
358✔
1098

358✔
1099
                if _, ok := cm.closed[inKey]; ok {
701✔
1100
                        closingCircuits[inKey] = struct{}{}
343✔
1101
                        delete(cm.closed, inKey)
343✔
1102
                }
343✔
1103

1104
                if circuit.HasKeystone() {
701✔
1105
                        delete(cm.opened, circuit.OutKey())
343✔
1106
                        cm.removeCircuitFromHashIndex(circuit)
343✔
1107
                }
343✔
1108

1109
                removedCircuits[inKey] = circuit
358✔
1110
        }
1111
        cm.mtx.Unlock()
1,689✔
1112

1,689✔
1113
        err := kvdb.Batch(cm.cfg.DB, func(tx kvdb.RwTx) error {
3,378✔
1114
                for _, circuit := range removedCircuits {
2,047✔
1115
                        // If this htlc made it to an outgoing link, load the
358✔
1116
                        // keystone bucket from which we will remove the
358✔
1117
                        // outgoing circuit key.
358✔
1118
                        if circuit.HasKeystone() {
701✔
1119
                                keystoneBkt := tx.ReadWriteBucket(circuitKeystoneKey)
343✔
1120
                                if keystoneBkt == nil {
343✔
1121
                                        return ErrCorruptedCircuitMap
×
1122
                                }
×
1123

1124
                                outKey := circuit.OutKey()
343✔
1125

343✔
1126
                                err := keystoneBkt.Delete(outKey.Bytes())
343✔
1127
                                if err != nil {
343✔
1128
                                        return err
×
1129
                                }
×
1130
                        }
1131

1132
                        // Remove the circuit itself based on the incoming
1133
                        // circuit key.
1134
                        circuitBkt := tx.ReadWriteBucket(circuitAddKey)
358✔
1135
                        if circuitBkt == nil {
358✔
1136
                                return ErrCorruptedCircuitMap
×
1137
                        }
×
1138

1139
                        inKey := circuit.InKey()
358✔
1140
                        if err := circuitBkt.Delete(inKey.Bytes()); err != nil {
358✔
1141
                                return err
×
1142
                        }
×
1143
                }
1144

1145
                return nil
1,689✔
1146
        })
1147

1148
        // Return if the write succeeded.
1149
        if err == nil {
3,378✔
1150
                return nil
1,689✔
1151
        }
1,689✔
1152

1153
        // If the persistent changes failed, restore the circuit map to it's
1154
        // previous state.
1155
        cm.mtx.Lock()
×
1156
        for inKey, circuit := range removedCircuits {
×
1157
                cm.pending[inKey] = circuit
×
1158

×
1159
                if _, ok := closingCircuits[inKey]; ok {
×
1160
                        cm.closed[inKey] = struct{}{}
×
1161
                }
×
1162

1163
                if circuit.HasKeystone() {
×
1164
                        cm.opened[circuit.OutKey()] = circuit
×
1165
                        cm.addCircuitToHashIndex(circuit)
×
1166
                }
×
1167
        }
1168
        cm.mtx.Unlock()
×
1169

×
1170
        return err
×
1171
}
1172

1173
// removeCircuitFromHashIndex removes the given circuit from the hash index,
1174
// pruning any unnecessary memory optimistically.
1175
func (cm *circuitMap) removeCircuitFromHashIndex(c *PaymentCircuit) {
362✔
1176
        // Locate bucket containing this circuit's payment hashes.
362✔
1177
        circuitsWithHash, ok := cm.hashIndex[c.PaymentHash]
362✔
1178
        if !ok {
362✔
1179
                return
×
1180
        }
×
1181

1182
        outKey := c.OutKey()
362✔
1183

362✔
1184
        // Remove this circuit from the set of circuitsWithHash.
362✔
1185
        delete(circuitsWithHash, outKey)
362✔
1186

362✔
1187
        // Prune the payment hash bucket if no other entries remain.
362✔
1188
        if len(circuitsWithHash) == 0 {
703✔
1189
                delete(cm.hashIndex, c.PaymentHash)
341✔
1190
        }
341✔
1191
}
1192

1193
// NumPending returns the number of active circuits added to the circuit map.
1194
func (cm *circuitMap) NumPending() int {
74✔
1195
        cm.mtx.RLock()
74✔
1196
        defer cm.mtx.RUnlock()
74✔
1197

74✔
1198
        return len(cm.pending)
74✔
1199
}
74✔
1200

1201
// NumOpen returns the number of circuits that have been opened by way of
1202
// setting their keystones. This is the number of HTLCs that are waiting for a
1203
// settle/fail response from a remote peer.
1204
func (cm *circuitMap) NumOpen() int {
94✔
1205
        cm.mtx.RLock()
94✔
1206
        defer cm.mtx.RUnlock()
94✔
1207

94✔
1208
        return len(cm.opened)
94✔
1209
}
94✔
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