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

lightningnetwork / lnd / 17830307614

18 Sep 2025 01:29PM UTC coverage: 54.617% (-12.0%) from 66.637%
17830307614

Pull #10200

github

web-flow
Merge 181a0a7bc into b34fc964b
Pull Request #10200: github: change to form-based issue template

109249 of 200028 relevant lines covered (54.62%)

21896.43 hits per line

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

0.0
/discovery/chan_series.go
1
package discovery
2

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

7
        "github.com/btcsuite/btcd/chaincfg/chainhash"
8
        graphdb "github.com/lightningnetwork/lnd/graph/db"
9
        "github.com/lightningnetwork/lnd/lnwire"
10
        "github.com/lightningnetwork/lnd/netann"
11
        "github.com/lightningnetwork/lnd/routing/route"
12
)
13

14
// ChannelGraphTimeSeries is an interface that provides time and block based
15
// querying into our view of the channel graph. New channels will have
16
// monotonically increasing block heights, and new channel updates will have
17
// increasing timestamps. Once we connect to a peer, we'll use the methods in
18
// this interface to determine if we're already in sync, or need to request
19
// some new information from them.
20
type ChannelGraphTimeSeries interface {
21
        // HighestChanID should return the channel ID of the channel we know of
22
        // that's furthest in the target chain. This channel will have a block
23
        // height that's close to the current tip of the main chain as we
24
        // know it.  We'll use this to start our QueryChannelRange dance with
25
        // the remote node.
26
        HighestChanID(ctx context.Context,
27
                chain chainhash.Hash) (*lnwire.ShortChannelID, error)
28

29
        // UpdatesInHorizon returns all known channel and node updates with an
30
        // update timestamp between the start time and end time. We'll use this
31
        // to catch up a remote node to the set of channel updates that they
32
        // may have missed out on within the target chain.
33
        UpdatesInHorizon(chain chainhash.Hash,
34
                startTime time.Time, endTime time.Time) ([]lnwire.Message, error)
35

36
        // FilterKnownChanIDs takes a target chain, and a set of channel ID's,
37
        // and returns a filtered set of chan ID's. This filtered set of chan
38
        // ID's represents the ID's that we don't know of which were in the
39
        // passed superSet.
40
        FilterKnownChanIDs(chain chainhash.Hash,
41
                superSet []graphdb.ChannelUpdateInfo,
42
                isZombieChan func(time.Time, time.Time) bool) (
43
                []lnwire.ShortChannelID, error)
44

45
        // FilterChannelRange returns the set of channels that we created
46
        // between the start height and the end height. The channel IDs are
47
        // grouped by their common block height. We'll use this to to a remote
48
        // peer's QueryChannelRange message.
49
        FilterChannelRange(chain chainhash.Hash, startHeight, endHeight uint32,
50
                withTimestamps bool) ([]graphdb.BlockChannelRange, error)
51

52
        // FetchChanAnns returns a full set of channel announcements as well as
53
        // their updates that match the set of specified short channel ID's.
54
        // We'll use this to reply to a QueryShortChanIDs message sent by a
55
        // remote peer. The response will contain a unique set of
56
        // ChannelAnnouncements, the latest ChannelUpdate for each of the
57
        // announcements, and a unique set of NodeAnnouncements.
58
        FetchChanAnns(chain chainhash.Hash,
59
                shortChanIDs []lnwire.ShortChannelID) ([]lnwire.Message, error)
60

61
        // FetchChanUpdates returns the latest channel update messages for the
62
        // specified short channel ID. If no channel updates are known for the
63
        // channel, then an empty slice will be returned.
64
        FetchChanUpdates(chain chainhash.Hash,
65
                shortChanID lnwire.ShortChannelID) ([]*lnwire.ChannelUpdate1,
66
                error)
67
}
68

