Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions common/features.c
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ static const struct feature_style feature_styles[] = {
.copy_style = { [INIT_FEATURE] = FEATURE_REPRESENT,
[NODE_ANNOUNCE_FEATURE] = FEATURE_REPRESENT,
[CHANNEL_FEATURE] = FEATURE_DONT_REPRESENT} },
{ OPT_BOLT11_REQUEST,
.copy_style = { [BOLT12_OFFER_FEATURE] = FEATURE_REPRESENT } },
};

struct dependency {
Expand Down
5 changes: 5 additions & 0 deletions common/features.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,9 @@ struct feature_set *feature_set_dup(const tal_t *ctx,

#define OPT_SHUTDOWN_WRONG_FUNDING 104

/* BOLT-fetch-bolt11 #9:
* | 2/3 | `option_bolt11_request` | Invoice request for a bolt11 invoice | 2 | | [BOLT #12][bolt12-bolt11-req] |
*/
#define OPT_BOLT11_REQUEST 2

#endif /* LIGHTNING_COMMON_FEATURES_H */
23 changes: 22 additions & 1 deletion contrib/msggen/msggen/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -13907,6 +13907,13 @@
],
"added": "v25.02"
},
"bolt11": {
"type": "boolean",
"description": [
"Indicates we should fetch a bolt11 invoice instead (EXPERIMENTAL)"
],
"added": "v26.09"
},
"dev_reply_path": {
"hidden": true
},
Expand All @@ -13925,7 +13932,7 @@
"invoice": {
"type": "string",
"description": [
"The BOLT12 invoice we fetched."
"The BOLT12 invoice we fetched (or BOLT11 if `bolt11` parameter was set)."
]
},
"changes": {
Expand Down Expand Up @@ -41740,6 +41747,13 @@
"BOLT #12 `invoice` payload."
]
},
"bolt11_invoice": {
"type": "string",
"added": "v26.09",
"description": [
"BOLT #11 invoice payload."
]
},
"invoice_error": {
"type": "hex",
"description": [
Expand Down Expand Up @@ -41925,6 +41939,13 @@
"BOLT #12 `invoice` payload."
]
},
"bolt11_invoice": {
"type": "string",
"added": "v26.09",
"description": [
"BOLT #11 invoice payload."
]
},
"invoice_error": {
"type": "hex",
"description": [
Expand Down
3 changes: 2 additions & 1 deletion contrib/pyln-client/pyln/client/lightning.py
Original file line number Diff line number Diff line change
Expand Up @@ -752,7 +752,7 @@ def feerates(self, style, urgent=None, normal=None, slow=None):

def fetchinvoice(self, offer, amount_msat=None, quantity=None, recurrence_counter=None,
recurrence_start=None, recurrence_label=None, timeout=None, payer_note=None,
payer_metadata=None, bip353=None):
payer_metadata=None, bip353=None, bolt11=None):
"""
Fetch an invoice for an offer.
"""
Expand All @@ -767,6 +767,7 @@ def fetchinvoice(self, offer, amount_msat=None, quantity=None, recurrence_counte
"payer_note": payer_note,
"payer_metadata": payer_metadata,
"bip353": bip353,
"bolt11": bolt11,
}
return self.call("fetchinvoice", payload)

Expand Down
9 changes: 8 additions & 1 deletion doc/schemas/fetchinvoice.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@
],
"added": "v25.02"
},
"bolt11": {
"type": "boolean",
"description": [
"Indicates we should fetch a bolt11 invoice instead (EXPERIMENTAL)"
],
"added": "v26.09"
},
"dev_reply_path": {
"hidden": true
},
Expand All @@ -94,7 +101,7 @@
"invoice": {
"type": "string",
"description": [
"The BOLT12 invoice we fetched."
"The BOLT12 invoice we fetched (or BOLT11 if `bolt11` parameter was set)."
]
},
"changes": {
Expand Down
7 changes: 7 additions & 0 deletions doc/schemas/hook/onion_message_recv.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,13 @@
"BOLT #12 `invoice` payload."
]
},
"bolt11_invoice": {
"type": "string",
"added": "v26.09",
"description": [
"BOLT #11 invoice payload."
]
},
"invoice_error": {
"type": "hex",
"description": [
Expand Down
7 changes: 7 additions & 0 deletions doc/schemas/hook/onion_message_recv_secret.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@
"BOLT #12 `invoice` payload."
]
},
"bolt11_invoice": {
"type": "string",
"added": "v26.09",
"description": [
"BOLT #11 invoice payload."
]
},
"invoice_error": {
"type": "hex",
"description": [
Expand Down
6 changes: 0 additions & 6 deletions lightningd/invoice.c
Original file line number Diff line number Diff line change
Expand Up @@ -1125,7 +1125,6 @@ static struct command_result *json_invoice(struct command *cmd,
struct jsonrpc_request *req;
struct plugin *plugin;
bool *hashonly;
const size_t inv_max_label_len = 128;
const jsmntok_t *dev_routes;

info = tal(cmd, struct invoice_info);
Expand All @@ -1151,11 +1150,6 @@ static struct command_result *json_invoice(struct command *cmd,
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"dev-routes requires --developer");

if (strlen(info->label->s) > inv_max_label_len) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Label '%s' over %zu bytes", info->label->s, inv_max_label_len);
}

if (strlen(desc_val) > BOLT11_FIELD_BYTE_LIMIT && !*hashonly) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Description greater than %d bytes "
Expand Down
5 changes: 5 additions & 0 deletions lightningd/onion_message.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ static void onion_message_serialize(struct onion_message_hook_payload *payload,
if (payload->om->invoice)
json_add_hex_talarr(stream, "invoice", payload->om->invoice);

if (payload->om->bolt11_invoice)
json_add_stringn(stream, "bolt11_invoice",
payload->om->bolt11_invoice,
tal_bytelen(payload->om->bolt11_invoice));

if (payload->om->invoice_error)
json_add_hex_talarr(stream, "invoice_error",
payload->om->invoice_error);
Expand Down
37 changes: 36 additions & 1 deletion plugins/fetchinvoice.c
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,12 @@ static struct command_result *handle_invreq_response(struct command *cmd,
u64 *expected_amount;
const struct recurrence *recurrence;

invtok = json_get_member(buf, om, "invoice");
/* Handle bolt11 case separately */
if (sent->invreq->invreq_bolt11) {
invtok = json_get_member(buf, om, "bolt11_invoice");
} else {
invtok = json_get_member(buf, om, "invoice");
}
if (!invtok) {
plugin_log(cmd->plugin, LOG_UNUSUAL,
"Neither invoice nor invoice_request_failed in reply %.*s",
Expand All @@ -185,6 +190,27 @@ static struct command_result *handle_invreq_response(struct command *cmd,
return command_hook_success(cmd);
}

if (sent->invreq->invreq_bolt11) {
const char *bolt11, *err;

/* Sanity check that they sent us a valid reply */
bolt11 = json_strdup(tmpctx, buf, invtok);
if (!bolt11_decode(tmpctx, bolt11,
plugin_feature_set(cmd->plugin),
NULL,
chainparams,
&err)) {
badfield = tal_fmt(tmpctx, "invoice: %s", err);
goto badinv;
}
out = jsonrpc_stream_success(sent->cmd);
json_add_string(out, "invoice", bolt11);
json_object_start(out, "changes");
json_object_end(out);
discard_result(command_finished(sent->cmd, out));
return command_hook_success(cmd);
}

invbin = json_tok_bin_from_hex(cmd, buf, invtok);
cursor = invbin;
len = tal_bytelen(invbin);
Expand Down Expand Up @@ -929,6 +955,7 @@ struct command_result *json_fetchinvoice(struct command *cmd,
u32 *timeout;
u64 *quantity;
u32 *recurrence_counter, *recurrence_start;
bool *bolt11;

if (!param_check(cmd, buffer, params,
p_req("offer", param_offer, &sent->offer),
Expand All @@ -941,6 +968,7 @@ struct command_result *json_fetchinvoice(struct command *cmd,
p_opt("payer_note", param_string, &payer_note),
p_opt("payer_metadata", param_bin_from_hex, &payer_metadata),
p_opt("bip353", param_bip353, &bip353),
p_opt_def("bolt11", param_bool, &bolt11, false),
p_opt("dev_path_use_scidd", param_dev_scidd, &sent->dev_path_use_scidd),
p_opt("dev_reply_path", param_dev_reply_path, &sent->dev_reply_path),
NULL))
Expand Down Expand Up @@ -1148,6 +1176,13 @@ struct command_result *json_fetchinvoice(struct command *cmd,
strlen(payer_note),
0);

if (*bolt11) {
if (!feature_offered(invreq->offer_features, OPT_BOLT11_REQUEST))
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Cannot request bolt11: offer does not implement it");
invreq->invreq_bolt11 = tal(invreq, struct tlv_invoice_request_invreq_bolt11);
}

/* If only checking, we're done now */
if (command_check_only(cmd))
return command_check_done(cmd);
Expand Down
121 changes: 118 additions & 3 deletions plugins/offers_invreq_hook.c
Original file line number Diff line number Diff line change
Expand Up @@ -224,9 +224,12 @@ static struct command_result *createinvoice_error(struct command *cmd,
JSON_SCAN(json_to_u32, &code),
JSON_SCAN_TAL(tmpctx, json_strdup, &status)) == NULL
&& code == INVOICE_LABEL_ALREADY_EXISTS) {
if (streq(status, "unpaid"))
return createinvoice_done(cmd, method, buf,
json_get_member(buf, err, "data"), ir);
if (streq(status, "unpaid")) {
const jsmntok_t *data = json_get_member(buf, err, "data");
if (!json_get_member(buf, data, "bolt12"))
return fail_invreq(cmd, ir, "invoice previously requested as a bolt11");
return createinvoice_done(cmd, method, buf, data, ir);
}
if (streq(status, "expired"))
return fail_invreq(cmd, ir, "invoice expired (cancelled?)");
}
Expand Down Expand Up @@ -823,6 +826,78 @@ static struct command_result *convert_currency(struct command *cmd,
return send_outreq(req);
}

static struct command_result *bolt11_invoice_done(struct command *cmd,
const char *method,
const char *buf,
const jsmntok_t *result,
struct invreq *ir)
{
struct tlv_onionmsg_tlv *payload;
const jsmntok_t *t = json_get_member(buf, result, "bolt11");

/* In case it was a bolt12 last time! */
if (!t)
return fail_invreq(cmd, ir, "invoice previously requested as a bolt12");

payload = tlv_onionmsg_tlv_new(tmpctx);
/* Not nul-terminated! */
payload->bolt11_invoice = tal_dup_arr(payload, char,
buf + t->start,
t->end - t->start,
0);
return send_onion_reply(cmd, ir->reply_path, payload);
}

static struct command_result *bolt11_listinvoices_done(struct command *cmd,
const char *method,
const char *buf,
const jsmntok_t *result,
struct invreq *ir)
{
const jsmntok_t *arr = json_get_member(buf, result, "invoices"), *inv, *status;

if (arr->size == 0)
return fail_internalerr(cmd, ir, "Listing invoices gave no invoice?");

inv = arr + 1;
status = json_get_member(buf, inv, "status");
if (!json_tok_streq(buf, status, "unpaid")) {
return fail_invreq(cmd, ir, "Invoice %.*s",
status->end - status->start,
buf + status->start);
}

/* If we hand it the first entry, it's exactly like we just generated it */
return bolt11_invoice_done(cmd, method, buf, inv, ir);
}

/* This can fail because they've already asked for it */
static struct command_result *bolt11_invoice_error(struct command *cmd,
const char *method,
const char *buf,
const jsmntok_t *err,
struct invreq *ir)
{
struct out_req *req;
u32 code;
if (json_scan(tmpctx, buf, err,
"{code:%}",
JSON_SCAN(json_to_u32, &code)) != NULL
|| code != INVOICE_LABEL_ALREADY_EXISTS) {
return fail_internalerr(cmd, ir,
"Bad invoice error %.*s",
json_tok_full_len(err),
json_tok_full(buf, err));
}

req = jsonrpc_request_start(cmd, "listinvoices",
bolt11_listinvoices_done,
error,
ir);
json_add_label(req->js, &ir->offer_id, ir->invreq->invreq_payer_id, 0);
return send_outreq(req);
}

static struct command_result *listoffers_done(struct command *cmd,
const char *method,
const char *buf,
Expand Down Expand Up @@ -1043,6 +1118,46 @@ static struct command_result *listoffers_done(struct command *cmd,
return err;
}

/* BOLT-fetch-bolt11 #12:
* - if `invreq_bolt11` is present:
* - if `offer_features` does not contain `option_bolt11_request`:
* - MUST reject the invoice request.
* - if `invreq_quantity` is present:
* - MUST reject the invoice request.
* - SHOULD create a BOLT 11 invoice and place it in the
* `bolt11_invoice` field of a response `onionmsg_tlv`, sent using
* the `onionmsg_tlv` `reply_path`.
*/
if (ir->invreq->invreq_bolt11) {
struct out_req *req;
if (!feature_offered(ir->invreq->offer_features, OPT_BOLT11_REQUEST))
return fail_invreq(cmd, ir, "This offer is not bolt11 compatible");
if (ir->invreq->invreq_quantity)
return fail_invreq(cmd, ir, "Cannot have quantity with bolt11");
if (ir->invreq->offer_amount) {
if (ir->invreq->invreq_amount
&& *ir->invreq->offer_amount != *ir->invreq->invreq_amount)
return fail_invreq(cmd, ir, "Requested amount %s != offer amount %s",
fmt_amount_msat(tmpctx, amount_msat(*ir->invreq->invreq_amount)),
fmt_amount_msat(tmpctx, amount_msat(*ir->invreq->offer_amount)));
amt = amount_msat(*ir->invreq->offer_amount);
} else {
if (!ir->invreq->invreq_amount)
return fail_invreq(cmd, ir, "Missing invreq_amount");
amt = amount_msat(*ir->invreq->invreq_amount);
}
req = jsonrpc_request_start(cmd, "invoice",
bolt11_invoice_done,
bolt11_invoice_error,
ir);
json_add_amount_msat(req->js, "amount_msat", amt);
json_add_label(req->js, &ir->offer_id, ir->invreq->invreq_payer_id, 0);
json_add_stringn(req->js, "description",
ir->invreq->offer_description,
tal_bytelen(ir->invreq->offer_description));
return send_outreq(req);
}

/* BOLT #12:
* A writer of an invoice:
*...
Expand Down
Loading
Loading