mirror of
https://github.com/ElementsProject/lightning.git
synced 2024-11-19 01:43:36 +01:00
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:
parent
f00f832b96
commit
91cd68920c
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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],
|
||||
|
@ -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 *
|
||||
|
@ -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"""
|
||||
|
Loading…
Reference in New Issue
Block a user