69
// ChanSeries is an implementation of the ChannelGraphTimeSeries
70
// interface backed by the channeldb ChannelGraph database. We'll provide this
71
// implementation to the AuthenticatedGossiper so it can properly use the
72
// in-protocol channel range queries to quickly and efficiently synchronize our
73
// channel state with all peers.
74
type ChanSeries struct {
75
        graph *graphdb.ChannelGraph
76
}
77

78
// NewChanSeries constructs a new ChanSeries backed by a channeldb.ChannelGraph.
79
// The returned ChanSeries implements the ChannelGraphTimeSeries interface.
80
func NewChanSeries(graph *graphdb.ChannelGraph) *ChanSeries {
×
81
        return &ChanSeries{
×
82
                graph: graph,
×
83
        }
×
84
}
×
85

86
// HighestChanID should return is the channel ID of the channel we know of
87
// that's furthest in the target chain. This channel will have a block height
88
// that's close to the current tip of the main chain as we know it.  We'll use
89
// this to start our QueryChannelRange dance with the remote node.
90
//
91
// NOTE: This is part of the ChannelGraphTimeSeries interface.
92
func (c *ChanSeries) HighestChanID(ctx context.Context,
93
        _ chainhash.Hash) (*lnwire.ShortChannelID, error) {
×
94

×
95
        chanID, err := c.graph.HighestChanID(ctx)
×
96
        if err != nil {
×
97
                return nil, err
×
98
        }
×
99

100
        shortChanID := lnwire.NewShortChanIDFromInt(chanID)
×
101
        return &shortChanID, nil
×
102
}
103

