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

lightningnetwork / lnd / 13586005509

28 Feb 2025 10:14AM UTC coverage: 68.629% (+9.9%) from 58.77%
13586005509

Pull #9521

github

web-flow
Merge 37d3a70a5 into 8532955b3
Pull Request #9521: unit: remove GOACC, use Go 1.20 native coverage functionality

129950 of 189351 relevant lines covered (68.63%)

23726.46 hits per line

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

96.71
/autopilot/simple_graph.go
1
package autopilot
2

3
// diameterCutoff is used to discard nodes in the diameter calculation.
4
// It is the multiplier for the eccentricity of the highest-degree node,
5
// serving as a cutoff to discard all nodes with a smaller hop distance. This
6
// number should not be set close to 1 and is a tradeoff for computation cost,
7
// where 0 is maximally costly.
8
const diameterCutoff = 0.75
9

10
// SimpleGraph stores a simplified adj graph of a channel graph to speed
11
// up graph processing by eliminating all unnecessary hashing and map access.
12
type SimpleGraph struct {
13
        // Nodes is a map from node index to NodeID.
14
        Nodes []NodeID
15

16
        // Adj stores nodes and neighbors in an adjacency list.
17
        Adj [][]int
18
}
19

20
// NewSimpleGraph creates a simplified graph from the current channel graph.
21
// Returns an error if the channel graph iteration fails due to underlying
22
// failure.
23
func NewSimpleGraph(g ChannelGraph) (*SimpleGraph, error) {
210✔
24
        nodes := make(map[NodeID]int)
210✔
25
        adj := make(map[int][]int)
210✔
26
        nextIndex := 0
210✔
27

210✔
28
        // getNodeIndex returns the integer index of the passed node.
210✔
29
        // The returned index is then used to create a simplified adjacency list
210✔
30
        // where each node is identified by its index instead of its pubkey, and
210✔
31
        // also to create a mapping from node index to node pubkey.
210✔
32
        getNodeIndex := func(node Node) int {
7,906✔
33
                key := NodeID(node.PubKey())
7,696✔
34
                nodeIndex, ok := nodes[key]
7,696✔
35

7,696✔
36
                if !ok {
9,568✔
37
                        nodes[key] = nextIndex
1,872✔
38
                        nodeIndex = nextIndex
1,872✔
39
                        nextIndex++
1,872✔
40
                }
1,872✔
41

42
                return nodeIndex
7,696✔
43
        }
44

45
        // Iterate over each node and each channel and update the adj and the node
46
        // index.
47
        err := g.ForEachNode(func(node Node) error {
2,082✔
48
                u := getNodeIndex(node)
1,872✔
49

1,872✔
50
                return node.ForEachChannel(func(edge ChannelEdge) error {
7,696✔
51
                        v := getNodeIndex(edge.Peer)
5,824✔
52

5,824✔
53
                        adj[u] = append(adj[u], v)
5,824✔
54
                        return nil
5,824✔
55
                })
5,824✔
56
        })
57
        if err != nil {
210✔
58
                return nil, err
×
59
        }
×
60

61
        graph := &SimpleGraph{
210✔
62
                Nodes: make([]NodeID, len(nodes)),
210✔
63
                Adj:   make([][]int, len(nodes)),
210✔
64
        }
210✔
65

210✔
66
        // Fill the adj and the node index to node pubkey mapping.
210✔
67
        for nodeID, nodeIndex := range nodes {
2,082✔
68
                graph.Adj[nodeIndex] = adj[nodeIndex]
1,872✔
69
                graph.Nodes[nodeIndex] = nodeID
1,872✔
70
        }
1,872✔
71

72
        // We prepare to give some debug output about the size of the graph.
73
        totalChannels := 0
210✔
74
        for _, channels := range graph.Adj {
2,082✔
75
                totalChannels += len(channels)
1,872✔
76
        }
1,872✔
77

78
        // The number of channels is double counted, so divide by two.
79
        log.Debugf("Initialized simple graph with %d nodes and %d "+
210✔
80
                "channels", len(graph.Adj), totalChannels/2)
210✔
81
        return graph, nil
210✔
82
}
83

84
// maxVal is a helper function to get the maximal value of all values of a map.
85
func maxVal(mapping map[int]uint32) uint32 {
22✔
86
        maxValue := uint32(0)
22✔
87
        for _, value := range mapping {
212✔
88
                maxValue = max(maxValue, value)
190✔
89
        }
190✔
90
        return maxValue
22✔
91
}
92

93
// degree determines the number of edges for a node in the graph.
94
func (graph *SimpleGraph) degree(node int) int {
9✔
95
        return len(graph.Adj[node])
9✔
96
}
9✔
97

98
// nodeMaxDegree determines the node with the max degree and its degree.
99
func (graph *SimpleGraph) nodeMaxDegree() (int, int) {
1✔
100
        var maxNode, maxDegree int
1✔
101
        for node := range graph.Adj {
10✔
102
                degree := graph.degree(node)
9✔
103
                if degree > maxDegree {
11✔
104
                        maxNode = node
2✔
105
                        maxDegree = degree
2✔
106
                }
2✔
107
        }
108
        return maxNode, maxDegree
1✔
109
}
110

