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

lightningnetwork / lnd / 17132206455

21 Aug 2025 03:56PM UTC coverage: 54.685% (-2.6%) from 57.321%
17132206455

Pull #10167

github

web-flow
Merge 5dd2ed093 into 0c2f045f5
Pull Request #10167: multi: bump Go to 1.24.6

4 of 31 new or added lines in 10 files covered. (12.9%)

23854 existing lines in 284 files now uncovered.

108937 of 199210 relevant lines covered (54.68%)

22026.48 hits per line

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

92.86
/graph/db/graph_cache.go
1
package graphdb
2

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

7
        "github.com/btcsuite/btcd/btcutil"
8
        "github.com/lightningnetwork/lnd/graph/db/models"
9
        "github.com/lightningnetwork/lnd/lnwire"
10
        "github.com/lightningnetwork/lnd/routing/route"
11
)
12

13
// DirectedChannel is a type that stores the channel information as seen from
14
// one side of the channel.
15
type DirectedChannel struct {
16
        // ChannelID is the unique identifier of this channel.
17
        ChannelID uint64
18

19
        // IsNode1 indicates if this is the node with the smaller public key.
20
        IsNode1 bool
21

22
        // OtherNode is the public key of the node on the other end of this
23
        // channel.
24
        OtherNode route.Vertex
25

26
        // Capacity is the announced capacity of this channel in satoshis.
27
        Capacity btcutil.Amount
28

29
        // OutPolicySet is a boolean that indicates whether the node has an
30
        // outgoing policy set. For pathfinding only the existence of the policy
31
        // is important to know, not the actual content.
32
        OutPolicySet bool
33

34
        // InPolicy is the incoming policy *from* the other node to this node.
35
        // In path finding, we're walking backward from the destination to the
36
        // source, so we're always interested in the edge that arrives to us
37
        // from the other node.
38
        InPolicy *models.CachedEdgePolicy
39

40
        // Inbound fees of this node.
41
        InboundFee lnwire.Fee
42
}
43

44
// DeepCopy creates a deep copy of the channel, including the incoming policy.
45
func (c *DirectedChannel) DeepCopy() *DirectedChannel {
1,595✔
46
        channelCopy := *c
1,595✔
47

1,595✔
48
        if channelCopy.InPolicy != nil {
3,164✔
49
                inPolicyCopy := *channelCopy.InPolicy
1,569✔
50
                channelCopy.InPolicy = &inPolicyCopy
1,569✔
51

1,569✔
52
                // The fields for the ToNode can be overwritten by the path
1,569✔
53
                // finding algorithm, which is why we need a deep copy in the
1,569✔
54
                // first place. So we always start out with nil values, just to
1,569✔
55
                // be sure they don't contain any old data.
1,569✔
56
                channelCopy.InPolicy.ToNodePubKey = nil
1,569✔
57
                channelCopy.InPolicy.ToNodeFeatures = nil
1,569✔
58
        }
1,569✔
59

60
        return &channelCopy
1,595✔
61
}
62

63
// GraphCache is a type that holds a minimal set of information of the public
64
// channel graph that can be used for pathfinding.
65
type GraphCache struct {
66
        nodeChannels map[route.Vertex]map[uint64]*DirectedChannel
67
        nodeFeatures map[route.Vertex]*lnwire.FeatureVector
68

69
        mtx sync.RWMutex
70
}
71

72
// NewGraphCache creates a new graphCache.
73
func NewGraphCache(preAllocNumNodes int) *GraphCache {
140✔
74
        return &GraphCache{
140✔
75
                nodeChannels: make(
140✔
76
                        map[route.Vertex]map[uint64]*DirectedChannel,
140✔
77
                        // A channel connects two nodes, so we can look it up
140✔
78
                        // from both sides, meaning we get double the number of
140✔
79
                        // entries.
140✔
80
                        preAllocNumNodes*2,
140✔
81
                ),
140✔
82
                nodeFeatures: make(
140✔
83
                        map[route.Vertex]*lnwire.FeatureVector,
140✔
84
                        preAllocNumNodes,
140✔
85
                ),
140✔
86
        }
140✔
87
}
140✔
88

