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

lightningnetwork / lnd / 9915780197

13 Jul 2024 12:30AM UTC coverage: 49.268% (-9.1%) from 58.413%
9915780197

push

github

web-flow
Merge pull request #8653 from ProofOfKeags/fn-prim

DynComms [0/n]: `fn` package additions

92837 of 188433 relevant lines covered (49.27%)

1.55 hits per line

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

64.8
/htlcswitch/circuit_map.go
1
package htlcswitch
2

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

8
        "github.com/davecgh/go-spew/spew"
9
        "github.com/go-errors/errors"
10
        "github.com/lightningnetwork/lnd/channeldb"
11
        "github.com/lightningnetwork/lnd/htlcswitch/hop"
12
        "github.com/lightningnetwork/lnd/kvdb"
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) {
3✔
224
        cm := &circuitMap{
3✔
225
                cfg: cfg,
3✔
226
        }
3✔
227

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

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

238
        // Load any previously persisted circuit into back into memory.
239
        if err := cm.restoreMemState(); err != nil {
3✔
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 {
3✔
249
                return nil, err
×
250
        }
×
251

252
        return cm, nil
3✔
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 {
3✔
258
        return kvdb.Update(cm.cfg.DB, func(tx kvdb.RwTx) error {
6✔
259
                _, err := tx.CreateTopLevelBucket(circuitKeystoneKey)
3✔
260
                if err != nil {
3✔
261
                        return err
×
262
                }
×
263

264
                _, err = tx.CreateTopLevelBucket(circuitAddKey)
3✔
265
                return err
3✔
266
        }, func() {})
3✔
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 {
3✔
280
        log.Infof("Cleaning circuits from disk for closed channels")
3✔
281

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

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

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

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

304
                _, ok := closedChanIDSet[chanID]
3✔
305
                return ok
3✔
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)
3✔
311
        if err != nil {
3✔
312
                return err
×
313
        }
×
314

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

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

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

3✔
326
        // Exit early if there are no closed channels.
3✔
327
        if len(closedChanIDSet) == 0 {
6✔
328
                log.Infof("Finished cleaning: no closed channels found, " +
3✔
329
                        "no actions taken.",
3✔
330
                )
3✔
331
                return nil
3✔
332
        }
3✔
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 {
6✔
336
                circuitBkt := tx.ReadBucket(circuitAddKey)
3✔
337
                if circuitBkt == nil {
3✔
338
                        return ErrCorruptedCircuitMap
×
339
                }
×
340
                keystoneBkt := tx.ReadBucket(circuitKeystoneKey)
3✔
341
                if keystoneBkt == nil {
3✔
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 {
6✔
350
                        circuit, err := cm.decodeCircuit(v)
3✔
351
                        if err != nil {
3✔
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) {
6✔
358
                                return nil
3✔
359
                        }
3✔
360

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

×
363
                        return nil
×
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 {
6✔
371
                        var (
3✔
372
                                inKey  CircuitKey
3✔
373
                                outKey CircuitKey
3✔
374
                        )
3✔
375

3✔
376
                        // Decode the incoming and outgoing circuit keys.
3✔
377
                        if err := inKey.SetBytes(v); err != nil {
3✔
378
                                return err
×
379
                        }
×
380
                        if err := outKey.SetBytes(k); err != nil {
3✔
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) {
3✔
387
                                // If the incoming channel is closed, we can
×
388
                                // skip checking on outgoing channel ID because
×
389
                                // this keystone will be deleted.
×
390
                                keystoneKeySet[outKey] = struct{}{}
×
391

×
392
                                // Technically the incoming keys found in
×
393
                                // keystone bucket should be a subset of
×
394
                                // circuit bucket. So a previous loop should
×
395
                                // have this inKey put inside circuitAddKey map
×
396
                                // already. We do this again to be sure the
×
397
                                // circuits are properly cleaned. Even this
×
398
                                // inKey doesn't exist in circuit bucket, we
×
399
                                // are fine as db deletion is a noop.
×
400
                                circuitKeySet[inKey] = struct{}{}
×
401
                                return nil
×
402
                        }
×
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) {
6✔
413
                                // Check the resolution message store. A return
3✔
414
                                // value of nil means we need to skip deleting
3✔
415
                                // these circuits.
3✔
416
                                if cm.cfg.CheckResolutionMsg(&outKey) == nil {
6✔
417
                                        return nil
3✔
418
                                }
3✔
419

420
                                keystoneKeySet[outKey] = struct{}{}
×
421

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

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

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

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

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

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

462
                        numCircuitsDeleted++
×
463
                }
464

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

472
                        numKeystonesDeleted++
×
473
                }
474

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

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

3✔
487
        return nil
3✔
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 {
3✔
497
        log.Infof("Restoring in-memory circuit state from disk")
3✔
498

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

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

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

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

3✔
521
                        return nil
3✔
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)
3✔
529
                if keystoneBkt == nil {
3✔
530
                        return ErrCorruptedCircuitMap
×
531
                }
×
532

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

3✔
540
                        // Decode the incoming and outgoing circuit keys.
3✔
541
                        if err := inKey.SetBytes(v); err != nil {
3✔
542
                                return err
×
543
                        }
×
544
                        if err := outKey.SetBytes(k); err != nil {
3✔
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]
3✔
551
                        if ok {
6✔
552
                                circuit.Outgoing = outKey
3✔
553
                                opened[*outKey] = circuit
3✔
554
                        } else {
3✔
555
                                strayKeystones = append(strayKeystones, Keystone{
×
556
                                        InKey:  inKey,
×
557
                                        OutKey: *outKey,
×
558
                                })
×
559
                        }
×
560

561
                        return nil
3✔
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 {
3✔
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
3✔
590

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

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

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

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

612
        return nil
3✔
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) {
3✔
620
        var circuit = &PaymentCircuit{}
3✔
621

3✔
622
        circuitReader := bytes.NewReader(v)
3✔
623
        if err := circuit.Decode(circuitReader); err != nil {
3✔
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 {
6✔
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(
3✔
636
                cm.cfg.ExtractErrorEncrypter,
3✔
637
        )
3✔
638
        if err != nil {
3✔
639
                return nil, err
×
640
        }
×
641

642
        return circuit, nil
3✔
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 {
3✔
654
        activeChannels, err := cm.cfg.FetchAllOpenChannels()
3✔
655
        if err != nil {
3✔
656
                return err
×
657
        }
×
658

659
        for _, activeChannel := range activeChannels {
6✔
660
                if activeChannel.IsPending {
3✔
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()
3✔
668
                if chanID == hop.Source {
3✔
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()
3✔
675
                if err != nil {
3✔
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)
3✔
684
                if err != nil {
3✔
685
                        return err
×
686
                }
×
687
        }
688

689
        return nil
3✔
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 {
3✔
700

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

3✔
704
        var trimmedOutKeys []CircuitKey
3✔
705

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

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

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

3✔
728
        if len(trimmedOutKeys) == 0 {
6✔
729
                return nil
3✔
730
        }
3✔
731

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

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

745
                return nil
×
746
        }, func() {})
×
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 {
3✔
752
        cm.mtx.RLock()
3✔
753
        defer cm.mtx.RUnlock()
3✔
754

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

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

3✔
764
        return cm.opened[outKey]
3✔
765
}
3✔
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 {
×
770
        cm.mtx.RLock()
×
771
        defer cm.mtx.RUnlock()
×
772

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

785
        return circuits
×
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) {
3✔
797

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

803
        log.Tracef("Committing fresh circuits: %v", newLogClosure(func() string {
3✔
804
                return spew.Sdump(inKeys)
×
805
        }))
×
806

807
        actions := &CircuitFwdActions{}
3✔
808

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

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

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

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

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

856
                        continue
3✔
857
                }
858

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

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

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

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

891
                for i, circuit := range adds {
6✔
892
                        inKeyBytes := circuit.InKey().Bytes()
3✔
893
                        circuitBytes := bs[i].Bytes()
3✔
894

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

901
                return nil
3✔
902
        })
903

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

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

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

×
925
        return actions, err
×
926
}
927

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

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

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

951
        log.Tracef("Opening finalized circuits: %v", newLogClosure(func() string {
3✔
952
                return spew.Sdump(keystones)
×
953
        }))
×
954

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

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

971
                openedCircuits = append(openedCircuits, circuit)
3✔
972
        }
973
        cm.mtx.RUnlock()
3✔
974

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

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

992
                return nil
3✔
993
        }, func() {})
3✔
994

995
        if err != nil {
3✔
996
                return err
×
997
        }
×
998

999
        cm.mtx.Lock()
3✔
1000
        for i, circuit := range openedCircuits {
6✔
1001
                ks := keystones[i]
3✔
1002

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

3✔
1011
                cm.opened[ks.OutKey] = circuit
3✔
1012
                cm.addCircuitToHashIndex(circuit)
3✔
1013
        }
3✔
1014
        cm.mtx.Unlock()
3✔
1015

3✔
1016
        return nil
3✔
1017
}
1018

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

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

3✔
1032
        cm.mtx.Lock()
3✔
1033
        defer cm.mtx.Unlock()
3✔
1034

3✔
1035
        circuit, ok := cm.pending[inKey]
3✔
1036
        if !ok {
3✔
1037
                return nil, ErrUnknownCircuit
×
1038
        }
×
1039

1040
        _, ok = cm.closed[inKey]
3✔
1041
        if ok {
3✔
1042
                return nil, ErrCircuitClosing
×
1043
        }
×
1044

1045
        cm.closed[inKey] = struct{}{}
3✔
1046

3✔
1047
        return circuit, nil
3✔
1048
}
1049

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

3✔
1055
        cm.mtx.Lock()
3✔
1056
        defer cm.mtx.Unlock()
3✔
1057

3✔
1058
        circuit, ok := cm.opened[outKey]
3✔
1059
        if !ok {
6✔
1060
                return nil, ErrUnknownCircuit
3✔
1061
        }
3✔
1062

1063
        _, ok = cm.closed[circuit.Incoming]
3✔
1064
        if ok {
6✔
1065
                return nil, ErrCircuitClosing
3✔
1066
        }
3✔
1067

1068
        cm.closed[circuit.Incoming] = struct{}{}
3✔
1069

3✔
1070
        return circuit, nil
3✔
1071
}
1072

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

3✔
1081
        log.Tracef("Deleting resolved circuits: %v", newLogClosure(func() string {
3✔
1082
                return spew.Sdump(inKeys)
×
1083
        }))
×
1084

1085
        var (
3✔
1086
                closingCircuits = make(map[CircuitKey]struct{})
3✔
1087
                removedCircuits = make(map[CircuitKey]*PaymentCircuit)
3✔
1088
        )
3✔
1089

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

3✔
1102
                if _, ok := cm.closed[inKey]; ok {
6✔
1103
                        closingCircuits[inKey] = struct{}{}
3✔
1104
                        delete(cm.closed, inKey)
3✔
1105
                }
3✔
1106

1107
                if circuit.HasKeystone() {
6✔
1108
                        delete(cm.opened, circuit.OutKey())
3✔
1109
                        cm.removeCircuitFromHashIndex(circuit)
3✔
1110
                }
3✔
1111

1112
                removedCircuits[inKey] = circuit
3✔
1113
        }
1114
        cm.mtx.Unlock()
3✔
1115

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

1127
                                outKey := circuit.OutKey()
3✔
1128

3✔
1129
                                err := keystoneBkt.Delete(outKey.Bytes())
3✔
1130
                                if err != nil {
3✔
1131
                                        return err
×
1132
                                }
×
1133
                        }
