forked from ElementsProject/lightning
-
Notifications
You must be signed in to change notification settings - Fork 0
/
topology.c
306 lines (264 loc) · 8.52 KB
/
topology.c
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
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
#include "config.h"
#include <assert.h>
#include <ccan/err/err.h>
#include <ccan/opt/opt.h>
#include <ccan/time/time.h>
#include <common/dijkstra.h>
#include <common/gossmap.h>
#include <common/route.h>
#include <common/type_to_string.h>
#include <devtools/clean_topo.h>
#include <inttypes.h>
#include <stdio.h>
/* We ignore capacity constraints */
static bool channel_usable(const struct gossmap *map,
const struct gossmap_chan *c,
int dir,
struct amount_msat amount,
void *unused)
{
if (!gossmap_chan_set(c, dir))
return false;
if (!c->half[dir].enabled)
return false;
return true;
}
/* Note: dijkstra() sets dir to the neighbor side; i.e. c->half[dir].node_idx is the
* neighbor. */
static bool channel_usable_to_excl(const struct gossmap *map,
const struct gossmap_chan *c,
int dir,
struct amount_msat amount,
struct gossmap_node *excl)
{
if (!channel_usable(map, c, dir, amount, NULL))
return false;
/* Don't go via excl. */
if (c->half[dir].nodeidx == gossmap_node_idx(map, excl))
return false;
return true;
}
/* What nodes can reach n without going through exclude? */
static size_t count_possible_sources(const struct gossmap *map,
struct gossmap_node *n,
struct gossmap_node *exclude,
bool is_last_node)
{
const struct dijkstra *dij;
size_t distance_budget, num;
dij = dijkstra(tmpctx, map, n, AMOUNT_MSAT(0), 0,
channel_usable_to_excl, route_score_shorter, exclude);
if (is_last_node)
distance_budget = ROUTING_MAX_HOPS - 1;
else
distance_budget = ROUTING_MAX_HOPS - 2;
assert(dijkstra_distance(dij, gossmap_node_idx(map, n)) == 0);
assert(dijkstra_distance(dij, gossmap_node_idx(map, exclude)) == UINT_MAX);
num = 0;
for (n = gossmap_first_node(map); n; n = gossmap_next_node(map, n)) {
if (dijkstra_distance(dij, gossmap_node_idx(map, n)) <= distance_budget)
num++;
}
return num;
}
/* Note: dijkstra() sets dir to the neighbor side; i.e. c->half[dir].node_idx is the
* neighbor. */
static bool channel_usable_from_excl(const struct gossmap *map,
const struct gossmap_chan *c,
int dir,
struct amount_msat amount,
struct gossmap_node *excl)
{
if (!channel_usable(map, c, !dir, amount, NULL))
return false;
/* Don't go via excl. */
if (c->half[dir].nodeidx == gossmap_node_idx(map, excl))
return false;
return true;
}
static size_t memcount(const void *mem, size_t len, char c)
{
size_t count = 0;
for (size_t i = 0; i < len; i++) {
if (((char *)mem)[i] == c)
count++;
}
return count;
}
static void visit(const struct gossmap *map,
struct gossmap_node *n,
struct gossmap_node *exclude,
bool *visited)
{
if (n == exclude)
return;
if (visited[gossmap_node_idx(map, n)])
return;
visited[gossmap_node_idx(map, n)] = true;
for (size_t i = 0; i < n->num_chans; i++) {
int dir;
struct gossmap_chan *c;
c = gossmap_nth_chan(map, n, i, &dir);
if (!channel_usable(map, c, dir, AMOUNT_MSAT(0), NULL))
continue;
visit(map, gossmap_nth_node(map, c, !dir), exclude, visited);
}
}
/* What nodes can n reach without going through exclude? */
static size_t count_possible_destinations(const struct gossmap *map,
struct gossmap_node *start,
struct gossmap_node *exclude,
bool is_first_node)
{
const struct dijkstra *dij;
size_t distance_budget, num;
dij = dijkstra(tmpctx, map, start, AMOUNT_MSAT(0), 0,
channel_usable_from_excl, route_score_shorter, exclude);
if (is_first_node)
distance_budget = ROUTING_MAX_HOPS - 1;
else
distance_budget = ROUTING_MAX_HOPS - 2;
assert(dijkstra_distance(dij, gossmap_node_idx(map, start)) == 0);
assert(dijkstra_distance(dij, gossmap_node_idx(map, exclude)) == UINT_MAX);
num = 0;
for (struct gossmap_node *n = gossmap_first_node(map);
n;
n = gossmap_next_node(map, n)) {
if (dijkstra_distance(dij, gossmap_node_idx(map, n)) <= distance_budget)
num++;
#if 0
else
printf("Can't reach %s (%u) if we exclude %s\n",
type_to_string(tmpctx, struct node_id,
gossmap_node_get_id(map, n)),
dijkstra_distance(dij, gossmap_node_idx(map, n)),
type_to_string(tmpctx, struct node_id,
gossmap_node_get_id(map, exclude)));
#endif
}
/* Now double-check with flood-fill. */
bool *visited = tal_arrz(tmpctx, bool, gossmap_max_node_idx(map));
visit(map, start, exclude, visited);
assert(memcount(visited, tal_bytelen(visited), true) == num);
return num;
}
static bool measure_least_cost(struct gossmap *map,
struct gossmap_node *src,
struct gossmap_node *dst)
{
const struct dijkstra *dij;
u32 srcidx = gossmap_node_idx(map, src);
/* 10ksat, budget is 0.5% */
const struct amount_msat sent = AMOUNT_MSAT(10000000);
const struct amount_msat budget = amount_msat_div(sent, 200);
const u32 riskfactor = 0;
/* Max distance is 20 */
const u32 distance_budget = ROUTING_MAX_HOPS;
struct amount_msat maxcost, fee;
struct route_hop *path;
struct timemono tstart, tstop;
struct node_id srcid;
gossmap_node_get_id(map, src, &srcid);
printf("# src %s (%u channels)\n",
type_to_string(tmpctx, struct node_id, &srcid),
src->num_chans);
tstart = time_mono();
dij = dijkstra(tmpctx, map, dst,
sent, riskfactor, channel_usable,
route_score_cheaper, NULL);
tstop = time_mono();
printf("# Time to find path: %"PRIu64" usec\n",
time_to_usec(timemono_between(tstop, tstart)));
if (dijkstra_distance(dij, srcidx) > distance_budget) {
printf("failed (%s)\n",
dijkstra_distance(dij, srcidx) == UINT_MAX ? "unreachable" : "too far");
return false;
}
if (!amount_msat_add(&maxcost, sent, budget))
abort();
path = route_from_dijkstra(map, map, dij, src, sent, 0);
if (amount_msat_greater(path[0].amount, maxcost)) {
printf("failed (too expensive)\n");
return false;
}
printf("# path length %zu\n", tal_count(path));
if (!amount_msat_sub(&fee, path[0].amount, sent))
abort();
printf("# path fee %s\n",
type_to_string(tmpctx, struct amount_msat, &fee));
/* Count possible sources */
for (size_t i = 0; i < tal_count(path); i++) {
struct gossmap_node *prev, *cur;
struct gossmap_chan *c = gossmap_find_chan(map, &path[i].scid);
/* N+1th node is at end of Nth hop */
prev = gossmap_nth_node(map, c, path[i].direction);
cur = gossmap_nth_node(map, c, !path[i].direction);
printf("source set size node %zu/%zu: %zu\n",
i+1, tal_count(path),
count_possible_sources(map, prev, cur, cur == dst));
}
/* Count possible destinations. */
for (size_t i = 0; i < tal_count(path); i++) {
struct gossmap_node *cur, *next;
struct gossmap_chan *c = gossmap_find_chan(map, &path[i].scid);
/* N+1th node is at end of Nth hop */
cur = gossmap_nth_node(map, c, path[i].direction);
next = gossmap_nth_node(map, c, !path[i].direction);
printf("destination set size node %zu/%zu: %zu\n",
i, tal_count(path),
count_possible_destinations(map, next, cur, cur == src));
}
return true;
}
int main(int argc, char *argv[])
{
struct timemono tstart, tstop;
struct gossmap_node *n, *dst;
struct gossmap *map;
struct node_id dstid;
bool no_singles = false;
setup_locale();
setup_tmpctx();
opt_register_noarg("--no-single-sources", opt_set_bool, &no_singles,
"Eliminate single-channel nodes");
opt_register_noarg("-h|--help", opt_usage_and_exit,
"<gossipstore> <srcid>|all <dstid>\n"
"A topology test program.",
"Get usage information");
opt_parse(&argc, argv, opt_log_stderr_exit);
if (argc != 4)
opt_usage_exit_fail("Expect 3 arguments");
tstart = time_mono();
map = gossmap_load(NULL, argv[1], NULL);
if (!map)
err(1, "Loading gossip store %s", argv[1]);
tstop = time_mono();
printf("# Time to load: %"PRIu64" msec\n",
time_to_msec(timemono_between(tstop, tstart)));
clean_topo(map, no_singles);
printf("# Reduced to %zu nodes and %zu channels\n",
gossmap_num_nodes(map), gossmap_num_chans(map));
if (!node_id_from_hexstr(argv[3], strlen(argv[3]), &dstid))
errx(1, "Bad dstid");
dst = gossmap_find_node(map, &dstid);
if (!dst)
errx(1, "Unknown destination node '%s'", argv[3]);
if (streq(argv[2], "all")) {
for (n = gossmap_first_node(map);
n;
n = gossmap_next_node(map, n)) {
measure_least_cost(map, n, dst);
clean_tmpctx();
}
} else {
struct node_id srcid;
if (!node_id_from_hexstr(argv[2], strlen(argv[2]), &srcid))
errx(1, "Bad srcid");
n = gossmap_find_node(map, &srcid);
if (!n)
errx(1, "Unknown source node '%s'", argv[2]);
if (!measure_least_cost(map, n, dst))
exit(1);
}
tal_free(map);
}