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

lightningnetwork / lnd / 18352382351

08 Oct 2025 05:06PM UTC coverage: 66.659% (+0.02%) from 66.641%
18352382351

Pull #10277

github

web-flow
Merge 380f79f9e into 87aed739e
Pull Request #10277: fix channel disable/enable race condition

10 of 10 new or added lines in 1 file covered. (100.0%)

61 existing lines in 12 files now uncovered.

137262 of 205916 relevant lines covered (66.66%)

21246.98 hits per line

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

85.32
/netann/chan_status_manager.go
1
package netann
2

3
import (
4
        "errors"
5
        "sync"
6
        "time"
7

8
        "github.com/btcsuite/btcd/btcec/v2"
9
        "github.com/btcsuite/btcd/wire"
10
        "github.com/lightningnetwork/lnd/channeldb"
11
        graphdb "github.com/lightningnetwork/lnd/graph/db"
12
        "github.com/lightningnetwork/lnd/keychain"
13
        "github.com/lightningnetwork/lnd/lnwallet"
14
        "github.com/lightningnetwork/lnd/lnwire"
15
)
16

17
var (
18
        // ErrChanStatusManagerExiting signals that a shutdown of the
19
        // ChanStatusManager has already been requested.
20
        ErrChanStatusManagerExiting = errors.New("chan status manager exiting")
21

22
        // ErrInvalidTimeoutConstraints signals that the ChanStatusManager could
23
        // not be initialized because the timeouts and sample intervals were
24
        // malformed.
25
        ErrInvalidTimeoutConstraints = errors.New("chan-enable-timeout + " +
26
                "chan-status-sample-interval must <= chan-disable-timeout " +
27
                "and all three chan configs must be positive integers")
28

29
        // ErrEnableInactiveChan signals that a request to enable a channel
30
        // could not be completed because the channel isn't actually active at
31
        // the time of the request.
32
        ErrEnableInactiveChan = errors.New("unable to enable channel which " +
33
                "is not currently active")
34

35
        // ErrEnableManuallyDisabledChan signals that an automatic / background
36
        // request to enable a channel could not be completed because the channel
37
        // was manually disabled.
38
        ErrEnableManuallyDisabledChan = errors.New("unable to enable channel " +
39
                "which was manually disabled")
40
)
41

42
// ChanStatusConfig holds parameters and resources required by the
43
// ChanStatusManager to perform its duty.
44
type ChanStatusConfig struct {
45
        // OurPubKey is the public key identifying this node on the network.
46
        OurPubKey *btcec.PublicKey
47

48
        // OurKeyLoc is the locator for the public key identifying this node on
49
        // the network.
50
        OurKeyLoc keychain.KeyLocator
51

52
        // MessageSigner signs messages that validate under OurPubKey.
53
        MessageSigner lnwallet.MessageSigner
54

55
        // IsChannelActive checks whether the channel identified by the provided
56
        // ChannelID is considered active. This should only return true if the
57
        // channel has been sufficiently confirmed, the channel has received
58
        // ChannelReady, and the remote peer is online.
59
        IsChannelActive func(lnwire.ChannelID) bool
60

61
        // ApplyChannelUpdate processes new ChannelUpdates signed by our node by
62
        // updating our local routing table and broadcasting the update to our
63
        // peers.
64
        ApplyChannelUpdate func(*lnwire.ChannelUpdate1, *wire.OutPoint,
65
                bool) error
66

67
        // DB stores the set of channels that are to be monitored.
68
        DB DB
69

70
        // Graph stores the channel info and policies for channels in DB.
71
        Graph ChannelGraph
72

73
        // ChanEnableTimeout is the duration a peer's connect must remain stable
74
        // before attempting to re-enable the channel.
75
        //
76
        // NOTE: This value is only used to verify that the relation between
77
        // itself, ChanDisableTimeout, and ChanStatusSampleInterval is correct.
78
        // The user is still responsible for ensuring that the same duration
79
        // elapses before attempting to re-enable a channel.
80
        ChanEnableTimeout time.Duration
81

82
        // ChanDisableTimeout is the duration the manager will wait after
83
        // detecting that a channel has become inactive before broadcasting an
84
        // update to disable the channel.
85
        ChanDisableTimeout time.Duration
86

87
        // ChanStatusSampleInterval is the long-polling interval used by the
88
        // manager to check if the channels being monitored have become
89
        // inactive.
90
        ChanStatusSampleInterval time.Duration
91
}
92

