Skip to content

Commit 7ef7c77

Browse files
committed
renepay: parse bolt12 invoices
A first step towards supporting bolt12 invoices and blinded paths. Changelog-None Signed-off-by: Lagrang3 <[email protected]>
1 parent 219623c commit 7ef7c77

File tree

5 files changed

+202
-223
lines changed

5 files changed

+202
-223
lines changed

plugins/renepay/main.c

Lines changed: 130 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ static struct command_result *json_pay(struct command *cmd, const char *buf,
168168
const char *invstr;
169169
struct amount_msat *msat;
170170
struct amount_msat *maxfee;
171+
struct amount_msat *inv_msat;
171172
u32 *maxdelay;
172173
u32 *retryfor;
173174
const char *description;
@@ -188,6 +189,9 @@ static struct command_result *json_pay(struct command *cmd, const char *buf,
188189
* than zero. */
189190
u64 *base_prob_success_millionths;
190191

192+
u64 invexpiry;
193+
struct sha256 *payment_hash = NULL;
194+
191195
if (!param(cmd, buf, params,
192196
p_req("invstring", param_invstring, &invstr),
193197
p_opt("amount_msat", param_msat, &msat),
@@ -205,9 +209,6 @@ static struct command_result *json_pay(struct command *cmd, const char *buf,
205209
p_opt("label", param_string, &label),
206210
p_opt("exclude", param_route_exclusion_array, &exclusions),
207211

208-
// FIXME add support for offers
209-
// p_opt("localofferid", param_sha256, &local_offer_id),
210-
211212
p_opt_dev("dev_use_shadow", param_bool, &use_shadow, true),
212213

213214
// MCF options
@@ -236,38 +237,56 @@ static struct command_result *json_pay(struct command *cmd, const char *buf,
236237

237238
/* === Parse invoice === */
238239

239-
// FIXME: add support for bolt12 invoices
240-
if (bolt12_has_prefix(invstr))
241-
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
242-
"BOLT12 invoices are not yet supported.");
243-
244240
char *fail;
245-
struct bolt11 *b11 =
246-
bolt11_decode(tmpctx, invstr, plugin_feature_set(cmd->plugin),
247-
description, chainparams, &fail);
248-
if (b11 == NULL)
249-
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
250-
"Invalid bolt11: %s", fail);
241+
struct bolt11 *b11;
242+
struct tlv_invoice *b12;
243+
244+
if (bolt12_has_prefix(invstr)) {
245+
b12 = invoice_decode(tmpctx, invstr, strlen(invstr),
246+
plugin_feature_set(cmd->plugin),
247+
chainparams, &fail);
248+
if (b12 == NULL)
249+
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
250+
"Invalid bolt12 invoice: %s", fail);
251251

252-
/* Sanity check */
253-
if (feature_offered(b11->features, OPT_VAR_ONION) &&
254-
!b11->payment_secret)
255-
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
256-
"Invalid bolt11:"
257-
" sets feature var_onion with no secret");
252+
invexpiry = invoice_expiry(b12);
253+
if (b12->invoice_amount) {
254+
inv_msat = tal(tmpctx, struct amount_msat);
255+
*inv_msat = amount_msat(*b12->invoice_amount);
256+
}
257+
payment_hash = b12->invoice_payment_hash;
258+
} else {
259+
b11 = bolt11_decode(tmpctx, invstr,
260+
plugin_feature_set(cmd->plugin),
261+
description, chainparams, &fail);
262+
if (b11 == NULL)
263+
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
264+
"Invalid bolt11 invoice: %s", fail);
258265

259-
if (b11->msat) {
260-
// amount is written in the invoice
261-
if (msat)
266+
/* Sanity check */
267+
if (feature_offered(b11->features, OPT_VAR_ONION) &&
268+
!b11->payment_secret)
262269
return command_fail(
263270
cmd, JSONRPC2_INVALID_PARAMS,
264-
"amount_msat parameter unnecessary");
265-
msat = b11->msat;
266-
} else {
267-
// amount is not written in the invoice
268-
if (!msat)
271+
"Invalid bolt11 invoice:"
272+
" sets feature var_onion with no secret");
273+
inv_msat = b11->msat;
274+
invexpiry = b11->timestamp + b11->expiry;
275+
payment_hash = &b11->payment_hash;
276+
}
277+
278+
/* === Set default values for non-trivial constraints === */
279+
280+
// Obtain amount from invoice or from arguments
281+
if (msat && inv_msat)
282+
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
283+
"amount_msat parameter cannot be specified "
284+
"on an invoice with an amount");
285+
if (!msat) {
286+
if (!inv_msat)
269287
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
270288
"amount_msat parameter required");
289+
msat = tal_dup(tmpctx, struct amount_msat, inv_msat);
271290
}
272291

