renepay: parse bolt12 invoices

A first step towards supporting bolt12 invoices and blinded paths.

Changelog-None

Signed-off-by: Lagrang3 <lagrang3@protonmail.com>
This commit is contained in:
Lagrang3
2025-01-10 16:36:48 +01:00
committed by Rusty Russell
parent 4a280148f1
commit 1311223da5
5 changed files with 204 additions and 225 deletions

View File

@@ -169,6 +169,7 @@ static struct command_result *json_pay(struct command *cmd, const char *buf,
const char *invstr;
struct amount_msat *msat;
struct amount_msat *maxfee;
struct amount_msat *inv_msat = NULL;
u32 *maxdelay;
u32 *retryfor;
const char *description;
@@ -189,6 +190,9 @@ static struct command_result *json_pay(struct command *cmd, const char *buf,
* than zero. */
u64 *base_prob_success_millionths;
u64 invexpiry;
struct sha256 *payment_hash = NULL;
if (!param(cmd, buf, params,
p_req("invstring", param_invstring, &invstr),
p_opt("amount_msat", param_msat, &msat),
@@ -206,9 +210,6 @@ static struct command_result *json_pay(struct command *cmd, const char *buf,
p_opt("label", param_string, &label),
p_opt("exclude", param_route_exclusion_array, &exclusions),
// FIXME add support for offers
// p_opt("localofferid", param_sha256, &local_offer_id),
p_opt_dev("dev_use_shadow", param_bool, &use_shadow, true),
// MCF options
@@ -237,38 +238,56 @@ static struct command_result *json_pay(struct command *cmd, const char *buf,
/* === Parse invoice === */
// FIXME: add support for bolt12 invoices
if (bolt12_has_prefix(invstr))
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"BOLT12 invoices are not yet supported.");
char *fail;
struct bolt11 *b11 =
bolt11_decode(tmpctx, invstr, plugin_feature_set(cmd->plugin),
description, chainparams, &fail);
if (b11 == NULL)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Invalid bolt11: %s", fail);
struct bolt11 *b11 = NULL;
struct tlv_invoice *b12 = NULL;
/* Sanity check */
if (feature_offered(b11->features, OPT_VAR_ONION) &&
!b11->payment_secret)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Invalid bolt11:"
" sets feature var_onion with no secret");
if (bolt12_has_prefix(invstr)) {
b12 = invoice_decode(tmpctx, invstr, strlen(invstr),
plugin_feature_set(cmd->plugin),
chainparams, &fail);
if (b12 == NULL)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Invalid bolt12 invoice: %s", fail);
if (b11->msat) {
// amount is written in the invoice
if (msat)
invexpiry = invoice_expiry(b12);
if (b12->invoice_amount) {
inv_msat = tal(tmpctx, struct amount_msat);
*inv_msat = amount_msat(*b12->invoice_amount);
}
payment_hash = b12->invoice_payment_hash;
} else {
b11 = bolt11_decode(tmpctx, invstr,
plugin_feature_set(cmd->plugin),
description, chainparams, &fail);
if (b11 == NULL)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Invalid bolt11 invoice: %s", fail);
/* Sanity check */
if (feature_offered(b11->features, OPT_VAR_ONION) &&
!b11->payment_secret)
return command_fail(
cmd, JSONRPC2_INVALID_PARAMS,
"amount_msat parameter unnecessary");
msat = b11->msat;
} else {
// amount is not written in the invoice
if (!msat)
"Invalid bolt11 invoice:"
" sets feature var_onion with no secret");
inv_msat = b11->msat;
invexpiry = b11->timestamp + b11->expiry;
payment_hash = &b11->payment_hash;
}
/* === Set default values for non-trivial constraints === */
// Obtain amount from invoice or from arguments
if (msat && inv_msat)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"amount_msat parameter cannot be specified "
"on an invoice with an amount");
if (!msat) {
if (!inv_msat)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"amount_msat parameter required");
msat = tal_dup(tmpctx, struct amount_msat, inv_msat);
}
// Default max fee is 5 sats, or 0.5%, whichever is *higher*
@@ -278,46 +297,93 @@ static struct command_result *json_pay(struct command *cmd, const char *buf,
fee = AMOUNT_MSAT(5000);
maxfee = tal_dup(tmpctx, struct amount_msat, &fee);
}
assert(msat);
assert(maxfee);
assert(maxdelay);
assert(retryfor);
assert(use_shadow);
assert(base_fee_penalty_millionths);
assert(prob_cost_factor_millionths);
assert(riskfactor_millionths);
assert(min_prob_success_millionths);
assert(base_prob_success_millionths);
/* === Is it expired? === */
const u64 now_sec = time_now().ts.tv_sec;
if (now_sec > (b11->timestamp + b11->expiry))
if (now_sec > invexpiry)
return command_fail(cmd, PAY_INVOICE_EXPIRED,
"Invoice expired");
/* === Get payment === */
// one payment_hash one payment is not assumed, it is enforced
assert(payment_hash);
struct payment *payment =
payment_map_get(pay_plugin->payment_map, b11->payment_hash);
payment_map_get(pay_plugin->payment_map, *payment_hash);
if(!payment)
{
payment = payment_new(
tmpctx,
&b11->payment_hash,
take(invstr),
take(label),
take(description),
b11->payment_secret,
b11->metadata,
cast_const2(const struct route_info**, b11->routes),
&b11->receiver_id,
*msat,
*maxfee,
*maxdelay,
*retryfor,
b11->min_final_cltv_expiry,
*base_fee_penalty_millionths,
*prob_cost_factor_millionths,
*riskfactor_millionths,
*min_prob_success_millionths,
*base_prob_success_millionths,
use_shadow,
cast_const2(const struct route_exclusion**, exclusions));
payment = payment_new(tmpctx, payment_hash, invstr);
if (!payment)
return command_fail(cmd, PLUGIN_ERROR,
"failed to create a new payment");
struct payment_info *pinfo = &payment->payment_info;
pinfo->label = tal_strdup_or_null(payment, label);
pinfo->description = tal_strdup_or_null(payment, description);
if (b11) {
pinfo->payment_secret =
tal_steal(payment, b11->payment_secret);
pinfo->payment_metadata =
tal_steal(payment, b11->metadata);
pinfo->routehints = tal_steal(payment, b11->routes);
pinfo->destination = b11->receiver_id;
pinfo->final_cltv = b11->min_final_cltv_expiry;
pinfo->blinded_paths = NULL;
pinfo->blinded_payinfos = NULL;
} else {
pinfo->payment_secret = NULL;
pinfo->routehints = NULL;
pinfo->payment_metadata = NULL;
pinfo->blinded_paths =
tal_steal(payment, b12->invoice_paths);
pinfo->blinded_payinfos =
tal_steal(payment, b12->invoice_blindedpay);
node_id_from_pubkey(&pinfo->destination,
b12->invoice_node_id);
/* FIXME: there is a different cltv_final for each
* blinded path, can we send this information to
* askrene? */
u32 max_final_cltv = 0;
for (size_t i = 0; i < tal_count(pinfo->blinded_payinfos);
i++) {
u32 final_cltv =
pinfo->blinded_payinfos[i]->cltv_expiry_delta;
if (max_final_cltv < final_cltv)
max_final_cltv = final_cltv;
}
pinfo->final_cltv = max_final_cltv;
}
if (!payment_set_constraints(
payment, *msat, *maxfee, *maxdelay, *retryfor,
*base_fee_penalty_millionths,
*prob_cost_factor_millionths, *riskfactor_millionths,
*min_prob_success_millionths,
*base_prob_success_millionths, use_shadow,
cast_const2(const struct route_exclusion **,
exclusions)) ||
!payment_refresh(payment))
return command_fail(
cmd, PLUGIN_ERROR,
"failed to update the payment parameters");
if (!payment_register_command(payment, cmd))
return command_fail(cmd, PLUGIN_ERROR,
"failed to register command");
@@ -338,20 +404,17 @@ static struct command_result *json_pay(struct command *cmd, const char *buf,
}
if (payment->status == PAYMENT_FAIL) {
// FIXME: should we refuse to pay if the invoices are different?
// or should we consider this a new payment?
if (!payment_update(payment,
*maxfee,
*maxdelay,
*retryfor,
b11->min_final_cltv_expiry,
*base_fee_penalty_millionths,
*prob_cost_factor_millionths,
*riskfactor_millionths,
*min_prob_success_millionths,
*base_prob_success_millionths,
use_shadow,
cast_const2(const struct route_exclusion**, exclusions)))
// FIXME: fail if invstring does not match
// FIXME: fail if payment_hash does not match
if (!payment_set_constraints(
payment, *msat, *maxfee, *maxdelay, *retryfor,
*base_fee_penalty_millionths,
*prob_cost_factor_millionths, *riskfactor_millionths,
*min_prob_success_millionths,
*base_prob_success_millionths, use_shadow,
cast_const2(const struct route_exclusion **,
exclusions)) ||
!payment_refresh(payment))
return command_fail(
cmd, PLUGIN_ERROR,
"failed to update the payment parameters");

View File

@@ -584,7 +584,7 @@ static struct command_result *routehints_done(struct command *cmd UNUSED,
assert(payment->local_gossmods);
const struct node_id *destination = &payment->payment_info.destination;
const struct route_info **routehints = payment->payment_info.routehints;
struct route_info **routehints = payment->payment_info.routehints;
assert(routehints);
const size_t nhints = tal_count(routehints);
/* Hints are added to the local_gossmods. */

View File

@@ -12,110 +12,37 @@
static struct command_result *payment_finish(struct payment *p);
struct payment *payment_new(
const tal_t *ctx,
const struct sha256 *payment_hash,
const char *invstr TAKES,
const char *label TAKES,
const char *description TAKES,
const struct secret *payment_secret TAKES,
const u8 *payment_metadata TAKES,
const struct route_info **routehints TAKES,
const struct node_id *destination,
struct amount_msat amount,
struct amount_msat maxfee,
unsigned int maxdelay,
u64 retryfor,
u16 final_cltv,
/* Tweakable in --developer mode */
u64 base_fee_penalty_millionths,
u64 prob_cost_factor_millionths,
u64 riskfactor_millionths,
u64 min_prob_success_millionths,
u64 base_prob_success_millionths,
bool use_shadow,
const struct route_exclusion **exclusions)
struct payment *payment_new(const tal_t *ctx, const struct sha256 *payment_hash,
const char *invstr TAKES)
{
struct payment *p = tal(ctx, struct payment);
memset(p, 0, sizeof(struct payment));
struct payment_info *pinfo = &p->payment_info;
/* === Unique properties === */
assert(payment_hash);
pinfo->payment_hash = *payment_hash;
assert(invstr);
pinfo->invstr = tal_strdup(p, invstr);
pinfo->label = tal_strdup_or_null(p, label);
pinfo->description = tal_strdup_or_null(p, description);
pinfo->payment_secret = tal_dup_or_null(p, struct secret, payment_secret);
pinfo->payment_metadata = tal_dup_talarr(p, u8, payment_metadata);
if (taken(routehints))
pinfo->routehints = tal_steal(p, routehints);
else {
/* Deep copy */
pinfo->routehints =
tal_dup_talarr(p, const struct route_info *, routehints);
for (size_t i = 0; i < tal_count(pinfo->routehints); i++)
pinfo->routehints[i] =
tal_steal(pinfo->routehints, pinfo->routehints[i]);
}
assert(destination);
pinfo->destination = *destination;
pinfo->amount = amount;
/* === Payment attempt parameters === */
if (!amount_msat_add(&pinfo->maxspend, amount, maxfee))
pinfo->maxspend = AMOUNT_MSAT(UINT64_MAX);
pinfo->maxdelay = maxdelay;
pinfo->start_time = time_now();
pinfo->stop_time = timeabs_add(pinfo->start_time, time_from_sec(retryfor));
pinfo->final_cltv = final_cltv;
/* === Developer options === */
pinfo->base_fee_penalty = base_fee_penalty_millionths / 1e6;
pinfo->prob_cost_factor = prob_cost_factor_millionths / 1e6;
pinfo->delay_feefactor = riskfactor_millionths / 1e6;
pinfo->min_prob_success = min_prob_success_millionths / 1e6;
pinfo->base_prob_success = base_prob_success_millionths / 1e6;
pinfo->use_shadow = use_shadow;
/* === Public State === */
p->status = PAYMENT_PENDING;
p->preimage = NULL;
p->error_code = LIGHTNINGD;
p->error_msg = NULL;
p->total_sent = AMOUNT_MSAT(0);
p->total_delivering = AMOUNT_MSAT(0);
p->paynotes = tal_arr(p, const char *, 0);
p->paynotes = tal_arr(p, const char*, 0);
p->groupid = 1;
/* === Hidden State === */
p->exec_state = INVALID_STATE;
p->next_partid = 1;
p->cmd_array = tal_arr(p, struct command *, 0);
p->local_gossmods = NULL;
p->disabledmap = disabledmap_new(p);
for (size_t i = 0; i < tal_count(exclusions); i++) {
const struct route_exclusion *ex = exclusions[i];
if (ex->type == EXCLUDE_CHANNEL)
disabledmap_add_channel(p->disabledmap, ex->u.chan_id);
else
disabledmap_add_node(p->disabledmap, ex->u.node_id);
}
p->have_results = false;
p->retry = false;
p->waitresult_timer = NULL;
p->routetracker = new_routetracker(p, p);
return p;
}
@@ -137,46 +64,10 @@ static void payment_cleanup(struct payment *p)
routetracker_cleanup(p->routetracker);
}
bool payment_update(
struct payment *p,
struct amount_msat maxfee,
unsigned int maxdelay,
u64 retryfor,
u16 final_cltv,
/* Tweakable in --developer mode */
u64 base_fee_penalty_millionths,
u64 prob_cost_factor_millionths,
u64 riskfactor_millionths,
u64 min_prob_success_millionths,
u64 base_prob_success_millionths,
bool use_shadow,
const struct route_exclusion **exclusions)
{
/* Sets state values to ongoing payment */
bool payment_refresh(struct payment *p){
assert(p);
struct payment_info *pinfo = &p->payment_info;
/* === Unique properties === */
// unchanged
/* === Payment attempt parameters === */
if (!amount_msat_add(&pinfo->maxspend, pinfo->amount, maxfee))
pinfo->maxspend = AMOUNT_MSAT(UINT64_MAX);
pinfo->maxdelay = maxdelay;
pinfo->start_time = time_now();
pinfo->stop_time = timeabs_add(pinfo->start_time, time_from_sec(retryfor));
pinfo->final_cltv = final_cltv;
/* === Developer options === */
pinfo->base_fee_penalty = base_fee_penalty_millionths / 1e6;
pinfo->prob_cost_factor = prob_cost_factor_millionths / 1e6;
pinfo->delay_feefactor = riskfactor_millionths / 1e6;
pinfo->min_prob_success = min_prob_success_millionths / 1e6;
pinfo->base_prob_success = base_prob_success_millionths / 1e6;
pinfo->use_shadow = use_shadow;
/* === Public State === */
p->status = PAYMENT_PENDING;
@@ -185,13 +76,12 @@ bool payment_update(
assert(p->preimage == NULL);
p->error_code = LIGHTNINGD;
p->error_msg = tal_free(p->error_msg);;
p->error_msg = tal_free(p->error_msg);
p->total_sent = AMOUNT_MSAT(0);
p->total_delivering = AMOUNT_MSAT(0);
// p->paynotes are unchanged, they accumulate messages
p->groupid++;
/* === Hidden State === */
p->exec_state = INVALID_STATE;
p->next_partid = 1;
@@ -202,6 +92,49 @@ bool payment_update(
assert(tal_count(p->cmd_array) == 0);
p->local_gossmods = tal_free(p->local_gossmods);
p->have_results = false;
p->retry = false;
p->waitresult_timer = tal_free(p->waitresult_timer);
pinfo->start_time = time_now();
pinfo->stop_time =
timeabs_add(pinfo->start_time, time_from_sec(pinfo->retryfor));
return true;
}
bool payment_set_constraints(
struct payment *p,
struct amount_msat amount,
struct amount_msat maxfee,
unsigned int maxdelay,
u64 retryfor,
u64 base_fee_penalty_millionths,
u64 prob_cost_factor_millionths,
u64 riskfactor_millionths,
u64 min_prob_success_millionths,
u64 base_prob_success_millionths,
bool use_shadow,
const struct route_exclusion **exclusions)
{
assert(p);
struct payment_info *pinfo = &p->payment_info;
/* === Payment attempt parameters === */
pinfo->amount = amount;
if (!amount_msat_add(&pinfo->maxspend, pinfo->amount, maxfee))
pinfo->maxspend = AMOUNT_MSAT(UINT64_MAX);
pinfo->maxdelay = maxdelay;
pinfo->retryfor = retryfor;
/* === Developer options === */
pinfo->base_fee_penalty = base_fee_penalty_millionths / 1e6;
pinfo->prob_cost_factor = prob_cost_factor_millionths / 1e6;
pinfo->delay_feefactor = riskfactor_millionths / 1e6;
pinfo->min_prob_success = min_prob_success_millionths / 1e6;
pinfo->base_prob_success = base_prob_success_millionths / 1e6;
pinfo->use_shadow = use_shadow;
assert(p->disabledmap);
disabledmap_reset(p->disabledmap);
@@ -214,10 +147,6 @@ bool payment_update(
disabledmap_add_node(p->disabledmap, ex->u.node_id);
}
p->have_results = false;
p->retry = false;
p->waitresult_timer = tal_free(p->waitresult_timer);
return true;
}

View File

@@ -101,41 +101,23 @@ HTABLE_DEFINE_NODUPS_TYPE(struct payment, payment_hash, payment_hash64,
struct payment *payment_new(
const tal_t *ctx,
const struct sha256 *payment_hash,
const char *invstr TAKES,
const char *label TAKES,
const char *description TAKES,
const struct secret *payment_secret TAKES,
const u8 *payment_metadata TAKES,
const struct route_info **routehints TAKES,
const struct node_id *destination,
struct amount_msat amount,
struct amount_msat maxfee,
unsigned int maxdelay,
u64 retryfor,
u16 final_cltv,
/* Tweakable in --developer mode */
u64 base_fee_penalty_millionths,
u64 prob_cost_factor_millionths,
u64 riskfactor_millionths,
u64 min_prob_success_millionths,
u64 base_prob_success_millionths,
bool use_shadow,
const struct route_exclusion **exclusions);
const char *invstr TAKES);
bool payment_update(
struct payment *p,
struct amount_msat maxfee,
unsigned int maxdelay,
u64 retryfor,
u16 final_cltv,
/* Tweakable in --developer mode */
u64 base_fee_penalty_millionths,
u64 prob_cost_factor_millionths,
u64 riskfactor_millionths,
u64 min_prob_success_millionths,
u64 base_prob_success_millionths,
bool use_shadow,
const struct route_exclusion **exclusions);
bool payment_set_constraints(
struct payment *p,
struct amount_msat amount,
struct amount_msat maxfee,
unsigned int maxdelay,
u64 retryfor,
u64 base_fee_penalty_millionths,
u64 prob_cost_factor_millionths,
u64 riskfactor_millionths,
u64 min_prob_success_millionths,
u64 base_prob_success_millionths,
bool use_shadow,
const struct route_exclusion **exclusions);
bool payment_refresh(struct payment *p);
struct amount_msat payment_sent(const struct payment *p);
struct amount_msat payment_delivered(const struct payment *p);

View File

@@ -26,7 +26,11 @@ struct payment_info {
const u8 *payment_metadata;
/* Extracted routehints */
const struct route_info **routehints;
struct route_info **routehints;
/* blinded paths */
struct blinded_path **blinded_paths;
struct blinded_payinfo **blinded_payinfos;
/* How much, what, where */
struct node_id destination;
@@ -44,6 +48,7 @@ struct payment_info {
// see common/gossip_constants.h:8:#define ROUTING_MAX_HOPS 20
// int max_num_hops;
u64 retryfor;
/* We promised this in pay() output */
struct timeabs start_time;