93
// ChanStatusManager facilitates requests to enable or disable a channel via a
94
// network announcement that sets the disable bit on the ChannelUpdate
95
// accordingly. The manager will periodically sample to detect cases where a
96
// link has become inactive, and facilitate the process of disabling the channel
97
// passively. The ChanStatusManager state machine is designed to reduce the
98
// likelihood of spamming the network with updates for flapping peers.
99
type ChanStatusManager struct {
100
        started sync.Once
101
        stopped sync.Once
102

103
        cfg *ChanStatusConfig
104

105
        // ourPubKeyBytes is the serialized compressed pubkey of our node.
106
        ourPubKeyBytes []byte
107

108
        // chanStates contains the set of channels being monitored for status
109
        // updates. Access to the map is serialized by the statusManager's event
110
        // loop.
111
        chanStates channelStates
112

113
        // enableRequests pipes external requests to enable a channel into the
114
        // primary event loop.
115
        enableRequests chan statusRequest
116

117
        // disableRequests pipes external requests to disable a channel into the
118
        // primary event loop.
119
        disableRequests chan statusRequest
120

121
        // autoRequests pipes external requests to restore automatic channel
122
        // state management into the primary event loop.
123
        autoRequests chan statusRequest
124

125
        // statusSampleTicker fires at the interval prescribed by
126
        // ChanStatusSampleInterval to check if channels in chanStates have
127
        // become inactive.
128
        statusSampleTicker *time.Ticker
129

130
        wg   sync.WaitGroup
131
        quit chan struct{}
132
}
133

134
// NewChanStatusManager initializes a new ChanStatusManager using the given
135
// configuration. An error is returned if the timeouts and sample interval fail
136
// to meet do not satisfy the equation:
137
//
138
//        ChanEnableTimeout + ChanStatusSampleInterval > ChanDisableTimeout.
139
func NewChanStatusManager(cfg *ChanStatusConfig) (*ChanStatusManager, error) {
41✔
140
        // Assert that the config timeouts are properly formed. We require the
41✔
141
        // enable_timeout + sample_interval to be less than or equal to the
41✔
142
        // disable_timeout and that all are positive values. A peer that
41✔
143
        // disconnects and reconnects quickly may cause a disable update to be
41✔
144
        // sent, shortly followed by a re-enable. Ensuring a healthy separation
41✔
145
        // helps dampen the possibility of spamming updates that toggle the
41✔
146
        // disable bit for such events.
41✔
147
        if cfg.ChanStatusSampleInterval <= 0 {
41✔
148
                return nil, ErrInvalidTimeoutConstraints
×
149
        }
×
150
        if cfg.ChanEnableTimeout <= 0 {
41✔
151
                return nil, ErrInvalidTimeoutConstraints
×
152
        }
×
153
        if cfg.ChanDisableTimeout <= 0 {
41✔
154
                return nil, ErrInvalidTimeoutConstraints
×
155
        }
×
156
        if cfg.ChanEnableTimeout+cfg.ChanStatusSampleInterval >
41✔
157
                cfg.ChanDisableTimeout {
41✔
158
                return nil, ErrInvalidTimeoutConstraints
×
159

×
160
        }
×
161

162
        return &ChanStatusManager{
41✔
163
                cfg:                cfg,
41✔
164
                ourPubKeyBytes:     cfg.OurPubKey.SerializeCompressed(),
41✔
165
                chanStates:         make(channelStates),
41✔
166
                statusSampleTicker: time.NewTicker(cfg.ChanStatusSampleInterval),
41✔
167
                enableRequests:     make(chan statusRequest),
41✔
168
                disableRequests:    make(chan statusRequest),
41✔
169
                autoRequests:       make(chan statusRequest),
41✔
170
                quit:               make(chan struct{}),
41✔
171
        }, nil
41✔
172
}
173