273292
// Default max fee is 5 sats, or 0.5%, whichever is *higher*
@@ -277,46 +296,93 @@ static struct command_result *json_pay(struct command *cmd, const char *buf,
277296
fee = AMOUNT_MSAT(5000);
278297
maxfee = tal_dup(tmpctx, struct amount_msat, &fee);
279298
}
299+
assert(msat);
300+
assert(maxfee);
301+
assert(maxdelay);
302+
assert(retryfor);
303+
assert(use_shadow);
304+
assert(base_fee_penalty_millionths);
305+
assert(prob_cost_factor_millionths);
306+
assert(riskfactor_millionths);
307+
assert(min_prob_success_millionths);
308+
assert(base_prob_success_millionths);
309+
310+
/* === Is it expired? === */
280311

281312
const u64 now_sec = time_now().ts.tv_sec;
282-
if (now_sec > (b11->timestamp + b11->expiry))
313+
if (now_sec > invexpiry)
283314
return command_fail(cmd, PAY_INVOICE_EXPIRED,
284315
"Invoice expired");
285316

286317
/* === Get payment === */
287318

288319
// one payment_hash one payment is not assumed, it is enforced
320+
assert(payment_hash);
289321
struct payment *payment =
290-
payment_map_get(pay_plugin->payment_map, b11->payment_hash);
322+
payment_map_get(pay_plugin->payment_map, *payment_hash);
291323

