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

lightningnetwork / lnd / 13396663489

18 Feb 2025 05:34PM UTC coverage: 58.636% (-0.2%) from 58.804%
13396663489

push

github

web-flow
Merge pull request #9525 from ellemouton/graph10

graph: Restrict interface to update channel proof instead of entire channel

9 of 10 new or added lines in 2 files covered. (90.0%)

446 existing lines in 31 files now uncovered.

135736 of 231488 relevant lines covered (58.64%)

19383.26 hits per line

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

83.0
/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,338✔
46
        channelCopy := *c
1,338✔
47

1,338✔
48
        if channelCopy.InPolicy != nil {
2,650✔
49
                inPolicyCopy := *channelCopy.InPolicy
1,312✔
50
                channelCopy.InPolicy = &inPolicyCopy
1,312✔
51

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

60
        return &channelCopy
1,338✔
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 {
144✔
74
        return &GraphCache{
144✔
75
                nodeChannels: make(
144✔
76
                        map[route.Vertex]map[uint64]*DirectedChannel,
144✔
77
                        // A channel connects two nodes, so we can look it up
144✔
78
                        // from both sides, meaning we get double the number of
144✔
79
                        // entries.
144✔
80
                        preAllocNumNodes*2,
144✔
81
                ),
144✔
82
                nodeFeatures: make(
144✔
83
                        map[route.Vertex]*lnwire.FeatureVector,
144✔
84
                        preAllocNumNodes,
144✔
85
                ),
144✔
86
        }
144✔
87
}
144✔
88

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

388✔
94
        numChannels := 0
388✔
95
        for node := range c.nodeChannels {
784✔
96
                numChannels += len(c.nodeChannels[node])
396✔
97
        }
396✔
98
        return fmt.Sprintf("num_node_features=%d, num_nodes=%d, "+
388✔
99
                "num_channels=%d", len(c.nodeFeatures), len(c.nodeChannels),
388✔
100
                numChannels)
388✔
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) {
717✔
106

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

717✔
110
        c.nodeFeatures[node] = features
717✔
111
}
717✔
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.ChannelEdgeInfo,
118
        policy1 *models.ChannelEdgePolicy, policy2 *models.ChannelEdgePolicy) {
1,687✔
119

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

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

2✔
127
                return
2✔
128
        }
2✔
129

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

1,687✔
146
        // The policy's node is always the to_node. So if policy 1 has to_node
1,687✔
147
        // of node 2 then we have the policy 1 as seen from node 1.
1,687✔
148
        if policy1 != nil {
2,087✔
149
                fromNode, toNode := info.NodeKey1Bytes, info.NodeKey2Bytes
400✔
150
                if policy1.ToNode != info.NodeKey2Bytes {
401✔
151
                        fromNode, toNode = toNode, fromNode
1✔
152
                }
1✔
153
                isEdge1 := policy1.ChannelFlags&lnwire.ChanUpdateDirection == 0
400✔
154
                c.UpdatePolicy(policy1, fromNode, toNode, isEdge1)
400✔
155
        }
156
        if policy2 != nil {
2,087✔
157
                fromNode, toNode := info.NodeKey2Bytes, info.NodeKey1Bytes
400✔
158
                if policy2.ToNode != info.NodeKey1Bytes {
401✔
159
                        fromNode, toNode = toNode, fromNode
1✔
160
                }
1✔
161
                isEdge1 := policy2.ChannelFlags&lnwire.ChanUpdateDirection == 0
400✔
162
                c.UpdatePolicy(policy2, fromNode, toNode, isEdge1)
400✔
163
        }
164
}
165

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

173
        c.nodeChannels[node][edge.ChannelID] = edge
3,372✔
174
}
175

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

3,070✔
183
        // Extract inbound fee if possible and available. If there is a decoding
3,070✔
184
        // error, ignore this policy.
3,070✔
185
        var inboundFee lnwire.Fee
3,070✔
186
        _, err := policy.ExtraOpaqueData.ExtractRecords(&inboundFee)
3,070✔
187
        if err != nil {
3,070✔
188
                log.Errorf("Failed to extract records from edge policy %v: %v",
×
189
                        policy.ChannelID, err)
×
190

×
191
                return
×
192
        }
×
193

194
        c.mtx.Lock()
3,070✔
195
        defer c.mtx.Unlock()
3,070✔
196