174
// Start safely starts the ChanStatusManager.
175
func (m *ChanStatusManager) Start() error {
36✔
176
        var err error
36✔
177
        m.started.Do(func() {
72✔
178
                log.Info("Channel Status Manager starting")
36✔
179
                err = m.start()
36✔
180
        })
36✔
181
        return err
36✔
182
}
183

184
func (m *ChanStatusManager) start() error {
36✔
185
        channels, err := m.fetchChannels()
36✔
186
        if err != nil {
36✔
187
                return err
×
188
        }
×
189

190
        // Populate the initial states of all confirmed, public channels.
191
        for _, c := range channels {
229✔
192
                _, err := m.getOrInitChanStatus(c.FundingOutpoint)
193✔
193
                switch {
193✔
194

195
                // If we can't retrieve the edge info for this channel, it may
196
                // have been pruned from the channel graph but not yet from our
197
                // set of channels. We'll skip it as we can't determine its
198
                // initial state.
199
                case errors.Is(err, graphdb.ErrEdgeNotFound):
3✔
200
                        log.Warnf("Unable to find channel policies for %v, "+
3✔
201
                                "skipping. This is typical if the channel is "+
3✔
202
                                "in the process of closing.", c.FundingOutpoint)
3✔
203
                        continue
3✔
204

205
                // If we are in the process of opening a channel, the funding
206
                // manager might not have added the ChannelUpdate to the graph
207
                // yet. We'll ignore the channel for now.
208
                case err == ErrUnableToExtractChanUpdate:
×
209
                        log.Warnf("Unable to find channel policies for %v, "+
×
210
                                "skipping. This is typical if the channel is "+
×
211
                                "in the process of being opened.",
×
212
                                c.FundingOutpoint)
×
213
                        continue
×
214

215
                case err != nil:
×
216
                        return err
×
217
                }
218
        }
219

220
        m.wg.Add(1)
36✔
221
        go m.statusManager()
36✔
222

36✔
223
        return nil
36✔
224
}
225

226
// Stop safely shuts down the ChanStatusManager.
227
func (m *ChanStatusManager) Stop() error {
36✔
228
        m.stopped.Do(func() {
72✔
229
                log.Info("Channel Status Manager shutting down...")
36✔
230
                defer log.Debug("Channel Status Manager shutdown complete")
36✔
231

36✔
232
                close(m.quit)
36✔
233
                m.wg.Wait()
36✔
234
        })
36✔
235
        return nil
36✔
236
}
237

238
// RequestEnable submits a request to immediately enable a channel identified by
239
// the provided outpoint. If the channel is already enabled, no action will be
240
// taken. If the channel is marked pending-disable the channel will be returned
241
// to an active status as the scheduled disable was never sent. Otherwise if the
242
// channel is found to be disabled, a new announcement will be signed with the
243
// disabled bit cleared and broadcast to the network.
244
//
245
// If the channel was manually disabled and RequestEnable is called with
246
// manual = false, then the request will be ignored.
247
//
248
// NOTE: RequestEnable should only be called after a stable connection with the
249
// channel's peer has lasted at least the ChanEnableTimeout. Failure to do so
250
// may result in behavior that deviates from the expected behavior of the state
251
// machine.
252
func (m *ChanStatusManager) RequestEnable(outpoint wire.OutPoint,
253
        manual bool) error {
112✔
254

112✔
255
        return m.submitRequest(m.enableRequests, outpoint, manual)
112✔
256
}
112✔
257

258
// RequestDisable submits a request to immediately disable a channel identified
259
// by the provided outpoint. If the channel is already disabled, no action will
260
// be taken. Otherwise, a new announcement will be signed with the disabled bit
261
// set and broadcast to the network.
262
//
263
// The channel state will be changed to either ChanStatusDisabled or
264
// ChanStatusManuallyDisabled, depending on the passed-in value of manual. In
265
// particular, note the following state transitions:
266
//
267
//        current state    | manual | new state
268
//        ---------------------------------------------------
269
//        Disabled         | false  | Disabled
270
//        ManuallyDisabled | false  | ManuallyDisabled (*)
271
//        Disabled         | true   | ManuallyDisabled
272
//        ManuallyDisabled | true   | ManuallyDisabled
273
//
274
// (*) If a channel was manually disabled, subsequent automatic / background
275
//
276
//        requests to disable the channel do not change the fact that the channel
277
//        was manually disabled.
278
func (m *ChanStatusManager) RequestDisable(outpoint wire.OutPoint,
279
        manual bool) error {
75✔
280

75✔
281
        return m.submitRequest(m.disableRequests, outpoint, manual)
75✔
282
}
75✔
283

