forked from lightningnetwork/lnd
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsimple_graph.go
235 lines (197 loc) · 7.19 KB
/
simple_graph.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
package autopilot
// diameterCutoff is used to discard nodes in the diameter calculation.
// It is the multiplier for the eccentricity of the highest-degree node,
// serving as a cutoff to discard all nodes with a smaller hop distance. This
// number should not be set close to 1 and is a tradeoff for computation cost,
// where 0 is maximally costly.
const diameterCutoff = 0.75
// SimpleGraph stores a simplified adj graph of a channel graph to speed
// up graph processing by eliminating all unnecessary hashing and map access.
type SimpleGraph struct {
// Nodes is a map from node index to NodeID.
Nodes []NodeID
// Adj stores nodes and neighbors in an adjacency list.
Adj [][]int
}
// NewSimpleGraph creates a simplified graph from the current channel graph.
// Returns an error if the channel graph iteration fails due to underlying
// failure.
func NewSimpleGraph(g ChannelGraph) (*SimpleGraph, error) {
nodes := make(map[NodeID]int)
adj := make(map[int][]int)
nextIndex := 0
// getNodeIndex returns the integer index of the passed node.
// The returned index is then used to create a simplified adjacency list
// where each node is identified by its index instead of its pubkey, and
// also to create a mapping from node index to node pubkey.
getNodeIndex := func(node Node) int {
key := NodeID(node.PubKey())
nodeIndex, ok := nodes[key]
if !ok {
nodes[key] = nextIndex
nodeIndex = nextIndex
nextIndex++
}
return nodeIndex
}
// Iterate over each node and each channel and update the adj and the node
// index.
err := g.ForEachNode(func(node Node) error {
u := getNodeIndex(node)
return node.ForEachChannel(func(edge ChannelEdge) error {
v := getNodeIndex(edge.Peer)
adj[u] = append(adj[u], v)
return nil
})
})
if err != nil {
return nil, err
}
graph := &SimpleGraph{
Nodes: make([]NodeID, len(nodes)),
Adj: make([][]int, len(nodes)),
}
// Fill the adj and the node index to node pubkey mapping.
for nodeID, nodeIndex := range nodes {
graph.Adj[nodeIndex] = adj[nodeIndex]
graph.Nodes[nodeIndex] = nodeID
}
// We prepare to give some debug output about the size of the graph.
totalChannels := 0
for _, channels := range graph.Adj {
totalChannels += len(channels)
}
// The number of channels is double counted, so divide by two.
log.Debugf("Initialized simple graph with %d nodes and %d "+
"channels", len(graph.Adj), totalChannels/2)
return graph, nil
}
// maxVal is a helper function to get the maximal value of all values of a map.
func maxVal(mapping map[int]uint32) uint32 {
maxValue := uint32(0)
for _, value := range mapping {
if maxValue < value {
maxValue = value
}
}
return maxValue
}
// degree determines the number of edges for a node in the graph.
func (graph *SimpleGraph) degree(node int) int {
return len(graph.Adj[node])
}
// nodeMaxDegree determines the node with the max degree and its degree.
func (graph *SimpleGraph) nodeMaxDegree() (int, int) {
var maxNode, maxDegree int
for node := range graph.Adj {
degree := graph.degree(node)
if degree > maxDegree {
maxNode = node
maxDegree = degree
}
}
return maxNode, maxDegree
}
// shortestPathLengths performs a breadth-first-search from a node to all other
// nodes, returning the lengths of the paths.
func (graph *SimpleGraph) shortestPathLengths(node int) map[int]uint32 {
// level indicates the shell of the search around the root node.
var level uint32
graphOrder := len(graph.Adj)
// nextLevel tracks which nodes should be visited in the next round.
nextLevel := make([]int, 0, graphOrder)
// The root node is put as a starting point for the exploration.
nextLevel = append(nextLevel, node)
// Seen tracks already visited nodes and tracks how far away they are.
seen := make(map[int]uint32, graphOrder)
// Mark the root node as seen.
seen[node] = level
// thisLevel contains the nodes that are explored in the round.
thisLevel := make([]int, 0, graphOrder)
// Abort if we have an empty graph.
if len(graph.Adj) == 0 {
return seen
}
// We discover other nodes in a ring-like structure as long as we don't
// have more nodes to explore.
for len(nextLevel) > 0 {
level++
// We swap the queues for efficient memory management.
thisLevel, nextLevel = nextLevel, thisLevel
// Visit all neighboring nodes of the level and mark them as
// seen if they were not discovered before.
for _, thisNode := range thisLevel {
for _, neighbor := range graph.Adj[thisNode] {
_, ok := seen[neighbor]
if !ok {
nextLevel = append(nextLevel, neighbor)
seen[neighbor] = level
}
// If we have seen all nodes, we return early.
if len(seen) == graphOrder {
return seen
}
}
}
// Empty the queue to be used in the next level.
thisLevel = thisLevel[:0:cap(thisLevel)]
}
return seen
}
// nodeEccentricity calculates the eccentricity (longest shortest path to all
// other nodes) of a node.
func (graph *SimpleGraph) nodeEccentricity(node int) uint32 {
pathLengths := graph.shortestPathLengths(node)
return maxVal(pathLengths)
}
// nodeEccentricities calculates the eccentricities for the given nodes.
func (graph *SimpleGraph) nodeEccentricities(nodes []int) map[int]uint32 {
eccentricities := make(map[int]uint32, len(graph.Adj))
for _, node := range nodes {
eccentricities[node] = graph.nodeEccentricity(node)
}
return eccentricities
}
// Diameter returns the maximal eccentricity (longest shortest path
// between any node pair) in the graph.
//
// Note: This method is exact but expensive, use DiameterRadialCutoff instead.
func (graph *SimpleGraph) Diameter() uint32 {
nodes := make([]int, len(graph.Adj))
for a := range nodes {
nodes[a] = a
}
eccentricities := graph.nodeEccentricities(nodes)
return maxVal(eccentricities)
}
// DiameterRadialCutoff is a method to efficiently evaluate the diameter of a
// graph. The highest-degree node is usually central in the graph. We can
// determine its eccentricity (shortest-longest path length to any other node)
// and use it as an approximation for the radius of the network. We then
// use this radius to compute a cutoff. All the nodes within a distance of the
// cutoff are discarded, as they represent the inside of the graph. We then
// loop over all outer nodes and determine their eccentricities, from which we
// get the diameter.
func (graph *SimpleGraph) DiameterRadialCutoff() uint32 {
// Determine the reference node as the node with the highest degree.
nodeMaxDegree, _ := graph.nodeMaxDegree()
distances := graph.shortestPathLengths(nodeMaxDegree)
eccentricityMaxDegreeNode := maxVal(distances)
// We use the eccentricity to define a cutoff for the interior of the
// graph from the reference node.
cutoff := uint32(float32(eccentricityMaxDegreeNode) * diameterCutoff)
log.Debugf("Cutoff radius is %d hops (max-degree node's "+
"eccentricity is %d)", cutoff, eccentricityMaxDegreeNode)
// Remove the nodes that are close to the reference node.
var nodes []int
for node, distance := range distances {
if distance > cutoff {
nodes = append(nodes, node)
}
}
log.Debugf("Evaluated nodes: %d, discarded nodes %d",
len(nodes), len(graph.Adj)-len(nodes))
// Compute the diameter of the remaining nodes.
eccentricities := graph.nodeEccentricities(nodes)
return maxVal(eccentricities)
}