1134

1135
                        // Remove the circuit itself based on the incoming
1136
                        // circuit key.
1137
                        circuitBkt := tx.ReadWriteBucket(circuitAddKey)
3✔
1138
                        if circuitBkt == nil {
3✔
1139
                                return ErrCorruptedCircuitMap
×
1140
                        }
×
1141

1142
                        inKey := circuit.InKey()
3✔
1143
                        if err := circuitBkt.Delete(inKey.Bytes()); err != nil {
3✔
1144
                                return err
×
1145
                        }
×
1146
                }
1147

1148
                return nil
3✔
1149
        })
1150

1151
        // Return if the write succeeded.
1152
        if err == nil {
6✔
1153
                return nil
3✔
1154
        }
3✔
1155

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

×
1162
                if _, ok := closingCircuits[inKey]; ok {
×
1163
                        cm.closed[inKey] = struct{}{}
×
1164
                }
×
1165

1166
                if circuit.HasKeystone() {
×
1167
                        cm.opened[circuit.OutKey()] = circuit
×
1168
                        cm.addCircuitToHashIndex(circuit)
×
1169
                }
×
1170
        }
1171
        cm.mtx.Unlock()
×
1172

×
1173
        return err
×
1174
}
1175

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

1185
        outKey := c.OutKey()
3✔
1186

3✔
1187
        // Remove this circuit from the set of circuitsWithHash.
3✔
1188
        delete(circuitsWithHash, outKey)
3✔
1189

3✔
1190
        // Prune the payment hash bucket if no other entries remain.
3✔
1191
        if len(circuitsWithHash) == 0 {
6✔
1192
                delete(cm.hashIndex, c.PaymentHash)
3✔
1193
        }
3✔
1194
}
1195

1196
// NumPending returns the number of active circuits added to the circuit map.
1197
func (cm *circuitMap) NumPending() int {
×
1198
        cm.mtx.RLock()
×
1199
        defer cm.mtx.RUnlock()
×
1200

×
1201
        return len(cm.pending)
×
1202
}
×
1203

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

×
1211
        return len(cm.opened)
×
1212
}
×
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