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

lightningnetwork / lnd / 15736109134

18 Jun 2025 02:46PM UTC coverage: 58.197% (-10.1%) from 68.248%
15736109134

Pull #9752

github

web-flow
Merge d2634a68c into 31c74f20f
Pull Request #9752: routerrpc: reject payment to invoice that don't have payment secret or blinded paths

6 of 13 new or added lines in 2 files covered. (46.15%)

28331 existing lines in 455 files now uncovered.

97860 of 168153 relevant lines covered (58.2%)

1.81 hits per line

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

89.62
/chanfitness/chanevent.go
1
package chanfitness
2

3
import (
4
        "fmt"
5
        "time"
6

7
        "github.com/btcsuite/btcd/wire"
8
        "github.com/lightningnetwork/lnd/clock"
9
)
10

11
type eventType int
12

13
const (
14
        peerOnlineEvent eventType = iota
15
        peerOfflineEvent
16
)
17

18
// String provides string representations of channel events.
19
func (e eventType) String() string {
×
20
        switch e {
×
21
        case peerOnlineEvent:
×
22
                return "peer_online"
×
23

24
        case peerOfflineEvent:
×
25
                return "peer_offline"
×
26
        }
27

28
        return "unknown"
×
29
}
30

31
type event struct {
32
        timestamp time.Time
33
        eventType eventType
34
}
35

36
// peerLog tracks events for a peer and its channels. If we currently have no
37
// channels with the peer, it will simply track its current online state. If we
38
// do have channels open with the peer, it will track the peer's online and
39
// offline events so that we can calculate uptime for our channels. A single
40
// event log is used for these online and offline events, and uptime for a
41
// channel is calculated by examining a subsection of this log.
42
type peerLog struct {
43
        // online stores whether the peer is currently online.
44
        online bool
45

46
        // onlineEvents is a log of timestamped events observed for the peer
47
        // that we have committed to allocating memory to.
48
        onlineEvents []*event
49

50
        // stagedEvent represents an event that is pending addition to the
51
        // events list. It has not yet been added because we rate limit the
52
        // frequency that we store events at. We need to store this value
53
        // in the log (rather than just ignore events) so that we can flush the
54
        // aggregate outcome to our event log once the rate limiting period has
55
        // ended.
56
        //
57
        // Take the following example:
58
        // - Peer online event recorded
59
        // - Peer offline event, not recorded due to rate limit
60
        // - No more events, we incorrectly believe our peer to be online
61
        // Instead of skipping events, we stage the most recent event during the
62
        // rate limited period so that we know what happened (on aggregate)
63
        // while we were rate limiting events.
64
        //
65
        // Note that we currently only store offline/online events so we can
66
        // use this field to track our online state. With the addition of other
67
        // event types, we need to only stage online/offline events, or split
68
        // them out.
69
        stagedEvent *event
70

71
        // flapCount is the number of times this peer has been observed as
72
        // going offline.
73
        flapCount int
74

75
        // lastFlap is the timestamp of the last flap we recorded for the peer.
76
        // This value will be nil if we have never recorded a flap for the peer.
77
        lastFlap *time.Time
78

79
        // clock allows creation of deterministic unit tests.
80
        clock clock.Clock
81

82
        // channels contains a set of currently open channels. Channels will be
83
        // added and removed from this map as they are opened and closed.
84
        channels map[wire.OutPoint]*channelInfo
85
}
86

87
// newPeerLog creates a log for a peer, taking its historical flap count and
88
// last flap time as parameters. These values may be zero/nil if we have no
89
// record of historical flap count for the peer.
90
func newPeerLog(clock clock.Clock, flapCount int,
91
        lastFlap *time.Time) *peerLog {
3✔
92

3✔
93
        return &peerLog{
3✔
94
                clock:     clock,
3✔
95
                flapCount: flapCount,
3✔
96
                lastFlap:  lastFlap,
3✔
97
                channels:  make(map[wire.OutPoint]*channelInfo),
3✔
98
        }
3✔
99
}
3✔
100

101
// channelInfo contains information about a channel.
102
type channelInfo struct {
103
        // openedAt tracks the first time this channel was seen. This is not
104
        // necessarily the time that it confirmed on chain because channel
105
        // events are not persisted at present.
106
        openedAt time.Time
107
}
108

