mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-02-20 13:54:36 +01:00
devtools/topology: new tool to explore lightning topology.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
parent
6815a1d9a2
commit
5714a8c139
4 changed files with 377 additions and 3 deletions
|
@ -1,12 +1,12 @@
|
|||
DEVTOOLS_SRC := devtools/gen_print_wire.c devtools/gen_print_onion_wire.c devtools/print_wire.c
|
||||
DEVTOOLS_OBJS := $(DEVTOOLS_SRC:.c=.o)
|
||||
DEVTOOLS := devtools/bolt11-cli devtools/decodemsg devtools/onion devtools/dump-gossipstore devtools/gossipwith devtools/create-gossipstore devtools/mkcommit devtools/mkfunding devtools/mkclose devtools/mkgossip devtools/mkencoded devtools/checkchannels devtools/mkquery devtools/lightning-checkmessage
|
||||
DEVTOOLS := devtools/bolt11-cli devtools/decodemsg devtools/onion devtools/dump-gossipstore devtools/gossipwith devtools/create-gossipstore devtools/mkcommit devtools/mkfunding devtools/mkclose devtools/mkgossip devtools/mkencoded devtools/checkchannels devtools/mkquery devtools/lightning-checkmessage devtools/topology
|
||||
ifeq ($(EXPERIMENTAL_FEATURES),1)
|
||||
DEVTOOLS += devtools/blindedpath
|
||||
endif
|
||||
|
||||
DEVTOOLS_TOOL_SRC := $(DEVTOOLS:=.c)
|
||||
DEVTOOLS_TOOL_OBJS := $(DEVTOOLS_TOOL_SRC:.c=.o)
|
||||
DEVTOOLS_TOOL_OBJS := $(DEVTOOLS_TOOL_SRC:.c=.o) devtools/clean_topo.o
|
||||
|
||||
DEVTOOLS_BOLT_DEPS += $(BOLT_DEPS) tools/gen/print_impl_template tools/gen/print_header_template
|
||||
|
||||
|
@ -93,9 +93,11 @@ devtools/mkquery: $(DEVTOOLS_COMMON_OBJS) $(CCAN_OBJS) $(BITCOIN_OBJS) wire/from
|
|||
|
||||
devtools/lightning-checkmessage: $(DEVTOOLS_COMMON_OBJS) $(CCAN_OBJS) $(BITCOIN_OBJS) wire/fromwire.o wire/towire.o devtools/lightning-checkmessage.o
|
||||
|
||||
devtools/topology: $(DEVTOOLS_COMMON_OBJS) $(CCAN_OBJS) $(BITCOIN_OBJS) wire/fromwire.o wire/towire.o wire/tlvstream.o common/gossmap.o common/random_select.o common/dijkstra.o common/route.o devtools/clean_topo.o devtools/topology.o
|
||||
|
||||
# Make sure these depend on everything.
|
||||
ALL_PROGRAMS += $(DEVTOOLS)
|
||||
ALL_OBJS += $(DEVTOOLS_OBJS) $(DEVTOOLS_TOOL_OBJS)
|
||||
ALL_OBJS += $(DEVTOOLS_OBJS) $(DEVTOOLS_TOOL_OBJS)
|
||||
|
||||
check-source: $(DEVTOOLS_SRC:%=check-src-include-order/%) $(DEVTOOLS_TOOLS_SRC:%=check-src-include-order/%)
|
||||
|
||||
|
|
53
devtools/clean_topo.c
Normal file
53
devtools/clean_topo.c
Normal file
|
@ -0,0 +1,53 @@
|
|||
#include <common/gossmap.h>
|
||||
#include <devtools/clean_topo.h>
|
||||
|
||||
static void visit(struct gossmap *map,
|
||||
struct gossmap_node *n,
|
||||
bool *visited)
|
||||
{
|
||||
visited[gossmap_node_idx(map, n)] = true;
|
||||
|
||||
for (size_t i = 0; i < n->num_chans; i++) {
|
||||
int dir;
|
||||
struct gossmap_chan *c = gossmap_nth_chan(map, n, i, &dir);
|
||||
struct gossmap_node *peer;
|
||||
|
||||
peer = gossmap_nth_node(map, c, !dir);
|
||||
if (!visited[gossmap_node_idx(map, peer)])
|
||||
visit(map, peer, visited);
|
||||
}
|
||||
}
|
||||
|
||||
void clean_topo(struct gossmap *map, bool remove_singles)
|
||||
{
|
||||
struct gossmap_node *n, *next;
|
||||
bool *visited;
|
||||
|
||||
/* Remove channels which are not enabled in both dirs. */
|
||||
for (struct gossmap_chan *c = gossmap_first_chan(map);
|
||||
c;
|
||||
c = gossmap_next_chan(map, c)) {
|
||||
if (!c->half[0].enabled || !c->half[1].enabled) {
|
||||
gossmap_remove_chan(map, c);
|
||||
}
|
||||
}
|
||||
|
||||
if (remove_singles) {
|
||||
for (n = gossmap_first_node(map); n; n = next) {
|
||||
next = gossmap_next_node(map, n);
|
||||
if (n->num_chans == 1)
|
||||
gossmap_remove_node(map, n);
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove isolated nodes (we assume first isn't isolated!) */
|
||||
visited = tal_arrz(NULL, bool, gossmap_max_node_idx(map));
|
||||
visit(map, gossmap_first_node(map), visited);
|
||||
|
||||
for (n = gossmap_first_node(map); n; n = next) {
|
||||
next = gossmap_next_node(map, n);
|
||||
if (!visited[gossmap_node_idx(map, n)])
|
||||
gossmap_remove_node(map, n);
|
||||
}
|
||||
tal_free(visited);
|
||||
}
|
13
devtools/clean_topo.h
Normal file
13
devtools/clean_topo.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
#ifndef LIGHTNING_DEVTOOLS_CLEAN_TOPO_H
|
||||
#define LIGHTNING_DEVTOOLS_CLEAN_TOPO_H
|
||||
#include "config.h"
|
||||
|
||||
struct gossmap;
|
||||
|
||||
/* Cleans topology:
|
||||
* 1. Removes channels not enabled in both dirs.
|
||||
* 2. (if remove_singles) remove nodes with only one connection.
|
||||
* 3. Remove isolated nodes (we assume first node is well-connected!).
|
||||
*/
|
||||
void clean_topo(struct gossmap *map, bool remove_singles);
|
||||
#endif /* LIGHTNING_DEVTOOLS_CLEAN_TOPO_H */
|
306
devtools/topology.c
Normal file
306
devtools/topology.c
Normal file
|
@ -0,0 +1,306 @@
|
|||
#include <assert.h>
|
||||
#include <ccan/err/err.h>
|
||||
#include <ccan/opt/opt.h>
|
||||
#include <ccan/time/time.h>
|
||||
#include <common/amount.h>
|
||||
#include <common/dijkstra.h>
|
||||
#include <common/gossmap.h>
|
||||
#include <common/node_id.h>
|
||||
#include <common/pseudorand.h>
|
||||
#include <common/random_select.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_path_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_path_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 **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_path_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();
|
||||
if (amount_msat_greater(dijkstra_amount(dij, srcidx), maxcost)) {
|
||||
printf("failed (too expensive)\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
path = route_from_dijkstra(map, dij, src);
|
||||
printf("# path length %zu\n", tal_count(path));
|
||||
if (!amount_msat_sub(&fee, dijkstra_amount(dij, srcidx), 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;
|
||||
|
||||
/* N+1th node is at end of Nth hop */
|
||||
prev = gossmap_nth_node(map, path[i]->c, path[i]->dir);
|
||||
cur = gossmap_nth_node(map, path[i]->c, !path[i]->dir);
|
||||
|
||||
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;
|
||||
|
||||
/* N+1th node is at end of Nth hop */
|
||||
cur = gossmap_nth_node(map, path[i]->c, path[i]->dir);
|
||||
next = gossmap_nth_node(map, path[i]->c, !path[i]->dir);
|
||||
|
||||
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]);
|
||||
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);
|
||||
}
|
Loading…
Add table
Reference in a new issue