284
// RequestAuto submits a request to restore automatic channel state management.
285
// If the channel is in the state ChanStatusManuallyDisabled, it will be moved
286
// back to the state ChanStatusDisabled. Otherwise, no action will be taken.
287
func (m *ChanStatusManager) RequestAuto(outpoint wire.OutPoint) error {
13✔
288
        return m.submitRequest(m.autoRequests, outpoint, true)
13✔
289
}
13✔
290

291
// statusRequest is passed to the statusManager to request a change in status
292
// for a particular channel point.  The exact action is governed by passing the
293
// request through one of the enableRequests or disableRequests channels.
294
type statusRequest struct {
295
        outpoint wire.OutPoint
296
        manual   bool
297
        errChan  chan error
298
}
299

300
// submitRequest sends a request for either enabling or disabling a particular
301
// outpoint and awaits an error response. The request type is dictated by the
302
// reqChan passed in, which can be either of the enableRequests or
303
// disableRequests channels.
304
func (m *ChanStatusManager) submitRequest(reqChan chan statusRequest,
305
        outpoint wire.OutPoint, manual bool) error {
194✔
306

194✔
307
        req := statusRequest{
194✔
308
                outpoint: outpoint,
194✔
309
                manual:   manual,
194✔
310
                errChan:  make(chan error, 1),
194✔
311
        }
194✔
312

194✔
313
        select {
194✔
314
        case reqChan <- req:
194✔
315
        case <-m.quit:
×
316
                return ErrChanStatusManagerExiting
×
317
        }
318

319
        select {
194✔
320
        case err := <-req.errChan:
194✔
321
                return err
194✔
322
        case <-m.quit:
×
323
                return ErrChanStatusManagerExiting
×
324
        }
325
}
326

327
// statusManager is the primary event loop for the ChanStatusManager, providing
328
// the necessary synchronization primitive to protect access to the chanStates
329
// map. All requests to explicitly enable or disable a channel are processed
330
// within this method. The statusManager will also periodically poll the active
331
// status of channels within the htlcswitch to see if a disable announcement
332
// should be scheduled or broadcast.
333
//
334
// NOTE: This method MUST be run as a goroutine.
335
func (m *ChanStatusManager) statusManager() {
36✔
336
        defer m.wg.Done()
36✔
337

36✔
338
        for {
1,091✔
339
                select {
1,055✔
340

341
                // Process any requests to mark channel as enabled.
342
                case req := <-m.enableRequests:
112✔
343
                        req.errChan <- m.processEnableRequest(req.outpoint, req.manual)
112✔
344

345
                // Process any requests to mark channel as disabled.
346
                case req := <-m.disableRequests:
75✔
347
                        req.errChan <- m.processDisableRequest(req.outpoint, req.manual)
75✔
348

349
                // Process any requests to restore automatic channel state management.
350
                case req := <-m.autoRequests:
13✔
351
                        req.errChan <- m.processAutoRequest(req.outpoint)
13✔
352

353
                // Use long-polling to detect when channels become inactive.
354
                case <-m.statusSampleTicker.C:
831✔
355
                        // First, do a sweep and mark any ChanStatusEnabled
831✔
356
                        // channels that are not active within the htlcswitch as
831✔
357
                        // ChanStatusPendingDisabled. The channel will then be
831✔
358
                        // disabled if no request to enable is received before
831✔
359
                        // the ChanDisableTimeout expires.
831✔
360
                        m.markPendingInactiveChannels()
831✔
361

831✔
362
                        // Now, do another sweep to disable any channels that
831✔
363
                        // were marked in a prior iteration as pending inactive
831✔
364
                        // if the inactive chan timeout has elapsed.
831✔
365
                        m.disableInactiveChannels()
831✔
366

367
                case <-m.quit:
36✔
368
                        return
36✔
369
                }
370
        }
371
}
372