111
// shortestPathLengths performs a breadth-first-search from a node to all other
112
// nodes, returning the lengths of the paths.
113
func (graph *SimpleGraph) shortestPathLengths(node int) map[int]uint32 {
21✔
114
        // level indicates the shell of the search around the root node.
21✔
115
        var level uint32
21✔
116
        graphOrder := len(graph.Adj)
21✔
117

21✔
118
        // nextLevel tracks which nodes should be visited in the next round.
21✔
119
        nextLevel := make([]int, 0, graphOrder)
21✔
120

21✔
121
        // The root node is put as a starting point for the exploration.
21✔
122
        nextLevel = append(nextLevel, node)
21✔
123

21✔
124
        // Seen tracks already visited nodes and tracks how far away they are.
21✔
125
        seen := make(map[int]uint32, graphOrder)
21✔
126

21✔
127
        // Mark the root node as seen.
21✔
128
        seen[node] = level
21✔
129

21✔
130
        // thisLevel contains the nodes that are explored in the round.
21✔
131
        thisLevel := make([]int, 0, graphOrder)
21✔
132

21✔
133
        // Abort if we have an empty graph.
21✔
134
        if len(graph.Adj) == 0 {
21✔
135
                return seen
×
136
        }
×
137

138
        // We discover other nodes in a ring-like structure as long as we don't
139
        // have more nodes to explore.
140
        for len(nextLevel) > 0 {
103✔
141
                level++
82✔
142

82✔
143
                // We swap the queues for efficient memory management.
82✔
144
                thisLevel, nextLevel = nextLevel, thisLevel
82✔
145

82✔
146
                // Visit all neighboring nodes of the level and mark them as
82✔
147
                // seen if they were not discovered before.
82✔
148
                for _, thisNode := range thisLevel {
226✔
149
                        for _, neighbor := range graph.Adj[thisNode] {
608✔
150
                                _, ok := seen[neighbor]
464✔
151
                                if !ok {
632✔
152
                                        nextLevel = append(nextLevel, neighbor)
168✔
153
                                        seen[neighbor] = level
168✔
154
                                }
168✔
155

156
                                // If we have seen all nodes, we return early.
157
                                if len(seen) == graphOrder {
485✔
158
                                        return seen
21✔
159
                                }
21✔
160
                        }
161
                }
162

163
                // Empty the queue to be used in the next level.
164
                thisLevel = thisLevel[:0:cap(thisLevel)]
61✔
165
        }
166

167
        return seen
×
168
}
169

170
// nodeEccentricity calculates the eccentricity (longest shortest path to all
171
// other nodes) of a node.
172
func (graph *SimpleGraph) nodeEccentricity(node int) uint32 {
19✔
173
        pathLengths := graph.shortestPathLengths(node)
19✔
174
        return maxVal(pathLengths)
19✔
175
}
19✔
176

177
// nodeEccentricities calculates the eccentricities for the given nodes.
178
func (graph *SimpleGraph) nodeEccentricities(nodes []int) map[int]uint32 {
3✔
179
        eccentricities := make(map[int]uint32, len(graph.Adj))
3✔
180
        for _, node := range nodes {
22✔
181
                eccentricities[node] = graph.nodeEccentricity(node)
19✔
182
        }
19✔
183
        return eccentricities
3✔
184
}
185

186
// Diameter returns the maximal eccentricity (longest shortest path
187
// between any node pair) in the graph.
188
//
189
// Note: This method is exact but expensive, use DiameterRadialCutoff instead.
190
func (graph *SimpleGraph) Diameter() uint32 {
1✔
191
        nodes := make([]int, len(graph.Adj))
1✔
192
        for a := range nodes {
10✔
193
                nodes[a] = a
9✔
194
        }
9✔
195
        eccentricities := graph.nodeEccentricities(nodes)
1✔
196
        return maxVal(eccentricities)
1✔
197
}
198

199
// DiameterRadialCutoff is a method to efficiently evaluate the diameter of a
200
// graph. The highest-degree node is usually central in the graph. We can
201
// determine its eccentricity (shortest-longest path length to any other node)
202
// and use it as an approximation for the radius of the network. We then
203
// use this radius to compute a cutoff. All the nodes within a distance of the
204
// cutoff are discarded, as they represent the inside of the graph. We then
205
// loop over all outer nodes and determine their eccentricities, from which we
206
// get the diameter.
207
func (graph *SimpleGraph) DiameterRadialCutoff() uint32 {
1✔
208
        // Determine the reference node as the node with the highest degree.
1✔
209
        nodeMaxDegree, _ := graph.nodeMaxDegree()
1✔
210

1✔
211
        distances := graph.shortestPathLengths(nodeMaxDegree)
1✔
212
        eccentricityMaxDegreeNode := maxVal(distances)
1✔
213

1✔
214
        // We use the eccentricity to define a cutoff for the interior of the
1✔
215
        // graph from the reference node.
1✔
216
        cutoff := uint32(float32(eccentricityMaxDegreeNode) * diameterCutoff)
1✔
217
        log.Debugf("Cutoff radius is %d hops (max-degree node's "+
1✔
218
                "eccentricity is %d)", cutoff, eccentricityMaxDegreeNode)
1✔
219

1✔
220
        // Remove the nodes that are close to the reference node.
1✔
221
        var nodes []int
1✔
222
        for node, distance := range distances {
10✔
223
                if distance > cutoff {
10✔
224
                        nodes = append(nodes, node)
1✔
225
                }
1✔
226
        }
227
        log.Debugf("Evaluated nodes: %d, discarded nodes %d",
1✔
228
                len(nodes), len(graph.Adj)-len(nodes))
1✔
229

1✔
230
        // Compute the diameter of the remaining nodes.
1✔
231
        eccentricities := graph.nodeEccentricities(nodes)
1✔
232
        return maxVal(eccentricities)
1✔
233
}
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