89
// Stats returns statistics about the current cache size.
90
func (c *GraphCache) Stats() string {
369✔
91
        c.mtx.RLock()
369✔
92
        defer c.mtx.RUnlock()
369✔
93

369✔
94
        numChannels := 0
369✔
95
        for node := range c.nodeChannels {
751✔
96
                numChannels += len(c.nodeChannels[node])
382✔
97
        }
382✔
98
        return fmt.Sprintf("num_node_features=%d, num_nodes=%d, "+
369✔
99
                "num_channels=%d", len(c.nodeFeatures), len(c.nodeChannels),
369✔
100
                numChannels)
369✔
101
}
102

103
// AddNodeFeatures adds a graph node and its features to the cache.
104
func (c *GraphCache) AddNodeFeatures(node route.Vertex,
105
        features *lnwire.FeatureVector) {
660✔
106

660✔
107
        c.mtx.Lock()
660✔
108
        defer c.mtx.Unlock()
660✔
109

660✔
110
        c.nodeFeatures[node] = features
660✔
111
}
660✔
112

113
// AddChannel adds a non-directed channel, meaning that the order of policy 1
114
// and policy 2 does not matter, the directionality is extracted from the info
115
// and policy flags automatically. The policy will be set as the outgoing policy
116
// on one node and the incoming policy on the peer's side.
117
func (c *GraphCache) AddChannel(info *models.CachedEdgeInfo,
118
        policy1, policy2 *models.CachedEdgePolicy) {
1,699✔
119

1,699✔
120
        if info == nil {
1,699✔
121
                return
×
122
        }
×
123

124
        if policy1 != nil && policy1.IsDisabled() &&
1,699✔
125
                policy2 != nil && policy2.IsDisabled() {
1,699✔
UNCOV
126

×
UNCOV
127
                return
×
UNCOV
128
        }
×
129

130
        // Create the edge entry for both nodes.
131
        c.mtx.Lock()
1,699✔
132
        c.updateOrAddEdge(info.NodeKey1Bytes, &DirectedChannel{
1,699✔
133
                ChannelID: info.ChannelID,
1,699✔
134
                IsNode1:   true,
1,699✔
135
                OtherNode: info.NodeKey2Bytes,
1,699✔
136
                Capacity:  info.Capacity,
1,699✔
137
        })
1,699✔
138
        c.updateOrAddEdge(info.NodeKey2Bytes, &DirectedChannel{
1,699✔
139
                ChannelID: info.ChannelID,
1,699✔
140
                IsNode1:   false,
1,699✔
141
                OtherNode: info.NodeKey1Bytes,
1,699✔
142
                Capacity:  info.Capacity,
1,699✔
143
        })
1,699✔
144
        c.mtx.Unlock()
1,699✔
145

1,699✔
146
        // The policy's node is always the to_node. So if policy 1 has to_node
1,699✔
147
        // of node 2 then we have the policy 1 as seen from node 1.
1,699✔
148
        if policy1 != nil {
2,097✔
149
                fromNode, toNode := info.NodeKey1Bytes, info.NodeKey2Bytes
398✔
150
                if !policy1.IsNode1() {
399✔
151
                        fromNode, toNode = toNode, fromNode
1✔
152
                }
1✔
153
                c.UpdatePolicy(policy1, fromNode, toNode)
398✔
154
        }
155
        if policy2 != nil {
2,097✔
156
                fromNode, toNode := info.NodeKey2Bytes, info.NodeKey1Bytes
398✔
157
                if policy2.IsNode1() {
399✔
158
                        fromNode, toNode = toNode, fromNode
1✔
159
                }
1✔
160
                c.UpdatePolicy(policy2, fromNode, toNode)
398✔
161
        }
162
}
163

164
// updateOrAddEdge makes sure the edge information for a node is either updated
165
// if it already exists or is added to that node's list of channels.
166
func (c *GraphCache) updateOrAddEdge(node route.Vertex, edge *DirectedChannel) {
3,398✔
167
        if len(c.nodeChannels[node]) == 0 {
4,247✔
168
                c.nodeChannels[node] = make(map[uint64]*DirectedChannel)
849✔
169
        }
849✔
170

171
        c.nodeChannels[node][edge.ChannelID] = edge
3,398✔
172
}
173