104
// UpdatesInHorizon returns all known channel and node updates with an update
105
// timestamp between the start time and end time. We'll use this to catch up a
106
// remote node to the set of channel updates that they may have missed out on
107
// within the target chain.
108
//
109
// NOTE: This is part of the ChannelGraphTimeSeries interface.
110
func (c *ChanSeries) UpdatesInHorizon(chain chainhash.Hash,
111
        startTime time.Time, endTime time.Time) ([]lnwire.Message, error) {
×
112

×
113
        var updates []lnwire.Message
×
114

×
115
        // First, we'll query for all the set of channels that have an update
×
116
        // that falls within the specified horizon.
×
117
        chansInHorizon, err := c.graph.ChanUpdatesInHorizon(
×
118
                startTime, endTime,
×
119
        )
×
120
        if err != nil {
×
121
                return nil, err
×
122
        }
×
123

124
        // nodesFromChan records the nodes seen from the channels.
125
        nodesFromChan := make(map[[33]byte]struct{}, len(chansInHorizon)*2)
×
126

×
127
        for _, channel := range chansInHorizon {
×
128
                // If the channel hasn't been fully advertised yet, or is a
×
129
                // private channel, then we'll skip it as we can't construct a
×
130
                // full authentication proof if one is requested.
×
131
                if channel.Info.AuthProof == nil {
×
132
                        continue
×
133
                }
134

135
                chanAnn, edge1, edge2, err := netann.CreateChanAnnouncement(
×
136
                        channel.Info.AuthProof, channel.Info, channel.Policy1,
×
137
                        channel.Policy2,
×
138
                )
×
139
                if err != nil {
×
140
                        return nil, err
×
141
                }
×
142

143
                // Create a slice to hold the `channel_announcement` and
144
                // potentially two `channel_update` msgs.
145
                //
146
                // NOTE: Based on BOLT7, if a channel_announcement has no
147
                // corresponding channel_updates, we must not send the
148
                // channel_announcement. Thus we use this slice to decide we
149
                // want to send this `channel_announcement` or not. By the end
150
                // of the operation, if the len of the slice is 1, we will not
151
                // send the `channel_announcement`. Otherwise, when sending the
152
                // msgs, the `channel_announcement` must be sent prior to any
153
                // corresponding `channel_update` or `node_annoucement`, that's
154
                // why we create a slice here to maintain the order.
155
                chanUpdates := make([]lnwire.Message, 0, 3)
×
156
                chanUpdates = append(chanUpdates, chanAnn)
×
157

×
158
                if edge1 != nil {
×
159
                        // We don't want to send channel updates that don't
×
160
                        // conform to the spec (anymore).
×
161
                        err := netann.ValidateChannelUpdateFields(0, edge1)
×
162
                        if err != nil {
×
163
                                log.Errorf("not sending invalid channel "+
×
164
                                        "update %v: %v", edge1, err)
×
165
                        } else {
×
166
                                chanUpdates = append(chanUpdates, edge1)
×
167
                        }
×
168
                }
169

170
                if edge2 != nil {
×
171
                        err := netann.ValidateChannelUpdateFields(0, edge2)
×
172
                        if err != nil {
×
173
                                log.Errorf("not sending invalid channel "+
×
174
                                        "update %v: %v", edge2, err)
×
175
                        } else {
×
176
                                chanUpdates = append(chanUpdates, edge2)
×
177
                        }
×
178
                }
179

180
                // If there's no corresponding `channel_update` to send, skip
181
                // sending this `channel_announcement`.
182
                if len(chanUpdates) < 2 {
×
183
                        continue
×
184
                }
185

186
                // Append the all the msgs to the slice.
187
                updates = append(updates, chanUpdates...)
×
188

×
189
                // Record the nodes seen.
×
190
                nodesFromChan[channel.Info.NodeKey1Bytes] = struct{}{}
×
191
                nodesFromChan[channel.Info.NodeKey2Bytes] = struct{}{}
×
192
        }
193

194
        // Next, we'll send out all the node announcements that have an update
195
        // within the horizon as well. We send these second to ensure that they
196
        // follow any active channels they have.
197
        nodeAnnsInHorizon, err := c.graph.NodeUpdatesInHorizon(
×
198
                startTime, endTime,
×
199
        )
×
200
        if err != nil {
×
201
                return nil, err
×
202
        }
×
203

204
        for _, nodeAnn := range nodeAnnsInHorizon {
×
205
                // If this node has not been seen in the above channels, we can
×
206
                // skip sending its NodeAnnouncement.
×
207
                if _, seen := nodesFromChan[nodeAnn.PubKeyBytes]; !seen {
×
208
                        log.Debugf("Skipping forwarding as node %x not found "+
×
209
                                "in channel announcement", nodeAnn.PubKeyBytes)
×
210
                        continue
×
211
                }
212

213
                // Ensure we only forward nodes that are publicly advertised to
214
                // prevent leaking information about nodes.
215
                isNodePublic, err := c.graph.IsPublicNode(nodeAnn.PubKeyBytes)
×
216
                if err != nil {
×
217
                        log.Errorf("Unable to determine if node %x is "+
×
218
                                "advertised: %v", nodeAnn.PubKeyBytes, err)
×
219
                        continue
×
220
                }
221

222
                if !isNodePublic {
×
223
                        log.Tracef("Skipping forwarding announcement for "+
×
224
                                "node %x due to being unadvertised",
×
225
                                nodeAnn.PubKeyBytes)
×
226
                        continue
×
227
                }
228

229
                nodeUpdate, err := nodeAnn.NodeAnnouncement(true)
×
230
                if err != nil {
×
231
                        return nil, err
×
232
                }
×
233

234
                if err := netann.ValidateNodeAnnFields(nodeUpdate); err != nil {
×
235
                        log.Debugf("Skipping forwarding invalid node "+
×
236
                                "announcement %x: %v", nodeAnn.PubKeyBytes, err)
×
237

×
238
                        continue
×
239
                }
240

241
                updates = append(updates, nodeUpdate)
×
242
        }
243

244
        return updates, nil
×
245
}
246