373
// processEnableRequest attempts to enable the given outpoint.
374
//
375
//   - If the channel is not active at the time of the request,
376
//     ErrEnableInactiveChan will be returned.
377
//   - If the channel was in the ManuallyDisabled state and manual = false,
378
//     the request will be ignored and ErrEnableManuallyDisabledChan will be
379
//     returned.
380
//   - Otherwise, the status of the channel in chanStates will be
381
//     ChanStatusEnabled and the method will return nil.
382
//
383
// An update will be broadcast only if the channel is currently disabled,
384
// otherwise no update will be sent on the network.
385
func (m *ChanStatusManager) processEnableRequest(outpoint wire.OutPoint,
386
        manual bool) error {
112✔
387

112✔
388
        curState, err := m.getOrInitChanStatus(outpoint)
112✔
389
        if err != nil {
118✔
390
                return err
6✔
391
        }
6✔
392

393
        // Quickly check to see if the requested channel is active within the
394
        // htlcswitch and return an error if it isn't.
395
        chanID := lnwire.NewChanIDFromOutPoint(outpoint)
106✔
396
        if !m.cfg.IsChannelActive(chanID) {
116✔
397
                return ErrEnableInactiveChan
10✔
398
        }
10✔
399

400
        switch curState.Status {
96✔
401

402
        // Channel is already enabled, nothing to do.
403
        case ChanStatusEnabled:
26✔
404
                log.Debugf("Channel(%v) already enabled, skipped "+
26✔
405
                        "announcement", outpoint)
26✔
406

26✔
407
                return nil
26✔
408

409
        // The channel is enabled, though we are now canceling the scheduled
410
        // disable.
411
        case ChanStatusPendingDisabled:
13✔
412
                log.Debugf("Channel(%v) already enabled, canceling scheduled "+
13✔
413
                        "disable", outpoint)
13✔
414

415
        // We'll sign a new update if the channel is still disabled.
416
        case ChanStatusManuallyDisabled:
33✔
417
                if !manual {
56✔
418
                        return ErrEnableManuallyDisabledChan
23✔
419
                }
23✔
420
                fallthrough
13✔
421

422
        case ChanStatusDisabled:
43✔
423
                log.Infof("Announcing channel(%v) enabled", outpoint)
43✔
424

43✔
425
                err := m.signAndSendNextUpdate(outpoint, false)
43✔
426
                if err != nil {
43✔
427
                        return err
×
428
                }
×
429
        }
430

431
        m.chanStates.markEnabled(outpoint)
53✔
432

53✔
433
        return nil
53✔
434
}
435

436
// processDisableRequest attempts to disable the given outpoint. If the method
437
// returns nil, the status of the channel in chanStates will be either
438
// ChanStatusDisabled or ChanStatusManuallyDisabled, depending on the
439
// passed-in value of manual.
440
//
441
// An update will only be sent if the channel has a status other than
442
// ChanStatusEnabled, otherwise no update will be sent on the network.
443
func (m *ChanStatusManager) processDisableRequest(outpoint wire.OutPoint,
444
        manual bool) error {
75✔
445

75✔
446
        curState, err := m.getOrInitChanStatus(outpoint)
75✔
447
        if err != nil {
87✔
448
                return err
12✔
449
        }
12✔
450

451
        status := curState.Status
63✔
452
        if status == ChanStatusEnabled || status == ChanStatusPendingDisabled {
116✔
453
                log.Infof("Announcing channel(%v) disabled [requested]",
53✔
454
                        outpoint)
53✔
455

53✔
456
                err := m.signAndSendNextUpdate(outpoint, true)
53✔
457
                if err != nil {
58✔
458
                        return err
5✔
459
                }
5✔
460
        }
461

462
        // Typically, a request to disable a channel via the manager's public
463
        // interface signals that the channel is being closed.
464
        //
465
        // If we don't need to keep track of a manual request to disable the
466
        // channel, then we can remove the outpoint to free up space in the map
467
        // of channel states. If for some reason the channel isn't closed, the
468
        // state will be repopulated on subsequent calls to the manager's public
469
        // interface via a db lookup, or on startup.
470
        if manual {
81✔
471
                m.chanStates.markManuallyDisabled(outpoint)
23✔
472
        } else if status != ChanStatusManuallyDisabled {
99✔
473
                delete(m.chanStates, outpoint)
38✔
474
        }
38✔
475

476
        return nil
58✔
477
}
478