174
// UpdatePolicy updates a single policy on both the from and to node. The order
175
// of the from and to node is not strictly important. But we assume that a
176
// channel edge was added beforehand so that the directed channel struct already
177
// exists in the cache.
178
func (c *GraphCache) UpdatePolicy(policy *models.CachedEdgePolicy, fromNode,
179
        toNode route.Vertex) {
3,078✔
180

3,078✔
181
        c.mtx.Lock()
3,078✔
182
        defer c.mtx.Unlock()
3,078✔
183

3,078✔
184
        updatePolicy := func(nodeKey route.Vertex) {
9,234✔
185
                if len(c.nodeChannels[nodeKey]) == 0 {
6,156✔
186
                        log.Warnf("Node=%v not found in graph cache", nodeKey)
×
187

×
188
                        return
×
189
                }
×
190

191
                channel, ok := c.nodeChannels[nodeKey][policy.ChannelID]
6,156✔
192
                if !ok {
6,156✔
193
                        log.Warnf("Channel=%v not found in graph cache",
×
194
                                policy.ChannelID)
×
195

×
196
                        return
×
197
                }
×
198

199
                // Edge 1 is defined as the policy for the direction of node1 to
200
                // node2.
201
                switch {
6,156✔
202
                // This is node 1, and it is edge 1, so this is the outgoing
203
                // policy for node 1.
204
                case channel.IsNode1 && policy.IsNode1():
1,541✔
205
                        channel.OutPolicySet = true
1,541✔
206
                        policy.InboundFee.WhenSome(func(fee lnwire.Fee) {
1,703✔
207
                                channel.InboundFee = fee
162✔
208
                        })
162✔
209

210
                // This is node 2, and it is edge 2, so this is the outgoing
211
                // policy for node 2.
212
                case !channel.IsNode1 && !policy.IsNode1():
1,537✔
213
                        channel.OutPolicySet = true
1,537✔
214
                        policy.InboundFee.WhenSome(func(fee lnwire.Fee) {
1,698✔
215
                                channel.InboundFee = fee
161✔
216
                        })
161✔
217

218
                // The other two cases left mean it's the inbound policy for the
219
                // node.
220
                default:
3,078✔
221
                        channel.InPolicy = policy
3,078✔
222
                }
223
        }
224

225
        updatePolicy(fromNode)
3,078✔
226
        updatePolicy(toNode)
3,078✔
227
}
228

229
// RemoveNode completely removes a node and all its channels (including the
230
// peer's side).
231
func (c *GraphCache) RemoveNode(node route.Vertex) {
66✔
232
        c.mtx.Lock()
66✔
233
        defer c.mtx.Unlock()
66✔
234

66✔
235
        delete(c.nodeFeatures, node)
66✔
236

66✔
237
        // First remove all channels from the other nodes' lists.
66✔
238
        for _, channel := range c.nodeChannels[node] {
67✔
239
                c.removeChannelIfFound(channel.OtherNode, channel.ChannelID)
1✔
240
        }
1✔
241

242
        // Then remove our whole node completely.
243
        delete(c.nodeChannels, node)
66✔
244
}
245

246
// RemoveChannel removes a single channel between two nodes.
247
func (c *GraphCache) RemoveChannel(node1, node2 route.Vertex, chanID uint64) {
272✔
248
        c.mtx.Lock()
272✔
249
        defer c.mtx.Unlock()
272✔
250

272✔
251
        // Remove that one channel from both sides.
272✔
252
        c.removeChannelIfFound(node1, chanID)
272✔
253
        c.removeChannelIfFound(node2, chanID)
272✔
254
}
272✔
255

256
// removeChannelIfFound removes a single channel from one side.
257
func (c *GraphCache) removeChannelIfFound(node route.Vertex, chanID uint64) {
545✔
258
        if len(c.nodeChannels[node]) == 0 {
721✔
259
                return
176✔
260
        }
176✔
261

262
        delete(c.nodeChannels[node], chanID)
369✔
263
}
264

