From b94be330e6dc92ccfd9b4b0b017f0df09f765968 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Mon, 13 May 2024 18:10:40 +0930 Subject: [PATCH] plugins: generalize "connect if we can't route" logic, link into offers plugin. We're going to dynamically connect if we need to, to reply to incoming invoice_requests. Signed-off-by: Rusty Russell --- plugins/Makefile | 6 +- plugins/establish_onion_path.c | 177 +++++++++++++++++++++++++++++++++ plugins/establish_onion_path.h | 46 +++++++++ plugins/fetchinvoice.c | 2 +- 4 files changed, 227 insertions(+), 4 deletions(-) create mode 100644 plugins/establish_onion_path.c create mode 100644 plugins/establish_onion_path.h diff --git a/plugins/Makefile b/plugins/Makefile index 306baa367..6468a9f80 100644 --- a/plugins/Makefile +++ b/plugins/Makefile @@ -31,11 +31,11 @@ PLUGIN_PAY_LIB_SRC := plugins/libplugin-pay.c PLUGIN_PAY_LIB_HEADER := plugins/libplugin-pay.h PLUGIN_PAY_LIB_OBJS := $(PLUGIN_PAY_LIB_SRC:.c=.o) -PLUGIN_OFFERS_SRC := plugins/offers.c plugins/offers_offer.c plugins/offers_invreq_hook.c plugins/offers_inv_hook.c +PLUGIN_OFFERS_SRC := plugins/offers.c plugins/offers_offer.c plugins/offers_invreq_hook.c plugins/offers_inv_hook.c plugins/establish_onion_path.c PLUGIN_OFFERS_OBJS := $(PLUGIN_OFFERS_SRC:.c=.o) PLUGIN_OFFERS_HEADER := $(PLUGIN_OFFERS_SRC:.c=.h) -PLUGIN_FETCHINVOICE_SRC := plugins/fetchinvoice.c +PLUGIN_FETCHINVOICE_SRC := plugins/fetchinvoice.c plugins/establish_onion_path.c PLUGIN_FETCHINVOICE_OBJS := $(PLUGIN_FETCHINVOICE_SRC:.c=.o) PLUGIN_FETCHINVOICE_HEADER := @@ -212,7 +212,7 @@ $(PLUGIN_KEYSEND_OBJS): $(PLUGIN_PAY_LIB_HEADER) plugins/spenderp: bitcoin/block.o bitcoin/preimage.o bitcoin/psbt.o common/psbt_open.o common/json_channel_type.o common/channel_type.o common/features.o wire/peer_wiregen.o $(PLUGIN_SPENDER_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) -plugins/offers: $(PLUGIN_OFFERS_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) common/addr.o common/bolt12.o common/bolt12_merkle.o common/bolt11_json.o common/iso4217.o $(WIRE_OBJS) $(WIRE_BOLT12_OBJS) bitcoin/block.o common/channel_id.o bitcoin/preimage.o common/blindedpath.o common/invoice_path_id.o common/blinding.o common/hmac.o common/json_blinded_path.o common/gossmap.o common/fp16.o $(JSMN_OBJS) +plugins/offers: $(PLUGIN_OFFERS_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) common/addr.o common/bolt12.o common/bolt12_merkle.o common/bolt11_json.o common/iso4217.o $(WIRE_OBJS) $(WIRE_BOLT12_OBJS) bitcoin/block.o common/channel_id.o bitcoin/preimage.o common/blindedpath.o common/invoice_path_id.o common/blinding.o common/hmac.o common/json_blinded_path.o common/gossmap.o common/fp16.o $(JSMN_OBJS) common/dijkstra.o common/route.o common/gossmods_listpeerchannels.o plugins/fetchinvoice: $(PLUGIN_FETCHINVOICE_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) common/bolt12.o common/bolt12_merkle.o common/iso4217.o $(WIRE_OBJS) $(WIRE_BOLT12_OBJS) bitcoin/block.o common/channel_id.o bitcoin/preimage.o $(JSMN_OBJS) common/gossmap.o common/fp16.o common/dijkstra.o common/route.o common/blindedpath.o common/hmac.o common/blinding.o common/gossmods_listpeerchannels.o diff --git a/plugins/establish_onion_path.c b/plugins/establish_onion_path.c new file mode 100644 index 000000000..161180552 --- /dev/null +++ b/plugins/establish_onion_path.c @@ -0,0 +1,177 @@ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include + +struct connect_info { + struct pubkey local_id, dst; + const char *connect_disable; + struct gossmap *gossmap; + struct command_result *(*cb)(struct command *, + const struct pubkey *, + void *arg); + struct command_result *(*fail)(struct command *, const char *, + void *arg); + void *arg; +}; + +static struct command_result *connect_ok(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct connect_info *ci) +{ + struct pubkey *path = tal_arr(tmpctx, struct pubkey, 2); + + /* Create direct mini-path */ + path[0] = ci->local_id; + path[1] = ci->dst; + + return ci->cb(cmd, path, ci->arg); +} + +static struct command_result *command_failed(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct connect_info *ci) +{ + return ci->fail(cmd, json_strdup(tmpctx, buf, result), ci->arg); +} + +static struct command_result *connect_direct(struct command *cmd, + struct connect_info *ci) +{ + struct out_req *req; + + if (ci->connect_disable) { + return ci->fail(cmd, + tal_fmt(tmpctx, "%s set: not initiating a new connection", + ci->connect_disable), + ci->arg); + } + + req = jsonrpc_request_start(cmd->plugin, cmd, + "connect", connect_ok, command_failed, ci); + json_add_pubkey(req->js, "id", &ci->dst); + return send_outreq(cmd->plugin, req); +} + +static bool can_carry_onionmsg(const struct gossmap *map, + const struct gossmap_chan *c, + int dir, + struct amount_msat amount UNUSED, + void *arg UNUSED) +{ + const struct gossmap_node *n; + /* Don't use it if either side says it's disabled */ + if (!c->half[dir].enabled || !c->half[!dir].enabled) + return false; + + /* Check features of recipient */ + n = gossmap_nth_node(map, c, !dir); + return gossmap_node_get_feature(map, n, OPT_ONION_MESSAGES) != -1; +} + +static const struct pubkey *path_to_node(const tal_t *ctx, + struct gossmap *gossmap, + struct plugin *plugin, + const char *buf, + const jsmntok_t *listpeerchannels, + const struct pubkey *local_id, + const struct pubkey *dst_key) +{ + struct route_hop *r; + const struct dijkstra *dij; + const struct gossmap_node *src; + const struct gossmap_node *dst; + struct pubkey *nodes; + struct gossmap_localmods *mods; + struct node_id local_nodeid, dst_nodeid; + + node_id_from_pubkey(&local_nodeid, local_id); + node_id_from_pubkey(&dst_nodeid, dst_key); + mods = gossmods_from_listpeerchannels(tmpctx, &local_nodeid, buf, listpeerchannels, + false, gossmod_add_localchan, NULL); + + gossmap_apply_localmods(gossmap, mods); + dst = gossmap_find_node(gossmap, &dst_nodeid); + if (!dst) + goto fail; + + /* If we don't exist in gossip, routing can't happen. */ + src = gossmap_find_node(gossmap, &local_nodeid); + if (!src) + goto fail; + + dij = dijkstra(tmpctx, gossmap, dst, AMOUNT_MSAT(0), 0, + can_carry_onionmsg, route_score_shorter, NULL); + + r = route_from_dijkstra(tmpctx, gossmap, dij, src, AMOUNT_MSAT(0), 0); + if (!r) + goto fail; + + nodes = tal_arr(ctx, struct pubkey, tal_count(r) + 1); + nodes[0] = *local_id; + for (size_t i = 0; i < tal_count(r); i++) { + if (!pubkey_from_node_id(&nodes[i+1], &r[i].node_id)) { + plugin_err(plugin, "Could not convert nodeid %s", + fmt_node_id(tmpctx, &r[i].node_id)); + } + } + + gossmap_remove_localmods(gossmap, mods); + return nodes; + +fail: + gossmap_remove_localmods(gossmap, mods); + return NULL; +} + +static struct command_result *listpeerchannels_done(struct command *cmd, + const char *buf, + const jsmntok_t *result, + struct connect_info *ci) +{ + const struct pubkey *path; + + path = path_to_node(tmpctx, ci->gossmap, cmd->plugin, buf, result, + &ci->local_id, &ci->dst); + if (!path) + return connect_direct(cmd, ci); + + return ci->cb(cmd, path, ci->arg); +} + +struct command_result *establish_onion_path_(struct command *cmd, + struct gossmap *gossmap, + const struct pubkey *local_id, + const struct pubkey *dst, + const char *connect_disable, + struct command_result *(*success)(struct command *, + const struct pubkey *, + void *arg), + struct command_result *(*fail)(struct command *, + const char *why, + void *arg), + void *arg) +{ + struct connect_info *ci = tal(cmd, struct connect_info); + struct out_req *req; + + ci->local_id = *local_id; + ci->dst = *dst; + ci->cb = success; + ci->fail = fail; + ci->arg = arg; + ci->connect_disable = connect_disable; + ci->gossmap = gossmap; + + req = jsonrpc_request_start(cmd->plugin, cmd, "listpeerchannels", + listpeerchannels_done, + command_failed, + ci); + return send_outreq(cmd->plugin, req); +} diff --git a/plugins/establish_onion_path.h b/plugins/establish_onion_path.h new file mode 100644 index 000000000..ad2900111 --- /dev/null +++ b/plugins/establish_onion_path.h @@ -0,0 +1,46 @@ +#ifndef LIGHTNING_PLUGINS_ESTABLISH_ONION_PATH_H +#define LIGHTNING_PLUGINS_ESTABLISH_ONION_PATH_H +#include "config.h" +#include +#include + +/** + * establish_onion_path: derive (or connect) a path to this peer. + * @cmd: the command context + * @gossmap: a gossip map to do lookup in + * @local_id: our own id + * @dst: the destination node + * @connect_disable: if non-null, the arg name which is disabling direct connection + * @success: the success callback + * @fail: the failure callback + * @arg: callback argument + * + * If it cannot find an onion-message-carrying path, will connect directly, + * unless connect_disable is non-NULL. + */ +struct command_result *establish_onion_path_(struct command *cmd, + struct gossmap *gossmap, + const struct pubkey *local_id, + const struct pubkey *dst, + const char *connect_disable, + struct command_result *(*success)(struct command *, + const struct pubkey *, + void *arg), + struct command_result *(*fail)(struct command *, + const char *why, + void *arg), + void *arg); + +#define establish_onion_path(cmd, gossmap, local_id, id, disable, success, fail, arg) \ + establish_onion_path_((cmd), (gossmap), (local_id), (id), (disable), \ + typesafe_cb_preargs(struct command_result *, void *, \ + (success), (arg), \ + struct command *, \ + const struct pubkey *), \ + typesafe_cb_preargs(struct command_result *, void *, \ + (fail), (arg), \ + struct command *, \ + const char *), \ + (arg)) + +#endif /* LIGHTNING_PLUGINS_ESTABLISH_ONION_PATH_H */ diff --git a/plugins/fetchinvoice.c b/plugins/fetchinvoice.c index 5b6cbd341..fdfcb4321 100644 --- a/plugins/fetchinvoice.c +++ b/plugins/fetchinvoice.c @@ -789,7 +789,7 @@ connect_direct(struct command *cmd, node_id_from_pubkey(&ca->node_id, dst); /* Make a direct path -> dst. */ - sent->path = tal_arr(sent, struct pubkey, 2); + sent->path = tal_arr(sent, struct pubkey, 2); sent->path[0] = local_id; if (!pubkey_from_node_id(&sent->path[1], &ca->node_id)) { /* Should not happen! */