109
func newChannelInfo(openedAt time.Time) *channelInfo {
3✔
110
        return &channelInfo{
3✔
111
                openedAt: openedAt,
3✔
112
        }
3✔
113
}
3✔
114

115
// onlineEvent records a peer online or offline event in the log and increments
116
// the peer's flap count.
117
func (p *peerLog) onlineEvent(online bool) {
3✔
118
        eventTime := p.clock.Now()
3✔
119

3✔
120
        // If we have a non-nil last flap time, potentially apply a cooldown
3✔
121
        // factor to the peer's flap count before we rate limit it. This allows
3✔
122
        // us to decrease the penalty for historical flaps over time, provided
3✔
123
        // the peer has not flapped for a while.
3✔
124
        if p.lastFlap != nil {
6✔
125
                p.flapCount = cooldownFlapCount(
3✔
126
                        p.clock.Now(), p.flapCount, *p.lastFlap,
3✔
127
                )
3✔
128
        }
3✔
129

130
        // Record flap count information and online state regardless of whether
131
        // we have any channels open with this peer.
132
        p.flapCount++
3✔
133
        p.lastFlap = &eventTime
3✔
134
        p.online = online
3✔
135

3✔
136
        // If we have no channels currently open with the peer, we do not want
3✔
137
        // to commit resources to tracking their online state beyond a simple
3✔
138
        // online boolean, so we exit early.
3✔
139
        if p.channelCount() == 0 {
6✔
140
                return
3✔
141
        }
3✔
142

143
        p.addEvent(online, eventTime)
3✔
144
}
145

146
// addEvent records an online or offline event in our event log. and increments
147
// the peer's flap count.
148
func (p *peerLog) addEvent(online bool, time time.Time) {
3✔
149
        eventType := peerOnlineEvent
3✔
150
        if !online {
6✔
151
                eventType = peerOfflineEvent
3✔
152
        }
3✔
153

154
        event := &event{
3✔
155
                timestamp: time,
3✔
156
                eventType: eventType,
3✔
157
        }
3✔
158

3✔
159
        // If we have no staged events, we can just stage this event and return.
3✔
160
        if p.stagedEvent == nil {
6✔
161
                p.stagedEvent = event
3✔
162
                return
3✔
163
        }
3✔
164

165
        // We get the amount of time we require between events according to
166
        // peer flap count.
167
        aggregation := getRateLimit(p.flapCount)
3✔
168
        nextRecordTime := p.stagedEvent.timestamp.Add(aggregation)
3✔
169
        flushEvent := nextRecordTime.Before(event.timestamp)
3✔
170

3✔
171
        // If enough time has passed since our last staged event, we add our
3✔
172
        // event to our in-memory list.
3✔
173
        if flushEvent {
6✔
174
                p.onlineEvents = append(p.onlineEvents, p.stagedEvent)
3✔
175
        }
3✔
176

177
        // Finally, we replace our staged event with the new event we received.
178
        p.stagedEvent = event
3✔
179
}
180

181
// addChannel adds a channel to our log. If we have not tracked any online
182
// events for our peer yet, we create one with our peer's current online state
183
// so that we know the state that the peer had at channel start, which is
184
// required to calculate uptime over the channel's lifetime.
185
func (p *peerLog) addChannel(channelPoint wire.OutPoint) error {
3✔
186
        _, ok := p.channels[channelPoint]
3✔
187
        if ok {
3✔
UNCOV
188
                return fmt.Errorf("channel: %v already present", channelPoint)
×
UNCOV
189
        }
×
190

191
        openTime := p.clock.Now()
3✔
192
        p.channels[channelPoint] = newChannelInfo(openTime)
3✔
193

3✔
194
        // If we do not have any online events tracked for our peer (which is
3✔
195
        // the case when we have no other channels open with the peer), we add
3✔
196
        // an event with the peer's current online state so that we know that
3✔
197
        // starting state for this peer when a channel was connected (which
3✔
198
        // allows us to calculate uptime over the lifetime of the channel).
3✔
199
        if len(p.onlineEvents) == 0 {
6✔
200
                p.addEvent(p.online, openTime)
3✔
201
        }
3✔
202

203
        return nil
3✔
204
}
205

