From 92b37ca3ac0b61d6e2a3e67235227d40e3287dbf Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Fri, 10 Jan 2025 16:36:48 +0100 Subject: [PATCH 01/19] renepay: parse bolt12 invoices A first step towards supporting bolt12 invoices and blinded paths. Changelog-None Signed-off-by: Lagrang3 --- plugins/renepay/main.c | 197 ++++++++++++++++++++++----------- plugins/renepay/mods.c | 2 +- plugins/renepay/payment.c | 167 ++++++++-------------------- plugins/renepay/payment.h | 52 +++------ plugins/renepay/payment_info.h | 7 +- 5 files changed, 202 insertions(+), 223 deletions(-) diff --git a/plugins/renepay/main.c b/plugins/renepay/main.c index c9b6e65bde92..5c7d683bfa69 100644 --- a/plugins/renepay/main.c +++ b/plugins/renepay/main.c @@ -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; + + 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); - /* 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"); + 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); - if (b11->msat) { - // amount is written in the invoice - if (msat) + /* 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"); diff --git a/plugins/renepay/mods.c b/plugins/renepay/mods.c index aa43fa1314c6..9f5941e6d9da 100644 --- a/plugins/renepay/mods.c +++ b/plugins/renepay/mods.c @@ -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. */ diff --git a/plugins/renepay/payment.c b/plugins/renepay/payment.c index 58c18f670a2e..02566b01cdff 100644 --- a/plugins/renepay/payment.c +++ b/plugins/renepay/payment.c @@ -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,13 +64,51 @@ static void payment_cleanup(struct payment *p) routetracker_cleanup(p->routetracker); } -bool payment_update( +/* Sets state values to ongoing payment */ +bool payment_refresh(struct payment *p){ + assert(p); + struct payment_info *pinfo = &p->payment_info; + /* === Public State === */ + p->status = PAYMENT_PENDING; + + /* I shouldn't be calling a payment_update on a payment that already + * succeed */ + assert(p->preimage == NULL); + + p->error_code = LIGHTNINGD; + 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; + + /* I shouldn't be calling a payment_update on a payment that has pending + * cmds. */ + assert(p->cmd_array); + 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, - u16 final_cltv, - /* Tweakable in --developer mode */ u64 base_fee_penalty_millionths, u64 prob_cost_factor_millionths, u64 riskfactor_millionths, @@ -155,18 +120,13 @@ bool payment_update( assert(p); struct payment_info *pinfo = &p->payment_info; - /* === Unique properties === */ - // unchanged - /* === 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->start_time = time_now(); - pinfo->stop_time = timeabs_add(pinfo->start_time, time_from_sec(retryfor)); - - pinfo->final_cltv = final_cltv; + pinfo->retryfor = retryfor; /* === Developer options === */ pinfo->base_fee_penalty = base_fee_penalty_millionths / 1e6; @@ -176,33 +136,6 @@ bool payment_update( pinfo->base_prob_success = base_prob_success_millionths / 1e6; pinfo->use_shadow = use_shadow; - - /* === Public State === */ - p->status = PAYMENT_PENDING; - - /* I shouldn't be calling a payment_update on a payment that already - * succeed */ - assert(p->preimage == NULL); - - p->error_code = LIGHTNINGD; - 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; - - /* I shouldn't be calling a payment_update on a payment that has pending - * cmds. */ - assert(p->cmd_array); - assert(tal_count(p->cmd_array) == 0); - - p->local_gossmods = tal_free(p->local_gossmods); - 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; } diff --git a/plugins/renepay/payment.h b/plugins/renepay/payment.h index d905841b8ac5..382da1a1d5a0 100644 --- a/plugins/renepay/payment.h +++ b/plugins/renepay/payment.h @@ -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); - -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); + const char *invstr TAKES); + +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); diff --git a/plugins/renepay/payment_info.h b/plugins/renepay/payment_info.h index 8906fc9285e9..c07bcdd29855 100644 --- a/plugins/renepay/payment_info.h +++ b/plugins/renepay/payment_info.h @@ -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; From b8108bab0f40fb28dde76ba93f093b6507c3293e Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Sat, 11 Jan 2025 11:33:26 +0100 Subject: [PATCH 02/19] renepay: enable routing through blinded paths Enable routing through blinded paths using fake channels in local gossmods. Changelog-None Signed-off-by: Lagrang3 --- plugins/renepay/main.c | 13 ++++ plugins/renepay/mods.c | 88 ++++++++++++++++++++++----- plugins/renepay/payment.h | 4 ++ plugins/renepay/route.c | 13 +++- plugins/renepay/route.h | 6 +- plugins/renepay/routebuilder.c | 4 +- plugins/renepay/routebuilder.h | 1 + plugins/renepay/test/run-bottleneck.c | 1 + plugins/renepay/test/run-testflow.c | 8 +-- 9 files changed, 115 insertions(+), 23 deletions(-) diff --git a/plugins/renepay/main.c b/plugins/renepay/main.c index 5c7d683bfa69..2bdd8bde8548 100644 --- a/plugins/renepay/main.c +++ b/plugins/renepay/main.c @@ -344,6 +344,7 @@ static struct command_result *json_pay(struct command *cmd, const char *buf, pinfo->blinded_paths = NULL; pinfo->blinded_payinfos = NULL; + payment->routing_destination = &pinfo->destination; } else { pinfo->payment_secret = NULL; pinfo->routehints = NULL; @@ -369,6 +370,18 @@ static struct command_result *json_pay(struct command *cmd, const char *buf, max_final_cltv = final_cltv; } pinfo->final_cltv = max_final_cltv; + + /* When dealing with BOLT12 blinded paths we compute the + * routing targeting a fake node to enable + * multi-destination minimum-cost-flow. Every blinded + * path entry node will be linked to this fake node + * using fake channels as well. */ + payment->routing_destination = + tal(payment, struct node_id); + if (!node_id_from_hexstr( + "02""0000000000000000000000000000000000000000000000000000000000000001", + 66, payment->routing_destination)) + abort(); } if (!payment_set_constraints( diff --git a/plugins/renepay/mods.c b/plugins/renepay/mods.c index 9f5941e6d9da..5efb80b8ede1 100644 --- a/plugins/renepay/mods.c +++ b/plugins/renepay/mods.c @@ -13,6 +13,7 @@ #include #include #include +#include #define INVALID_ID UINT32_MAX @@ -508,7 +509,9 @@ REGISTER_PAYMENT_MODIFIER(refreshgossmap, refreshgossmap_cb); static void add_hintchan(struct payment *payment, const struct node_id *src, const struct node_id *dst, u16 cltv_expiry_delta, const struct short_channel_id scid, u32 fee_base_msat, - u32 fee_proportional_millionths) + u32 fee_proportional_millionths, + const struct amount_msat *chan_htlc_min, + const struct amount_msat *chan_htlc_max) { assert(payment); assert(payment->local_gossmods); @@ -521,6 +524,12 @@ static void add_hintchan(struct payment *payment, const struct node_id *src, struct short_channel_id_dir scidd; /* We assume any HTLC is allowed */ struct amount_msat htlc_min = AMOUNT_MSAT(0), htlc_max = MAX_CAPACITY; + + if (chan_htlc_min) + htlc_min = *chan_htlc_min; + if (chan_htlc_max) + htlc_max = *chan_htlc_max; + struct amount_msat fee_base = amount_msat(fee_base_msat); bool enabled = true; scidd.scid = scid; @@ -597,7 +606,8 @@ static struct command_result *routehints_done(struct command *cmd UNUSED, add_hintchan(payment, &r[j].pubkey, end, r[j].cltv_expiry_delta, r[j].short_channel_id, r[j].fee_base_msat, - r[j].fee_proportional_millionths); + r[j].fee_proportional_millionths, + NULL, NULL); end = &r[j].pubkey; } } @@ -618,6 +628,8 @@ static struct command_result *routehints_done(struct command *cmd UNUSED, static struct command_result *routehints_cb(struct payment *payment) { + if (payment->payment_info.routehints == NULL) + return payment_continue(payment); struct command *cmd = payment_command(payment); assert(cmd); struct out_req *req = jsonrpc_request_start( @@ -629,6 +641,44 @@ static struct command_result *routehints_cb(struct payment *payment) REGISTER_PAYMENT_MODIFIER(routehints, routehints_cb); + +/***************************************************************************** + * blindedhints + * + * Similar to routehints but for bolt12 invoices: create fake channel that + * connect the blinded path entry point to the destination node. + */ + +static struct command_result *blindedhints_cb(struct payment *payment) +{ + if (payment->payment_info.blinded_paths == NULL) + return payment_continue(payment); + + struct payment_info *pinfo = &payment->payment_info; + struct short_channel_id scid; + struct node_id src; + + for (size_t i = 0; i < tal_count(pinfo->blinded_paths); i++) { + const struct blinded_payinfo *payinfo = + pinfo->blinded_payinfos[i]; + const struct blinded_path *path = pinfo->blinded_paths[i]; + + scid.u64 = i; // a fake scid + node_id_from_pubkey(&src, &path->first_node_id.pubkey); + + add_hintchan(payment, &src, payment->routing_destination, + payinfo->cltv_expiry_delta, scid, + payinfo->fee_base_msat, + payinfo->fee_proportional_millionths, + &payinfo->htlc_minimum_msat, + &payinfo->htlc_maximum_msat); + } + return payment_continue(payment); +} + +REGISTER_PAYMENT_MODIFIER(blindedhints, blindedhints_cb); + + /***************************************************************************** * compute_routes * @@ -687,6 +737,11 @@ static struct command_result *compute_routes_cb(struct payment *payment) * better to pass computed_routes as a reference? */ routetracker->computed_routes = tal_free(routetracker->computed_routes); + /* Send get_routes a note that it should discard the last hop because we + * are actually solving a multiple destinations problem. */ + bool blinded_destination = + payment->payment_info.blinded_paths != NULL; + // TODO: add an algorithm selector here /* We let this return an unlikely path, as it's better to try once than * simply refuse. Plus, models are not truth! */ @@ -694,7 +749,7 @@ static struct command_result *compute_routes_cb(struct payment *payment) routetracker, &payment->payment_info, &pay_plugin->my_id, - &payment->payment_info.destination, + payment->routing_destination, pay_plugin->gossmap, pay_plugin->uncertainty, payment->disabledmap, @@ -702,8 +757,10 @@ static struct command_result *compute_routes_cb(struct payment *payment) feebudget, &payment->next_partid, payment->groupid, + blinded_destination, &errcode, &err_msg); + /* Otherwise the error message remains a child of the routetracker. */ err_msg = tal_steal(tmpctx, err_msg); @@ -1223,20 +1280,21 @@ void *payment_virtual_program[] = { /*6*/ OP_CALL, &getmychannels_pay_mod, /*8*/ OP_CALL, &refreshgossmap_pay_mod, /*10*/ OP_CALL, &routehints_pay_mod, - /*12*/OP_CALL, &channelfilter_pay_mod, + /*12*/ OP_CALL, &blindedhints_pay_mod, + /*14*/OP_CALL, &channelfilter_pay_mod, // TODO shadow_additions /* do */ - /*14*/ OP_CALL, &pendingsendpays_pay_mod, - /*16*/ OP_CALL, &checktimeout_pay_mod, - /*18*/ OP_CALL, &refreshgossmap_pay_mod, - /*20*/ OP_CALL, &compute_routes_pay_mod, - /*22*/ OP_CALL, &send_routes_pay_mod, + /*16*/ OP_CALL, &pendingsendpays_pay_mod, + /*18*/ OP_CALL, &checktimeout_pay_mod, + /*20*/ OP_CALL, &refreshgossmap_pay_mod, + /*22*/ OP_CALL, &compute_routes_pay_mod, + /*24*/ OP_CALL, &send_routes_pay_mod, /*do*/ - /*24*/ OP_CALL, &sleep_pay_mod, - /*26*/ OP_CALL, &collect_results_pay_mod, + /*26*/ OP_CALL, &sleep_pay_mod, + /*28*/ OP_CALL, &collect_results_pay_mod, /*while*/ - /*28*/ OP_IF, ¬haveresults_pay_cond, (void *)24, + /*30*/ OP_IF, ¬haveresults_pay_cond, (void *)26, /* while */ - /*31*/ OP_IF, &retry_pay_cond, (void *)14, - /*34*/ OP_CALL, &end_pay_mod, /* safety net, default failure if reached */ - /*36*/ NULL}; + /*33*/ OP_IF, &retry_pay_cond, (void *)16, + /*36*/ OP_CALL, &end_pay_mod, /* safety net, default failure if reached */ + /*38*/ NULL}; diff --git a/plugins/renepay/payment.h b/plugins/renepay/payment.h index 382da1a1d5a0..e3d30e301c10 100644 --- a/plugins/renepay/payment.h +++ b/plugins/renepay/payment.h @@ -56,6 +56,10 @@ struct payment { /* Localmods to apply to gossip_map for our own use. */ struct gossmap_localmods *local_gossmods; + /* Routes will be computed to reach this node, could be a fake node that + * we use to handle multiple blinded paths. */ + struct node_id *routing_destination; + struct disabledmap *disabledmap; /* Flag to indicate wether we have collected enough results to make a diff --git a/plugins/renepay/route.c b/plugins/renepay/route.c index 06ac2526fa03..4b947e2c15ff 100644 --- a/plugins/renepay/route.c +++ b/plugins/renepay/route.c @@ -19,6 +19,7 @@ struct route *new_route(const tal_t *ctx, u32 groupid, route->amount = amount; route->amount_sent = amount_sent; + route->path_num = -1; return route; } @@ -32,7 +33,8 @@ struct route *new_route(const tal_t *ctx, u32 groupid, struct route *flow_to_route(const tal_t *ctx, u32 groupid, u32 partid, struct sha256 payment_hash, u32 final_cltv, struct gossmap *gossmap, - struct flow *flow) + struct flow *flow, + bool blinded_destination) { struct route *route = new_route(ctx, groupid, partid, payment_hash, @@ -67,6 +69,12 @@ struct route *flow_to_route(const tal_t *ctx, route->success_prob = flow->success_prob; route->amount = route->hops[pathlen - 1].amount; route->amount_sent = route->hops[0].amount; + + if (blinded_destination) { + route->path_num = route->hops[pathlen - 1].scid.u64; + tal_arr_remove(&route->hops, pathlen - 1); + } + return route; function_fail: @@ -85,7 +93,8 @@ struct route **flows_to_routes(const tal_t *ctx, for (size_t i = 0; i < N; i++) { routes[i] = flow_to_route(routes, groupid, partid++, - payment_hash, final_cltv, gossmap, flows[i]); + payment_hash, final_cltv, gossmap, flows[i], + false); if (!routes[i]) goto function_fail; } diff --git a/plugins/renepay/route.h b/plugins/renepay/route.h index 2aa9195980db..1425105d331a 100644 --- a/plugins/renepay/route.h +++ b/plugins/renepay/route.h @@ -68,6 +68,9 @@ struct route { /* result of waitsenday */ struct payment_result *result; + + /* blinded path index if any */ + int path_num; }; static inline struct routekey routekey(const struct sha256 *hash, u64 groupid, @@ -117,7 +120,8 @@ struct route *new_route(const tal_t *ctx, u32 groupid, struct route *flow_to_route(const tal_t *ctx, u32 groupid, u32 partid, struct sha256 payment_hash, u32 final_cltv, struct gossmap *gossmap, - struct flow *flow); + struct flow *flow, + bool blinded_destination); struct route **flows_to_routes(const tal_t *ctx, u32 groupid, u32 partid, diff --git a/plugins/renepay/routebuilder.c b/plugins/renepay/routebuilder.c index e128a881d64b..f4694b573e15 100644 --- a/plugins/renepay/routebuilder.c +++ b/plugins/renepay/routebuilder.c @@ -140,6 +140,7 @@ struct route **get_routes(const tal_t *ctx, u64 *next_partid, u64 groupid, + bool blinded_destination, enum jsonrpc_errcode *ecode, const char **fail) @@ -297,7 +298,8 @@ struct route **get_routes(const tal_t *ctx, struct route *r = flow_to_route( this_ctx, groupid, *next_partid, payment_info->payment_hash, - payment_info->final_cltv, gossmap, flows[i]); + payment_info->final_cltv, gossmap, flows[i], + blinded_destination); if (!r) { tal_report_error( diff --git a/plugins/renepay/routebuilder.h b/plugins/renepay/routebuilder.h index 2692d2a82adc..b7297e5410c4 100644 --- a/plugins/renepay/routebuilder.h +++ b/plugins/renepay/routebuilder.h @@ -23,6 +23,7 @@ struct route **get_routes(const tal_t *ctx, u64 *next_partid, u64 groupid, + bool blinded_destination, enum jsonrpc_errcode *ecode, const char **fail); diff --git a/plugins/renepay/test/run-bottleneck.c b/plugins/renepay/test/run-bottleneck.c index 9aa62115aebb..f22d45fbbf6e 100644 --- a/plugins/renepay/test/run-bottleneck.c +++ b/plugins/renepay/test/run-bottleneck.c @@ -263,6 +263,7 @@ int main(int argc, char *argv[]) /* feebudget */maxfee, &next_partid, groupid, + false, &errcode, &err_msg); diff --git a/plugins/renepay/test/run-testflow.c b/plugins/renepay/test/run-testflow.c index ffed14f7e55a..83ffe80e3766 100644 --- a/plugins/renepay/test/run-testflow.c +++ b/plugins/renepay/test/run-testflow.c @@ -691,7 +691,7 @@ static void test_flow_to_route(void) F->dirs[0]=0; deliver = AMOUNT_MSAT(250000000); F->amount = deliver; - route = flow_to_route(this_ctx, 1, 1, payment_hash, 0, gossmap, F); + route = flow_to_route(this_ctx, 1, 1, payment_hash, 0, gossmap, F, false); assert(route); assert(amount_msat_eq(route->hops[0].amount, deliver)); @@ -707,7 +707,7 @@ static void test_flow_to_route(void) F->dirs[1]=0; deliver = AMOUNT_MSAT(250000000); F->amount=deliver; - route = flow_to_route(this_ctx, 1, 1, payment_hash, 0, gossmap, F); + route = flow_to_route(this_ctx, 1, 1, payment_hash, 0, gossmap, F, false); assert(route); assert(amount_msat_eq(route->hops[0].amount, amount_msat(250050016))); @@ -725,7 +725,7 @@ static void test_flow_to_route(void) F->dirs[2]=0; deliver = AMOUNT_MSAT(250000000); F->amount=deliver; - route = flow_to_route(this_ctx, 1, 1, payment_hash, 0, gossmap, F); + route = flow_to_route(this_ctx, 1, 1, payment_hash, 0, gossmap, F, false); assert(route); assert(amount_msat_eq(route->hops[0].amount, amount_msat(250087534))); @@ -745,7 +745,7 @@ static void test_flow_to_route(void) F->dirs[3]=0; deliver = AMOUNT_MSAT(250000000); F->amount=deliver; - route = flow_to_route(this_ctx, 1, 1, payment_hash, 0, gossmap, F); + route = flow_to_route(this_ctx, 1, 1, payment_hash, 0, gossmap, F, false); assert(route); assert(amount_msat_eq(route->hops[0].amount, amount_msat(250112544))); From 87a8453e695da811dc4b7652252e703a5dbcb692 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Wed, 15 Jan 2025 14:39:03 +0100 Subject: [PATCH 03/19] renepay: add rpc that replaces sendpay Add an rpc to renepay that is similar to sendpay that handles BOLT11 and BOLT12 payments. This is not the most elegant solution but it is a workaround until we implement it into lightningd which has more development friction. Changelog-None. Signed-off-by: Lagrang3 --- plugins/renepay/Makefile | 4 +- plugins/renepay/main.c | 5 + plugins/renepay/sendpay.c | 435 ++++++++++++++++++++++++++++++++++++++ plugins/renepay/sendpay.h | 10 + 4 files changed, 453 insertions(+), 1 deletion(-) create mode 100644 plugins/renepay/sendpay.c create mode 100644 plugins/renepay/sendpay.h diff --git a/plugins/renepay/Makefile b/plugins/renepay/Makefile index 38406183df23..5300fecb0f04 100644 --- a/plugins/renepay/Makefile +++ b/plugins/renepay/Makefile @@ -10,6 +10,7 @@ PLUGIN_RENEPAY_SRC := \ plugins/renepay/routebuilder.c \ plugins/renepay/routetracker.c \ plugins/renepay/routefail.c \ + plugins/renepay/sendpay.c \ plugins/renepay/uncertainty.c \ plugins/renepay/mods.c \ plugins/renepay/errorcodes.c \ @@ -29,6 +30,7 @@ PLUGIN_RENEPAY_HDRS := \ plugins/renepay/routebuilder.h \ plugins/renepay/routetracker.h \ plugins/renepay/routefail.h \ + plugins/renepay/sendpay.h \ plugins/renepay/uncertainty.h \ plugins/renepay/mods.h \ plugins/renepay/errorcodes.h \ @@ -43,6 +45,6 @@ PLUGIN_ALL_HEADER += $(PLUGIN_RENEPAY_HDRS) # Make all plugins depend on all plugin headers, for simplicity. $(PLUGIN_RENEPAY_OBJS): $(PLUGIN_RENEPAY_HDRS) -plugins/cln-renepay: $(PLUGIN_RENEPAY_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS) bitcoin/chainparams.o common/gossmap.o common/gossmods_listpeerchannels.o common/fp16.o common/dijkstra.o common/bolt12.o common/bolt12_merkle.o common/sciddir_or_pubkey.o wire/bolt12_wiregen.o wire/onion_wiregen.o +plugins/cln-renepay: $(PLUGIN_RENEPAY_OBJS) $(PLUGIN_LIB_OBJS) $(PLUGIN_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS) bitcoin/chainparams.o common/gossmap.o common/gossmods_listpeerchannels.o common/fp16.o common/dijkstra.o common/bolt12.o common/bolt12_merkle.o common/sciddir_or_pubkey.o wire/bolt12_wiregen.o wire/onion_wiregen.o common/sphinx.o common/onion_encode.o common/hmac.o common/onionreply.o include plugins/renepay/test/Makefile diff --git a/plugins/renepay/main.c b/plugins/renepay/main.c index 2bdd8bde8548..f31f55a52a01 100644 --- a/plugins/renepay/main.c +++ b/plugins/renepay/main.c @@ -17,6 +17,7 @@ #include #include #include +#include #include // TODO(eduardo): notice that pending attempts performed with another @@ -459,6 +460,10 @@ static const struct plugin_command commands[] = { "renepay", json_pay }, + { + "renesendpay", + json_renesendpay + }, }; static const struct plugin_notification notifications[] = { diff --git a/plugins/renepay/sendpay.c b/plugins/renepay/sendpay.c new file mode 100644 index 000000000000..e3c14b8de7ba --- /dev/null +++ b/plugins/renepay/sendpay.c @@ -0,0 +1,435 @@ +#include "config.h" +#include +#include +#include +#include +#include + +static struct command_result *param_route_hops(struct command *cmd, + const char *name, + const char *buffer, + const jsmntok_t *tok, + struct route_hop **hops) +{ + size_t i; + const jsmntok_t *t; + const char *err; + + if (tok->type != JSMN_ARRAY) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "%s must be an array", name); + + *hops = tal_arr(cmd, struct route_hop, tok->size); + json_for_each_arr(i, t, tok) + { + struct amount_msat amount_msat; + struct node_id id; + struct short_channel_id channel; + unsigned delay, direction; + + err = json_scan(tmpctx, buffer, t, + "{amount_msat:%,id:%,channel:%,direction:%,delay:%}", + JSON_SCAN(json_to_msat, &amount_msat), + JSON_SCAN(json_to_node_id, &id), + JSON_SCAN(json_to_short_channel_id, &channel), + JSON_SCAN(json_to_number, &direction), + JSON_SCAN(json_to_number, &delay) + ); + if (err != NULL) { + return command_fail( + cmd, JSONRPC2_INVALID_PARAMS, + "Error parsing route_hop %s[%zu]: %s", name, i, + err); + } + + if (direction != 0 && direction != 1) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "direction must be either 0 or 1"); + + (*hops)[i].amount = amount_msat; + (*hops)[i].node_id = id; + (*hops)[i].delay = delay; + (*hops)[i].scid = channel; + (*hops)[i].direction = direction; + } + return NULL; +} + +static struct command_result *param_blinded_path(struct command *cmd, + const char *name, + const char *buffer, + const jsmntok_t *tok, + struct blinded_path **blinded_path) +{ + size_t i; + const jsmntok_t *t, *pathtok, *datatok; + const char *err; + + *blinded_path = tal(cmd, struct blinded_path); + err = json_scan( + tmpctx, buffer, tok, "{first_node_id:%,first_path_key:%}", + JSON_SCAN(json_to_pubkey, &(*blinded_path)->first_node_id.pubkey), + JSON_SCAN(json_to_pubkey, &(*blinded_path)->first_path_key)); + if (err != NULL) { + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Error parsing blinded_path %s: %s", name, + err); + } + pathtok = json_get_member(buffer, tok, "path"); + if (!pathtok) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "%s does not have a path", name); + if (pathtok->type != JSMN_ARRAY) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "path in %s must be an array", name); + + (*blinded_path)->path = + tal_arr(*blinded_path, struct blinded_path_hop *, pathtok->size); + json_for_each_arr(i, t, pathtok) + { + (*blinded_path)->path[i] = + tal((*blinded_path)->path, struct blinded_path_hop); + struct blinded_path_hop *hop = (*blinded_path)->path[i]; + + err = + json_scan(tmpctx, buffer, t, "{blinded_node_id:%}", + JSON_SCAN(json_to_pubkey, &hop->blinded_node_id)); + if (err != NULL) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Error parsing path[%zu]: %s", i, + err); + + datatok = + json_get_member(buffer, t, "encrypted_recipient_data"); + if (!datatok) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "Error parsing path[%zu]: unable " + "to get encrypted_recipient_data", + i); + hop->encrypted_recipient_data = + json_tok_bin_from_hex(hop, buffer, datatok); + } + return NULL; +} + +struct renesendpay { + struct route_hop *route; + struct sha256 payment_hash; + u64 groupid, partid; + + u32 final_cltv; + struct amount_msat total_amount; + struct amount_msat deliver_amount; + struct amount_msat sent_amount; + struct node_id destination; + + struct secret *payment_secret; + struct blinded_path *blinded_path; + + const char *invoice, *label, *description; + const u8 *metadata; + + struct secret *shared_secrets; + unsigned int blockheight; +}; + +static struct command_result *sendpay_rpc_failure(struct command *cmd, + const char *method UNUSED, + const char *buffer, + const jsmntok_t *toks, + struct renesendpay *renesendpay) +{ + const jsmntok_t *codetok = json_get_member(buffer, toks, "code"); + u32 errcode; + if (codetok != NULL) + json_to_u32(buffer, codetok, &errcode); + else + errcode = LIGHTNINGD; + + return command_fail( + cmd, errcode, "renesendpay failed to error in RPC: %.*s", + json_tok_full_len(toks), json_tok_full(buffer, toks)); +} + +static void sphinx_append_blinded_path(const tal_t *ctx, + struct sphinx_path *sp, + const struct blinded_path *blinded_path, + const struct amount_msat deliver, + const struct amount_msat total, + const u32 final_cltv) +{ + const size_t pathlen = tal_count(blinded_path->path); + bool ret; + + for (size_t i = 0; i < pathlen; i++) { + bool first = (i == 0); + bool final = (i == pathlen - 1); + + const struct blinded_path_hop *bhop = blinded_path->path[i]; + const u8 *payload = onion_blinded_hop( + ctx, final ? &deliver : NULL, final ? &total : NULL, + final ? &final_cltv : NULL, bhop->encrypted_recipient_data, + first ? &blinded_path->first_path_key : NULL); + // FIXME: better handle error here + ret = sphinx_add_hop_has_length( + sp, + first ? &blinded_path->first_node_id.pubkey + : &bhop->blinded_node_id, + take(payload)); + assert(ret); + } +} + +static void sphinx_append_final_hop(const tal_t *ctx, + struct sphinx_path *sp, + const struct secret *payment_secret, + const struct node_id *node, + const struct amount_msat deliver, + const struct amount_msat total, + const u32 final_cltv, + const u8 *payment_metadata) +{ + struct pubkey destination; + bool ret = pubkey_from_node_id(&destination, node); + assert(ret); + + const u8 *payload = onion_final_hop(ctx, deliver, final_cltv, total, + payment_secret, payment_metadata); + // FIXME: better handle error here + ret = sphinx_add_hop_has_length(sp, &destination, take(payload)); + assert(ret); +} + +static const u8 *create_onion(const tal_t *ctx, struct renesendpay *renesendpay) +{ + bool ret; + const tal_t *this_ctx = tal(ctx, tal_t); + struct pubkey node; + const u8 *payload; + const size_t pathlen = tal_count(renesendpay->route); + + struct sphinx_path *sp = + sphinx_path_new(this_ctx, renesendpay->payment_hash.u.u8, + sizeof(renesendpay->payment_hash.u.u8)); + + for (size_t i = 0; i < pathlen - 1; i++) { + /* Encrypted message is for node[i] but the data is hop[i+1], + * therein lays the problem with sendpay's API. */ + ret = pubkey_from_node_id(&node, &renesendpay->route[i].node_id); + assert(ret); + + struct route_hop *hop = &renesendpay->route[i + 1]; + payload = + onion_nonfinal_hop(this_ctx, &hop->scid, hop->amount, + hop->delay + renesendpay->blockheight); + // FIXME: better handle error here + ret = sphinx_add_hop_has_length(sp, &node, take(payload)); + assert(ret); + } + + const u32 final_cltv = renesendpay->final_cltv + renesendpay->blockheight; + if(renesendpay->blinded_path){ + sphinx_append_blinded_path(this_ctx, + sp, + renesendpay->blinded_path, + renesendpay->deliver_amount, + renesendpay->total_amount, + final_cltv); + }else{ + sphinx_append_final_hop(this_ctx, + sp, + renesendpay->payment_secret, + &renesendpay->destination, + renesendpay->deliver_amount, + renesendpay->total_amount, + final_cltv, + renesendpay->metadata); + } + + struct secret *shared_secrets; + struct onionpacket *packet = create_onionpacket( + this_ctx, sp, ROUTING_INFO_SIZE, &shared_secrets); + renesendpay->shared_secrets = tal_steal(renesendpay, shared_secrets); + + const u8 *onion = serialize_onionpacket(ctx, packet); + tal_free(this_ctx); + return onion; +} + +static struct command_result *sendonion_done(struct command *cmd, + const char *method UNUSED, + const char *buffer, + const jsmntok_t *toks, + struct renesendpay *renesendpay) +{ + const char *err; + u64 created_index; + u32 timestamp; + err = json_scan(tmpctx, buffer, toks, "{created_index:%,created_at:%}", + JSON_SCAN(json_to_u64, &created_index), + JSON_SCAN(json_to_u32, ×tamp)); + if (err) + return command_fail( + cmd, JSONRPC2_INVALID_PARAMS, + "renesendpay failed to read response from sendonion: %s", + err); + + struct json_stream *response = jsonrpc_stream_success(cmd); + json_add_string(response, "message", + "Monitor status with listpays or waitsendpay"); + + json_add_u64(response, "created_index", created_index); + json_add_u32(response, "created_at", timestamp); + json_add_sha256(response, "payment_hash", &renesendpay->payment_hash); + json_add_u64(response, "groupid", renesendpay->groupid); + json_add_u64(response, "partid", renesendpay->partid); + json_add_node_id(response, "destination", &renesendpay->destination); + json_add_amount_msat(response, "amount_sent_msat", + renesendpay->sent_amount); + json_add_amount_msat(response, "amount_delivered_msat", + renesendpay->deliver_amount); + json_add_amount_msat(response, "amount_total_msat", + renesendpay->total_amount); + json_add_string(response, "invoice", renesendpay->invoice); + + if (renesendpay->label) + json_add_string(response, "label", renesendpay->label); + if (renesendpay->description) + json_add_string(response, "description", + renesendpay->description); + if (renesendpay->metadata) + json_add_hex_talarr(response, "payment_metadata", + renesendpay->metadata); + + /* FIXME: shall we report the blinded path, secret and route used? */ + return command_finished(cmd, response); +} + +static struct command_result *waitblockheight_done(struct command *cmd, + const char *method UNUSED, + const char *buffer, + const jsmntok_t *toks, + struct renesendpay *renesendpay) +{ + const char *err; + err = json_scan(tmpctx, buffer, toks, "{blockheight:%}", + JSON_SCAN(json_to_u32, &renesendpay->blockheight)); + renesendpay->blockheight += 1; + if (err) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "renesendpay failed to read blockheight " + "from waitblockheight response."); + + const u8 *onion = create_onion(tmpctx, renesendpay); + struct out_req *req = jsonrpc_request_start( + cmd, "sendonion", sendonion_done, sendpay_rpc_failure, renesendpay); + json_add_hex_talarr(req->js, "onion", onion); + json_add_sha256(req->js, "payment_hash", &renesendpay->payment_hash); + json_add_u64(req->js, "partid", renesendpay->partid); + json_add_u64(req->js, "groupid", renesendpay->groupid); + json_add_node_id(req->js, "destination", &renesendpay->destination); + json_add_amount_msat(req->js, "amount_msat", renesendpay->deliver_amount); + + const struct route_hop *hop = &renesendpay->route[0]; + json_object_start(req->js, "first_hop"); + json_add_amount_msat(req->js, "amount_msat", hop->amount); + json_add_num(req->js, "delay", hop->delay + renesendpay->blockheight); + json_add_node_id(req->js, "id", &hop->node_id); + json_add_short_channel_id(req->js, "channel", hop->scid); + json_object_end(req->js); + + json_array_start(req->js, "shared_secrets"); + for (size_t i = 0; i < tal_count(renesendpay->shared_secrets); i++) { + json_add_secret(req->js, NULL, &renesendpay->shared_secrets[i]); + } + json_array_end(req->js); + + if (renesendpay->label) + json_add_string(req->js, "label", renesendpay->label); + if (renesendpay->description) + json_add_string(req->js, "description", + renesendpay->description); + if (renesendpay->invoice) + json_add_string(req->js, "bolt11", renesendpay->invoice); + + return send_outreq(req); +} + +struct command_result *json_renesendpay(struct command *cmd, + const char *buf, + const jsmntok_t *params) +{ + struct route_hop *route; + struct sha256 *payment_hash; + const char *invoice, *label, *description; + struct amount_msat *amount, *total_amount; + u64 *groupid, *partid; + u32 *final_cltv; + struct node_id *destination; + u8 *metadata; + + /* only used in the case of BOLT11 */ + struct secret *payment_secret; + + /* only used in the case of BOLT12 */ + struct blinded_path *blinded_path; + + if (!param(cmd, buf, params, + p_req("route", param_route_hops, &route), + p_req("payment_hash", param_sha256, &payment_hash), + p_req("groupid", param_u64, &groupid), + p_req("partid", param_u64, &partid), + p_req("amount_msat", param_msat, &amount), + p_req("total_amount_msat", param_msat, &total_amount), + p_req("destination", param_node_id, &destination), + p_req("final_cltv", param_u32, &final_cltv), + p_opt("payment_secret", param_secret, &payment_secret), + p_opt("blinded_path", param_blinded_path, &blinded_path), + p_opt("invoice", param_invstring, &invoice), + p_opt("label", param_string, &label), + p_opt("description", param_string, &description), + p_opt("metadata", param_bin_from_hex, &metadata), + NULL)) + return command_param_failed(); + + if (payment_secret && blinded_path) + return command_fail( + cmd, JSONRPC2_INVALID_PARAMS, + "A payment cannot have both a secret and a blinded path."); + if (!payment_secret && !blinded_path) + return command_fail( + cmd, JSONRPC2_INVALID_PARAMS, + "For a BOLT11 payment a payment_secret " + "must be specified and for a BOLT12 " + "payment a blinded_path must be specified."); + + plugin_log(cmd->plugin, LOG_DBG, "renesendpay called: %.*s", + json_tok_full_len(params), json_tok_full(buf, params)); + + struct renesendpay *renesendpay = tal(cmd, struct renesendpay); + renesendpay->route = tal_steal(renesendpay, route); + renesendpay->payment_hash = *payment_hash; + renesendpay->partid = *partid; + renesendpay->groupid = *groupid; + + renesendpay->sent_amount = renesendpay->route[0].amount; + renesendpay->total_amount = *total_amount; + renesendpay->deliver_amount = *amount; + renesendpay->final_cltv = *final_cltv; + + renesendpay->destination = *destination; + + renesendpay->payment_secret = tal_steal(renesendpay, payment_secret); + renesendpay->blinded_path = tal_steal(renesendpay, blinded_path); + + renesendpay->invoice = tal_steal(renesendpay, invoice); + renesendpay->label = tal_steal(renesendpay, label); + renesendpay->description = tal_steal(renesendpay, description); + renesendpay->metadata = tal_steal(renesendpay, metadata); + + struct out_req *req = + jsonrpc_request_start(cmd, "waitblockheight", waitblockheight_done, + sendpay_rpc_failure, renesendpay); + json_add_num(req->js, "blockheight", 0); + return send_outreq(req); +} diff --git a/plugins/renepay/sendpay.h b/plugins/renepay/sendpay.h new file mode 100644 index 000000000000..1012063368fe --- /dev/null +++ b/plugins/renepay/sendpay.h @@ -0,0 +1,10 @@ +#ifndef LIGHTNING_PLUGINS_RENEPAY_SENDPAY_H +#define LIGHTNING_PLUGINS_RENEPAY_SENDPAY_H + +#include "config.h" + +struct command_result *json_renesendpay(struct command *cmd, + const char *buf, + const jsmntok_t *params); + +#endif /* LIGHTNING_PLUGINS_RENEPAY_SENDPAY_H */ From bdeb22cecbc77bb714d3651c1e5fa010d93c73c8 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Thu, 16 Jan 2025 10:15:39 +0100 Subject: [PATCH 04/19] renepay: change of variable name for clarity Changing route.amount to route.amount_deliver for clarity. This variable hold the value that the route delivers to destination. Changelog-None Signed-off-by: Lagrang3 --- plugins/renepay/json.c | 2 +- plugins/renepay/route.c | 6 +++--- plugins/renepay/route.h | 12 +++++------- plugins/renepay/routebuilder.c | 3 ++- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/plugins/renepay/json.c b/plugins/renepay/json.c index 1e95165f70d9..419e61a52783 100644 --- a/plugins/renepay/json.c +++ b/plugins/renepay/json.c @@ -50,7 +50,7 @@ struct route *tal_route_from_json(const tal_t *ctx, const char *buf, goto fail; if (!json_to_sha256(buf, hashtok, &route->key.payment_hash)) goto fail; - if (!json_to_msat(buf, amttok, &route->amount)) + if (!json_to_msat(buf, amttok, &route->amount_deliver)) goto fail; if (!json_to_msat(buf, senttok, &route->amount_sent)) goto fail; diff --git a/plugins/renepay/route.c b/plugins/renepay/route.c index 4b947e2c15ff..d35483b40d9d 100644 --- a/plugins/renepay/route.c +++ b/plugins/renepay/route.c @@ -3,7 +3,7 @@ struct route *new_route(const tal_t *ctx, u32 groupid, u32 partid, struct sha256 payment_hash, - struct amount_msat amount, + struct amount_msat amount_deliver, struct amount_msat amount_sent) { struct route *route = tal(ctx, struct route); @@ -17,7 +17,7 @@ struct route *new_route(const tal_t *ctx, u32 groupid, route->success_prob = 0.0; route->result = NULL; - route->amount = amount; + route->amount_deliver = amount_deliver; route->amount_sent = amount_sent; route->path_num = -1; return route; @@ -67,7 +67,7 @@ struct route *flow_to_route(const tal_t *ctx, goto function_fail; } route->success_prob = flow->success_prob; - route->amount = route->hops[pathlen - 1].amount; + route->amount_deliver = route->hops[pathlen - 1].amount; route->amount_sent = route->hops[0].amount; if (blinded_destination) { diff --git a/plugins/renepay/route.h b/plugins/renepay/route.h index 1425105d331a..d23ebe1bca49 100644 --- a/plugins/renepay/route.h +++ b/plugins/renepay/route.h @@ -60,8 +60,10 @@ struct route { struct route_hop *hops; /* amounts are redundant here if we know the hops, however sometimes we - * don't know the hops, eg. by calling listsendpays */ - struct amount_msat amount, amount_sent; + * don't know the hops, eg. by calling listsendpays, or if we have + * blinded paths */ + struct amount_msat amount_sent; + struct amount_msat amount_deliver; /* Probability estimate (0-1) */ double success_prob; @@ -142,11 +144,7 @@ const char *fmt_route_path(const tal_t *ctx, const struct route *route); static inline struct amount_msat route_delivers(const struct route *route) { assert(route); - if (route->hops && tal_count(route->hops) > 0) - assert(amount_msat_eq( - route->amount, - route->hops[tal_count(route->hops) - 1].amount)); - return route->amount; + return route->amount_deliver; } static inline struct amount_msat route_sends(const struct route *route) { diff --git a/plugins/renepay/routebuilder.c b/plugins/renepay/routebuilder.c index f4694b573e15..eeb35e500d59 100644 --- a/plugins/renepay/routebuilder.c +++ b/plugins/renepay/routebuilder.c @@ -65,7 +65,8 @@ route_check_constraints(struct route *route, struct gossmap *gossmap, assert(route); assert(route->hops); const size_t pathlen = tal_count(route->hops); - if (!amount_msat_eq(route->amount, route->hops[pathlen - 1].amount)) + if (!amount_msat_eq(route->amount_deliver, + route->hops[pathlen - 1].amount)) return RENEPAY_PRECONDITION_ERROR; if (!amount_msat_eq(route->amount_sent, route->hops[0].amount)) return RENEPAY_PRECONDITION_ERROR; From b60e95f6df5e8fd85e70b7402738e58ee2929aec Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Fri, 17 Jan 2025 07:30:17 +0100 Subject: [PATCH 05/19] renepay: use our own sendpay rpc Use renesendpay to send the payment allowing to pay to BOLT12 invoices from a higher level interface. Changelog-Add: renepay: Add support for BOLT12 payments Signed-off-by: Lagrang3 --- plugins/renepay/routetracker.c | 71 +++++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 6 deletions(-) diff --git a/plugins/renepay/routetracker.c b/plugins/renepay/routetracker.c index 5309af1a8bf8..aae6a079ae1a 100644 --- a/plugins/renepay/routetracker.c +++ b/plugins/renepay/routetracker.c @@ -330,14 +330,73 @@ struct command_result *route_sendpay_request(struct command *cmd, struct route *route TAKES, struct payment *payment) { - struct out_req *req = - jsonrpc_request_start(cmd, "sendpay", - sendpay_done, sendpay_failed, route); - - json_add_route(req->js, route, payment); + const struct payment_info *pinfo = &payment->payment_info; + struct out_req *req = jsonrpc_request_start( + cmd, "renesendpay", sendpay_done, sendpay_failed, route); + + const size_t pathlen = tal_count(route->hops); + json_add_sha256(req->js, "payment_hash", &pinfo->payment_hash); + json_add_u64(req->js, "partid", route->key.partid); + json_add_u64(req->js, "groupid", route->key.groupid); + json_add_string(req->js, "invoice", pinfo->invstr); + json_add_node_id(req->js, "destination", &pinfo->destination); + json_add_amount_msat(req->js, "amount_msat", route->amount_deliver); + json_add_amount_msat(req->js, "total_amount_msat", pinfo->amount); + json_add_u32(req->js, "final_cltv", pinfo->final_cltv); + + if (pinfo->label) + json_add_string(req->js, "label", pinfo->label); + if (pinfo->description) + json_add_string(req->js, "description", pinfo->description); + + json_array_start(req->js, "route"); + /* An empty route means a payment to oneself, pathlen=0 */ + for (size_t j = 0; j < pathlen; j++) { + const struct route_hop *hop = &route->hops[j]; + json_object_start(req->js, NULL); + json_add_node_id(req->js, "id", &hop->node_id); + json_add_short_channel_id(req->js, "channel", hop->scid); + json_add_amount_msat(req->js, "amount_msat", hop->amount); + json_add_num(req->js, "direction", hop->direction); + json_add_u32(req->js, "delay", hop->delay); + json_add_string(req->js, "style", "tlv"); + json_object_end(req->js); + } + json_array_end(req->js); + + /* Either we have a payment_secret for BOLT11 or blinded_paths for + * BOLT12 */ + if (pinfo->payment_secret) + json_add_secret(req->js, "payment_secret", pinfo->payment_secret); + else { + assert(pinfo->blinded_paths); + const struct blinded_path *bpath = + pinfo->blinded_paths[route->path_num]; + + // FIXME: how can we support the case when the entry point is a + // scid? + assert(bpath->first_node_id.is_pubkey); + json_object_start(req->js, "blinded_path"); + json_add_pubkey(req->js, "first_node_id", + &bpath->first_node_id.pubkey); + json_add_pubkey(req->js, "first_path_key", + &bpath->first_path_key); + json_array_start(req->js, "path"); + for (size_t i = 0; i < tal_count(bpath->path); i++) { + const struct blinded_path_hop *hop = bpath->path[i]; + json_object_start(req->js, NULL); + json_add_pubkey(req->js, "blinded_node_id", + &hop->blinded_node_id); + json_add_hex_talarr(req->js, "encrypted_recipient_data", + hop->encrypted_recipient_data); + json_object_end(req->js); + } + json_array_end(req->js); + json_object_end(req->js); + } route_map_add(payment->routetracker->sent_routes, route); - if(taken(route)) + if (taken(route)) tal_steal(payment->routetracker->sent_routes, route); return send_outreq(req); } From 6857d9bec765c18aa2a31db127b9ba5031468f6d Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Fri, 17 Jan 2025 12:46:04 +0100 Subject: [PATCH 06/19] renepay: fix error handling Fix error handling since we moved from sendpay to sendonion rpc. With sendonion once a route fails we don't get the scid and node_id that failed along the route, so we have to deduce those from our own internal data. Changelog-None Signed-off-by: Lagrang3 --- plugins/renepay/routefail.c | 240 ++++++++++++++++++++++++++---------- tests/test_renepay.py | 2 +- 2 files changed, 174 insertions(+), 68 deletions(-) diff --git a/plugins/renepay/routefail.c b/plugins/renepay/routefail.c index 29d515349565..55e853dd09d6 100644 --- a/plugins/renepay/routefail.c +++ b/plugins/renepay/routefail.c @@ -126,18 +126,28 @@ static struct command_result *update_gossip_failure(struct command *cmd UNUSED, { assert(r); assert(r->payment); + assert(r->route->result->erring_index); + + const int index = *r->route->result->erring_index; + struct short_channel_id_dir scidd; + + if (r->route->result->erring_channel) { + scidd.scid = *r->route->result->erring_channel; + scidd.dir = *r->route->result->erring_direction; + } else if (r->route->hops) { + assert(index < tal_count(r->route->hops)); + const struct route_hop *hop = &r->route->hops[index]; + scidd.scid = hop->scid; + scidd.dir = hop->direction; + + } else /* don't have information to disable the erring channel */ + goto finish; - /* FIXME it might be too strong assumption that erring_channel should - * always be present here, but at least the documentation for - * waitsendpay says it is present in the case of error. */ - assert(r->route->result->erring_channel); - struct short_channel_id_dir scidd = { - .scid = *r->route->result->erring_channel, - .dir = *r->route->result->erring_direction}; payment_disable_chan( - r->payment, scidd, LOG_INFORM, - "addgossip failed (%.*s)", json_tok_full_len(result), - json_tok_full(buf, result)); + r->payment, scidd, LOG_INFORM, "addgossip failed (%.*s)", + json_tok_full_len(result), json_tok_full(buf, result)); + +finish: return update_gossip_done(cmd, method, buf, result, r); } @@ -188,6 +198,7 @@ static void route_final_error(struct route *route, enum jsonrpc_errcode error, route->final_msg = tal_strdup(route, what); } +/* FIXME: do proper error handling for BOLT12 */ static struct command_result *handle_failure(struct routefail *r) { /* BOLT #4: @@ -242,7 +253,12 @@ static struct command_result *handle_failure(struct routefail *r) if (route->hops) path_len = tal_count(route->hops); - assert(result->erring_index); + if (!result->erring_index) { + payment_note( + payment, LOG_UNUSUAL, + "The erring_index is unknown we skip error handling"); + goto finish; + } enum node_type node_type = UNKNOWN_NODE; if (route->hops) { @@ -254,23 +270,28 @@ static struct command_result *handle_failure(struct routefail *r) node_type = INTERMEDIATE_NODE; } - assert(result->erring_node); - switch (result->failcode) { // intermediate only case WIRE_INVALID_ONION_VERSION: case WIRE_INVALID_ONION_HMAC: case WIRE_INVALID_ONION_KEY: - if (node_type == FINAL_NODE) + switch (node_type) { + case FINAL_NODE: payment_note(payment, LOG_UNUSUAL, - "Final node %s reported strange " + "Final node reported strange " "error code %04x (%s)", - fmt_node_id(tmpctx, result->erring_node), result->failcode, onion_wire_name(result->failcode)); + break; + case ORIGIN_NODE: + case INTERMEDIATE_NODE: + case UNKNOWN_NODE: + break; + } case WIRE_INVALID_ONION_BLINDING: - if (node_type == FINAL_NODE) { + switch (node_type) { + case FINAL_NODE: /* these errors from a final node mean a permanent * failure */ route_final_error( @@ -278,52 +299,99 @@ static struct command_result *handle_failure(struct routefail *r) "Received error code %04x (%s) at final node.", result->failcode, onion_wire_name(result->failcode)); - } else if (node_type == INTERMEDIATE_NODE || - node_type == ORIGIN_NODE) { + + break; + case INTERMEDIATE_NODE: + case ORIGIN_NODE: + if (!route->hops) + break; + /* we disable the next node in the hop */ assert(*result->erring_index < path_len); payment_disable_node( - payment, - route->hops[*result->erring_index].node_id, LOG_DBG, - "received %s from previous hop", + payment, route->hops[*result->erring_index].node_id, + LOG_DBG, "received %s from previous hop", onion_wire_name(result->failcode)); + break; + case UNKNOWN_NODE: + break; } break; // final only case WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS: + switch (node_type) { + case FINAL_NODE: + route_final_error(route, PAY_DESTINATION_PERM_FAIL, + "Unknown invoice or wrong payment " + "details at destination."); + break; + case ORIGIN_NODE: + route_final_error( + route, PAY_UNSPECIFIED_ERROR, + "Error code %04x (%s) reported at the origin.", + failcode, + onion_wire_name(failcode)); + break; + case INTERMEDIATE_NODE: + if (!route->hops) + break; + payment_disable_node( + payment, + route->hops[*result->erring_index - 1].node_id, + LOG_INFORM, "received error %s", + onion_wire_name(failcode)); + break; + case UNKNOWN_NODE: + break; + } + break; case WIRE_FINAL_INCORRECT_HTLC_AMOUNT: case WIRE_FINAL_INCORRECT_CLTV_EXPIRY: - if (node_type == INTERMEDIATE_NODE) + switch (node_type) { + case INTERMEDIATE_NODE: payment_note(payment, LOG_UNUSUAL, - "Intermediate node %s reported strange " + "Intermediate node reported strange " "error code %04x (%s)", - fmt_node_id(tmpctx, result->erring_node), result->failcode, onion_wire_name(result->failcode)); + break; + case ORIGIN_NODE: + case FINAL_NODE: + case UNKNOWN_NODE: + break; + } case WIRE_PERMANENT_NODE_FAILURE: case WIRE_REQUIRED_NODE_FEATURE_MISSING: case WIRE_TEMPORARY_NODE_FAILURE: case WIRE_INVALID_ONION_PAYLOAD: - - if (node_type == FINAL_NODE) { + switch (node_type) { + case FINAL_NODE: route_final_error( route, PAY_DESTINATION_PERM_FAIL, "Received error code %04x (%s) at final node.", result->failcode, onion_wire_name(result->failcode)); - } else if (node_type == ORIGIN_NODE) { + break; + case ORIGIN_NODE: route_final_error( route, PAY_UNSPECIFIED_ERROR, "Error code %04x (%s) reported at the origin.", result->failcode, onion_wire_name(result->failcode)); - } else { - payment_disable_node(payment, - *result->erring_node, LOG_INFORM, - "received error %s", - onion_wire_name(result->failcode)); + break; + case INTERMEDIATE_NODE: + if (!route->hops) + break; + payment_disable_node( + payment, + route->hops[*result->erring_index - 1].node_id, + LOG_INFORM, "received error %s", + onion_wire_name(result->failcode)); + break; + case UNKNOWN_NODE: + break; } break; @@ -333,11 +401,11 @@ static struct command_result *handle_failure(struct routefail *r) case WIRE_UNKNOWN_NEXT_PEER: case WIRE_EXPIRY_TOO_FAR: case WIRE_CHANNEL_DISABLED: - if (node_type == FINAL_NODE) { + switch (node_type) { + case FINAL_NODE: payment_note(payment, LOG_UNUSUAL, - "Final node %s reported strange " + "Final node reported strange " "error code %04x (%s)", - fmt_node_id(tmpctx, result->erring_node), result->failcode, onion_wire_name(result->failcode)); @@ -347,35 +415,56 @@ static struct command_result *handle_failure(struct routefail *r) result->failcode, onion_wire_name(result->failcode)); - } else { - assert(result->erring_channel); + break; + case ORIGIN_NODE: + payment_note(payment, LOG_UNUSUAL, + "First node reported strange " + "error code %04x (%s)", + result->failcode, + onion_wire_name(result->failcode)); + + break; + case INTERMEDIATE_NODE: + if (!route->hops) + break; struct short_channel_id_dir scidd = { - .scid = *result->erring_channel, - .dir = *result->erring_direction}; - payment_disable_chan( - payment, scidd, LOG_INFORM, - "%s", onion_wire_name(result->failcode)); + .scid = route->hops[*result->erring_index].scid, + .dir = route->hops[*result->erring_index].direction}; + payment_disable_chan(payment, scidd, LOG_INFORM, "%s", + onion_wire_name(result->failcode)); + + break; + case UNKNOWN_NODE: + break; } break; // final only case WIRE_MPP_TIMEOUT: - - if (node_type == INTERMEDIATE_NODE) { + switch (node_type) { + case INTERMEDIATE_NODE: /* Normally WIRE_MPP_TIMEOUT is raised by the final * node. If this is not the final node, then something * wrong is going on. We report it and disable that * node. */ payment_note(payment, LOG_UNUSUAL, - "Intermediate node %s reported strange " + "Intermediate node reported strange " "error code %04x (%s)", - fmt_node_id(tmpctx, result->erring_node), result->failcode, onion_wire_name(result->failcode)); - payment_disable_node(payment, - *result->erring_node, LOG_INFORM, - "received error %s", - onion_wire_name(result->failcode)); + if (!route->hops) + break; + payment_disable_node( + payment, + route->hops[*result->erring_index - 1].node_id, + LOG_INFORM, "received error %s", + onion_wire_name(result->failcode)); + + break; + case ORIGIN_NODE: + case FINAL_NODE: + case UNKNOWN_NODE: + break; } break; @@ -384,12 +473,11 @@ static struct command_result *handle_failure(struct routefail *r) case WIRE_INCORRECT_CLTV_EXPIRY: case WIRE_FEE_INSUFFICIENT: case WIRE_AMOUNT_BELOW_MINIMUM: - - if (node_type == FINAL_NODE) { + switch (node_type) { + case FINAL_NODE: payment_note(payment, LOG_UNUSUAL, - "Final node %s reported strange " + "Final node reported strange " "error code %04x (%s)", - fmt_node_id(tmpctx, result->erring_node), result->failcode, onion_wire_name(result->failcode)); @@ -399,33 +487,44 @@ static struct command_result *handle_failure(struct routefail *r) result->failcode, onion_wire_name(result->failcode)); - } else { + break; + case ORIGIN_NODE: + payment_note(payment, LOG_UNUSUAL, + "First node reported strange " + "error code %04x (%s)", + result->failcode, + onion_wire_name(result->failcode)); + + break; + case INTERMEDIATE_NODE: + if (!route->hops) + break; /* Usually this means we need to update the channel * information and try again. To avoid hitting this * error again with the same channel we flag it. */ - assert(result->erring_channel); struct short_channel_id_dir scidd = { - .scid = *result->erring_channel, - .dir = *result->erring_direction}; - payment_warn_chan(payment, - scidd, LOG_INFORM, + .scid = route->hops[*result->erring_index].scid, + .dir = route->hops[*result->erring_index].direction}; + payment_warn_chan(payment, scidd, LOG_INFORM, "received error %s", onion_wire_name(result->failcode)); - } + break; + case UNKNOWN_NODE: + break; + } break; // intermediate only case WIRE_TEMPORARY_CHANNEL_FAILURE: - - if (node_type == FINAL_NODE) { + switch (node_type) { + case FINAL_NODE: /* WIRE_TEMPORARY_CHANNEL_FAILURE could mean that the * next channel has not enough outbound liquidity or * cannot add another HTLC. A final node cannot raise * this error. */ payment_note(payment, LOG_UNUSUAL, - "Final node %s reported strange " + "Final node reported strange " "error code %04x (%s)", - fmt_node_id(tmpctx, result->erring_node), result->failcode, onion_wire_name(result->failcode)); @@ -434,9 +533,16 @@ static struct command_result *handle_failure(struct routefail *r) "Received error code %04x (%s) at final node.", result->failcode, onion_wire_name(result->failcode)); - } + break; + case INTERMEDIATE_NODE: + case ORIGIN_NODE: + case UNKNOWN_NODE: + break; + } break; } + +finish: return routefail_end(take(r)); } diff --git a/tests/test_renepay.py b/tests/test_renepay.py index 2c5221eeec8f..95ab67246b0f 100644 --- a/tests/test_renepay.py +++ b/tests/test_renepay.py @@ -140,7 +140,7 @@ def test_errors(node_factory, bitcoind): PAY_DESTINATION_PERM_FAIL = 203 assert err.value.error["code"] == PAY_DESTINATION_PERM_FAIL - assert "WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS" in err.value.error["message"] + assert "Unknown invoice" in err.value.error["message"] @pytest.mark.openchannel("v1") From 696bf8b42d0760fa02a8be7843fe2f033d5f232e Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 20 Jan 2025 12:43:47 +0100 Subject: [PATCH 07/19] renepay: skip test_previous_sendpays sendonion RPC does not allow to set the total amount in lightningd's wallet, therefore it mixing sendpay and sendonion payment parts would not work. That means for the time being we cannot complete a payment initialized with sendpay until we add a total_amount parameter to sendonion. Changelog-None. Signed-off-by: Lagrang3 --- tests/test_renepay.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_renepay.py b/tests/test_renepay.py index 95ab67246b0f..a1fc9a8b9a9a 100644 --- a/tests/test_renepay.py +++ b/tests/test_renepay.py @@ -509,6 +509,7 @@ def test_htlc_max(node_factory): assert invoice["amount_received_msat"] >= Millisatoshi("800000sat") +@unittest.skip def test_previous_sendpays(node_factory, bitcoind): """ Check that renepay can complete a payment that already started From e466b39465190ace7b4fe7a30feb455392d4df99 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Wed, 22 Jan 2025 14:25:23 +0100 Subject: [PATCH 08/19] renepay: add function blinded path to json Changelog-None. Signed-off-by: Lagrang3 --- plugins/renepay/json.c | 24 ++++++++++++++++++++++++ plugins/renepay/json.h | 4 ++++ plugins/renepay/routetracker.c | 21 +-------------------- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/plugins/renepay/json.c b/plugins/renepay/json.c index 419e61a52783..bb3ed22ffa72 100644 --- a/plugins/renepay/json.c +++ b/plugins/renepay/json.c @@ -327,3 +327,27 @@ void json_add_route(struct json_stream *js, const struct route *route, json_add_string(js, "description", pinfo->description); } + +void json_myadd_blinded_path(struct json_stream *s, + const char *fieldname, + const struct blinded_path *blinded_path) +{ + // FIXME: how can we support the case when the entry point is a + // scid? + assert(blinded_path->first_node_id.is_pubkey); + json_object_start(s, fieldname); + json_add_pubkey(s, "first_node_id", + &blinded_path->first_node_id.pubkey); + json_add_pubkey(s, "first_path_key", &blinded_path->first_path_key); + json_array_start(s, "path"); + for (size_t i = 0; i < tal_count(blinded_path->path); i++) { + const struct blinded_path_hop *hop = blinded_path->path[i]; + json_object_start(s, NULL); + json_add_pubkey(s, "blinded_node_id", &hop->blinded_node_id); + json_add_hex_talarr(s, "encrypted_recipient_data", + hop->encrypted_recipient_data); + json_object_end(s); + } + json_array_end(s); + json_object_end(s); +} diff --git a/plugins/renepay/json.h b/plugins/renepay/json.h index d08749c2c0f5..36320933b744 100644 --- a/plugins/renepay/json.h +++ b/plugins/renepay/json.h @@ -20,4 +20,8 @@ void json_add_payment(struct json_stream *s, const struct payment *payment); void json_add_route(struct json_stream *s, const struct route *route, const struct payment *payment); +void json_myadd_blinded_path(struct json_stream *s, + const char *fieldname, + const struct blinded_path *blinded_path); + #endif /* LIGHTNING_PLUGINS_RENEPAY_JSON_H */ diff --git a/plugins/renepay/routetracker.c b/plugins/renepay/routetracker.c index aae6a079ae1a..d6cda7feeb09 100644 --- a/plugins/renepay/routetracker.c +++ b/plugins/renepay/routetracker.c @@ -372,27 +372,8 @@ struct command_result *route_sendpay_request(struct command *cmd, assert(pinfo->blinded_paths); const struct blinded_path *bpath = pinfo->blinded_paths[route->path_num]; + json_myadd_blinded_path(req->js, "blinded_path", bpath); - // FIXME: how can we support the case when the entry point is a - // scid? - assert(bpath->first_node_id.is_pubkey); - json_object_start(req->js, "blinded_path"); - json_add_pubkey(req->js, "first_node_id", - &bpath->first_node_id.pubkey); - json_add_pubkey(req->js, "first_path_key", - &bpath->first_path_key); - json_array_start(req->js, "path"); - for (size_t i = 0; i < tal_count(bpath->path); i++) { - const struct blinded_path_hop *hop = bpath->path[i]; - json_object_start(req->js, NULL); - json_add_pubkey(req->js, "blinded_node_id", - &hop->blinded_node_id); - json_add_hex_talarr(req->js, "encrypted_recipient_data", - hop->encrypted_recipient_data); - json_object_end(req->js); - } - json_array_end(req->js); - json_object_end(req->js); } route_map_add(payment->routetracker->sent_routes, route); From f542d33476f5e182ac67893cb987b916269a4f95 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Wed, 22 Jan 2025 14:36:47 +0100 Subject: [PATCH 09/19] renepay: refactor create_onion Refactor create_onion function, now we could use the same function to build onions for sendonion and injectpaymentonion. Changelog-None. Signed-off-by: Lagrang3 --- plugins/renepay/sendpay.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/plugins/renepay/sendpay.c b/plugins/renepay/sendpay.c index e3c14b8de7ba..3520690f2062 100644 --- a/plugins/renepay/sendpay.c +++ b/plugins/renepay/sendpay.c @@ -200,10 +200,14 @@ static void sphinx_append_final_hop(const tal_t *ctx, assert(ret); } -static const u8 *create_onion(const tal_t *ctx, struct renesendpay *renesendpay) +static const u8 *create_onion(const tal_t *ctx, + struct renesendpay *renesendpay, + const struct node_id first_node, + const size_t first_index) { bool ret; const tal_t *this_ctx = tal(ctx, tal_t); + struct node_id current_node = first_node; struct pubkey node; const u8 *payload; const size_t pathlen = tal_count(renesendpay->route); @@ -212,19 +216,20 @@ static const u8 *create_onion(const tal_t *ctx, struct renesendpay *renesendpay) sphinx_path_new(this_ctx, renesendpay->payment_hash.u.u8, sizeof(renesendpay->payment_hash.u.u8)); - for (size_t i = 0; i < pathlen - 1; i++) { + for (size_t i = first_index; i < pathlen; i++) { /* Encrypted message is for node[i] but the data is hop[i+1], * therein lays the problem with sendpay's API. */ - ret = pubkey_from_node_id(&node, &renesendpay->route[i].node_id); + ret = pubkey_from_node_id(&node, ¤t_node); assert(ret); - struct route_hop *hop = &renesendpay->route[i + 1]; + struct route_hop *hop = &renesendpay->route[i]; payload = onion_nonfinal_hop(this_ctx, &hop->scid, hop->amount, hop->delay + renesendpay->blockheight); // FIXME: better handle error here ret = sphinx_add_hop_has_length(sp, &node, take(payload)); assert(ret); + current_node = renesendpay->route[i].node_id; } const u32 final_cltv = renesendpay->final_cltv + renesendpay->blockheight; @@ -239,7 +244,7 @@ static const u8 *create_onion(const tal_t *ctx, struct renesendpay *renesendpay) sphinx_append_final_hop(this_ctx, sp, renesendpay->payment_secret, - &renesendpay->destination, + ¤t_node, renesendpay->deliver_amount, renesendpay->total_amount, final_cltv, @@ -320,9 +325,13 @@ static struct command_result *waitblockheight_done(struct command *cmd, "renesendpay failed to read blockheight " "from waitblockheight response."); - const u8 *onion = create_onion(tmpctx, renesendpay); - struct out_req *req = jsonrpc_request_start( - cmd, "sendonion", sendonion_done, sendpay_rpc_failure, renesendpay); + const u8 *onion; + struct out_req *req; + + onion = + create_onion(tmpctx, renesendpay, renesendpay->route[0].node_id, 1); + req = jsonrpc_request_start(cmd, "sendonion", sendonion_done, + sendpay_rpc_failure, renesendpay); json_add_hex_talarr(req->js, "onion", onion); json_add_sha256(req->js, "payment_hash", &renesendpay->payment_hash); json_add_u64(req->js, "partid", renesendpay->partid); From e5449ab37097ce1c02197290119b87ca07ef61a0 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Wed, 22 Jan 2025 14:55:13 +0100 Subject: [PATCH 10/19] renepay: add test for simple offer Changelog-None. Signed-off-by: Lagrang3 --- tests/test_renepay.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_renepay.py b/tests/test_renepay.py index a1fc9a8b9a9a..ffc3d405b9f2 100644 --- a/tests/test_renepay.py +++ b/tests/test_renepay.py @@ -840,3 +840,11 @@ def test_description(node_factory): "renepay", {"invstring": inv_with_hash, "description": "paying for coffee"} ) assert details["status"] == "complete" + + +def test_offers(node_factory): + l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True) + offer = l3.rpc.offer("1000sat", "test_renepay_offers")['bolt12'] + invoice = l1.rpc.fetchinvoice(offer)['invoice'] + response = l1.rpc.call("renepay", {"invstring": invoice}) + assert response["status"] == "complete" From b3e9b548e13ceb95114ead283ac336438be68fcb Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Wed, 22 Jan 2025 22:04:47 +0100 Subject: [PATCH 11/19] renepay: collect shared secrets Collect the shared secrets when making a payment request. We would need this if we use injectpaymentonion instead of sendonion. Changelog-None. Signed-off-by: Lagrang3 --- plugins/renepay/json.c | 1 + plugins/renepay/route.c | 1 + plugins/renepay/route.h | 3 +++ plugins/renepay/routetracker.c | 19 +++++++++++++++++-- plugins/renepay/sendpay.c | 11 +++++++++++ 5 files changed, 33 insertions(+), 2 deletions(-) diff --git a/plugins/renepay/json.c b/plugins/renepay/json.c index bb3ed22ffa72..77736b1e4071 100644 --- a/plugins/renepay/json.c +++ b/plugins/renepay/json.c @@ -64,6 +64,7 @@ struct route *tal_route_from_json(const tal_t *ctx, const char *buf, route->hops = NULL; route->final_msg = NULL; route->final_error = LIGHTNINGD; + route->shared_secrets = NULL; return route; fail: diff --git a/plugins/renepay/route.c b/plugins/renepay/route.c index d35483b40d9d..66e07cdb0436 100644 --- a/plugins/renepay/route.c +++ b/plugins/renepay/route.c @@ -20,6 +20,7 @@ struct route *new_route(const tal_t *ctx, u32 groupid, route->amount_deliver = amount_deliver; route->amount_sent = amount_sent; route->path_num = -1; + route->shared_secrets = NULL; return route; } diff --git a/plugins/renepay/route.h b/plugins/renepay/route.h index d23ebe1bca49..d95e4b65a718 100644 --- a/plugins/renepay/route.h +++ b/plugins/renepay/route.h @@ -59,6 +59,9 @@ struct route { /* The series of channels and nodes to traverse. */ struct route_hop *hops; + /* shared secrets to unwrap the onions */ + struct secret *shared_secrets; + /* amounts are redundant here if we know the hops, however sometimes we * don't know the hops, eg. by calling listsendpays, or if we have * blinded paths */ diff --git a/plugins/renepay/routetracker.c b/plugins/renepay/routetracker.c index d6cda7feeb09..3946658f3be1 100644 --- a/plugins/renepay/routetracker.c +++ b/plugins/renepay/routetracker.c @@ -195,13 +195,28 @@ static void route_pending_register(struct routetracker *routetracker, /* Callback function for sendpay request success. */ static struct command_result *sendpay_done(struct command *cmd, const char *method UNUSED, - const char *buf UNUSED, - const jsmntok_t *result UNUSED, + const char *buf, + const jsmntok_t *result, struct route *route) { assert(route); struct payment *payment = route_get_payment_verify(route); route_pending_register(payment->routetracker, route); + + const jsmntok_t *t; + size_t i; + bool ret; + + const jsmntok_t *secretstok = + json_get_member(buf, result, "shared_secrets"); + assert(secretstok->type == JSMN_ARRAY); + + route->shared_secrets = tal_arr(route, struct secret, secretstok->size); + json_for_each_arr(i, t, secretstok) + { + ret = json_to_secret(buf, t, &route->shared_secrets[i]); + assert(ret); + } return command_still_pending(cmd); } diff --git a/plugins/renepay/sendpay.c b/plugins/renepay/sendpay.c index 3520690f2062..65f6b91b30ab 100644 --- a/plugins/renepay/sendpay.c +++ b/plugins/renepay/sendpay.c @@ -306,6 +306,16 @@ static struct command_result *sendonion_done(struct command *cmd, json_add_hex_talarr(response, "payment_metadata", renesendpay->metadata); + if (renesendpay->shared_secrets) { + json_array_start(response, "shared_secrets"); + for (size_t i = 0; i < tal_count(renesendpay->shared_secrets); + i++) { + json_add_secret(response, NULL, + &renesendpay->shared_secrets[i]); + } + json_array_end(response); + } + /* FIXME: shall we report the blinded path, secret and route used? */ return command_finished(cmd, response); } @@ -435,6 +445,7 @@ struct command_result *json_renesendpay(struct command *cmd, renesendpay->label = tal_steal(renesendpay, label); renesendpay->description = tal_steal(renesendpay, description); renesendpay->metadata = tal_steal(renesendpay, metadata); + renesendpay->shared_secrets = NULL; struct out_req *req = jsonrpc_request_start(cmd, "waitblockheight", waitblockheight_done, From 4238933f080daedda07d7a9f727640eeb8bc31d2 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Thu, 23 Jan 2025 09:04:18 +0100 Subject: [PATCH 12/19] renepay: make self payments with renesendpay Changelog-None. Signed-off-by: Lagrang3 --- plugins/renepay/mods.c | 59 ++++++++++++++++------------ plugins/renepay/sendpay.c | 81 +++++++++++++++++++++++++++------------ 2 files changed, 91 insertions(+), 49 deletions(-) diff --git a/plugins/renepay/mods.c b/plugins/renepay/mods.c index 5efb80b8ede1..0cabbf3d1d8e 100644 --- a/plugins/renepay/mods.c +++ b/plugins/renepay/mods.c @@ -258,13 +258,8 @@ static struct command_result *selfpay_success(struct command *cmd, const char *method UNUSED, const char *buf, const jsmntok_t *tok, - struct route *route) + struct payment *payment) { - tal_steal(tmpctx, route); // discard this route when tmpctx clears - struct payment *payment = - payment_map_get(pay_plugin->payment_map, route->key.payment_hash); - assert(payment); - struct preimage preimage; const char *err; err = json_scan(tmpctx, buf, tok, "{payment_preimage:%}", @@ -282,18 +277,17 @@ static struct command_result *selfpay_failure(struct command *cmd, const char *method UNUSED, const char *buf, const jsmntok_t *tok, - struct route *route) + struct payment *payment) { - tal_steal(tmpctx, route); // discard this route when tmpctx clears - struct payment *payment = - payment_map_get(pay_plugin->payment_map, route->key.payment_hash); - assert(payment); - struct payment_result *result = tal_sendpay_result_from_json(tmpctx, buf, tok); - if (result == NULL) - plugin_err(pay_plugin->plugin, + struct payment_result *result = + tal_sendpay_result_from_json(tmpctx, buf, tok); + if (result == NULL) { + plugin_log(pay_plugin->plugin, LOG_UNUSUAL, "Unable to parse sendpay failure: %.*s", json_tok_full_len(tok), json_tok_full(buf, tok)); - + return payment_fail(payment, LIGHTNINGD, + "Self pay failed for unknown reason"); + } return payment_fail(payment, result->code, "%s", result->message); } @@ -310,17 +304,32 @@ static struct command_result *selfpay_cb(struct payment *payment) "Selfpay: cannot get a valid cmd."); struct payment_info *pinfo = &payment->payment_info; - /* Self-payment routes are not part of the routetracker, we build them - * on-the-fly here and release them on success or failure. */ - struct route *route = - new_route(payment, payment->groupid, - /*partid=*/0, pinfo->payment_hash, - pinfo->amount, pinfo->amount); struct out_req *req; - req = jsonrpc_request_start(cmd, "sendpay", - selfpay_success, selfpay_failure, route); - route->hops = tal_arr(route, struct route_hop, 0); - json_add_route(req->js, route, payment); + req = jsonrpc_request_start(cmd, "renesendpay", selfpay_success, + selfpay_failure, payment); + json_add_sha256(req->js, "payment_hash", &pinfo->payment_hash); + json_add_u64(req->js, "partid", 0); + json_add_u64(req->js, "groupid", payment->groupid); + json_add_string(req->js, "invoice", pinfo->invstr); + json_add_node_id(req->js, "destination", &pinfo->destination); + json_add_amount_msat(req->js, "amount_msat", pinfo->amount); + json_add_amount_msat(req->js, "total_amount_msat", pinfo->amount); + json_add_u32(req->js, "final_cltv", pinfo->final_cltv); + if (pinfo->label) + json_add_string(req->js, "label", pinfo->label); + if (pinfo->description) + json_add_string(req->js, "description", pinfo->description); + /* An empty route means a payment to oneself, pathlen=0 */ + json_array_start(req->js, "route"); + json_array_end(req->js); + if (pinfo->payment_secret) + json_add_secret(req->js, "payment_secret", + pinfo->payment_secret); + else { + assert(pinfo->blinded_paths); + const struct blinded_path *bpath = pinfo->blinded_paths[0]; + json_myadd_blinded_path(req->js, "blinded_path", bpath); + } return send_outreq(req); } diff --git a/plugins/renepay/sendpay.c b/plugins/renepay/sendpay.c index 65f6b91b30ab..90787c7f7b4e 100644 --- a/plugins/renepay/sendpay.c +++ b/plugins/renepay/sendpay.c @@ -145,7 +145,7 @@ static struct command_result *sendpay_rpc_failure(struct command *cmd, json_to_u32(buffer, codetok, &errcode); else errcode = LIGHTNINGD; - + // FIXME: make this response more useful, maybe similar to waitsendpay return command_fail( cmd, errcode, "renesendpay failed to error in RPC: %.*s", json_tok_full_len(toks), json_tok_full(buffer, toks)); @@ -297,6 +297,11 @@ static struct command_result *sendonion_done(struct command *cmd, renesendpay->total_amount); json_add_string(response, "invoice", renesendpay->invoice); + const jsmntok_t *preimagetok = + json_get_member(buffer, toks, "payment_preimage"); + if (preimagetok) + json_add_tok(response, "payment_preimage", preimagetok, buffer); + if (renesendpay->label) json_add_string(response, "label", renesendpay->label); if (renesendpay->description) @@ -338,31 +343,55 @@ static struct command_result *waitblockheight_done(struct command *cmd, const u8 *onion; struct out_req *req; - onion = - create_onion(tmpctx, renesendpay, renesendpay->route[0].node_id, 1); - req = jsonrpc_request_start(cmd, "sendonion", sendonion_done, - sendpay_rpc_failure, renesendpay); - json_add_hex_talarr(req->js, "onion", onion); + if (tal_count(renesendpay->route) > 0) { + onion = create_onion(tmpctx, renesendpay, + renesendpay->route[0].node_id, 1); + req = jsonrpc_request_start(cmd, "sendonion", sendonion_done, + sendpay_rpc_failure, renesendpay); + json_add_hex_talarr(req->js, "onion", onion); + + json_add_amount_msat(req->js, "amount_msat", + renesendpay->deliver_amount); + + const struct route_hop *hop = &renesendpay->route[0]; + json_object_start(req->js, "first_hop"); + json_add_amount_msat(req->js, "amount_msat", hop->amount); + json_add_num(req->js, "delay", + hop->delay + renesendpay->blockheight); + json_add_node_id(req->js, "id", &hop->node_id); + json_add_short_channel_id(req->js, "channel", hop->scid); + json_object_end(req->js); + + json_array_start(req->js, "shared_secrets"); + for (size_t i = 0; i < tal_count(renesendpay->shared_secrets); + i++) { + json_add_secret(req->js, NULL, + &renesendpay->shared_secrets[i]); + } + json_array_end(req->js); + + json_add_node_id(req->js, "destination", + &renesendpay->destination); + + } else { + /* self payment */ + onion = NULL; + req = jsonrpc_request_start(cmd, "sendpay", sendonion_done, + sendpay_rpc_failure, renesendpay); + json_array_start(req->js, "route"); + json_array_end(req->js); + json_add_amount_msat(req->js, "amount_msat", + renesendpay->total_amount); + if(renesendpay->payment_secret) + json_add_secret(req->js, "payment_secret", renesendpay->payment_secret); + else{ + // FIXME: get payment_secret from blinded path + } + } + json_add_sha256(req->js, "payment_hash", &renesendpay->payment_hash); json_add_u64(req->js, "partid", renesendpay->partid); json_add_u64(req->js, "groupid", renesendpay->groupid); - json_add_node_id(req->js, "destination", &renesendpay->destination); - json_add_amount_msat(req->js, "amount_msat", renesendpay->deliver_amount); - - const struct route_hop *hop = &renesendpay->route[0]; - json_object_start(req->js, "first_hop"); - json_add_amount_msat(req->js, "amount_msat", hop->amount); - json_add_num(req->js, "delay", hop->delay + renesendpay->blockheight); - json_add_node_id(req->js, "id", &hop->node_id); - json_add_short_channel_id(req->js, "channel", hop->scid); - json_object_end(req->js); - - json_array_start(req->js, "shared_secrets"); - for (size_t i = 0; i < tal_count(renesendpay->shared_secrets); i++) { - json_add_secret(req->js, NULL, &renesendpay->shared_secrets[i]); - } - json_array_end(req->js); - if (renesendpay->label) json_add_string(req->js, "label", renesendpay->label); if (renesendpay->description) @@ -431,7 +460,11 @@ struct command_result *json_renesendpay(struct command *cmd, renesendpay->partid = *partid; renesendpay->groupid = *groupid; - renesendpay->sent_amount = renesendpay->route[0].amount; + if (tal_count(renesendpay->route) > 0) + renesendpay->sent_amount = renesendpay->route[0].amount; + else /* this might be a self pay */ + renesendpay->sent_amount = *amount; + renesendpay->total_amount = *total_amount; renesendpay->deliver_amount = *amount; renesendpay->final_cltv = *final_cltv; From 80c718feaab9733aaaf010d1ddc8df781653ec99 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Thu, 23 Jan 2025 11:42:45 +0100 Subject: [PATCH 13/19] renepay: don't fail parsing sendpay error Changelog-None. Signed-off-by: Lagrang3 --- plugins/renepay/json.c | 42 +++++++++++------- plugins/renepay/route.h | 4 +- plugins/renepay/routefail.c | 78 +++++++++++++++++++--------------- plugins/renepay/routetracker.c | 4 +- 4 files changed, 75 insertions(+), 53 deletions(-) diff --git a/plugins/renepay/json.c b/plugins/renepay/json.c index 77736b1e4071..c026fb638494 100644 --- a/plugins/renepay/json.c +++ b/plugins/renepay/json.c @@ -76,7 +76,7 @@ struct payment_result *tal_sendpay_result_from_json(const tal_t *ctx, const char *buffer, const jsmntok_t *toks) { - const jsmntok_t *idtok = json_get_member(buffer, toks, "id"); + const jsmntok_t *idtok = json_get_member(buffer, toks, "created_index"); const jsmntok_t *hashtok = json_get_member(buffer, toks, "payment_hash"); const jsmntok_t *senttok = @@ -85,29 +85,35 @@ struct payment_result *tal_sendpay_result_from_json(const tal_t *ctx, const jsmntok_t *preimagetok = json_get_member(buffer, toks, "payment_preimage"); const jsmntok_t *codetok = json_get_member(buffer, toks, "code"); + const jsmntok_t *msgtok = json_get_member(buffer, toks, "message"); const jsmntok_t *datatok = json_get_member(buffer, toks, "data"); - const jsmntok_t *erridxtok, *msgtok, *failcodetok, *rawmsgtok, + const jsmntok_t *erridxtok, *failcodetok, *rawmsgtok, *failcodenametok, *errchantok, *errnodetok, *errdirtok; struct payment_result *result; /* Check if we have an error and need to descend into data to get * details. */ if (codetok != NULL && datatok != NULL) { - idtok = json_get_member(buffer, datatok, "id"); + idtok = json_get_member(buffer, datatok, "create_index"); hashtok = json_get_member(buffer, datatok, "payment_hash"); senttok = json_get_member(buffer, datatok, "amount_sent_msat"); statustok = json_get_member(buffer, datatok, "status"); } /* Initial sanity checks, all these fields must exist. */ - if (idtok == NULL || idtok->type != JSMN_PRIMITIVE || hashtok == NULL || - hashtok->type != JSMN_STRING || senttok == NULL || - statustok == NULL || statustok->type != JSMN_STRING) { + if (hashtok == NULL || hashtok->type != JSMN_STRING || + senttok == NULL || statustok == NULL || + statustok->type != JSMN_STRING) { return NULL; } result = tal(ctx, struct payment_result); + if (msgtok) + result->message = json_strdup(result, buffer, msgtok); + else + result->message = NULL; + if (codetok != NULL) // u32? isn't this an int? // json_to_u32(buffer, codetok, &result->code); @@ -115,7 +121,12 @@ struct payment_result *tal_sendpay_result_from_json(const tal_t *ctx, else result->code = 0; - json_to_u64(buffer, idtok, &result->id); + if (idtok) { + result->created_index = tal(result, u64); + json_to_u64(buffer, idtok, result->created_index); + } else + result->created_index = NULL; + json_to_msat(buffer, senttok, &result->amount_sent); if (json_tok_streq(buffer, statustok, "pending")) { result->status = SENDPAY_PENDING; @@ -133,7 +144,7 @@ struct payment_result *tal_sendpay_result_from_json(const tal_t *ctx, } /* Now extract the error details if the error code is not 0 */ - if (result->code != 0) { + if (result->code != 0 && datatok) { erridxtok = json_get_member(buffer, datatok, "erring_index"); errnodetok = json_get_member(buffer, datatok, "erring_node"); errchantok = json_get_member(buffer, datatok, "erring_channel"); @@ -142,17 +153,16 @@ struct payment_result *tal_sendpay_result_from_json(const tal_t *ctx, failcodetok = json_get_member(buffer, datatok, "failcode"); failcodenametok = json_get_member(buffer, datatok, "failcodename"); - msgtok = json_get_member(buffer, toks, "message"); rawmsgtok = json_get_member(buffer, datatok, "raw_message"); - if (failcodetok == NULL || - failcodetok->type != JSMN_PRIMITIVE || + /* check type for sanity */ + if ((failcodetok != NULL && + failcodetok->type != JSMN_PRIMITIVE) || (failcodenametok != NULL && failcodenametok->type != JSMN_STRING) || (erridxtok != NULL && erridxtok->type != JSMN_PRIMITIVE) || (errnodetok != NULL && errnodetok->type != JSMN_STRING) || (errchantok != NULL && errchantok->type != JSMN_STRING) || (errdirtok != NULL && errdirtok->type != JSMN_PRIMITIVE) || - msgtok == NULL || msgtok->type != JSMN_STRING || (rawmsgtok != NULL && rawmsgtok->type != JSMN_STRING)) goto fail; @@ -168,9 +178,11 @@ struct payment_result *tal_sendpay_result_from_json(const tal_t *ctx, else result->failcodename = NULL; - json_to_u32(buffer, failcodetok, &result->failcode); - result->message = json_strdup(result, buffer, msgtok); - + if(failcodetok){ + result->failcode = tal(result, enum onion_wire); + json_to_u32(buffer, failcodetok, result->failcode); + }else + result->failcode = NULL; if (erridxtok != NULL) { result->erring_index = tal(result, u32); json_to_u32(buffer, erridxtok, result->erring_index); diff --git a/plugins/renepay/route.h b/plugins/renepay/route.h index d95e4b65a718..f1871999f7b9 100644 --- a/plugins/renepay/route.h +++ b/plugins/renepay/route.h @@ -29,13 +29,13 @@ enum sendpay_result_status { struct payment_result { /* DB internal id */ // TODO check all this variables - u64 id; + u64 *created_index; struct preimage *payment_preimage; enum sendpay_result_status status; struct amount_msat amount_sent; enum jsonrpc_errcode code; const char *failcodename; - enum onion_wire failcode; + enum onion_wire *failcode; const u8 *raw_message; const char *message; u32 *erring_index; diff --git a/plugins/renepay/routefail.c b/plugins/renepay/routefail.c index 55e853dd09d6..4f450c6a4ace 100644 --- a/plugins/renepay/routefail.c +++ b/plugins/renepay/routefail.c @@ -253,6 +253,16 @@ static struct command_result *handle_failure(struct routefail *r) if (route->hops) path_len = tal_count(route->hops); + enum onion_wire failcode; + if(result->failcode) + failcode = *result->failcode; + else{ + payment_note( + payment, LOG_UNUSUAL, + "The failcode is unknown we skip error handling"); + goto finish; + } + if (!result->erring_index) { payment_note( payment, LOG_UNUSUAL, @@ -270,7 +280,7 @@ static struct command_result *handle_failure(struct routefail *r) node_type = INTERMEDIATE_NODE; } - switch (result->failcode) { + switch (failcode) { // intermediate only case WIRE_INVALID_ONION_VERSION: case WIRE_INVALID_ONION_HMAC: @@ -280,8 +290,8 @@ static struct command_result *handle_failure(struct routefail *r) payment_note(payment, LOG_UNUSUAL, "Final node reported strange " "error code %04x (%s)", - result->failcode, - onion_wire_name(result->failcode)); + failcode, + onion_wire_name(failcode)); break; case ORIGIN_NODE: case INTERMEDIATE_NODE: @@ -297,8 +307,8 @@ static struct command_result *handle_failure(struct routefail *r) route_final_error( route, PAY_DESTINATION_PERM_FAIL, "Received error code %04x (%s) at final node.", - result->failcode, - onion_wire_name(result->failcode)); + failcode, + onion_wire_name(failcode)); break; case INTERMEDIATE_NODE: @@ -311,7 +321,7 @@ static struct command_result *handle_failure(struct routefail *r) payment_disable_node( payment, route->hops[*result->erring_index].node_id, LOG_DBG, "received %s from previous hop", - onion_wire_name(result->failcode)); + onion_wire_name(failcode)); break; case UNKNOWN_NODE: break; @@ -353,8 +363,8 @@ static struct command_result *handle_failure(struct routefail *r) payment_note(payment, LOG_UNUSUAL, "Intermediate node reported strange " "error code %04x (%s)", - result->failcode, - onion_wire_name(result->failcode)); + failcode, + onion_wire_name(failcode)); break; case ORIGIN_NODE: case FINAL_NODE: @@ -371,15 +381,15 @@ static struct command_result *handle_failure(struct routefail *r) route_final_error( route, PAY_DESTINATION_PERM_FAIL, "Received error code %04x (%s) at final node.", - result->failcode, - onion_wire_name(result->failcode)); + failcode, + onion_wire_name(failcode)); break; case ORIGIN_NODE: route_final_error( route, PAY_UNSPECIFIED_ERROR, "Error code %04x (%s) reported at the origin.", - result->failcode, - onion_wire_name(result->failcode)); + failcode, + onion_wire_name(failcode)); break; case INTERMEDIATE_NODE: if (!route->hops) @@ -388,7 +398,7 @@ static struct command_result *handle_failure(struct routefail *r) payment, route->hops[*result->erring_index - 1].node_id, LOG_INFORM, "received error %s", - onion_wire_name(result->failcode)); + onion_wire_name(failcode)); break; case UNKNOWN_NODE: break; @@ -406,22 +416,22 @@ static struct command_result *handle_failure(struct routefail *r) payment_note(payment, LOG_UNUSUAL, "Final node reported strange " "error code %04x (%s)", - result->failcode, - onion_wire_name(result->failcode)); + failcode, + onion_wire_name(failcode)); route_final_error( route, PAY_DESTINATION_PERM_FAIL, "Received error code %04x (%s) at final node.", - result->failcode, - onion_wire_name(result->failcode)); + failcode, + onion_wire_name(failcode)); break; case ORIGIN_NODE: payment_note(payment, LOG_UNUSUAL, "First node reported strange " "error code %04x (%s)", - result->failcode, - onion_wire_name(result->failcode)); + failcode, + onion_wire_name(failcode)); break; case INTERMEDIATE_NODE: @@ -431,7 +441,7 @@ static struct command_result *handle_failure(struct routefail *r) .scid = route->hops[*result->erring_index].scid, .dir = route->hops[*result->erring_index].direction}; payment_disable_chan(payment, scidd, LOG_INFORM, "%s", - onion_wire_name(result->failcode)); + onion_wire_name(failcode)); break; case UNKNOWN_NODE: @@ -449,8 +459,8 @@ static struct command_result *handle_failure(struct routefail *r) payment_note(payment, LOG_UNUSUAL, "Intermediate node reported strange " "error code %04x (%s)", - result->failcode, - onion_wire_name(result->failcode)); + failcode, + onion_wire_name(failcode)); if (!route->hops) break; @@ -458,7 +468,7 @@ static struct command_result *handle_failure(struct routefail *r) payment, route->hops[*result->erring_index - 1].node_id, LOG_INFORM, "received error %s", - onion_wire_name(result->failcode)); + onion_wire_name(failcode)); break; case ORIGIN_NODE: @@ -478,22 +488,22 @@ static struct command_result *handle_failure(struct routefail *r) payment_note(payment, LOG_UNUSUAL, "Final node reported strange " "error code %04x (%s)", - result->failcode, - onion_wire_name(result->failcode)); + failcode, + onion_wire_name(failcode)); route_final_error( route, PAY_DESTINATION_PERM_FAIL, "Received error code %04x (%s) at final node.", - result->failcode, - onion_wire_name(result->failcode)); + failcode, + onion_wire_name(failcode)); break; case ORIGIN_NODE: payment_note(payment, LOG_UNUSUAL, "First node reported strange " "error code %04x (%s)", - result->failcode, - onion_wire_name(result->failcode)); + failcode, + onion_wire_name(failcode)); break; case INTERMEDIATE_NODE: @@ -507,7 +517,7 @@ static struct command_result *handle_failure(struct routefail *r) .dir = route->hops[*result->erring_index].direction}; payment_warn_chan(payment, scidd, LOG_INFORM, "received error %s", - onion_wire_name(result->failcode)); + onion_wire_name(failcode)); break; case UNKNOWN_NODE: @@ -525,14 +535,14 @@ static struct command_result *handle_failure(struct routefail *r) payment_note(payment, LOG_UNUSUAL, "Final node reported strange " "error code %04x (%s)", - result->failcode, - onion_wire_name(result->failcode)); + failcode, + onion_wire_name(failcode)); route_final_error( route, PAY_DESTINATION_PERM_FAIL, "Received error code %04x (%s) at final node.", - result->failcode, - onion_wire_name(result->failcode)); + failcode, + onion_wire_name(failcode)); break; case INTERMEDIATE_NODE: diff --git a/plugins/renepay/routetracker.c b/plugins/renepay/routetracker.c index 3946658f3be1..64b3e8ae1fed 100644 --- a/plugins/renepay/routetracker.c +++ b/plugins/renepay/routetracker.c @@ -102,7 +102,7 @@ void route_failure_register(struct routetracker *routetracker, assert(result); /* Update the knowledge in the uncertaity network. */ - if (route->hops) { + if (route->hops && result->failcode) { assert(result->erring_index); int path_len = tal_count(route->hops); @@ -123,7 +123,7 @@ void route_failure_register(struct routetracker *routetracker, route->hops[i].direction); } - if (result->failcode == WIRE_TEMPORARY_CHANNEL_FAILURE && + if (*result->failcode == WIRE_TEMPORARY_CHANNEL_FAILURE && (last_good_channel + 1) < path_len) { /* A WIRE_TEMPORARY_CHANNEL_FAILURE could mean not * enough liquidity to forward the payment or cannot add From 9d2476ee44ee528704e3a4d0e4ad2ad5d91bf583 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Thu, 23 Jan 2025 11:53:17 +0100 Subject: [PATCH 14/19] renepay: more informative renesendpay error reply Changelog-None. Signed-off-by: Lagrang3 --- plugins/renepay/sendpay.c | 83 ++++++++++++++++++++++++++++++++++----- 1 file changed, 74 insertions(+), 9 deletions(-) diff --git a/plugins/renepay/sendpay.c b/plugins/renepay/sendpay.c index 90787c7f7b4e..6ab7d2a62854 100644 --- a/plugins/renepay/sendpay.c +++ b/plugins/renepay/sendpay.c @@ -261,11 +261,42 @@ static const u8 *create_onion(const tal_t *ctx, return onion; } -static struct command_result *sendonion_done(struct command *cmd, - const char *method UNUSED, - const char *buffer, - const jsmntok_t *toks, - struct renesendpay *renesendpay) +static struct command_result *sendpay_fail(struct command *cmd, + const char *method UNUSED, + const char *buffer, + const jsmntok_t *toks, + struct renesendpay *renesendpay) +{ + plugin_log(cmd->plugin, LOG_UNUSUAL, + "renesendpay failed calling sendpay: %.*s", + json_tok_full_len(toks), json_tok_full(buffer, toks)); + const jsmntok_t *codetok = json_get_member(buffer, toks, "code"); + const jsmntok_t *msgtok = json_get_member(buffer, toks, "message"); + const char *msg = NULL; + if (msgtok) + msg = json_strdup(tmpctx, buffer, msgtok); + u32 errcode; + if (codetok != NULL) + json_to_u32(buffer, codetok, &errcode); + else + errcode = PLUGIN_ERROR; + struct json_stream *response = jsonrpc_stream_fail( + cmd, errcode, + tal_fmt(tmpctx, "sendpay failed: %s", msg ? msg : "\"\"")); + json_object_start(response, "data"); + json_add_sha256(response, "payment_hash", &renesendpay->payment_hash); + json_add_string(response, "status", "failed"); + json_add_amount_msat(response, "amount_sent_msat", + renesendpay->sent_amount); + json_object_end(response); + return command_finished(cmd, response); +} + +static struct command_result *renesendpay_done(struct command *cmd, + const char *method UNUSED, + const char *buffer, + const jsmntok_t *toks, + struct renesendpay *renesendpay) { const char *err; u64 created_index; @@ -296,6 +327,7 @@ static struct command_result *sendonion_done(struct command *cmd, json_add_amount_msat(response, "amount_total_msat", renesendpay->total_amount); json_add_string(response, "invoice", renesendpay->invoice); + json_add_string(response, "status", "pending"); const jsmntok_t *preimagetok = json_get_member(buffer, toks, "payment_preimage"); @@ -325,6 +357,39 @@ static struct command_result *sendonion_done(struct command *cmd, return command_finished(cmd, response); } +static struct command_result *waitblockheight_fail(struct command *cmd, + const char *method UNUSED, + const char *buffer, + const jsmntok_t *toks, + struct renesendpay *renesendpay) +{ + plugin_log(cmd->plugin, LOG_UNUSUAL, + "renesendpay failed calling waitblockheight: %.*s", + json_tok_full_len(toks), json_tok_full(buffer, toks)); + const jsmntok_t *codetok = json_get_member(buffer, toks, "code"); + const jsmntok_t *msgtok = json_get_member(buffer, toks, "message"); + const char *msg; + if (msgtok) + msg = json_strdup(tmpctx, buffer, msgtok); + else + msg = ""; + u32 errcode; + if (codetok != NULL) + json_to_u32(buffer, codetok, &errcode); + else + errcode = PLUGIN_ERROR; + struct json_stream *response = jsonrpc_stream_fail( + cmd, errcode, + tal_fmt(tmpctx, "waitblockheight failed: %s", msg ? msg : "\"\"")); + json_object_start(response, "data"); + json_add_sha256(response, "payment_hash", &renesendpay->payment_hash); + json_add_string(response, "status", "failed"); + json_add_amount_msat(response, "amount_sent_msat", + renesendpay->sent_amount); + json_object_end(response); + return command_finished(cmd, response); +} + static struct command_result *waitblockheight_done(struct command *cmd, const char *method UNUSED, const char *buffer, @@ -346,7 +411,7 @@ static struct command_result *waitblockheight_done(struct command *cmd, if (tal_count(renesendpay->route) > 0) { onion = create_onion(tmpctx, renesendpay, renesendpay->route[0].node_id, 1); - req = jsonrpc_request_start(cmd, "sendonion", sendonion_done, + req = jsonrpc_request_start(cmd, "sendonion", renesendpay_done, sendpay_rpc_failure, renesendpay); json_add_hex_talarr(req->js, "onion", onion); @@ -376,8 +441,8 @@ static struct command_result *waitblockheight_done(struct command *cmd, } else { /* self payment */ onion = NULL; - req = jsonrpc_request_start(cmd, "sendpay", sendonion_done, - sendpay_rpc_failure, renesendpay); + req = jsonrpc_request_start(cmd, "sendpay", renesendpay_done, + sendpay_fail, renesendpay); json_array_start(req->js, "route"); json_array_end(req->js); json_add_amount_msat(req->js, "amount_msat", @@ -482,7 +547,7 @@ struct command_result *json_renesendpay(struct command *cmd, struct out_req *req = jsonrpc_request_start(cmd, "waitblockheight", waitblockheight_done, - sendpay_rpc_failure, renesendpay); + waitblockheight_fail, renesendpay); json_add_num(req->js, "blockheight", 0); return send_outreq(req); } From 49de44b13535766e35a2d8d7d74cefcdedb8df57 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Fri, 24 Jan 2025 08:43:23 +0100 Subject: [PATCH 15/19] renepay: simplify rpc error callback functions Changelog-None. Signed-off-by: Lagrang3 --- plugins/renepay/sendpay.c | 109 ++++++++++---------------------------- 1 file changed, 29 insertions(+), 80 deletions(-) diff --git a/plugins/renepay/sendpay.c b/plugins/renepay/sendpay.c index 6ab7d2a62854..d07e16c0c5dc 100644 --- a/plugins/renepay/sendpay.c +++ b/plugins/renepay/sendpay.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -133,22 +134,37 @@ struct renesendpay { unsigned int blockheight; }; -static struct command_result *sendpay_rpc_failure(struct command *cmd, - const char *method UNUSED, - const char *buffer, - const jsmntok_t *toks, - struct renesendpay *renesendpay) +static struct command_result *rpc_fail(struct command *cmd, + const char *method, + const char *buffer, + const jsmntok_t *toks, + struct renesendpay *renesendpay) { + plugin_log(cmd->plugin, LOG_UNUSUAL, + "renesendpay failed calling %s: %.*s", method, + json_tok_full_len(toks), json_tok_full(buffer, toks)); const jsmntok_t *codetok = json_get_member(buffer, toks, "code"); + const jsmntok_t *msgtok = json_get_member(buffer, toks, "message"); + const char *msg; + if (msgtok) + msg = json_strdup(tmpctx, buffer, msgtok); + else + msg = ""; u32 errcode; if (codetok != NULL) json_to_u32(buffer, codetok, &errcode); else - errcode = LIGHTNINGD; - // FIXME: make this response more useful, maybe similar to waitsendpay - return command_fail( - cmd, errcode, "renesendpay failed to error in RPC: %.*s", - json_tok_full_len(toks), json_tok_full(buffer, toks)); + errcode = PLUGIN_ERROR; + struct json_stream *response = jsonrpc_stream_fail( + cmd, errcode, + tal_fmt(tmpctx, "%s failed: %s", method, msg ? msg : "\"\"")); + json_object_start(response, "data"); + json_add_sha256(response, "payment_hash", &renesendpay->payment_hash); + json_add_string(response, "status", "failed"); + json_add_amount_msat(response, "amount_sent_msat", + renesendpay->sent_amount); + json_object_end(response); + return command_finished(cmd, response); } static void sphinx_append_blinded_path(const tal_t *ctx, @@ -261,37 +277,6 @@ static const u8 *create_onion(const tal_t *ctx, return onion; } -static struct command_result *sendpay_fail(struct command *cmd, - const char *method UNUSED, - const char *buffer, - const jsmntok_t *toks, - struct renesendpay *renesendpay) -{ - plugin_log(cmd->plugin, LOG_UNUSUAL, - "renesendpay failed calling sendpay: %.*s", - json_tok_full_len(toks), json_tok_full(buffer, toks)); - const jsmntok_t *codetok = json_get_member(buffer, toks, "code"); - const jsmntok_t *msgtok = json_get_member(buffer, toks, "message"); - const char *msg = NULL; - if (msgtok) - msg = json_strdup(tmpctx, buffer, msgtok); - u32 errcode; - if (codetok != NULL) - json_to_u32(buffer, codetok, &errcode); - else - errcode = PLUGIN_ERROR; - struct json_stream *response = jsonrpc_stream_fail( - cmd, errcode, - tal_fmt(tmpctx, "sendpay failed: %s", msg ? msg : "\"\"")); - json_object_start(response, "data"); - json_add_sha256(response, "payment_hash", &renesendpay->payment_hash); - json_add_string(response, "status", "failed"); - json_add_amount_msat(response, "amount_sent_msat", - renesendpay->sent_amount); - json_object_end(response); - return command_finished(cmd, response); -} - static struct command_result *renesendpay_done(struct command *cmd, const char *method UNUSED, const char *buffer, @@ -357,39 +342,6 @@ static struct command_result *renesendpay_done(struct command *cmd, return command_finished(cmd, response); } -static struct command_result *waitblockheight_fail(struct command *cmd, - const char *method UNUSED, - const char *buffer, - const jsmntok_t *toks, - struct renesendpay *renesendpay) -{ - plugin_log(cmd->plugin, LOG_UNUSUAL, - "renesendpay failed calling waitblockheight: %.*s", - json_tok_full_len(toks), json_tok_full(buffer, toks)); - const jsmntok_t *codetok = json_get_member(buffer, toks, "code"); - const jsmntok_t *msgtok = json_get_member(buffer, toks, "message"); - const char *msg; - if (msgtok) - msg = json_strdup(tmpctx, buffer, msgtok); - else - msg = ""; - u32 errcode; - if (codetok != NULL) - json_to_u32(buffer, codetok, &errcode); - else - errcode = PLUGIN_ERROR; - struct json_stream *response = jsonrpc_stream_fail( - cmd, errcode, - tal_fmt(tmpctx, "waitblockheight failed: %s", msg ? msg : "\"\"")); - json_object_start(response, "data"); - json_add_sha256(response, "payment_hash", &renesendpay->payment_hash); - json_add_string(response, "status", "failed"); - json_add_amount_msat(response, "amount_sent_msat", - renesendpay->sent_amount); - json_object_end(response); - return command_finished(cmd, response); -} - static struct command_result *waitblockheight_done(struct command *cmd, const char *method UNUSED, const char *buffer, @@ -412,7 +364,7 @@ static struct command_result *waitblockheight_done(struct command *cmd, onion = create_onion(tmpctx, renesendpay, renesendpay->route[0].node_id, 1); req = jsonrpc_request_start(cmd, "sendonion", renesendpay_done, - sendpay_rpc_failure, renesendpay); + rpc_fail, renesendpay); json_add_hex_talarr(req->js, "onion", onion); json_add_amount_msat(req->js, "amount_msat", @@ -442,16 +394,13 @@ static struct command_result *waitblockheight_done(struct command *cmd, /* self payment */ onion = NULL; req = jsonrpc_request_start(cmd, "sendpay", renesendpay_done, - sendpay_fail, renesendpay); + rpc_fail, renesendpay); json_array_start(req->js, "route"); json_array_end(req->js); json_add_amount_msat(req->js, "amount_msat", renesendpay->total_amount); if(renesendpay->payment_secret) json_add_secret(req->js, "payment_secret", renesendpay->payment_secret); - else{ - // FIXME: get payment_secret from blinded path - } } json_add_sha256(req->js, "payment_hash", &renesendpay->payment_hash); @@ -547,7 +496,7 @@ struct command_result *json_renesendpay(struct command *cmd, struct out_req *req = jsonrpc_request_start(cmd, "waitblockheight", waitblockheight_done, - waitblockheight_fail, renesendpay); + rpc_fail, renesendpay); json_add_num(req->js, "blockheight", 0); return send_outreq(req); } From a14c004aa67685c43885e543f209d3727d5df47b Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Fri, 24 Jan 2025 13:20:56 +0100 Subject: [PATCH 16/19] renepay: use injectpaymentonion Use injectpaymentonion for payments with |routes|=0, ie. self-payments and blinded paths where our node is the first node in the blinded path. Changelog-None. Signed-off-by: Lagrang3 --- plugins/renepay/sendpay.c | 70 +++++++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 21 deletions(-) diff --git a/plugins/renepay/sendpay.c b/plugins/renepay/sendpay.c index d07e16c0c5dc..1903fb3bccdb 100644 --- a/plugins/renepay/sendpay.c +++ b/plugins/renepay/sendpay.c @@ -342,6 +342,13 @@ static struct command_result *renesendpay_done(struct command *cmd, return command_finished(cmd, response); } +static u32 initial_cltv_delta(const struct renesendpay *renesendpay) +{ + if (tal_count(renesendpay->route) == 0) + return renesendpay->final_cltv; + return renesendpay->route[0].delay; +} + static struct command_result *waitblockheight_done(struct command *cmd, const char *method UNUSED, const char *buffer, @@ -389,31 +396,52 @@ static struct command_result *waitblockheight_done(struct command *cmd, json_add_node_id(req->js, "destination", &renesendpay->destination); - + json_add_sha256(req->js, "payment_hash", + &renesendpay->payment_hash); + json_add_u64(req->js, "partid", renesendpay->partid); + json_add_u64(req->js, "groupid", renesendpay->groupid); + if (renesendpay->label) + json_add_string(req->js, "label", renesendpay->label); + if (renesendpay->description) + json_add_string(req->js, "description", + renesendpay->description); + if (renesendpay->invoice) + json_add_string(req->js, "bolt11", + renesendpay->invoice); } else { - /* self payment */ - onion = NULL; - req = jsonrpc_request_start(cmd, "sendpay", renesendpay_done, - rpc_fail, renesendpay); - json_array_start(req->js, "route"); - json_array_end(req->js); + /* This is either a self-payment or a payment through a blinded + * path that starts at our node. */ + // FIXME: do this with injectpaymentonion. + // FIXME: we could make all payments with injectpaymentonion but + // we need to make sure first that we don't lose older features, + // like for example to be able to show in listsendpays the + // recepient of the payment. + onion = create_onion(tmpctx, renesendpay, pay_plugin->my_id, 0); + req = jsonrpc_request_start(cmd, "injectpaymentonion", + renesendpay_done, rpc_fail, + renesendpay); + + json_add_hex_talarr(req->js, "onion", onion); + json_add_sha256(req->js, "payment_hash", + &renesendpay->payment_hash); + json_add_u64(req->js, "partid", renesendpay->partid); + json_add_u64(req->js, "groupid", renesendpay->groupid); + if (renesendpay->label) + json_add_string(req->js, "label", renesendpay->label); + if (renesendpay->invoice) + json_add_string(req->js, "invstring", + renesendpay->invoice); json_add_amount_msat(req->js, "amount_msat", - renesendpay->total_amount); - if(renesendpay->payment_secret) - json_add_secret(req->js, "payment_secret", renesendpay->payment_secret); + renesendpay->sent_amount); + json_add_amount_msat(req->js, "destination_msat", + renesendpay->deliver_amount); + json_add_amount_msat(req->js, "destination_msat", + renesendpay->deliver_amount); + json_add_u32(req->js, "cltv_expiry", + initial_cltv_delta(renesendpay) + + renesendpay->blockheight); } - json_add_sha256(req->js, "payment_hash", &renesendpay->payment_hash); - json_add_u64(req->js, "partid", renesendpay->partid); - json_add_u64(req->js, "groupid", renesendpay->groupid); - if (renesendpay->label) - json_add_string(req->js, "label", renesendpay->label); - if (renesendpay->description) - json_add_string(req->js, "description", - renesendpay->description); - if (renesendpay->invoice) - json_add_string(req->js, "bolt11", renesendpay->invoice); - return send_outreq(req); } From ba7485b8234957b7f5cdbecfa5cb383cbe680d17 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Fri, 24 Jan 2025 13:53:45 +0100 Subject: [PATCH 17/19] renepay: resolve self payments with fake node Always use a fake destination node, the self-payments are no longer a corner case for the routing problem in this way. Also it is ok for get_routes to return routes with zero length. Changelog-None. Signed-off-by: Lagrang3 --- plugins/renepay/json.c | 2 + plugins/renepay/main.c | 24 +++---- plugins/renepay/mods.c | 123 +++++++++++---------------------- plugins/renepay/route.h | 3 +- plugins/renepay/routebuilder.c | 2 + plugins/renepay/sendpay.c | 2 - plugins/renepay/uncertainty.c | 4 +- plugins/renepay/uncertainty.h | 2 +- tests/test_renepay.py | 10 ++- 9 files changed, 68 insertions(+), 104 deletions(-) diff --git a/plugins/renepay/json.c b/plugins/renepay/json.c index c026fb638494..1952fad8b0fc 100644 --- a/plugins/renepay/json.c +++ b/plugins/renepay/json.c @@ -76,6 +76,8 @@ struct payment_result *tal_sendpay_result_from_json(const tal_t *ctx, const char *buffer, const jsmntok_t *toks) { + // FIXME: we will be getting onionreply try to decode these with + // shared_secrets const jsmntok_t *idtok = json_get_member(buffer, toks, "created_index"); const jsmntok_t *hashtok = json_get_member(buffer, toks, "payment_hash"); diff --git a/plugins/renepay/main.c b/plugins/renepay/main.c index f31f55a52a01..0fb75f3aed61 100644 --- a/plugins/renepay/main.c +++ b/plugins/renepay/main.c @@ -345,7 +345,6 @@ static struct command_result *json_pay(struct command *cmd, const char *buf, pinfo->blinded_paths = NULL; pinfo->blinded_payinfos = NULL; - payment->routing_destination = &pinfo->destination; } else { pinfo->payment_secret = NULL; pinfo->routehints = NULL; @@ -371,19 +370,18 @@ static struct command_result *json_pay(struct command *cmd, const char *buf, max_final_cltv = final_cltv; } pinfo->final_cltv = max_final_cltv; - - /* When dealing with BOLT12 blinded paths we compute the - * routing targeting a fake node to enable - * multi-destination minimum-cost-flow. Every blinded - * path entry node will be linked to this fake node - * using fake channels as well. */ - payment->routing_destination = - tal(payment, struct node_id); - if (!node_id_from_hexstr( - "02""0000000000000000000000000000000000000000000000000000000000000001", - 66, payment->routing_destination)) - abort(); } + /* When dealing with BOLT12 blinded paths we compute the + * routing targeting a fake node to enable + * multi-destination minimum-cost-flow. Every blinded + * path entry node will be linked to this fake node + * using fake channels as well. This is also useful for + * solving self-payments. */ + payment->routing_destination = tal(payment, struct node_id); + if (!node_id_from_hexstr("0200000000000000000000000000000000000" + "00000000000000000000000000001", + 66, payment->routing_destination)) + abort(); if (!payment_set_constraints( payment, *msat, *maxfee, *maxdelay, *retryfor, diff --git a/plugins/renepay/mods.c b/plugins/renepay/mods.c index 0cabbf3d1d8e..f8650db16724 100644 --- a/plugins/renepay/mods.c +++ b/plugins/renepay/mods.c @@ -92,6 +92,13 @@ static struct command_result *payment_rpc_failure(struct command *cmd, json_tok_full_len(toks), json_tok_full(buffer, toks)); } +static void add_hintchan(struct payment *payment, const struct node_id *src, + const struct node_id *dst, u16 cltv_expiry_delta, + const struct short_channel_id scid, u32 fee_base_msat, + u32 fee_proportional_millionths, + const struct amount_msat *chan_htlc_min, + const struct amount_msat *chan_htlc_max); + /***************************************************************************** * previoussuccess * @@ -249,88 +256,23 @@ REGISTER_PAYMENT_MODIFIER(initial_sanity_checks, initial_sanity_checks_cb); /***************************************************************************** * selfpay - * - * Checks if the payment destination is the sender's node and perform a self - * payment. */ -static struct command_result *selfpay_success(struct command *cmd, - const char *method UNUSED, - const char *buf, - const jsmntok_t *tok, - struct payment *payment) -{ - struct preimage preimage; - const char *err; - err = json_scan(tmpctx, buf, tok, "{payment_preimage:%}", - JSON_SCAN(json_to_preimage, &preimage)); - if (err) - plugin_err( - cmd->plugin, "selfpay didn't have payment_preimage: %.*s", - json_tok_full_len(tok), json_tok_full(buf, tok)); - - - payment_note(payment, LOG_DBG, "Paid with self-pay."); - return payment_success(payment, &preimage); -} -static struct command_result *selfpay_failure(struct command *cmd, - const char *method UNUSED, - const char *buf, - const jsmntok_t *tok, - struct payment *payment) -{ - struct payment_result *result = - tal_sendpay_result_from_json(tmpctx, buf, tok); - if (result == NULL) { - plugin_log(pay_plugin->plugin, LOG_UNUSUAL, - "Unable to parse sendpay failure: %.*s", - json_tok_full_len(tok), json_tok_full(buf, tok)); - return payment_fail(payment, LIGHTNINGD, - "Self pay failed for unknown reason"); - } - return payment_fail(payment, result->code, "%s", result->message); -} - static struct command_result *selfpay_cb(struct payment *payment) { - if (!node_id_eq(&pay_plugin->my_id, - &payment->payment_info.destination)) { - return payment_continue(payment); - } - - struct command *cmd = payment_command(payment); - if (!cmd) - plugin_err(pay_plugin->plugin, - "Selfpay: cannot get a valid cmd."); - - struct payment_info *pinfo = &payment->payment_info; - struct out_req *req; - req = jsonrpc_request_start(cmd, "renesendpay", selfpay_success, - selfpay_failure, payment); - json_add_sha256(req->js, "payment_hash", &pinfo->payment_hash); - json_add_u64(req->js, "partid", 0); - json_add_u64(req->js, "groupid", payment->groupid); - json_add_string(req->js, "invoice", pinfo->invstr); - json_add_node_id(req->js, "destination", &pinfo->destination); - json_add_amount_msat(req->js, "amount_msat", pinfo->amount); - json_add_amount_msat(req->js, "total_amount_msat", pinfo->amount); - json_add_u32(req->js, "final_cltv", pinfo->final_cltv); - if (pinfo->label) - json_add_string(req->js, "label", pinfo->label); - if (pinfo->description) - json_add_string(req->js, "description", pinfo->description); - /* An empty route means a payment to oneself, pathlen=0 */ - json_array_start(req->js, "route"); - json_array_end(req->js); - if (pinfo->payment_secret) - json_add_secret(req->js, "payment_secret", - pinfo->payment_secret); - else { - assert(pinfo->blinded_paths); - const struct blinded_path *bpath = pinfo->blinded_paths[0]; - json_myadd_blinded_path(req->js, "blinded_path", bpath); + /* A different approach to self-pay: create a fake channel from the + * bolt11 destination to the routing_destination (a fake node_id). */ + if (!payment->payment_info.blinded_paths) { + struct amount_msat htlc_min = AMOUNT_MSAT(0); + struct amount_msat htlc_max = AMOUNT_MSAT((u64)1000*100000000); + struct short_channel_id scid = {.u64 = 0}; + add_hintchan(payment, &payment->payment_info.destination, + payment->routing_destination, + /* cltv delta = */ 0, scid, + /* base fee = */ 0, + /* ppm = */ 0, &htlc_min, &htlc_max); } - return send_outreq(req); + return payment_continue(payment); } REGISTER_PAYMENT_MODIFIER(selfpay, selfpay_cb); @@ -513,7 +455,12 @@ REGISTER_PAYMENT_MODIFIER(refreshgossmap, refreshgossmap_cb); * Use route hints from the invoice to update the local gossmods and uncertainty * network. */ -// TODO check how this is done in pay.c + +static void uncertainty_remove_channel(struct chan_extra *ce, + struct uncertainty *uncertainty) +{ + chan_extra_map_del(uncertainty->chan_extra_map, ce); +} static void add_hintchan(struct payment *payment, const struct node_id *src, const struct node_id *dst, u16 cltv_expiry_delta, @@ -526,7 +473,7 @@ static void add_hintchan(struct payment *payment, const struct node_id *src, assert(payment->local_gossmods); const char *errmsg; - const struct chan_extra *ce = + struct chan_extra *ce = uncertainty_find_channel(pay_plugin->uncertainty, scid); if (!ce) { @@ -573,6 +520,15 @@ static void add_hintchan(struct payment *payment, const struct node_id *src, fmt_short_channel_id(tmpctx, scid)); goto function_error; } + /* We want these channel hints destroyed when the local_gossmods + * are freed. */ + /* FIXME: these hints are global in the uncertainty network if + * two payments happen concurrently we will have race + * conditions. The best way to avoid this is to use askrene and + * it's layered API. */ + tal_steal(payment->local_gossmods, ce); + tal_add_destructor2(ce, uncertainty_remove_channel, + pay_plugin->uncertainty); } else { /* The channel is pubic and we already keep track of it in the * gossmap and uncertainty network. It would be wrong to assume @@ -748,8 +704,7 @@ static struct command_result *compute_routes_cb(struct payment *payment) /* Send get_routes a note that it should discard the last hop because we * are actually solving a multiple destinations problem. */ - bool blinded_destination = - payment->payment_info.blinded_paths != NULL; + bool blinded_destination = true; // TODO: add an algorithm selector here /* We let this return an unlikely path, as it's better to try once than @@ -1284,9 +1239,9 @@ REGISTER_PAYMENT_CONDITION(retry, retry_cb); // add check pre-approved invoice void *payment_virtual_program[] = { /*0*/ OP_CALL, &previoussuccess_pay_mod, - /*2*/ OP_CALL, &selfpay_pay_mod, - /*4*/ OP_CALL, &knowledgerelax_pay_mod, - /*6*/ OP_CALL, &getmychannels_pay_mod, + /*2*/ OP_CALL, &knowledgerelax_pay_mod, + /*4*/ OP_CALL, &getmychannels_pay_mod, + /*6*/ OP_CALL, &selfpay_pay_mod, /*8*/ OP_CALL, &refreshgossmap_pay_mod, /*10*/ OP_CALL, &routehints_pay_mod, /*12*/ OP_CALL, &blindedhints_pay_mod, diff --git a/plugins/renepay/route.h b/plugins/renepay/route.h index f1871999f7b9..60d167c9a53f 100644 --- a/plugins/renepay/route.h +++ b/plugins/renepay/route.h @@ -170,7 +170,8 @@ static inline u32 route_delay(const struct route *route) { assert(route); assert(route->hops); - assert(tal_count(route->hops) > 0); + if (tal_count(route->hops) == 0) + return 0; const size_t pathlen = tal_count(route->hops); assert(route->hops[0].delay >= route->hops[pathlen - 1].delay); return route->hops[0].delay - route->hops[pathlen - 1].delay; diff --git a/plugins/renepay/routebuilder.c b/plugins/renepay/routebuilder.c index eeb35e500d59..4e9d15142326 100644 --- a/plugins/renepay/routebuilder.c +++ b/plugins/renepay/routebuilder.c @@ -65,6 +65,8 @@ route_check_constraints(struct route *route, struct gossmap *gossmap, assert(route); assert(route->hops); const size_t pathlen = tal_count(route->hops); + if (pathlen == 0) + return RENEPAY_NOERROR; if (!amount_msat_eq(route->amount_deliver, route->hops[pathlen - 1].amount)) return RENEPAY_PRECONDITION_ERROR; diff --git a/plugins/renepay/sendpay.c b/plugins/renepay/sendpay.c index 1903fb3bccdb..60a6c0cb6cec 100644 --- a/plugins/renepay/sendpay.c +++ b/plugins/renepay/sendpay.c @@ -435,8 +435,6 @@ static struct command_result *waitblockheight_done(struct command *cmd, renesendpay->sent_amount); json_add_amount_msat(req->js, "destination_msat", renesendpay->deliver_amount); - json_add_amount_msat(req->js, "destination_msat", - renesendpay->deliver_amount); json_add_u32(req->js, "cltv_expiry", initial_cltv_delta(renesendpay) + renesendpay->blockheight); diff --git a/plugins/renepay/uncertainty.c b/plugins/renepay/uncertainty.c index d151471229a0..15ca19288aef 100644 --- a/plugins/renepay/uncertainty.c +++ b/plugins/renepay/uncertainty.c @@ -147,12 +147,12 @@ uncertainty_get_chan_extra_map(struct uncertainty *uncertainty) } /* Add channel to the Uncertainty Network if it doesn't already exist. */ -const struct chan_extra * +struct chan_extra * uncertainty_add_channel(struct uncertainty *uncertainty, const struct short_channel_id scid, struct amount_msat capacity) { - const struct chan_extra *ce = + struct chan_extra *ce = chan_extra_map_get(uncertainty->chan_extra_map, scid); if (ce) return ce; diff --git a/plugins/renepay/uncertainty.h b/plugins/renepay/uncertainty.h index 894a95732b49..8ae65eab6c99 100644 --- a/plugins/renepay/uncertainty.h +++ b/plugins/renepay/uncertainty.h @@ -43,7 +43,7 @@ struct uncertainty *uncertainty_new(const tal_t *ctx); struct chan_extra_map * uncertainty_get_chan_extra_map(struct uncertainty *uncertainty); -const struct chan_extra * +struct chan_extra * uncertainty_add_channel(struct uncertainty *uncertainty, const struct short_channel_id scid, struct amount_msat capacity); diff --git a/tests/test_renepay.py b/tests/test_renepay.py index ffc3d405b9f2..30a2f7e26f75 100644 --- a/tests/test_renepay.py +++ b/tests/test_renepay.py @@ -110,7 +110,7 @@ def test_errors(node_factory, bitcoind): node_factory.join_nodes([l1, l2, l4], wait_for_announce=True, fundamount=1000000) node_factory.join_nodes([l1, l3, l5], wait_for_announce=True, fundamount=1000000) - failmsg = r"Destination is unknown in the network gossip." + failmsg = r"failed to find a feasible flow" with pytest.raises(RpcError, match=failmsg): l1.rpc.call("renepay", {"invstring": inv}) @@ -848,3 +848,11 @@ def test_offers(node_factory): invoice = l1.rpc.fetchinvoice(offer)['invoice'] response = l1.rpc.call("renepay", {"invstring": invoice}) assert response["status"] == "complete" + + +def test_offer_selfpay(node_factory): + """We can fetch an pay our own offer""" + l1 = node_factory.get_node() + offer = l1.rpc.offer(amount="2msat", description="test_offer_path_self")["bolt12"] + inv = l1.rpc.fetchinvoice(offer)["invoice"] + l1.rpc.call("renepay", {"invstring": inv}) From 32172577956620f481be0ea2cf3eae94511480c9 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 27 Jan 2025 18:08:07 +0100 Subject: [PATCH 18/19] renepay: decode error onion When paying with injectpaymentonion we need to manually decode the error from the onion. Changelog-None. Signed-off-by: Lagrang3 --- plugins/renepay/json.c | 175 +++++++++++++++++++-------------- plugins/renepay/json.h | 3 +- plugins/renepay/routetracker.c | 25 +++-- plugins/renepay/sendpay.c | 44 ++++++++- 4 files changed, 161 insertions(+), 86 deletions(-) diff --git a/plugins/renepay/json.c b/plugins/renepay/json.c index 1952fad8b0fc..e48388fb308e 100644 --- a/plugins/renepay/json.c +++ b/plugins/renepay/json.c @@ -1,5 +1,7 @@ #include "config.h" #include +#include +#include #include /* See if this notification is about one of our flows. */ @@ -72,12 +74,103 @@ struct route *tal_route_from_json(const tal_t *ctx, const char *buf, return tal_free(route); } +static bool get_data_details_onionreply(struct payment_result *result, + const char *buffer, + const jsmntok_t *datatok, + struct secret *shared_secrets) +{ + const tal_t *this_ctx = tal(result, tal_t); + const jsmntok_t *onionreplytok; + struct onionreply *onionreply, *wonionreply; + const u8 *replymsg; + int index; + + onionreplytok = json_get_member(buffer, datatok, "onionreply"); + if (!onionreplytok || !shared_secrets) + goto fail; + onionreply = new_onionreply( + this_ctx, + take(json_tok_bin_from_hex(this_ctx, buffer, onionreplytok))); + assert(onionreply); + /* FIXME: It seems that lightningd will unwrap top portion of the + * onionreply for us before serializing it, while unwrap_onionreply will + * try to do the entire unwraping. It would be a better API if either + * lightningd unwraps the entire thing or it doesn't do any unwraping. + * Also it wouldn't hurt if injectpaymentonion accepted the shared + * secrets to allow lightningd do the decoding for us. */ + wonionreply = wrap_onionreply(this_ctx, &shared_secrets[0], onionreply); + replymsg = unwrap_onionreply(this_ctx, shared_secrets, + tal_count(shared_secrets), + wonionreply, &index); + if (replymsg) { + result->failcode = tal(result, enum onion_wire); + *result->failcode = fromwire_peektype(replymsg); + + result->erring_index = tal(result, u32); + *result->erring_index = index; + } + tal_free(this_ctx); + return true; +fail: + tal_free(this_ctx); + return false; +} + +static bool get_data_details(struct payment_result *result, + const char *buffer, + const jsmntok_t *datatok) +{ + + const jsmntok_t *erridxtok, *failcodetok, *errnodetok, *errchantok, + *errdirtok, *rawmsgtok, *failcodenametok; + erridxtok = json_get_member(buffer, datatok, "erring_index"); + failcodetok = json_get_member(buffer, datatok, "failcode"); + + if (!erridxtok || !failcodetok) + return false; + result->failcode = tal(result, enum onion_wire); + json_to_u32(buffer, failcodetok, result->failcode); + + result->erring_index = tal(result, u32); + json_to_u32(buffer, erridxtok, result->erring_index); + + // search for other fields + errnodetok = json_get_member(buffer, datatok, "erring_node"); + errchantok = json_get_member(buffer, datatok, "erring_channel"); + errdirtok = json_get_member(buffer, datatok, "erring_direction"); + failcodenametok = json_get_member(buffer, datatok, "failcodename"); + rawmsgtok = json_get_member(buffer, datatok, "raw_message"); + + if (errnodetok != NULL) { + result->erring_node = tal(result, struct node_id); + json_to_node_id(buffer, errnodetok, result->erring_node); + } + + if (errchantok != NULL) { + result->erring_channel = tal(result, struct short_channel_id); + json_to_short_channel_id(buffer, errchantok, + result->erring_channel); + } + if (errdirtok != NULL) { + result->erring_direction = tal(result, int); + json_to_int(buffer, errdirtok, result->erring_direction); + } + if (rawmsgtok != NULL) + result->raw_message = + json_tok_bin_from_hex(result, buffer, rawmsgtok); + + if (failcodenametok != NULL) + result->failcodename = + json_strdup(result, buffer, failcodenametok); + + return true; +} + struct payment_result *tal_sendpay_result_from_json(const tal_t *ctx, const char *buffer, - const jsmntok_t *toks) + const jsmntok_t *toks, + struct secret *shared_secrets) { - // FIXME: we will be getting onionreply try to decode these with - // shared_secrets const jsmntok_t *idtok = json_get_member(buffer, toks, "created_index"); const jsmntok_t *hashtok = json_get_member(buffer, toks, "payment_hash"); @@ -89,8 +182,6 @@ struct payment_result *tal_sendpay_result_from_json(const tal_t *ctx, const jsmntok_t *codetok = json_get_member(buffer, toks, "code"); const jsmntok_t *msgtok = json_get_member(buffer, toks, "message"); const jsmntok_t *datatok = json_get_member(buffer, toks, "data"); - const jsmntok_t *erridxtok, *failcodetok, *rawmsgtok, - *failcodenametok, *errchantok, *errnodetok, *errdirtok; struct payment_result *result; /* Check if we have an error and need to descend into data to get @@ -110,6 +201,7 @@ struct payment_result *tal_sendpay_result_from_json(const tal_t *ctx, } result = tal(ctx, struct payment_result); + memset(result, 0, sizeof(struct payment_result)); if (msgtok) result->message = json_strdup(result, buffer, msgtok); @@ -147,77 +239,12 @@ struct payment_result *tal_sendpay_result_from_json(const tal_t *ctx, /* Now extract the error details if the error code is not 0 */ if (result->code != 0 && datatok) { - erridxtok = json_get_member(buffer, datatok, "erring_index"); - errnodetok = json_get_member(buffer, datatok, "erring_node"); - errchantok = json_get_member(buffer, datatok, "erring_channel"); - errdirtok = - json_get_member(buffer, datatok, "erring_direction"); - failcodetok = json_get_member(buffer, datatok, "failcode"); - failcodenametok = - json_get_member(buffer, datatok, "failcodename"); - rawmsgtok = json_get_member(buffer, datatok, "raw_message"); - /* check type for sanity */ - if ((failcodetok != NULL && - failcodetok->type != JSMN_PRIMITIVE) || - (failcodenametok != NULL && - failcodenametok->type != JSMN_STRING) || - (erridxtok != NULL && erridxtok->type != JSMN_PRIMITIVE) || - (errnodetok != NULL && errnodetok->type != JSMN_STRING) || - (errchantok != NULL && errchantok->type != JSMN_STRING) || - (errdirtok != NULL && errdirtok->type != JSMN_PRIMITIVE) || - (rawmsgtok != NULL && rawmsgtok->type != JSMN_STRING)) + /* try one, then try the other, then fail */ + if (!get_data_details(result, buffer, datatok) && + !get_data_details_onionreply(result, buffer, datatok, + shared_secrets)) goto fail; - - if (rawmsgtok != NULL) - result->raw_message = - json_tok_bin_from_hex(result, buffer, rawmsgtok); - else - result->raw_message = NULL; - - if (failcodenametok != NULL) - result->failcodename = - json_strdup(result, buffer, failcodenametok); - else - result->failcodename = NULL; - - if(failcodetok){ - result->failcode = tal(result, enum onion_wire); - json_to_u32(buffer, failcodetok, result->failcode); - }else - result->failcode = NULL; - if (erridxtok != NULL) { - result->erring_index = tal(result, u32); - json_to_u32(buffer, erridxtok, result->erring_index); - } else { - result->erring_index = NULL; - } - - if (errdirtok != NULL) { - result->erring_direction = tal(result, int); - json_to_int(buffer, errdirtok, - result->erring_direction); - } else { - result->erring_direction = NULL; - } - - if (errnodetok != NULL) { - result->erring_node = tal(result, struct node_id); - json_to_node_id(buffer, errnodetok, - result->erring_node); - } else { - result->erring_node = NULL; - } - - if (errchantok != NULL) { - result->erring_channel = - tal(result, struct short_channel_id); - json_to_short_channel_id(buffer, errchantok, - result->erring_channel); - } else { - result->erring_channel = NULL; - } } - return result; fail: return tal_free(result); diff --git a/plugins/renepay/json.h b/plugins/renepay/json.h index 36320933b744..5abaa8348997 100644 --- a/plugins/renepay/json.h +++ b/plugins/renepay/json.h @@ -13,7 +13,8 @@ struct route *tal_route_from_json(const tal_t *ctx, const char *buf, struct payment_result *tal_sendpay_result_from_json(const tal_t *ctx, const char *buffer, - const jsmntok_t *toks); + const jsmntok_t *toks, + struct secret *shared_secrets); void json_add_payment(struct json_stream *s, const struct payment *payment); diff --git a/plugins/renepay/routetracker.c b/plugins/renepay/routetracker.c index 64b3e8ae1fed..2e8b9ba2b08d 100644 --- a/plugins/renepay/routetracker.c +++ b/plugins/renepay/routetracker.c @@ -209,14 +209,19 @@ static struct command_result *sendpay_done(struct command *cmd, const jsmntok_t *secretstok = json_get_member(buf, result, "shared_secrets"); - assert(secretstok->type == JSMN_ARRAY); - route->shared_secrets = tal_arr(route, struct secret, secretstok->size); - json_for_each_arr(i, t, secretstok) - { - ret = json_to_secret(buf, t, &route->shared_secrets[i]); - assert(ret); - } + if (secretstok) { + assert(secretstok->type == JSMN_ARRAY); + + route->shared_secrets = + tal_arr(route, struct secret, secretstok->size); + json_for_each_arr(i, t, secretstok) + { + ret = json_to_secret(buf, t, &route->shared_secrets[i]); + assert(ret); + } + } else + route->shared_secrets = NULL; return command_still_pending(cmd); } @@ -440,7 +445,8 @@ struct command_result *notification_sendpay_failure(struct command *cmd, } assert(route->result == NULL); - route->result = tal_sendpay_result_from_json(route, buf, sub); + route->result = tal_sendpay_result_from_json(route, buf, sub, + route->shared_secrets); if (route->result == NULL) plugin_err(pay_plugin->plugin, "Unable to parse sendpay_failure: %.*s", @@ -504,7 +510,8 @@ struct command_result *notification_sendpay_success(struct command *cmd, } assert(route->result == NULL); - route->result = tal_sendpay_result_from_json(route, buf, sub); + route->result = tal_sendpay_result_from_json(route, buf, sub, + route->shared_secrets); if (route->result == NULL) plugin_err(pay_plugin->plugin, "Unable to parse sendpay_success: %.*s", diff --git a/plugins/renepay/sendpay.c b/plugins/renepay/sendpay.c index 60a6c0cb6cec..3c3049902147 100644 --- a/plugins/renepay/sendpay.c +++ b/plugins/renepay/sendpay.c @@ -342,6 +342,46 @@ static struct command_result *renesendpay_done(struct command *cmd, return command_finished(cmd, response); } +static struct command_result *renesendpay_finished(struct command *cmd, + struct renesendpay *renesendpay) +{ + struct json_stream *response = jsonrpc_stream_success(cmd); + json_add_string(response, "message", + "Monitor status with listpays or waitsendpay"); + json_add_sha256(response, "payment_hash", &renesendpay->payment_hash); + json_add_u64(response, "groupid", renesendpay->groupid); + json_add_u64(response, "partid", renesendpay->partid); + json_add_node_id(response, "destination", &renesendpay->destination); + json_add_amount_msat(response, "amount_sent_msat", + renesendpay->sent_amount); + json_add_amount_msat(response, "amount_delivered_msat", + renesendpay->deliver_amount); + json_add_amount_msat(response, "amount_total_msat", + renesendpay->total_amount); + json_add_string(response, "invoice", renesendpay->invoice); + json_add_string(response, "status", "pending"); + + if (renesendpay->label) + json_add_string(response, "label", renesendpay->label); + if (renesendpay->description) + json_add_string(response, "description", + renesendpay->description); + if (renesendpay->metadata) + json_add_hex_talarr(response, "payment_metadata", + renesendpay->metadata); + + if (renesendpay->shared_secrets) { + json_array_start(response, "shared_secrets"); + for (size_t i = 0; i < tal_count(renesendpay->shared_secrets); + i++) { + json_add_secret(response, NULL, + &renesendpay->shared_secrets[i]); + } + json_array_end(response); + } + return command_finished(cmd, response); +} + static u32 initial_cltv_delta(const struct renesendpay *renesendpay) { if (tal_count(renesendpay->route) == 0) @@ -439,8 +479,8 @@ static struct command_result *waitblockheight_done(struct command *cmd, initial_cltv_delta(renesendpay) + renesendpay->blockheight); } - - return send_outreq(req); + send_outreq(req); + return renesendpay_finished(cmd, renesendpay); } struct command_result *json_renesendpay(struct command *cmd, From 0b81024f6b628acb114e9cec2bf2f912d4c32820 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Wed, 29 Jan 2025 10:42:37 +0100 Subject: [PATCH 19/19] renepay: add test_unannounced Changelog-None. Signed-off-by: Lagrang3 --- tests/test_renepay.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test_renepay.py b/tests/test_renepay.py index 30a2f7e26f75..a9c12efd27cc 100644 --- a/tests/test_renepay.py +++ b/tests/test_renepay.py @@ -856,3 +856,18 @@ def test_offer_selfpay(node_factory): offer = l1.rpc.offer(amount="2msat", description="test_offer_path_self")["bolt12"] inv = l1.rpc.fetchinvoice(offer)["invoice"] l1.rpc.call("renepay", {"invstring": inv}) + + +def test_unannounced(node_factory): + l1, l2 = node_factory.line_graph(2, announce_channels=False) + # BOLT11 direct peer + b11 = l2.rpc.invoice( + "100sat", "test_renepay_unannounced", "test_renepay_unannounced" + )["bolt11"] + ret = l1.rpc.call("renepay", {"invstring": b11}) + assert ret["status"] == "complete" + # BOLT12 direct peer + offer = l2.rpc.offer("any")["bolt12"] + b12 = l1.rpc.fetchinvoice(offer, "21sat")["invoice"] + ret = l1.rpc.call("renepay", {"invstring": b12}) + assert ret["status"] == "complete"