247
// FilterKnownChanIDs takes a target chain, and a set of channel ID's, and
248
// returns a filtered set of chan ID's. This filtered set of chan ID's
249
// represents the ID's that we don't know of which were in the passed superSet.
250
//
251
// NOTE: This is part of the ChannelGraphTimeSeries interface.
252
func (c *ChanSeries) FilterKnownChanIDs(_ chainhash.Hash,
253
        superSet []graphdb.ChannelUpdateInfo,
254
        isZombieChan func(time.Time, time.Time) bool) (
255
        []lnwire.ShortChannelID, error) {
×
256

×
257
        newChanIDs, err := c.graph.FilterKnownChanIDs(superSet, isZombieChan)
×
258
        if err != nil {
×
259
                return nil, err
×
260
        }
×
261

262
        filteredIDs := make([]lnwire.ShortChannelID, 0, len(newChanIDs))
×
263
        for _, chanID := range newChanIDs {
×
264
                filteredIDs = append(
×
265
                        filteredIDs, lnwire.NewShortChanIDFromInt(chanID),
×
266
                )
×
267
        }
×
268

269
        return filteredIDs, nil
×
270
}
271

272
// FilterChannelRange returns the set of channels that we created between the
273
// start height and the end height. The channel IDs are grouped by their common
274
// block height. We'll use this respond to a remote peer's QueryChannelRange
275
// message.
276
//
277
// NOTE: This is part of the ChannelGraphTimeSeries interface.
278
func (c *ChanSeries) FilterChannelRange(_ chainhash.Hash, startHeight,
279
        endHeight uint32, withTimestamps bool) ([]graphdb.BlockChannelRange,
280
        error) {
×
281

×
282
        return c.graph.FilterChannelRange(
×
283
                startHeight, endHeight, withTimestamps,
×
284
        )
×
285
}
×
286

287
// FetchChanAnns returns a full set of channel announcements as well as their
288
// updates that match the set of specified short channel ID's.  We'll use this
289
// to reply to a QueryShortChanIDs message sent by a remote peer. The response
290
// will contain a unique set of ChannelAnnouncements, the latest ChannelUpdate
291
// for each of the announcements, and a unique set of NodeAnnouncements.
292
// Invalid node announcements are skipped and logged for debugging purposes.
293
//
294
// NOTE: This is part of the ChannelGraphTimeSeries interface.
295
func (c *ChanSeries) FetchChanAnns(chain chainhash.Hash,
296
        shortChanIDs []lnwire.ShortChannelID) ([]lnwire.Message, error) {
×
297

×
298
        chanIDs := make([]uint64, 0, len(shortChanIDs))
×
299
        for _, chanID := range shortChanIDs {
×
300
                chanIDs = append(chanIDs, chanID.ToUint64())
×
301
        }
×
302

303
        channels, err := c.graph.FetchChanInfos(chanIDs)
×
304
        if err != nil {
×
305
                return nil, err
×
306
        }
×
307

308
        // We'll use this map to ensure we don't send the same node
309
        // announcement more than one time as one node may have many channel
310
        // anns we'll need to send.
311
        nodePubsSent := make(map[route.Vertex]struct{})
×
312

×
313
        chanAnns := make([]lnwire.Message, 0, len(channels)*3)
×
314
        for _, channel := range channels {
×
315
                // If the channel doesn't have an authentication proof, then we
×
316
                // won't send it over as it may not yet be finalized, or be a
×
317
                // non-advertised channel.
×
318
                if channel.Info.AuthProof == nil {
×
319
                        continue
×
320
                }
321

322
                chanAnn, edge1, edge2, err := netann.CreateChanAnnouncement(
×
323
                        channel.Info.AuthProof, channel.Info, channel.Policy1,
×
324
                        channel.Policy2,
×
325
                )
×
326
                if err != nil {
×
327
                        return nil, err
×
328
                }
×
329

330
                chanAnns = append(chanAnns, chanAnn)
×
331
                if edge1 != nil {
×
332
                        chanAnns = append(chanAnns, edge1)
×
333

×
334
                        // If this edge has a validated node announcement, that
×
335
                        // we haven't yet sent, then we'll send that as well.
×
336
                        nodePub := channel.Node2.PubKeyBytes
×
337
                        hasNodeAnn := channel.Node2.HaveNodeAnnouncement
×
338
                        if _, ok := nodePubsSent[nodePub]; !ok && hasNodeAnn {
×
339
                                nodeAnn, err := channel.Node2.NodeAnnouncement(
×
340
                                        true,
×
341
                                )
×
342
                                if err != nil {
×
343
                                        return nil, err
×
344
                                }
×
345

346
                                err = netann.ValidateNodeAnnFields(nodeAnn)
×
347
                                if err != nil {
×
348
                                        log.Debugf("Skipping forwarding "+
×
349
                                                "invalid node announcement "+
×
350
                                                "%x: %v", nodeAnn.NodeID, err)
×
351
                                } else {
×
352
                                        chanAnns = append(chanAnns, nodeAnn)
×
353
                                        nodePubsSent[nodePub] = struct{}{}
×
354
                                }
×
355
                        }
356
                }
357
                if edge2 != nil {
×
358
                        chanAnns = append(chanAnns, edge2)
×
359

×
360
                        // If this edge has a validated node announcement, that
×
361
                        // we haven't yet sent, then we'll send that as well.
×
362
                        nodePub := channel.Node1.PubKeyBytes
×
363
                        hasNodeAnn := channel.Node1.HaveNodeAnnouncement
×
364
                        if _, ok := nodePubsSent[nodePub]; !ok && hasNodeAnn {
×
365
                                nodeAnn, err := channel.Node1.NodeAnnouncement(
×
366
                                        true,
×
367
                                )
×
368
                                if err != nil {
×
369
                                        return nil, err
×
370
                                }
×
371

372
                                err = netann.ValidateNodeAnnFields(nodeAnn)
×
373
                                if err != nil {
×
374
                                        log.Debugf("Skipping forwarding "+
×
375
                                                "invalid node announcement "+
×
376
                                                "%x: %v", nodeAnn.NodeID, err)
×
377
                                } else {
×
378
                                        chanAnns = append(chanAnns, nodeAnn)
×
379
                                        nodePubsSent[nodePub] = struct{}{}
×
380
                                }
×
381
                        }
382
                }
383
        }
384

385
        return chanAnns, nil
×
386
}
387

