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

lightningnetwork / lnd / 13211764208

08 Feb 2025 03:08AM UTC coverage: 49.288% (-9.5%) from 58.815%
13211764208

Pull #9489

github

calvinrzachman
itest: verify switchrpc server enforces send then track

We prevent the rpc server from allowing onion dispatches for
attempt IDs which have already been tracked by rpc clients.

This helps protect the client from leaking a duplicate onion
attempt. NOTE: This is not the only method for solving this
issue! The issue could be addressed via careful client side
programming which accounts for the uncertainty and async
nature of dispatching onions to a remote process via RPC.
This would require some lnd ChannelRouter changes for how
we intend to use these RPCs though.
Pull Request #9489: multi: add BuildOnion, SendOnion, and TrackOnion RPCs

474 of 990 new or added lines in 11 files covered. (47.88%)

27321 existing lines in 435 files now uncovered.

101192 of 205306 relevant lines covered (49.29%)

1.54 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