292324
if(!payment)
293325
{
294-
payment = payment_new(
295-
tmpctx,
296-
&b11->payment_hash,
297-
take(invstr),
298-
take(label),
299-
take(description),
300-
b11->payment_secret,
301-
b11->metadata,
302-
cast_const2(const struct route_info**, b11->routes),
303-
&b11->receiver_id,
304-
*msat,
305-
*maxfee,
306-
*maxdelay,
307-
*retryfor,
308-
b11->min_final_cltv_expiry,
309-
*base_fee_penalty_millionths,
310-
*prob_cost_factor_millionths,
311-
*riskfactor_millionths,
312-
*min_prob_success_millionths,
313-
*base_prob_success_millionths,
314-
use_shadow,
315-
cast_const2(const struct route_exclusion**, exclusions));
316-
326+
payment = payment_new(tmpctx, payment_hash, invstr);
317327
if (!payment)
318328
return command_fail(cmd, PLUGIN_ERROR,
319329
"failed to create a new payment");
330+
331+
struct payment_info *pinfo = &payment->payment_info;
332+
pinfo->label = tal_strdup_or_null(payment, label);
333+
pinfo->description = tal_strdup_or_null(payment, description);
334+
335+
if (b11) {
336+
pinfo->payment_secret =
337+
tal_steal(payment, b11->payment_secret);
338+
pinfo->payment_metadata =
339+
tal_steal(payment, b11->metadata);
340+
pinfo->routehints = tal_steal(payment, b11->routes);
341+
pinfo->destination = b11->receiver_id;
342+
pinfo->final_cltv = b11->min_final_cltv_expiry;
343+
344+
pinfo->blinded_paths = NULL;
345+
pinfo->blinded_payinfos = NULL;
346+
} else {
347+
pinfo->payment_secret = NULL;
348+
pinfo->routehints = NULL;
349+
pinfo->payment_metadata = NULL;
350+
351+
pinfo->blinded_paths =
352+
tal_steal(payment, b12->invoice_paths);
353+
pinfo->blinded_payinfos =
354+
tal_steal(payment, b12->invoice_blindedpay);
355+
356+
node_id_from_pubkey(&pinfo->destination,
357+
b12->invoice_node_id);
358+
359+
/* FIXME: there is a different cltv_final for each
360+
* blinded path, can we send this information to
361+
* askrene? */
362+
u32 max_final_cltv = 0;
363+
for (size_t i = 0; i < tal_count(pinfo->blinded_payinfos);
364+
i++) {
365+
u32 final_cltv =
366+
pinfo->blinded_payinfos[i]->cltv_expiry_delta;
367+
if (max_final_cltv < final_cltv)
368+
max_final_cltv = final_cltv;
369+
}
370+
pinfo->final_cltv = max_final_cltv;
371+
}
372+
373+
if (!payment_set_constraints(
374+
payment, *msat, *maxfee, *maxdelay, *retryfor,
375+
*base_fee_penalty_millionths,
376+
*prob_cost_factor_millionths, *riskfactor_millionths,
377+
*min_prob_success_millionths,
378+
*base_prob_success_millionths, use_shadow,
379+
cast_const2(const struct route_exclusion **,
380+
exclusions)) ||
381+
!payment_refresh(payment))
382+
return command_fail(
383+
cmd, PLUGIN_ERROR,
384+
"failed to update the payment parameters");
385+
320386
if (!payment_register_command(payment, cmd))
321387
return command_fail(cmd, PLUGIN_ERROR,
322388
"failed to register command");
@@ -337,20 +403,17 @@ static struct command_result *json_pay(struct command *cmd, const char *buf,
337403
}
338404

339405
if (payment->status == PAYMENT_FAIL) {
340-
// FIXME: should we refuse to pay if the invoices are different?
341-
// or should we consider this a new payment?
342-
if (!payment_update(payment,
343-
*maxfee,
344-
*maxdelay,
345-
*retryfor,
346-
b11->min_final_cltv_expiry,
347-
*base_fee_penalty_millionths,
348-
*prob_cost_factor_millionths,
349-
*riskfactor_millionths,
350-
*min_prob_success_millionths,
351-
*base_prob_success_millionths,
352-
use_shadow,
353-
cast_const2(const struct route_exclusion**, exclusions)))
406+
// FIXME: fail if invstring does not match
407+
// FIXME: fail if payment_hash does not match
408+
if (!payment_set_constraints(
409+
payment, *msat, *maxfee, *maxdelay, *retryfor,
410+
*base_fee_penalty_millionths,
411+
*prob_cost_factor_millionths, *riskfactor_millionths,
412+
*min_prob_success_millionths,
413+
*base_prob_success_millionths, use_shadow,
414+
cast_const2(const struct route_exclusion **,
415+
exclusions)) ||
416+
!payment_refresh(payment))
354417
return command_fail(
355418
cmd, PLUGIN_ERROR,
356419
"failed to update the payment parameters");

plugins/renepay/mods.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,7 @@ static struct command_result *routehints_done(struct command *cmd UNUSED,
591591
assert(payment->local_gossmods);
592592

593593
const struct node_id *destination = &payment->payment_info.destination;
594-
const struct route_info **routehints = payment->payment_info.routehints;
594+
struct route_info **routehints = payment->payment_info.routehints;
595595
assert(routehints);
596596
const size_t nhints = tal_count(routehints);
597597
/* Hints are added to the local_gossmods. */

0 commit comments

Comments
 (0)