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

lightningnetwork / lnd / 15561477203

10 Jun 2025 01:54PM UTC coverage: 58.351% (-10.1%) from 68.487%
15561477203

Pull #9356

github

web-flow
Merge 6440b25db into c6d6d4c0b
Pull Request #9356: lnrpc: add incoming/outgoing channel ids filter to forwarding history request

33 of 36 new or added lines in 2 files covered. (91.67%)

28366 existing lines in 455 files now uncovered.

97715 of 167461 relevant lines covered (58.35%)

1.81 hits per line

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

65.81
/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) {
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 {
3✔
UNCOV
301
                        return false
×
UNCOV
302
                }
×
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

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

×
UNCOV
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✔
UNCOV
387
                                // If the incoming channel is closed, we can
×
UNCOV
388
                                // skip checking on outgoing channel ID because
×
UNCOV
389
                                // this keystone will be deleted.
×
UNCOV
390
                                keystoneKeySet[outKey] = struct{}{}
×
UNCOV
391

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

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

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

UNCOV
427
                        return nil
×
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✔
UNCOV
458
                        if err := circuitBkt.Delete(inKey.Bytes()); err != nil {
×
459
                                return err
×
460
                        }
×
461

UNCOV
462
                        numCircuitsDeleted++
×
463
                }
464

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

UNCOV
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

UNCOV
721
                circuit.Outgoing = nil
×
UNCOV
722
                delete(cm.opened, outKey)
×
UNCOV
723
                trimmedOutKeys = append(trimmedOutKeys, outKey)
×
UNCOV
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

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

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

UNCOV
745
                return nil
×
UNCOV
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.
UNCOV
769
func (cm *circuitMap) LookupByPaymentHash(hash [32]byte) []*PaymentCircuit {
×
UNCOV
770
        cm.mtx.RLock()
×
UNCOV
771
        defer cm.mtx.RUnlock()
×
UNCOV
772

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

UNCOV
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", lnutils.SpewLogClosure(
3✔
804
                inKeys))
3✔
805

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

3✔
808
        // If an empty list was passed, return early to avoid grabbing the lock.
3✔
809
        if len(circuits) == 0 {
3✔
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()
3✔
823
        var adds, drops, fails, addFails []*PaymentCircuit
3✔
824
        for _, circuit := range circuits {
6✔
825
                inKey := circuit.InKey()
3✔
826
                if foundCircuit, ok := cm.pending[inKey]; ok {
6✔
827
                        switch {
3✔
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():
3✔
833
                                drops = append(drops, circuit)
3✔
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.
UNCOV
843
                        case !foundCircuit.LoadedFromDisk:
×
UNCOV
844
                                drops = append(drops, circuit)
×
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.
UNCOV
850
                        default:
×
UNCOV
851
                                fails = append(fails, circuit)
×
UNCOV
852
                                addFails = append(addFails, circuit)
×
853
                        }
854

855
                        continue
3✔
856
                }
857

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

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

871
        // Now, optimistically serialize the circuits to add.
872
        var bs = make([]bytes.Buffer, len(adds))
3✔
873
        for i, circuit := range adds {
6✔
874
                if err := circuit.Encode(&bs[i]); err != nil {
3✔
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 {
6✔
885
                circuitBkt := tx.ReadWriteBucket(circuitAddKey)
3✔
886
                if circuitBkt == nil {
3✔
887
                        return ErrCorruptedCircuitMap
×
888
                }
×
889

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

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

900
                return nil
3✔
901
        })
902

903
        // Return if the write succeeded.
904
        if err == nil {
6✔
905
                actions.Adds = adds
3✔
906
                actions.Drops = drops
3✔
907
                actions.Fails = fails
3✔
908
                return actions, nil
3✔
909
        }
3✔
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 {
3✔
938
        return fmt.Sprintf("%s --> %s", k.InKey, k.OutKey)
3✔
939
}
3✔
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 {
3✔
946
        if len(keystones) == 0 {
6✔
947
                return nil
3✔
948
        }
3✔
949

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

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

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

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

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

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

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

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

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

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

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

3✔
1014
        return nil
3✔
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) {
3✔
1020
        if _, ok := cm.hashIndex[c.PaymentHash]; !ok {
6✔
1021
                cm.hashIndex[c.PaymentHash] = make(map[CircuitKey]struct{})
3✔
1022
        }
3✔
1023
        cm.hashIndex[c.PaymentHash][c.OutKey()] = struct{}{}
3✔
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) {
3✔
1029

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

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

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

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

3✔
1045
        return circuit, nil
3✔
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) {
3✔
1052

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

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

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

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

3✔
1068
        return circuit, nil
3✔
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 {
3✔
1078

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

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

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

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

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

1109
                removedCircuits[inKey] = circuit
3✔
1110
        }
1111
        cm.mtx.Unlock()
3✔
1112

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

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

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

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

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

1145
                return nil
3✔
1146
        })
1147

1148
        // Return if the write succeeded.
1149
        if err == nil {
6✔
1150
                return nil
3✔
1151
        }
3✔
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) {
3✔
1176
        // Locate bucket containing this circuit's payment hashes.
3✔
1177
        circuitsWithHash, ok := cm.hashIndex[c.PaymentHash]
3✔
1178
        if !ok {
3✔
1179
                return
×
1180
        }
×
1181

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

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

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

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

×
UNCOV
1198
        return len(cm.pending)
×
UNCOV
1199
}
×
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.
UNCOV
1204
func (cm *circuitMap) NumOpen() int {
×
UNCOV
1205
        cm.mtx.RLock()
×
UNCOV
1206
        defer cm.mtx.RUnlock()
×
UNCOV
1207

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