388
// FetchChanUpdates returns the latest channel update messages for the
389
// specified short channel ID. If no channel updates are known for the channel,
390
// then an empty slice will be returned.
391
//
392
// NOTE: This is part of the ChannelGraphTimeSeries interface.
393
func (c *ChanSeries) FetchChanUpdates(chain chainhash.Hash,
394
        shortChanID lnwire.ShortChannelID) ([]*lnwire.ChannelUpdate1, error) {
×
395

×
396
        chanInfo, e1, e2, err := c.graph.FetchChannelEdgesByID(
×
397
                shortChanID.ToUint64(),
×
398
        )
×
399
        if err != nil {
×
400
                return nil, err
×
401
        }
×
402

403
        chanUpdates := make([]*lnwire.ChannelUpdate1, 0, 2)
×
404
        if e1 != nil {
×
405
                chanUpdate, err := netann.ChannelUpdateFromEdge(chanInfo, e1)
×
406
                if err != nil {
×
407
                        return nil, err
×
408
                }
×
409

410
                chanUpdates = append(chanUpdates, chanUpdate)
×
411
        }
412
        if e2 != nil {
×
413
                chanUpdate, err := netann.ChannelUpdateFromEdge(chanInfo, e2)
×
414
                if err != nil {
×
415
                        return nil, err
×
416
                }
×
417

418
                chanUpdates = append(chanUpdates, chanUpdate)
×
419
        }
420

421
        return chanUpdates, nil
×
422
}
423

424
// A compile-time assertion to ensure that ChanSeries meets the
425
// ChannelGraphTimeSeries interface.
426
var _ ChannelGraphTimeSeries = (*ChanSeries)(nil)
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