pay: handle case where we are head of blinded path, and next hop is scid.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2024-07-17 18:37:11 +09:30
parent f00f832b96
commit 91cd68920c
5 changed files with 138 additions and 17 deletions

View File

@ -36,6 +36,7 @@ u16 cltv_final;
bool offers_enabled;
bool disable_connect;
bool dev_invoice_bpath_scid;
struct short_channel_id *dev_invoice_internal_scid;
struct secret invoicesecret_base;
struct secret offerblinding_base;
static struct gossmap *global_gossmap;
@ -1437,6 +1438,26 @@ static const struct plugin_command commands[] = {
},
};
static char *scid_option(struct plugin *plugin, const char *arg, bool check_only,
struct short_channel_id **scidp)
{
struct short_channel_id scid;
if (!short_channel_id_from_str(arg, strlen(arg), &scid))
return tal_fmt(tmpctx, "'%s' is not an scid", arg);
*scidp = notleak(tal_dup(plugin, struct short_channel_id, &scid));
return NULL;
}
static bool scid_jsonfmt(struct plugin *plugin, struct json_stream *js, const char *fieldname,
struct short_channel_id **scid)
{
if (!*scid)
return false;
json_add_short_channel_id(js, fieldname, **scid);
return true;
}
int main(int argc, char *argv[])
{
@ -1453,7 +1474,10 @@ int main(int argc, char *argv[])
"Don't try to connect directly to fetch/pay an invoice.",
flag_option, flag_jsonfmt, &disable_connect),
plugin_option_dev("dev-invoice-bpath-scid", "flag",
"Use short_channel_id instead of pubkey when creating a blinded payment path",
"Use short_channel_id instead of pubkey when creating a blinded payment path",
flag_option, flag_jsonfmt, &dev_invoice_bpath_scid),
plugin_option_dev("dev-invoice-internal-scid", "string",
"Use short_channel_id instead of pubkey when creating a blinded payment path",
scid_option, scid_jsonfmt, &dev_invoice_internal_scid),
NULL);
}

View File

@ -23,6 +23,8 @@ extern struct secret invoicesecret_base;
extern struct secret offerblinding_base;
/* --dev-invoice-bpath-scid */
extern bool dev_invoice_bpath_scid;
/* --dev-invoice-internal-scid */
extern struct short_channel_id *dev_invoice_internal_scid;
/* This is me. */
extern struct pubkey id;

View File

