diff --git a/plugins/fetchinvoice.c b/plugins/fetchinvoice.c index c4dffa45f..064553b6d 100644 --- a/plugins/fetchinvoice.c +++ b/plugins/fetchinvoice.c @@ -548,42 +548,61 @@ static struct command_result *send_message(struct command *cmd, const jsmntok_t *result UNUSED, struct sent *sent)) { - const struct dijkstra *dij; - const struct gossmap_node *dst, *src; - struct route **r; + const struct gossmap_node *dst; struct gossmap *gossmap = get_gossmap(cmd->plugin); const struct pubkey *backwards; struct onionmsg_path **path; struct pubkey blinding; struct out_req *req; - struct node_id dstid; + struct node_id dstid, *nodes; /* FIXME: Use blinded path if avail. */ gossmap_guess_node_id(gossmap, sent->offer->node_id, &dstid); dst = gossmap_find_node(gossmap, &dstid); - if (!dst) - return command_fail(cmd, LIGHTNINGD, - "Unknown destination %s", - type_to_string(tmpctx, struct node_id, - &dstid)); - - /* 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"); - - dij = dijkstra(tmpctx, gossmap, dst, AMOUNT_MSAT(0), 0, - can_carry_onionmsg, route_score_shorter, NULL); - - r = route_from_dijkstra(tmpctx, gossmap, dij, src); - if (!r) - /* FIXME: We need to retry kind of like keysend here... */ - return command_fail(cmd, OFFER_ROUTE_NOT_FOUND, - "Can't find route"); + 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); + /* 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); + } else { + struct route **r; + const struct dijkstra *dij; + const struct gossmap_node *src; + + /* 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"); + + dij = dijkstra(tmpctx, gossmap, dst, AMOUNT_MSAT(0), 0, + can_carry_onionmsg, route_score_shorter, NULL); + + r = route_from_dijkstra(tmpctx, gossmap, dij, src); + if (!r) + /* FIXME: try connecting directly. */ + return command_fail(cmd, OFFER_ROUTE_NOT_FOUND, + "Can't find route"); + + backwards = route_backwards(tmpctx, gossmap, r); + nodes = tal_arr(tmpctx, struct node_id, tal_count(r)); + for (size_t i = 0; i < tal_count(r); i++) { + gossmap_node_get_id(gossmap, + gossmap_nth_node(gossmap, r[i]->c, !r[i]->dir), + &nodes[i]); + } + } /* Ok, now make reply for onion_message */ - backwards = route_backwards(tmpctx, gossmap, r); path = make_blindedpath(tmpctx, backwards, &blinding, &sent->reply_blinding); @@ -592,15 +611,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(r); i++) { - struct node_id id; - + for (size_t i = 0; i < tal_count(nodes); i++) { json_object_start(req->js, NULL); - gossmap_node_get_id(gossmap, - gossmap_nth_node(gossmap, r[i]->c, !r[i]->dir), - &id); - json_add_node_id(req->js, "id", &id); - if (i == tal_count(r) - 1) + json_add_node_id(req->js, "id", &nodes[i]); + if (i == tal_count(nodes) - 1) json_add_hex_talarr(req->js, msgfield, msgval); json_object_end(req->js); } diff --git a/tests/test_pay.py b/tests/test_pay.py index 1070eb03d..1e734dd87 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -3930,6 +3930,13 @@ def test_fetchinvoice(node_factory, bitcoind): 'recurrence_counter': 2, 'recurrence_label': 'test recurrence'}) + # Check we can request invoice without a channel. + l4 = node_factory.get_node() + l4.rpc.connect(l2.info['id'], 'localhost', l2.port) + ret = l4.rpc.call('fetchinvoice', {'offer': offer3, + 'recurrence_counter': 0, + 'recurrence_label': 'test nochannel'}) + # Test timeout. l3.stop() with pytest.raises(RpcError, match='Timeout waiting for response'):