479
// processAutoRequest attempts to restore automatic channel state management
480
// for the given outpoint. If the method returns nil, the state of the channel
481
// will no longer be ChanStatusManuallyDisabled (currently the only state in
482
// which automatic / background requests are ignored).
483
//
484
// No update will be sent on the network.
485
func (m *ChanStatusManager) processAutoRequest(outpoint wire.OutPoint) error {
13✔
486
        curState, err := m.getOrInitChanStatus(outpoint)
13✔
487
        if err != nil {
13✔
488
                return err
×
489
        }
×
490

491
        if curState.Status == ChanStatusManuallyDisabled {
26✔
492
                log.Debugf("Restoring automatic control for manually disabled "+
13✔
493
                        "channel(%v)", outpoint)
13✔
494

13✔
495
                m.chanStates.markDisabled(outpoint)
13✔
496
        }
13✔
497
        return nil
13✔
498
}
499

500
// markPendingInactiveChannels performs a sweep of the database's active
501
// channels and determines which, if any, should have a disable announcement
502
// scheduled. Once an active channel is determined to be pending-inactive, one
503
// of two transitions can follow. Either the channel is disabled because no
504
// request to enable is received before the scheduled disable is broadcast, or
505
// the channel is successfully re-enabled and channel is returned to an active
506
// state from the POV of the ChanStatusManager.
507
func (m *ChanStatusManager) markPendingInactiveChannels() {
831✔
508
        channels, err := m.fetchChannels()
831✔
509
        if err != nil {
831✔
510
                log.Errorf("Unable to load active channels: %v", err)
×
511
                return
×
512
        }
×
513

514
        for _, c := range channels {
8,992✔
515
                // Determine the initial status of the active channel, and
8,161✔
516
                // populate the entry in the chanStates map.
8,161✔
517
                curState, err := m.getOrInitChanStatus(c.FundingOutpoint)
8,161✔
518
                if err != nil {
8,254✔
519
                        log.Errorf("Unable to retrieve chan status for "+
93✔
520
                                "Channel(%v): %v", c.FundingOutpoint, err)
93✔
521
                        continue
93✔
522
                }
523

524
                // If the channel's status is not ChanStatusEnabled, we are
525
                // done.  Either it is already disabled, or it has been marked
526
                // ChanStatusPendingDisable meaning that we have already
527
                // scheduled the time at which it will be disabled.
528
                if curState.Status != ChanStatusEnabled {
12,631✔
529
                        continue
4,563✔
530
                }
531

532
                // If our bookkeeping shows the channel as active, sample the
533
                // htlcswitch to see if it believes the link is also active. If
534
                // so, we will skip marking it as ChanStatusPendingDisabled.
535
                chanID := lnwire.NewChanIDFromOutPoint(c.FundingOutpoint)
3,508✔
536
                if m.cfg.IsChannelActive(chanID) {
6,986✔
537
                        continue
3,478✔
538
                }
539

540
                // Otherwise, we discovered that this link was inactive within
541
                // the switch. Compute the time at which we will send out a
542
                // disable if the peer is unable to reestablish a stable
543
                // connection.
544
                disableTime := time.Now().Add(m.cfg.ChanDisableTimeout)
33✔
545

33✔
546
                log.Debugf("Marking channel(%v) pending-inactive",
33✔
547
                        c.FundingOutpoint)
33✔
548

33✔
549
                m.chanStates.markPendingDisabled(c.FundingOutpoint, disableTime)
33✔
550
        }
551
}
552