@ -260,6 +260,7 @@ static struct command_result *found_best_peer(struct command *cmd,
} else {
struct tlv_encrypted_data_tlv **etlvs;
struct pubkey *ids;
struct short_channel_id **scids;
u32 base;
/* Make a small 1-hop path to us */
@ -267,8 +268,14 @@ static struct command_result *found_best_peer(struct command *cmd,
ids[0] = best->id;
ids[1] = id;
/* This does nothing unless dev_invoice_internal_scid is set */
scids = tal_arrz(tmpctx, struct short_channel_id *, 2);
scids[1] = dev_invoice_internal_scid;
/* Make basic tlvs, add payment restrictions */
etlvs = new_encdata_tlvs(tmpctx, ids, NULL);
etlvs = new_encdata_tlvs(tmpctx, ids,
cast_const2(const struct short_channel_id **,
scids));
/* Tell the first node what restrictions we have on relaying */
etlvs[0]->payment_relay = tal(etlvs[0],

View File

@ -1035,6 +1035,69 @@ start_payment(struct command *cmd, struct payment *p)
return send_outreq(cmd->plugin, req);
}
static bool scidtok_eq(const char *buf,
const jsmntok_t *scidtok,
struct short_channel_id scid)
{
struct short_channel_id scid_from_tok;
if (!scidtok)
return false;
if (!json_to_short_channel_id(buf, scidtok, &scid_from_tok))
return false;
return short_channel_id_eq(scid, scid_from_tok);
}
/* We are the entry point, so the next hop could actually be an scid alias,
* so we can't just use gossmap. */
static struct command_result *listpeerchannels_done(struct command *cmd,
const char *buf,
const jsmntok_t *result,
struct payment *p)
{
const jsmntok_t *arr, *t;
size_t i;
assert(!p->blindedpath->first_node_id.is_pubkey);
arr = json_get_member(buf, result, "channels");
json_for_each_arr(i, t, arr) {
const jsmntok_t *alias, *local_alias, *scid;
struct pubkey id;
alias = json_get_member(buf, t, "alias");
if (alias)
local_alias = json_get_member(buf, alias, "local");
else
local_alias = NULL;
scid = json_get_member(buf, t, "short_channel_id");
if (!scidtok_eq(buf, scid, p->blindedpath->first_node_id.scidd.scid)
&& !scidtok_eq(buf, local_alias, p->blindedpath->first_node_id.scidd.scid)) {
continue;
}
if (!json_to_pubkey(buf, json_get_member(buf, t, "peer_id"), &id))
plugin_err(cmd->plugin, "listpeerchannels no peer_id: %.*s",
json_tok_full_len(result),
json_tok_full(buf, result));
plugin_log(cmd->plugin, LOG_DBG,
"Mapped decrypted next hop from %s -> %s",
fmt_short_channel_id(tmpctx, p->blindedpath->first_node_id.scidd.scid),
fmt_pubkey(tmpctx, &id));
sciddir_or_pubkey_from_pubkey(&p->blindedpath->first_node_id, &id);
/* Fix up our destination */
node_id_from_pubkey(p->route_destination, &p->blindedpath->first_node_id.pubkey);
return start_payment(cmd, p);
}
return command_fail(cmd, PAY_UNPARSEABLE_ONION,
"Invalid next short_channel_id %s for second hop of onion",
fmt_short_channel_id(tmpctx,
p->blindedpath->first_node_id.scidd.scid));
}
static struct command_result *
decrypt_done(struct command *cmd,
const char *buf,
@ -1083,15 +1146,6 @@ decrypt_done(struct command *cmd,
return start_payment(cmd, p);
}
if (!enctlv->short_channel_id && !enctlv->next_node_id) {
return command_fail(cmd, PAY_UNPARSEABLE_ONION,
"Invalid TLV for blinded path (no next!): %s",
tal_hex(tmpctx, encdata));
}
if (!enctlv->next_node_id)
return command_fail(cmd, LIGHTNINGD, "FIXME: blinded path start by scidd!");
/* Promote second hop to first hop */
if (enctlv->next_blinding_override)
p->blindedpath->blinding = *enctlv->next_blinding_override;
@ -1102,12 +1156,29 @@ decrypt_done(struct command *cmd,
tal_free(p->blindedpath->path[0]);
tal_arr_remove(&p->blindedpath->path, 0);
sciddir_or_pubkey_from_pubkey(&p->blindedpath->first_node_id,
enctlv->next_node_id);
if (enctlv->next_node_id) {
sciddir_or_pubkey_from_pubkey(&p->blindedpath->first_node_id,
enctlv->next_node_id);
/* Fix up our destination */
node_id_from_pubkey(p->route_destination, &p->blindedpath->first_node_id.pubkey);
return start_payment(cmd, p);
/* Fix up our destination */
node_id_from_pubkey(p->route_destination, &p->blindedpath->first_node_id.pubkey);
return start_payment(cmd, p);
} else if (enctlv->short_channel_id) {
/* We need to resolve this: stash in first_node_id for now. */
struct out_req *req;
p->blindedpath->first_node_id.is_pubkey = false;
p->blindedpath->first_node_id.scidd.scid = *enctlv->short_channel_id;
req = jsonrpc_request_with_filter_start(cmd->plugin, cmd, "listpeerchannels",
"{\"channels\":[{\"peer_id\":true,\"short_channel_id\":true,\"alias\":{\"local\":true}}]}",
listpeerchannels_done, forward_error, p);
return send_outreq(cmd->plugin, req);
} else {
return command_fail(cmd, PAY_UNPARSEABLE_ONION,
"Invalid TLV for blinded path (no next!): %s",
tal_hex(tmpctx, encdata));
}
}
static struct command_result *

View File

@ -5804,7 +5804,8 @@ def test_onionmessage_ratelimit(node_factory):
def test_offer_path_self(node_factory):
"""We can fetch an offer, and pay an invoice which uses a blinded path starting at us"""
l1, l2, l3 = node_factory.line_graph(3, fundchannel=False,
opts={'experimental-offers': None})
opts={'experimental-offers': None,
'may_reconnect': True})
# Private channel from l2->l3, makes l3 add a hint.
node_factory.join_nodes([l1, l2], wait_for_announce=True)
@ -5826,6 +5827,22 @@ def test_offer_path_self(node_factory):
# And can pay it!
l2.rpc.pay(inv)
# We can also handle it if invoice has next hop specified by real scid, or alias.
scid = only_one(l2.rpc.listpeerchannels(l3.info['id'])['channels'])['alias']['local']
l3.stop()
l3.daemon.opts['dev-invoice-internal-scid'] = scid
l3.start()
l2.rpc.connect(l3.info['id'], 'localhost', l3.port)
inv = l2.rpc.fetchinvoice(offer['bolt12'])['invoice']
# And can pay it!
l2.rpc.pay(inv)
# It should have mapped the hop.
l2.daemon.wait_for_log(f"Mapped decrypted next hop from {scid} -> {l3.info['id']}")
def test_offer_selfpay(node_factory):
"""We can fetch an pay our own offer"""