lnvis

nanovg lightning network visualizer
git clone git://jb55.com/lnvis
Log | Files | Refs | README | LICENSE

commit 75b582227aae98f9fc3d4af5f8fb7f6031351781
parent 2c8b700a9aa63cdb35c0ec3e2c8d6d5de8500532
Author: William Casarin <jb55@jb55.com>
Date:   Sat, 11 Aug 2018 17:39:18 -0700

very cool

Diffstat:
A.rgignore | 2++
Mdefs.h | 18++++++++++++++++++
Mjson.c | 44+++++++++++++++++++++++++++++++++-----------
Mjson.h | 3+++
Mln.c | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mln.h | 16++++++++++++++--
Mmain.c | 71++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Mrender.c | 73+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Mupdate.c | 57+++++++++++++++++++++++++++++++++------------------------
9 files changed, 266 insertions(+), 84 deletions(-)

diff --git a/.rgignore b/.rgignore @@ -0,0 +1 @@ +*.json+ \ No newline at end of file diff --git a/defs.h b/defs.h @@ -5,6 +5,7 @@ #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) #include <inttypes.h> +#include <string.h> typedef unsigned char u8; typedef signed char s8; @@ -18,5 +19,22 @@ typedef signed int s32; typedef uint64_t u64; typedef int64_t s64; +static inline int streq(const char *a, const char *b) +{ + return strcmp(a, b) == 0; +} + +#define min(a,b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a < _b ? _a : _b; }) + +#define max(a,b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a > _b ? _a : _b; }) + + + #endif /* LNVIS_DEFS_H */ diff --git a/json.c b/json.c @@ -11,11 +11,6 @@ #define ERROR_READING_CHANNELS -2 #define ERROR_ALLOCATING_CHANNELS -3 -#define min(a,b) \ - ({ __typeof__ (a) _a = (a); \ - __typeof__ (b) _b = (b); \ - _a < _b ? _a : _b; }) - struct channel_parser { const char *field; enum channel_parsing_state state; @@ -39,6 +34,8 @@ struct node_parser { /* "fee_per_millionth": 1, */ /* "delay": 14 */ static const struct channel_parser channel_parsers[] = { + { "source", PARSING_CHAN_SOURCE }, + { "destination", PARSING_CHAN_DESTINATION }, { "short_channel_id", PARSING_CHAN_SHORTID }, { "public", PARSING_CHAN_PUBLIC }, { "satoshis", PARSING_CHAN_SATOSHIS }, @@ -61,8 +58,9 @@ static const struct channel_parser channel_parsers[] = { /* "port": 9735 */ /* }] */ static const struct node_parser node_parsers[] = { - { "alias", PARSING_NODE_ALIAS }, - { "color", PARSING_NODE_COLOR }, + { "nodeid", PARSING_NODE_ID }, + { "alias", PARSING_NODE_ALIAS }, + { "color", PARSING_NODE_COLOR }, { "addresses", PARSING_NODE_ADDRESSES }, }; @@ -181,14 +179,14 @@ static void parse_color(int toklen, const char *tokstr, union color *color) { goto colorerr; for (int i = 0; i < 3; i++) { - u8 c1 = tokstr[i]; - u8 c2 = tokstr[i+1]; + u8 c1 = tokstr[i*2]; + u8 c2 = tokstr[i*2+1]; if (!isxdigit(c1) || !isxdigit(c2)) goto colorerr; u8 val = hexdigit(c1) << 4 | hexdigit(c2); - color->rgba[i] = val / 255.0f; + color->rgba[i] = ((float)val) / 255.0f; } return; @@ -214,6 +212,8 @@ int parse_clightning_nodes(FILE *fd, int *node_count, struct node **pnodes) nodes = calloc(nodecap, sizeof(struct node)); + int acount = 0; + int rez = parse_json(fd, &toks, &ntoks, &buffer); if (rez < 0) return rez; @@ -221,8 +221,15 @@ int parse_clightning_nodes(FILE *fd, int *node_count, struct node **pnodes) for (i = 0; i < ntoks; i++) { tok = &toks[i]; - if (tok->type == JSMN_ARRAY) + if (tok->type == JSMN_ARRAY) { + if (state == PARSING_NODE_ADDRESSES) { + if (++acount == 1) { + acount = 0; + state = PARSING_NODE_TOKEN; + } + } continue; + } if (tok->type == JSMN_OBJECT) { if (state == PARSING_NODE_ADDRESSES) @@ -251,6 +258,11 @@ int parse_clightning_nodes(FILE *fd, int *node_count, struct node **pnodes) } switch (state) { + case PARSING_NODE_ID: + strncpy(node->id, tokstr, min(toklen, PUBKEY_SIZE)); + state = PARSING_NODE_TOKEN; + break; + case PARSING_NODE_ALIAS: strncpy(node->alias, tokstr, min(toklen, MAX_ALIAS_SIZE)); state = PARSING_NODE_TOKEN; @@ -336,6 +348,16 @@ int parse_clightning_channels(FILE *fd, int *nchans, struct channel **pchannels) // TODO: lookup node id, assign nodes switch (state) { + case PARSING_CHAN_SOURCE: + strncpy(chan->source, tokstr, min(PUBKEY_SIZE, toklen)); + state = PARSING_CHAN_TOKEN; + break; + + case PARSING_CHAN_DESTINATION: + strncpy(chan->destination, tokstr, min(PUBKEY_SIZE, toklen)); + state = PARSING_CHAN_TOKEN; + break; + case PARSING_CHAN_SHORTID: parse_short_channel_id(toklen, tokstr, &chan->short_channel_id); diff --git a/json.h b/json.h @@ -10,6 +10,7 @@ enum node_parsing_state { PARSING_NODE_TOKEN, PARSING_NODE_ALIAS, PARSING_NODE_COLOR, + PARSING_NODE_ID, PARSING_NODE_ADDRESSES, }; @@ -24,6 +25,8 @@ enum channel_parsing_state { PARSING_CHAN_BASE_FEE, PARSING_CHAN_FEE_PER, PARSING_CHAN_DELAY, + PARSING_CHAN_SOURCE, + PARSING_CHAN_DESTINATION, }; int parse_clightning_nodes(FILE *fd, int *node_count, struct node **nodes); diff --git a/ln.c b/ln.c @@ -19,6 +19,22 @@ static double rand_0to1() { return (double) rand() / RAND_MAX; } +void init_network(int ww, int wh, struct ln *ln) { + struct node *n; + + for (u32 i = 0; i < ln->node_count; ++i) { + n = &ln->nodes[i]; + + n->x = ww * rand_0to1(); + n->y = wh * rand_0to1(); + n->ax = 0.0; + n->ay = 0.0; + n->vx = 0.0; + n->vy = 0.0; + n->size = 10; + } +} + void random_network(int ww, int wh, int max_per_node, int num_nodes, struct ln *ln) { int i, j; int from, to; @@ -85,3 +101,53 @@ void random_network(int ww, int wh, int max_per_node, int num_nodes, struct ln * // keep trying until we find on that isn't already connected } } + + +static void print_node(struct node *node) +{ + printf("node %s #%02X%02X%02X\n", node->alias, + (int)(node->color.r * 255.0f), + (int)(node->color.g * 255.0f), + (int)(node->color.b * 255.0f)); +} + + + +void filter_network(const char *nodeid, struct ln *ln) +{ + u32 i; + struct node *node = NULL; + struct channel *chan = NULL; + ln->filter = (char*)nodeid; + + for (i = 0; i < ln->node_count; i++) { + node = &ln->nodes[i]; + + if (streq(nodeid, node->id) || rand_0to1() < 0.002) { + node->filtered = 1; + printf("filtering "); + print_node(node); + } + else + node->filtered = 0; + } + + for (i = 0; i < ln->channel_count; i++) { + chan = &ln->channels[i]; + + if (chan->nodes[0]->filtered) + chan->nodes[1]->mark_filtered = 1; + + if (chan->nodes[1]->filtered) + chan->nodes[0]->mark_filtered = 1; + } + + for (i = 0; i < ln->node_count; i++) { + node = &ln->nodes[i]; + + if (node->mark_filtered) { + node->filtered = 1; + node->mark_filtered = 0; + } + } +} diff --git a/ln.h b/ln.h @@ -8,6 +8,9 @@ #define CELL_MAX_ELEMS 32 #define MAX_ALIAS_SIZE 32 +// TODO: parse pubkeys +#define PUBKEY_SIZE 66 + union color { float rgba[4]; struct { @@ -17,8 +20,10 @@ union color { }; struct node { - char alias[MAX_ALIAS_SIZE]; - const char *id; // pubkey + char alias[MAX_ALIAS_SIZE+1]; + char id[PUBKEY_SIZE+1]; + + int filtered, mark_filtered; union color color; @@ -76,6 +81,9 @@ struct short_channel_id { struct channel { struct node *nodes[2]; + char source[PUBKEY_SIZE+1]; + char destination[PUBKEY_SIZE+1]; + struct short_channel_id short_channel_id; u8 public; u8 active; @@ -106,6 +114,7 @@ struct ln { int window_width; int window_height; + char *filter; union color clear_color; u64 display_flags; @@ -113,6 +122,7 @@ struct ln { struct cell *grid; struct node *drag_target; + struct node *last_drag_target; struct node *nodes; u32 node_count; @@ -124,5 +134,7 @@ struct ln { void init_ln(struct ln *ln, int grid_div); void free_ln(struct ln *ln); void random_network(int ww, int wh, int max_per_node, int num_nodes, struct ln *ln); +void init_network(int ww, int wh, struct ln *ln); +void filter_network(const char *nodeid, struct ln *ln); #endif /* LNVIS_LN_H */ diff --git a/main.c b/main.c @@ -2,6 +2,7 @@ #include <stdio.h> #include <time.h> +#include <assert.h> #ifdef NANOVG_GLEW #include <GL/glew.h> #endif @@ -101,14 +102,6 @@ void key_callback(GLFWwindow* window, int key, int scancode, int action, int mod } } -static void print_node(struct node *node) -{ - printf("node %s #%02X%02X%02X\n", node->alias, - (int)node->color.r * 255, - (int)node->color.g * 255, - (int)node->color.b * 255); -} - static void print_channel(struct channel *chan) { printf("chan shortid=%u:%u:%hu public=%d sats=%"PRIu64" active=%d " @@ -126,13 +119,41 @@ static void print_channel(struct channel *chan) chan->delay); } +static struct node *find_node(const char *pubkey, struct node *nodes, int node_count) +{ + // TODO: hash table + for (int i = 0; i < node_count; i++) { + if (streq(pubkey, nodes[i].id)) + return &nodes[i]; + /* printf("%s != %s\n", pubkey, nodes[i].id); */ + } + + return NULL; +} + -void test_read_json() +static void connect_node_channels(struct node *nodes, int node_count, + struct channel *channels, int channel_count) +{ + struct channel *chan = NULL; + + for (int i = 0; i < channel_count; i++) { + chan = &channels[i]; + + chan->nodes[0] = find_node(chan->source, nodes, node_count); + chan->nodes[1] = find_node(chan->destination, nodes, node_count); + + assert(chan->nodes[0]); + assert(chan->nodes[1]); + } +} + +void test_read_json(struct ln *ln) { FILE *channels_fd = fopen("clightning-channels.json", "r"); FILE *nodes_fd = fopen("clightning-nodes.json", "r"); - int nchans = 0; + int channel_count = 0; int node_count = 0; int i; struct channel *channels; @@ -146,7 +167,7 @@ void test_read_json() exit(1); } - res = parse_clightning_channels(channels_fd, &nchans, &channels); + res = parse_clightning_channels(channels_fd, &channel_count, &channels); fclose(channels_fd); if (res != 0) { @@ -154,16 +175,13 @@ void test_read_json() exit(1); } - for (i = 0; i < nchans; i++) - print_channel(&channels[i]); + printf("parsed %d nodes, %d channels\n", node_count, channel_count); - for (i = 0; i < node_count; i++) - print_node(&nodes[i]); - - printf("%d nodes, %d channels\n", node_count, nchans); - - - exit(0); + connect_node_channels(nodes, node_count, channels, channel_count); + ln->channels = channels; + ln->channel_count = channel_count; + ln->nodes = nodes; + ln->node_count = node_count; } int main() @@ -182,12 +200,13 @@ int main() static const int dark_theme = 1; u64 flags = DISP_DARK - /* | DISP_ALIASES */ + | DISP_ALIASES | DISP_GRID - /* | DISP_STROKE_NODES */ + | DISP_STROKE_NODES ; - test_read_json(); + const char *filter = "03f3c108ccd536b8526841f0a5c58212bb9e6584a1eb493080e7c1cc34f82dad71"; + test_read_json(&ln); ln.display_flags = flags; @@ -239,6 +258,7 @@ int main() } ln.vg = vg; + init_ln(&ln, grid_div); if (loadDemoData(vg, &data) == -1) @@ -280,8 +300,9 @@ int main() ln.window_width = winWidth; if (first) { - random_network(winWidth, winHeight, 3, 500, &ln); - printf("channels %d\n", ln.channel_count); + /* random_network(winWidth, winHeight, 3, 500, &ln); */ + init_network(winWidth, winHeight, &ln); + filter_network(filter, &ln); first = 0; } diff --git a/render.c b/render.c @@ -1,14 +1,21 @@ #include "render.h" +#include "defs.h" #include <stdio.h> #include <assert.h> +#include <math.h> #include "nanovg/nanovg.h" -void draw_channel(NVGcontext *vg, struct channel *channel) +void draw_channel(NVGcontext *vg, struct ln *ln, struct channel *channel) { const struct node *n1 = channel->nodes[0]; const struct node *n2 = channel->nodes[1]; - static const float stroke = 2.0f; + + if (!(n1->filtered && n2->filtered)) + return; + + const float stroke = max(1.0, channel->satoshis * 0.000001f); + /* const float stroke = (logf(channel->satoshis) / logf(10)) * 0.0f; */ const float sx = n1->x; const float sy = n1->y; @@ -16,10 +23,19 @@ void draw_channel(NVGcontext *vg, struct channel *channel) const float ex = n2->x; const float ey = n2->y; + union color n1t, n2t; + + n1t.nvg_color = n1->color.nvg_color; + n2t.nvg_color = n2->color.nvg_color; + + if (n1 != ln->last_drag_target) + n1t.a = 0.4; + + if (n2 != ln->last_drag_target) + n2t.a = 0.4; + NVGpaint linear_grad = - nvgLinearGradient(vg, sx, sy, ex, ey, - n1->color.nvg_color, - n2->color.nvg_color); + nvgLinearGradient(vg, sx, sy, ex, ey, n1t.nvg_color, n2t.nvg_color); nvgSave(vg); nvgStrokeWidth(vg, stroke); @@ -70,20 +86,30 @@ void draw_grid(NVGcontext *vg, struct ln *ln) { void draw_node(NVGcontext *vg, struct ln *ln, struct node *node) { + if (!node->filtered) + return; + + nvgFontSize(vg, 18.0f); + nvgFontFace(vg, "sans"); + nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_TOP); + const float r = node->size; - /* const float pos = 500.0; */ - - /* const float h = r; */ - /* const float w = r; */ - /* const float x = pos; */ - /* const float y = pos; */ - /* const float kr = (int)(h * 0.35f); */ - /* const float cy = y + (int)(h * 0.5f); */ + float bounds[4]; + static const float pad = 2.0f; + + nvgTextBounds(vg, 0, 0, node->alias, NULL, &bounds); + NVGpaint bg; + /* if (streq(node->alias, "@jb55")) */ + /* printf("%f %f %f\n", node->color.r, */ + /* node->color.g, node->color.b); */ + NVGcolor node_color = - nvgRGBAf(node->color.r, node->color.g, node->color.b, - node->color.a); + nvgRGBf(node->color.r, node->color.g, node->color.b); + + NVGcolor node_color_inv = + nvgRGBf(1.0-node->color.r, 1.0-node->color.g, 1.0-node->color.b); static const float adj = 0.3f; @@ -105,7 +131,7 @@ void draw_node(NVGcontext *vg, struct ln *ln, struct node *node) const float light = 2.0f; const int dark_theme = ln->display_flags & DISP_DARK; - // TODO: use brightness instead of clear color for white theme + //TODO: use brightness instead of clear color for white theme if (!dark_theme) bg = nvgRadialGradient(vg, -light, -light, 0, r+2.0, ln->clear_color.nvg_color, @@ -127,12 +153,15 @@ void draw_node(NVGcontext *vg, struct ln *ln, struct node *node) nvgFill(vg); if (ln->display_flags & DISP_ALIASES) { - nvgFontSize(vg, 18.0f); - nvgFontFace(vg, "sans"); - nvgTextAlign(vg, NVG_ALIGN_LEFT | NVG_ALIGN_TOP); + + nvgBeginPath(vg); + nvgRoundedRect(vg, -bounds[2] / 2.0, bounds[3], bounds[2], bounds[3], 5.0); + nvgFillColor(vg, nvgRGBAf(node_color.r, node_color.g, node_color.b, 0.9)); + nvgFill(vg); + /* nvgTextMetrics(vg, NULL, NULL, &lineh); */ - nvgFillColor(vg, node_color); - nvgText(vg, -r, r, node->alias, NULL); + nvgFillColor(vg, node_color_inv); + nvgText(vg, -bounds[2] / 2.0, bounds[3], node->alias, NULL); } nvgRestore(vg); @@ -147,7 +176,7 @@ void render_ln(struct ln *ln) // render channels first for (i = 0; i < ln->channel_count; i++) - draw_channel(vg, &ln->channels[i]); + draw_channel(vg, ln, &ln->channels[i]); for (i = 0; i < ln->node_count; i++) draw_node(vg, ln, &ln->nodes[i]); diff --git a/update.c b/update.c @@ -31,7 +31,7 @@ void repel_nodes(struct node *n1, struct node *n2, double dt) { double d = sqrt(dx*dx + dy*dy); - static const double mindist = 100.0; + static const double mindist = 200.0; if (d < mindist) { // normalized vector between two nodes @@ -81,36 +81,39 @@ static void force_graph(struct ln *ln, double dt) { struct node *n1 = channel->nodes[0]; struct node *n2 = channel->nodes[1]; + if (!n1->filtered || !n2->filtered) + continue; + repel_nodes(n1, n2, dt); } } -/* static void repel_nearby(struct node *node, double dt) */ -/* { */ -/* struct node *n = NULL; */ -/* struct cell *cell = node->cell; */ +static void repel_nearby(struct node *node, double dt) +{ + struct node *n = NULL; + struct cell *cell = node->cell; -/* // might happen the first iteration? */ -/* if (cell == NULL) */ -/* return; */ + // might happen the first iteration? + if (cell == NULL) + return; -/* // we're the only one in this cell, there's nothing to repel */ -/* if (cell->node_count == 1) */ -/* return; */ + // we're the only one in this cell, there's nothing to repel + if (cell->node_count == 1) + return; -/* for (int i = 0; i < cell->node_count; ++i) { */ -/* n = cell->nodes[i]; */ + for (int i = 0; i < cell->node_count; ++i) { + n = cell->nodes[i]; -/* // dont repel against ourselves */ -/* if (n == node) */ -/* continue; */ + // dont repel against ourselves + if (n == node) + continue; -/* assert(n); */ -/* assert(node); */ -/* repel_nodes(n, node, dt); */ -/* } */ -/* } */ + assert(n); + assert(node); + repel_nodes(n, node, dt); + } +} static void physics(struct ln *ln, double dt) @@ -122,6 +125,9 @@ static void physics(struct ln *ln, double dt) for (u32 i = 0; i < ln->node_count; i++) { struct node *node = &ln->nodes[i]; + if (!node->filtered) + continue; + /* repel_nearby(node, dt); */ // semi-implicit euler @@ -150,11 +156,14 @@ void update(struct ln *ln, double dt) if (ln->clicked) { struct node *hit = hit_node(ln); ln->drag_target = hit; + ln->last_drag_target = hit; } // stop dragging - if (!ln->mdown && ln->drag_target) + if (!ln->mdown && ln->drag_target) { + ln->last_drag_target = ln->drag_target; ln->drag_target = NULL; + } // drag if (ln->mdown && ln->drag_target) { @@ -164,9 +173,9 @@ void update(struct ln *ln, double dt) ln->drag_target->vy = 0; } - force_graph(ln, dt); + /* force_graph(ln, dt); */ - physics(ln, dt); + /* physics(ln, dt); */ /* update_grid_move_nodes(ln); */ }