Skip to content

Commit

Permalink
fetchinvoice: try to connect to note if we can't find a path for mess…
Browse files Browse the repository at this point in the history
…ages.

This also adds a `fetchinvoice-noconnect` option to suppress it too.

Signed-off-by: Rusty Russell <[email protected]>
Fixes: ElementsProject#4624
  • Loading branch information
rustyrussell committed Jun 30, 2021
1 parent 8167af5 commit 883bedf
Showing 1 changed file with 141 additions and 56 deletions.
197 changes: 141 additions & 56 deletions plugins/fetchinvoice.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

static struct gossmap *global_gossmap;
static struct node_id local_id;
static bool disable_connect = false;
static LIST_HEAD(sent_list);

struct sent {
Expand All @@ -37,6 +38,8 @@ struct sent {
struct command *cmd;
/* The offer we are trying to get an invoice/payment for. */
struct tlv_offer *offer;
/* Path to use. */
struct node_id *path;

/* The invreq we sent, OR the invoice we sent */
struct tlv_invoice_request *invreq;
Expand Down Expand Up @@ -516,41 +519,31 @@ static bool can_carry_onionmsg(const struct gossmap *map,
return n && gossmap_node_get_feature(map, n, OPT_ONION_MESSAGES) != -1;
}

static struct command_result *send_message(struct command *cmd,
struct sent *sent,
const char *msgfield,
const u8 *msgval,
struct command_result *(*done)
(struct command *cmd,
const char *buf UNUSED,
const jsmntok_t *result UNUSED,
struct sent *sent))
/* Create path to node which can carry onion messages; if it can't find
* one, create singleton path and sets @try_connect. */
static struct node_id *path_to_node(const tal_t *ctx,
struct gossmap *gossmap,
const struct pubkey32 *node32_id,
bool *try_connect)
{
const struct gossmap_node *dst;
struct gossmap *gossmap = get_gossmap(cmd->plugin);
struct pubkey *backwards;
struct onionmsg_path **path;
struct pubkey blinding;
struct out_req *req;
struct node_id dstid, *nodes;
struct node_id *nodes, dstid;

/* FIXME: Use blinded path if avail. */
gossmap_guess_node_id(gossmap, sent->offer->node_id, &dstid);
gossmap_guess_node_id(gossmap, node32_id, &dstid);
dst = gossmap_find_node(gossmap, &dstid);
if (!dst) {
/* Try direct. */
struct pubkey *us = tal_arr(tmpctx, struct pubkey, 1);
if (!pubkey_from_node_id(&us[0], &local_id))
abort();
backwards = us;

nodes = tal_arr(tmpctx, struct node_id, 1);
nodes = tal_arr(ctx, struct node_id, 1);
/* We don't know the pubkey y-sign, but sendonionmessage will
* fix it up if we guess wrong. */
nodes[0].k[0] = SECP256K1_TAG_PUBKEY_EVEN;
secp256k1_xonly_pubkey_serialize(secp256k1_ctx,
nodes[0].k+1,
&sent->offer->node_id->pubkey);
&node32_id->pubkey);
/* Since it's not it gossmap, we don't know how to connect,
* so don't try. */
*try_connect = false;
return nodes;
} else {
struct route_hop *r;
const struct dijkstra *dij;
Expand All @@ -559,37 +552,60 @@ static struct command_result *send_message(struct command *cmd,
/* If we don't exist in gossip, routing can't happen. */
src = gossmap_find_node(gossmap, &local_id);
if (!src)
return command_fail(cmd, PAY_ROUTE_NOT_FOUND,
"We don't have any channels");
goto go_direct_dst;

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)
/* FIXME: try connecting directly. */
return command_fail(cmd, OFFER_ROUTE_NOT_FOUND,
"Can't find route");

/* FIXME: Maybe we should allow this? */
if (tal_bytelen(r) == 0)
return command_fail(cmd, PAY_ROUTE_NOT_FOUND,
"Refusing to talk to ourselves");
goto go_direct_dst;

nodes = tal_arr(tmpctx, struct node_id, tal_count(r));
*try_connect = false;
nodes = tal_arr(ctx, struct node_id, tal_count(r));
for (size_t i = 0; i < tal_count(r); i++)
nodes[i] = r[i].node_id;
return nodes;
}

/* Reverse path is offset by one: we are the final node. */
backwards = tal_arr(tmpctx, struct pubkey, tal_count(r));
for (size_t i = 0; i < tal_count(r) - 1; i++) {
if (!pubkey_from_node_id(&backwards[tal_count(r)-2-i],
&nodes[i]))
abort();
}
if (!pubkey_from_node_id(&backwards[tal_count(r)-1], &local_id))
go_direct_dst:
/* Try direct route, maybe it's connected? */
nodes = tal_arr(ctx, struct node_id, 1);
gossmap_node_get_id(gossmap, dst, &nodes[0]);
*try_connect = true;
return nodes;
}

/* Send this message down this path, with blinded reply path */
static struct command_result *send_message(struct command *cmd,
struct sent *sent,
const char *msgfield,
const u8 *msgval,
struct command_result *(*done)
(struct command *cmd,
const char *buf UNUSED,
const jsmntok_t *result UNUSED,
struct sent *sent))
{
struct pubkey *backwards;
struct onionmsg_path **path;
struct pubkey blinding;
struct out_req *req;

/* FIXME: Maybe we should allow this? */
if (tal_bytelen(sent->path) == 0)
return command_fail(cmd, PAY_ROUTE_NOT_FOUND,
"Refusing to talk to ourselves");

/* Reverse path is offset by one: we are the final node. */
backwards = tal_arr(tmpctx, struct pubkey, tal_count(sent->path));
for (size_t i = 0; i < tal_count(sent->path) - 1; i++) {
if (!pubkey_from_node_id(&backwards[tal_count(sent->path)-2-i],
&sent->path[i]))
abort();
}
if (!pubkey_from_node_id(&backwards[tal_count(sent->path)-1], &local_id))
abort();

/* Ok, now make reply for onion_message */
path = make_blindedpath(tmpctx, backwards, &blinding,
Expand All @@ -600,10 +616,10 @@ static struct command_result *send_message(struct command *cmd,
forward_error,
sent);
json_array_start(req->js, "hops");
for (size_t i = 0; i < tal_count(nodes); i++) {
for (size_t i = 0; i < tal_count(sent->path); i++) {
json_object_start(req->js, NULL);
json_add_node_id(req->js, "id", &nodes[i]);
if (i == tal_count(nodes) - 1)
json_add_node_id(req->js, "id", &sent->path[i]);
if (i == tal_count(sent->path) - 1)
json_add_hex_talarr(req->js, msgfield, msgval);
json_object_end(req->js);
}
Expand Down Expand Up @@ -650,14 +666,60 @@ static struct command_result *prepare_inv_timeout(struct command *cmd,
return sendonionmsg_done(cmd, buf, result, sent);
}

/* We've connected (if we tried), so send the invreq. */
static struct command_result *
sendinvreq_after_connect(struct command *cmd,
const char *buf UNUSED,
const jsmntok_t *result UNUSED,
struct sent *sent)
{
u8 *rawinvreq = tal_arr(tmpctx, u8, 0);
towire_invoice_request(&rawinvreq, sent->invreq);

return send_message(cmd, sent, "invoice_request", rawinvreq,
sendonionmsg_done);
}

/* We can't find a route, so we're going to try to connect, then just blast it
* to them. */
static struct command_result *
connect_direct(struct command *cmd,
const struct node_id *dst,
struct command_result *(*cb)(struct command *command,
const char *buf,
const jsmntok_t *result,
struct sent *sent),
struct sent *sent)
{
struct out_req *req;

if (disable_connect) {
plugin_notify_message(cmd, LOG_UNUSUAL,
"Cannot find route, but"
" fetchplugin-noconnect set:"
" trying direct anyway to %s",
type_to_string(tmpctx, struct node_id,
dst));
return cb(cmd, NULL, NULL, sent);
}

plugin_notify_message(cmd, LOG_INFORM,
"Cannot find route, trying connect to %s directly",
type_to_string(tmpctx, struct node_id, dst));

req = jsonrpc_request_start(cmd->plugin, cmd, "connect", cb, cb, sent);
json_add_node_id(req->js, "id", dst);
return send_outreq(cmd->plugin, req);
}

static struct command_result *invreq_done(struct command *cmd,
const char *buf,
const jsmntok_t *result,
struct sent *sent)
{
const jsmntok_t *t;
char *fail;
u8 *rawinvreq;
bool try_connect;

/* Get invoice request */
t = json_get_member(buf, result, "bolt12");
Expand Down Expand Up @@ -750,10 +812,14 @@ static struct command_result *invreq_done(struct command *cmd,
}
}

rawinvreq = tal_arr(tmpctx, u8, 0);
towire_invoice_request(&rawinvreq, sent->invreq);
return send_message(cmd, sent, "invoice_request", rawinvreq,
sendonionmsg_done);
sent->path = path_to_node(sent, get_gossmap(cmd->plugin),
sent->offer->node_id,
&try_connect);
if (try_connect)
return connect_direct(cmd, &sent->path[0],
sendinvreq_after_connect, sent);

return sendinvreq_after_connect(cmd, NULL, NULL, sent);
}

/* Fetches an invoice for this offer, and makes sure it corresponds. */
Expand Down Expand Up @@ -987,14 +1053,26 @@ static struct command_result *invoice_payment(struct command *cmd,
return command_hook_success(cmd);
}

/* We've connected (if we tried), so send the invoice. */
static struct command_result *
sendinvoice_after_connect(struct command *cmd,
const char *buf UNUSED,
const jsmntok_t *result UNUSED,
struct sent *sent)
{
u8 *rawinv = tal_arr(tmpctx, u8, 0);
towire_invoice(&rawinv, sent->inv);
return send_message(cmd, sent, "invoice", rawinv, prepare_inv_timeout);
}

static struct command_result *createinvoice_done(struct command *cmd,
const char *buf,
const jsmntok_t *result,
struct sent *sent)
{
const jsmntok_t *invtok = json_get_member(buf, result, "bolt12");
char *fail;
u8 *rawinv;
bool try_connect;

/* Replace invoice with signed one */
tal_free(sent->inv);
Expand All @@ -1014,9 +1092,14 @@ static struct command_result *createinvoice_done(struct command *cmd,
"Bad createinvoice response %s", fail);
}

rawinv = tal_arr(tmpctx, u8, 0);
towire_invoice(&rawinv, sent->inv);
return send_message(cmd, sent, "invoice", rawinv, prepare_inv_timeout);
sent->path = path_to_node(sent, get_gossmap(cmd->plugin),
sent->offer->node_id,
&try_connect);
if (try_connect)
return connect_direct(cmd, &sent->path[0],
sendinvoice_after_connect, sent);

return sendinvoice_after_connect(cmd, NULL, NULL, sent);
}

static struct command_result *sign_invoice(struct command *cmd,
Expand Down Expand Up @@ -1377,6 +1460,8 @@ int main(int argc, char *argv[])
NULL, 0,
hooks, ARRAY_SIZE(hooks),
NULL, 0,
/* No options */
plugin_option("fetchinvoice-noconnect", "flag",
"Don't try to connect directly to fetch an invoice.",
flag_option, &disable_connect),
NULL);
}

0 comments on commit 883bedf

Please sign in to comment.