206
// removeChannel removes a channel from our log. If we have no more channels
207
// with the peer after removing this one, we clear our list of events.
208
func (p *peerLog) removeChannel(channelPoint wire.OutPoint) error {
3✔
209
        _, ok := p.channels[channelPoint]
3✔
210
        if !ok {
6✔
211
                return fmt.Errorf("channel: %v not present", channelPoint)
3✔
212
        }
3✔
213

214
        delete(p.channels, channelPoint)
3✔
215

3✔
216
        // If we have no more channels in our event log, we can discard all of
3✔
217
        // our online events in memory, since we don't need them anymore.
3✔
218
        // TODO(carla): this could be done on a per channel basis.
3✔
219
        if p.channelCount() == 0 {
6✔
220
                p.onlineEvents = nil
3✔
221
                p.stagedEvent = nil
3✔
222
        }
3✔
223

224
        return nil
3✔
225
}
226

227
// channelCount returns the number of channels that we currently have
228
// with the peer.
229
func (p *peerLog) channelCount() int {
3✔
230
        return len(p.channels)
3✔
231
}
3✔
232

233
// channelUptime looks up a channel and returns the amount of time that the
234
// channel has been monitored for and its uptime over this period.
235
func (p *peerLog) channelUptime(channelPoint wire.OutPoint) (time.Duration,
236
        time.Duration, error) {
3✔
237

3✔
238
        channel, ok := p.channels[channelPoint]
3✔
239
        if !ok {
6✔
240
                return 0, 0, ErrChannelNotFound
3✔
241
        }
3✔
242

243
        now := p.clock.Now()
3✔
244

3✔
245
        uptime, err := p.uptime(channel.openedAt, now)
3✔
246
        if err != nil {
3✔
247
                return 0, 0, err
×
248
        }
×
249

250
        return now.Sub(channel.openedAt), uptime, nil
3✔
251
}
252

253
// getFlapCount returns the peer's flap count and the timestamp that we last
254
// recorded a flap.
255
func (p *peerLog) getFlapCount() (int, *time.Time) {
3✔
256
        return p.flapCount, p.lastFlap
3✔
257
}
3✔
258

259
// listEvents returns all of the events that our event log has tracked,
260
// including events that are staged for addition to our set of events but have
261
// not yet been committed to (because we rate limit and store only the aggregate
262
// outcome over a period).
263
func (p *peerLog) listEvents() []*event {
3✔
264
        if p.stagedEvent == nil {
3✔
UNCOV
265
                return p.onlineEvents
×
UNCOV
266
        }
×
267

268
        return append(p.onlineEvents, p.stagedEvent)
3✔
269
}
270

271
// onlinePeriod represents a period of time over which a peer was online.
272
type onlinePeriod struct {
273
        start, end time.Time
274
}
275