553
// disableInactiveChannels scans through the set of monitored channels, and
554
// broadcast a disable update for any pending inactive channels whose
555
// SendDisableTime has been superseded by the current time.
556
func (m *ChanStatusManager) disableInactiveChannels() {
831✔
557
        // Now, disable any channels whose inactive chan timeout has elapsed.
831✔
558
        now := time.Now()
831✔
559
        for outpoint, state := range m.chanStates {
9,204✔
560
                // Ignore statuses that are not in the pending-inactive state.
8,373✔
561
                if state.Status != ChanStatusPendingDisabled {
16,286✔
562
                        continue
7,913✔
563
                }
564

565
                // Ignore statuses for which the disable timeout has not
566
                // expired.
567
                if state.SendDisableTime.After(now) {
906✔
568
                        continue
443✔
569
                }
570

571
                // Re-verify the channel is still inactive before disabling.
572
                // This prevents the race condition where a channel becomes
573
                // active again after being marked as pending disabled but
574
                // before the disable timeout expires. Otherwise there is a
575
                // race condition where the channel is disabled even though it
576
                // is active.
577
                chanID := lnwire.NewChanIDFromOutPoint(outpoint)
23✔
578
                if m.cfg.IsChannelActive(chanID) {
35✔
579
                        // Channel became active again, cancel the pending
12✔
580
                        // disable.
12✔
581
                        log.Debugf("Channel(%v) became active, canceling "+
12✔
582
                                "scheduled disable", outpoint)
12✔
583

12✔
584
                        m.chanStates.markEnabled(outpoint)
12✔
585

12✔
586
                        continue
12✔
587
                }
588

589
                log.Infof("Announcing channel(%v) disabled "+
13✔
590
                        "[detected]", outpoint)
13✔
591

13✔
592
                // Sign an update disabling the channel.
13✔
593
                err := m.signAndSendNextUpdate(outpoint, true)
13✔
594
                if err != nil {
13✔
UNCOV
595
                        log.Errorf("Unable to sign update disabling "+
×
UNCOV
596
                                "channel(%v): %v", outpoint, err)
×
UNCOV
597

×
UNCOV
598
                        // If the edge was not found, this is a likely indicator
×
UNCOV
599
                        // that the channel has been closed. Thus we remove the
×
UNCOV
600
                        // outpoint from the set of tracked outpoints to prevent
×
UNCOV
601
                        // further attempts.
×
UNCOV
602
                        if errors.Is(err, graphdb.ErrEdgeNotFound) {
×
UNCOV
603
                                log.Debugf("Removing channel(%v) from "+
×
UNCOV
604
                                        "consideration for passive disabling",
×
UNCOV
605
                                        outpoint)
×
UNCOV
606
                                delete(m.chanStates, outpoint)
×
UNCOV
607
                        }
×
608

UNCOV
609
                        continue
×
610
                }
611

612
                // Record that the channel has now been disabled.
613
                m.chanStates.markDisabled(outpoint)
13✔
614
        }
615
}
616

617
// fetchChannels returns the working set of channels managed by the
618
// ChanStatusManager. The returned channels are filtered to only contain public
619
// channels.
620
func (m *ChanStatusManager) fetchChannels() ([]*channeldb.OpenChannel, error) {
864✔
621
        allChannels, err := m.cfg.DB.FetchAllOpenChannels()
864✔
622
        if err != nil {
864✔
623
                return nil, err
×
624
        }
×
625

626
        // Filter out private channels.
627
        var channels []*channeldb.OpenChannel
864✔
628
        for _, c := range allChannels {
9,215✔
629
                // We'll skip any private channels, as they aren't used for
8,351✔
630
                // routing within the network by other nodes.
8,351✔
631
                if c.ChannelFlags&lnwire.FFAnnounceChannel == 0 {
8,354✔
632
                        continue
3✔
633
                }
634

635
                channels = append(channels, c)
8,351✔
636
        }
637

638
        return channels, nil
864✔
639
}
640

641
// signAndSendNextUpdate computes and signs a valid update for the passed
642
// outpoint, with the ability to toggle the disabled bit. The new update will
643
// use the current time as the update's timestamp, or increment the old
644
// timestamp by 1 to ensure the update can propagate. If signing is successful,
645
// the new update will be sent out on the network.
646
func (m *ChanStatusManager) signAndSendNextUpdate(outpoint wire.OutPoint,
647
        disabled bool) error {
103✔
648

103✔
649
        // Retrieve the latest update for this channel. We'll use this
103✔
650
        // as our starting point to send the new update.
103✔
651
        chanUpdate, private, err := m.fetchLastChanUpdateByOutPoint(outpoint)
103✔
652
        if err != nil {
108✔
653
                return err
5✔
654
        }
5✔
655

656
        err = SignChannelUpdate(
98✔
657
                m.cfg.MessageSigner, m.cfg.OurKeyLoc, chanUpdate,
98✔
658
                ChanUpdSetDisable(disabled), ChanUpdSetTimestamp,
98✔
659
        )
98✔
660
        if err != nil {
98✔
661
                return err
×
662
        }
×
663

664
        return m.cfg.ApplyChannelUpdate(chanUpdate, &outpoint, private)
98✔
665
}
666

