From f75a73ded8516b14a1a3bb5efb67c7d54b683787 Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Sat, 11 Jan 2025 11:33:26 +0100 Subject: [PATCH] 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 e0a3ba24ab55..cf017204154b 100644 --- a/plugins/renepay/main.c +++ b/plugins/renepay/main.c @@ -343,6 +343,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; @@ -368,6 +369,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 9a83a60739ff..c5bbb16cbcb0 100644 --- a/plugins/renepay/mods.c +++ b/plugins/renepay/mods.c @@ -13,6 +13,7 @@ #include #include #include +#include #define INVALID_ID UINT32_MAX @@ -515,7 +516,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); @@ -528,6 +531,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; @@ -604,7 +613,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; } } @@ -625,6 +635,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( @@ -636,6 +648,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 * @@ -694,6 +744,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! */ @@ -701,7 +756,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, @@ -709,8 +764,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); @@ -1230,20 +1287,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 69571990ae8a..a763a3d3108b 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 52a80d7c2165..2f5bd2a845d6 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 2173ebd54392..fe6a9572d268 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 a570fd9eb7d0..9b22279613e4 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)));