3,070✔
197
        updatePolicy := func(nodeKey route.Vertex) {
9,208✔
198
                if len(c.nodeChannels[nodeKey]) == 0 {
6,138✔
199
                        log.Warnf("Node=%v not found in graph cache", nodeKey)
×
200

×
201
                        return
×
202
                }
×
203

204
                channel, ok := c.nodeChannels[nodeKey][policy.ChannelID]
6,138✔
205
                if !ok {
6,138✔
206
                        log.Warnf("Channel=%v not found in graph cache",
×
207
                                policy.ChannelID)
×
208

×
209
                        return
×
210
                }
×
211

212
                // Edge 1 is defined as the policy for the direction of node1 to
213
                // node2.
214
                switch {
6,138✔
215
                // This is node 1, and it is edge 1, so this is the outgoing
216
                // policy for node 1.
217
                case channel.IsNode1 && edge1:
1,539✔
218
                        channel.OutPolicySet = true
1,539✔
219
                        channel.InboundFee = inboundFee
1,539✔
220

221
                // This is node 2, and it is edge 2, so this is the outgoing
222
                // policy for node 2.
223
                case !channel.IsNode1 && !edge1:
1,533✔
224
                        channel.OutPolicySet = true
1,533✔
225
                        channel.InboundFee = inboundFee
1,533✔
226

227
                // The other two cases left mean it's the inbound policy for the
228
                // node.
229
                default:
3,070✔
230
                        channel.InPolicy = models.NewCachedPolicy(policy)
3,070✔
231
                }
232
        }
233

234
        updatePolicy(fromNode)
3,070✔
235
        updatePolicy(toNode)
3,070✔
236
}
237

238
// RemoveNode completely removes a node and all its channels (including the
239
// peer's side).
240
func (c *GraphCache) RemoveNode(node route.Vertex) {
73✔
241
        c.mtx.Lock()
73✔
242
        defer c.mtx.Unlock()
73✔
243

73✔
244
        delete(c.nodeFeatures, node)
73✔
245

73✔
246
        // First remove all channels from the other nodes' lists.
73✔
247
        for _, channel := range c.nodeChannels[node] {
73✔
248
                c.removeChannelIfFound(channel.OtherNode, channel.ChannelID)
×
249
        }
×
250

251
        // Then remove our whole node completely.
252
        delete(c.nodeChannels, node)
73✔
253
}
254

255
// RemoveChannel removes a single channel between two nodes.
256
func (c *GraphCache) RemoveChannel(node1, node2 route.Vertex, chanID uint64) {
266✔
257
        c.mtx.Lock()
266✔
258
        defer c.mtx.Unlock()
266✔
259

266✔
260
        // Remove that one channel from both sides.
266✔
261
        c.removeChannelIfFound(node1, chanID)
266✔
262
        c.removeChannelIfFound(node2, chanID)
266✔
263
}
266✔
264

265
// removeChannelIfFound removes a single channel from one side.
266
func (c *GraphCache) removeChannelIfFound(node route.Vertex, chanID uint64) {
530✔
267
        if len(c.nodeChannels[node]) == 0 {
708✔
268
                return
178✔
269
        }
178✔
270

271
        delete(c.nodeChannels[node], chanID)
354✔
272
}
273

274
// UpdateChannel updates the channel edge information for a specific edge. We
275
// expect the edge to already exist and be known. If it does not yet exist, this
276
// call is a no-op.
UNCOV
277
func (c *GraphCache) UpdateChannel(info *models.ChannelEdgeInfo) {
×
UNCOV
278
        c.mtx.Lock()
×
UNCOV
279
        defer c.mtx.Unlock()
×
UNCOV
280

×
UNCOV
281
        if len(c.nodeChannels[info.NodeKey1Bytes]) == 0 ||
×
UNCOV
282
                len(c.nodeChannels[info.NodeKey2Bytes]) == 0 {
×
283

×
284
                return
×
285
        }
×
286

UNCOV
287
        channel, ok := c.nodeChannels[info.NodeKey1Bytes][info.ChannelID]
×
UNCOV
288
        if ok {
×
UNCOV
289
                // We only expect to be called when the channel is already
×
UNCOV
290
                // known.
×
UNCOV
291
                channel.Capacity = info.Capacity
×
UNCOV
292
                channel.OtherNode = info.NodeKey2Bytes
×
UNCOV
293
        }
×
294

UNCOV
295
        channel, ok = c.nodeChannels[info.NodeKey2Bytes][info.ChannelID]
×
UNCOV
296
        if ok {
×
UNCOV
297
                channel.Capacity = info.Capacity
×
UNCOV
298
                channel.OtherNode = info.NodeKey1Bytes
×
UNCOV
299
        }
×
300
}
301