276
// getOnlinePeriods returns a list of all the periods that the event log has
277
// recorded the remote peer as being online. In the unexpected case where there
278
// are no events, the function returns early. Online periods are defined as a
279
// peer online event which is terminated by a peer offline event. If the event
280
// log ends on a peer online event, it appends a final period which is
281
// calculated until the present. This function expects the event log provided
282
// to be ordered by ascending timestamp, and can tolerate multiple consecutive
283
// online or offline events.
284
func (p *peerLog) getOnlinePeriods() []*onlinePeriod {
3✔
285
        events := p.listEvents()
3✔
286

3✔
287
        // Return early if there are no events, there are no online periods.
3✔
288
        if len(events) == 0 {
3✔
UNCOV
289
                return nil
×
UNCOV
290
        }
×
291

292
        var (
3✔
293
                // lastEvent tracks the last event that we had that was of
3✔
294
                // a different type to our own. It is used to determine the
3✔
295
                // start time of our online periods when we experience an
3✔
296
                // offline event, and to track our last recorded state.
3✔
297
                lastEvent     *event
3✔
298
                onlinePeriods []*onlinePeriod
3✔
299
        )
3✔
300

3✔
301
        // Loop through all events to build a list of periods that the peer was
3✔
302
        // online. Online periods are added when they are terminated with a peer
3✔
303
        // offline event. If the log ends on an online event, the period between
3✔
304
        // the online event and the present is not tracked. The type of the most
3✔
305
        // recent event is tracked using the offline bool so that we can add a
3✔
306
        // final online period if necessary.
3✔
307
        for _, event := range events {
6✔
308
                switch event.eventType {
3✔
309
                case peerOnlineEvent:
3✔
310
                        // If our previous event is nil, we just set it and
3✔
311
                        // break out of the switch.
3✔
312
                        if lastEvent == nil {
6✔
313
                                lastEvent = event
3✔
314
                                break
3✔
315
                        }
316

317
                        // If our previous event was an offline event, we update
318
                        // it to this event. We do not do this if it was an
319
                        // online event because duplicate online events would
320
                        // progress our online timestamp forward (rather than
321
                        // keep it at our earliest online event timestamp).
322
                        if lastEvent.eventType == peerOfflineEvent {
6✔
323
                                lastEvent = event
3✔
324
                        }
3✔
325

326
                case peerOfflineEvent:
3✔
327
                        // If our previous event is nil, we just set it and
3✔
328
                        // break out of the switch since we cannot record an
3✔
329
                        // online period from this single event.
3✔
330
                        if lastEvent == nil {
6✔
331
                                lastEvent = event
3✔
332
                                break
3✔
333
                        }
334

335
                        // If the last event we saw was an online event, we
336
                        // add an online period to our set and progress our
337
                        // previous event to this offline event. We do not
338
                        // do this if we have had duplicate offline events
339
                        // because we would be tracking the most recent offline
340
                        // event (rather than keep it at our earliest offline
341
                        // event timestamp).
342
                        if lastEvent.eventType == peerOnlineEvent {
6✔
343
                                onlinePeriods = append(
3✔
344
                                        onlinePeriods, &onlinePeriod{
3✔
345
                                                start: lastEvent.timestamp,
3✔
346
                                                end:   event.timestamp,
3✔
347
                                        },
3✔
348
                                )
3✔
349

3✔
350
                                lastEvent = event
3✔
351
                        }
3✔
352
                }
353
        }
354

355
        // If the last event was an peer offline event, we do not need to
356
        // calculate a final online period and can return online periods as is.
357
        if lastEvent.eventType == peerOfflineEvent {
6✔
358
                return onlinePeriods
3✔
359
        }
3✔
360

361
        // The log ended on an online event, so we need to add a final online
362
        // period which terminates at the present.
363
        finalEvent := &onlinePeriod{
3✔
364
                start: lastEvent.timestamp,
3✔
365
                end:   p.clock.Now(),
3✔
366
        }
3✔
367

3✔
368
        // Add the final online period to the set and return.
3✔
369
        return append(onlinePeriods, finalEvent)
3✔
370
}
371

372
// uptime calculates the total uptime we have recorded for a peer over the
373
// inclusive range specified. An error is returned if the end of the range is
374
// before the start or a zero end time is returned.
375
func (p *peerLog) uptime(start, end time.Time) (time.Duration, error) {
3✔
376
        // Error if we are provided with an invalid range to calculate uptime
3✔
377
        // for.
3✔
378
        if end.Before(start) {
3✔
UNCOV
379
                return 0, fmt.Errorf("end time: %v before start time: %v",
×
UNCOV
380
                        end, start)
×
UNCOV
381
        }
×
382
        if end.IsZero() {
3✔
UNCOV
383
                return 0, fmt.Errorf("zero end time")
×
UNCOV
384
        }
×
385

386
        var uptime time.Duration
3✔
387

3✔
388
        for _, p := range p.getOnlinePeriods() {
6✔
389
                // The online period ends before the range we're looking at, so
3✔
390
                // we can skip over it.
3✔
391
                if p.end.Before(start) {
3✔
392
                        continue
×
393
                }
394
                // The online period starts after the range we're looking at, so
395
                // can stop calculating uptime.
396
                if p.start.After(end) {
3✔
397
                        break
×
398
                }
399

400
                // If the online period starts before our range, shift the start
401
                // time up so that we only calculate uptime from the start of
402
                // our range.
403
                if p.start.Before(start) {
6✔
404
                        p.start = start
3✔
405
                }
3✔
406

407
                // If the online period ends after our range, shift the end
408
                // time forward so that we only calculate uptime until the end
409
                // of the range.
410
                if p.end.After(end) {
6✔
411
                        p.end = end
3✔
412
                }
3✔
413

414
                uptime += p.end.Sub(p.start)
3✔
415
        }
416

417
        return uptime, nil
3✔
418
}
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