Skip to content

Commit

Permalink
renepay: resolve self payments with fake node
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
Lagrang3 committed Jan 27, 2025
1 parent 7f5ffb4 commit 081bfeb
Show file tree
Hide file tree
Showing 6 changed files with 46 additions and 98 deletions.
24 changes: 11 additions & 13 deletions plugins/renepay/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,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;
Expand All @@ -370,19 +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();
}
/* 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,
Expand Down
105 changes: 23 additions & 82 deletions plugins/renepay/mods.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -755,8 +697,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
Expand Down Expand Up @@ -1291,9 +1232,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,
Expand Down
3 changes: 2 additions & 1 deletion plugins/renepay/route.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions plugins/renepay/routebuilder.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 0 additions & 2 deletions plugins/renepay/sendpay.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
8 changes: 8 additions & 0 deletions tests/test_renepay.py
Original file line number Diff line number Diff line change
Expand Up @@ -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})

0 comments on commit 081bfeb

Please sign in to comment.