265
// getChannels returns a copy of the passed node's channels or nil if there
266
// isn't any.
267
func (c *GraphCache) getChannels(node route.Vertex) []*DirectedChannel {
546✔
268
        c.mtx.RLock()
546✔
269
        defer c.mtx.RUnlock()
546✔
270

546✔
271
        channels, ok := c.nodeChannels[node]
546✔
272
        if !ok {
553✔
273
                return nil
7✔
274
        }
7✔
275

276
        features, ok := c.nodeFeatures[node]
539✔
277
        if !ok {
691✔
278
                // If the features were set to nil explicitly, that's fine here.
152✔
279
                // The router will overwrite the features of the destination
152✔
280
                // node with those found in the invoice if necessary. But if we
152✔
281
                // didn't yet get a node announcement we want to mimic the
152✔
282
                // behavior of the old DB based code that would always set an
152✔
283
                // empty feature vector instead of leaving it nil.
152✔
284
                features = lnwire.EmptyFeatureVector()
152✔
285
        }
152✔
286

287
        toNodeCallback := func() route.Vertex {
987✔
288
                return node
448✔
289
        }
448✔
290

291
        i := 0
539✔
292
        channelsCopy := make([]*DirectedChannel, len(channels))
539✔
293
        for _, channel := range channels {
2,130✔
294
                // We need to copy the channel and policy to avoid it being
1,591✔
295
                // updated in the cache if the path finding algorithm sets
1,591✔
296
                // fields on it (currently only the ToNodeFeatures of the
1,591✔
297
                // policy).
1,591✔
298
                channelCopy := channel.DeepCopy()
1,591✔
299
                if channelCopy.InPolicy != nil {
3,157✔
300
                        channelCopy.InPolicy.ToNodePubKey = toNodeCallback
1,566✔
301
                        channelCopy.InPolicy.ToNodeFeatures = features
1,566✔
302
                }
1,566✔
303

304
                channelsCopy[i] = channelCopy
1,591✔
305
                i++
1,591✔
306
        }
307

308
        return channelsCopy
539✔
309
}
310

311
// ForEachChannel invokes the given callback for each channel of the given node.
312
func (c *GraphCache) ForEachChannel(node route.Vertex,
313
        cb func(channel *DirectedChannel) error) error {
546✔
314

546✔
315
        // Obtain a copy of the node's channels. We need do this in order to
546✔
316
        // avoid deadlocks caused by interaction with the graph cache, channel
546✔
317
        // state and the graph database from multiple goroutines. This snapshot
546✔
318
        // is only used for path finding where being stale is acceptable since
546✔
319
        // the real world graph and our representation may always become
546✔
320
        // slightly out of sync for a short time and the actual channel state
546✔
321
        // is stored separately.
546✔
322
        channels := c.getChannels(node)
546✔
323
        for _, channel := range channels {
2,120✔
324
                if err := cb(channel); err != nil {
1,585✔
325
                        return err
11✔
326
                }
11✔
327
        }
328

329
        return nil
535✔
330
}
331

332
// ForEachNode iterates over the adjacency list of the graph, executing the
333
// call back for each node and the set of channels that emanate from the given
334
// node.
335
//
336
// NOTE: This method should be considered _read only_, the channels or nodes
337
// passed in MUST NOT be modified.
338
func (c *GraphCache) ForEachNode(cb func(node route.Vertex,
339
        channels map[uint64]*DirectedChannel) error) error {
2✔
340

2✔
341
        c.mtx.RLock()
2✔
342
        defer c.mtx.RUnlock()
2✔
343

2✔
344
        for node, channels := range c.nodeChannels {
6✔
345
                // We don't make a copy here since this is a read-only RPC
4✔
346
                // call. We also don't need the node features either for this
4✔
347
                // call.
4✔
348
                if err := cb(node, channels); err != nil {
4✔
349
                        return err
×
350
                }
×
351
        }
352

353
        return nil
2✔
354
}
355

356
// GetFeatures returns the features of the node with the given ID. If no
357
// features are known for the node, an empty feature vector is returned.
358
func (c *GraphCache) GetFeatures(node route.Vertex) *lnwire.FeatureVector {
469✔
359
        c.mtx.RLock()
469✔
360
        defer c.mtx.RUnlock()
469✔
361

469✔
362
        features, ok := c.nodeFeatures[node]
469✔
363
        if !ok || features == nil {
549✔
364
                // The router expects the features to never be nil, so we return
80✔
365
                // an empty feature set instead.
80✔
366
                return lnwire.EmptyFeatureVector()
80✔
367
        }
80✔
368

369
        return features
389✔
370
}
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