667
// fetchLastChanUpdateByOutPoint fetches the latest policy for our direction of
668
// a channel, and crafts a new ChannelUpdate with this policy. Returns an error
669
// in case our ChannelEdgePolicy is not found in the database. Also returns if
670
// the channel is private by checking AuthProof for nil.
671
func (m *ChanStatusManager) fetchLastChanUpdateByOutPoint(op wire.OutPoint) (
672
        *lnwire.ChannelUpdate1, bool, error) {
442✔
673

442✔
674
        // Get the edge info and policies for this channel from the graph.
442✔
675
        info, edge1, edge2, err := m.cfg.Graph.FetchChannelEdgesByOutpoint(&op)
442✔
676
        if err != nil {
561✔
677
                return nil, false, err
119✔
678
        }
119✔
679

680
        update, err := ExtractChannelUpdate(
326✔
681
                m.ourPubKeyBytes, info, edge1, edge2,
326✔
682
        )
326✔
683
        return update, info.AuthProof == nil, err
326✔
684
}
685

686
// loadInitialChanState determines the initial ChannelState for a particular
687
// outpoint. The initial ChanStatus for a given outpoint will either be
688
// ChanStatusEnabled or ChanStatusDisabled, determined by inspecting the bits on
689
// the most recent announcement. An error is returned if the latest update could
690
// not be retrieved.
691
func (m *ChanStatusManager) loadInitialChanState(
692
        outpoint *wire.OutPoint) (ChannelState, error) {
342✔
693

342✔
694
        lastUpdate, _, err := m.fetchLastChanUpdateByOutPoint(*outpoint)
342✔
695
        if err != nil {
456✔
696
                return ChannelState{}, err
114✔
697
        }
114✔
698

699
        // Determine the channel's starting status by inspecting the disable bit
700
        // on last announcement we sent out.
701
        var initialStatus ChanStatus
231✔
702
        if lastUpdate.ChannelFlags&lnwire.ChanUpdateDisabled == 0 {
337✔
703
                initialStatus = ChanStatusEnabled
106✔
704
        } else {
234✔
705
                initialStatus = ChanStatusDisabled
128✔
706
        }
128✔
707

708
        return ChannelState{
231✔
709
                Status: initialStatus,
231✔
710
        }, nil
231✔
711
}
712

713
// getOrInitChanStatus retrieves the current ChannelState for a particular
714
// outpoint. If the chanStates map already contains an entry for the outpoint,
715
// the value in the map is returned. Otherwise, the outpoint's initial status is
716
// computed and updated in the chanStates map before being returned.
717
func (m *ChanStatusManager) getOrInitChanStatus(
718
        outpoint wire.OutPoint) (ChannelState, error) {
8,542✔
719

8,542✔
720
        // Return the current ChannelState from the chanStates map if it is
8,542✔
721
        // already known to the ChanStatusManager.
8,542✔
722
        if curState, ok := m.chanStates[outpoint]; ok {
16,745✔
723
                return curState, nil
8,203✔
724
        }
8,203✔
725

726
        // Otherwise, determine the initial state based on the last update we
727
        // sent for the outpoint.
728
        initialState, err := m.loadInitialChanState(&outpoint)
342✔
729
        if err != nil {
456✔
730
                return ChannelState{}, err
114✔
731
        }
114✔
732

733
        // Finally, store the initial state in the chanStates map. This will
734
        // serve as are up-to-date view of the outpoint's current status, in
735
        // addition to making the channel eligible for detecting inactivity.
736
        m.chanStates[outpoint] = initialState
231✔
737

231✔
738
        return initialState, nil
231✔
739
}
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