302
// getChannels returns a copy of the passed node's channels or nil if there
303
// isn't any.
304
func (c *GraphCache) getChannels(node route.Vertex) []*DirectedChannel {
503✔
305
        c.mtx.RLock()
503✔
306
        defer c.mtx.RUnlock()
503✔
307

503✔
308
        channels, ok := c.nodeChannels[node]
503✔
309
        if !ok {
512✔
310
                return nil
9✔
311
        }
9✔
312

313
        features, ok := c.nodeFeatures[node]
496✔
314
        if !ok {
512✔
315
                // If the features were set to nil explicitly, that's fine here.
16✔
316
                // The router will overwrite the features of the destination
16✔
317
                // node with those found in the invoice if necessary. But if we
16✔
318
                // didn't yet get a node announcement we want to mimic the
16✔
319
                // behavior of the old DB based code that would always set an
16✔
320
                // empty feature vector instead of leaving it nil.
16✔
321
                features = lnwire.EmptyFeatureVector()
16✔
322
        }
16✔
323

324
        toNodeCallback := func() route.Vertex {
946✔
325
                return node
450✔
326
        }
450✔
327

328
        i := 0
496✔
329
        channelsCopy := make([]*DirectedChannel, len(channels))
496✔
330
        for _, channel := range channels {
1,830✔
331
                // We need to copy the channel and policy to avoid it being
1,334✔
332
                // updated in the cache if the path finding algorithm sets
1,334✔
333
                // fields on it (currently only the ToNodeFeatures of the
1,334✔
334
                // policy).
1,334✔
335
                channelCopy := channel.DeepCopy()
1,334✔
336
                if channelCopy.InPolicy != nil {
2,643✔
337
                        channelCopy.InPolicy.ToNodePubKey = toNodeCallback
1,309✔
338
                        channelCopy.InPolicy.ToNodeFeatures = features
1,309✔
339
                }
1,309✔
340

341
                channelsCopy[i] = channelCopy
1,334✔
342
                i++
1,334✔
343
        }
344

345
        return channelsCopy
496✔
346
}
347

348
// ForEachChannel invokes the given callback for each channel of the given node.
349
func (c *GraphCache) ForEachChannel(node route.Vertex,
350
        cb func(channel *DirectedChannel) error) error {
503✔
351

503✔
352
        // Obtain a copy of the node's channels. We need do this in order to
503✔
353
        // avoid deadlocks caused by interaction with the graph cache, channel
503✔
354
        // state and the graph database from multiple goroutines. This snapshot
503✔
355
        // is only used for path finding where being stale is acceptable since
503✔
356
        // the real world graph and our representation may always become
503✔
357
        // slightly out of sync for a short time and the actual channel state
503✔
358
        // is stored separately.
503✔
359
        channels := c.getChannels(node)
503✔
360
        for _, channel := range channels {
1,837✔
361
                if err := cb(channel); err != nil {
1,334✔
362
                        return err
×
363
                }
×
364
        }
365

366
        return nil
503✔
367
}
368

369
// ForEachNode iterates over the adjacency list of the graph, executing the
370
// call back for each node and the set of channels that emanate from the given
371
// node.
372
//
373
// NOTE: This method should be considered _read only_, the channels or nodes
374
// passed in MUST NOT be modified.
375
func (c *GraphCache) ForEachNode(cb func(node route.Vertex,
376
        channels map[uint64]*DirectedChannel) error) error {
2✔
377

2✔
378
        c.mtx.RLock()
2✔
379
        defer c.mtx.RUnlock()
2✔
380

2✔
381
        for node, channels := range c.nodeChannels {
6✔
382
                // We don't make a copy here since this is a read-only RPC
4✔
383
                // call. We also don't need the node features either for this
4✔
384
                // call.
4✔
385
                if err := cb(node, channels); err != nil {
4✔
386
                        return err
×
387
                }
×
388
        }
389

390
        return nil
2✔
391
}
392

393
// GetFeatures returns the features of the node with the given ID. If no
394
// features are known for the node, an empty feature vector is returned.
395
func (c *GraphCache) GetFeatures(node route.Vertex) *lnwire.FeatureVector {
461✔
396
        c.mtx.RLock()
461✔
397
        defer c.mtx.RUnlock()
461✔
398

461✔
399
        features, ok := c.nodeFeatures[node]
461✔
400
        if !ok || features == nil {
473✔
401
                // The router expects the features to never be nil, so we return
12✔
402
                // an empty feature set instead.
12✔
403
                return lnwire.EmptyFeatureVector()
12✔
404
        }
12✔
405

406
        return features
451✔
407
}
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