What changed, and why it matters
This commit fixes a timing bug in Core Lightning's experimental 'renepay' payment plugin. Previously, the plugin used an internal helper command (renesendpay) to send payment routes. Because of the way commands were chained, the plugin could receive a failure notification with an encrypted onion reply before it had saved the secrets needed to decode that reply. That caused error messages and could prevent the plugin from correctly learning why a payment failed. The fix builds the onion and stores the shared secrets directly before calling sendonion/injectpaymentonion, removing the race condition.
Apply the patch. After deployment, monitor renepay logs for the previously observed 'Unable to parse sendpay_failure' and 'JSON reply with unknown id' messages to confirm the race is resolved. No immediate incident response is indicated beyond normal update testing.
Security signals we found
Race condition between asynchronous sendpay_failure notifications and shared-secret availability
Loss or delayed handling of payment failure feedback due to RPC command lifecycle mismanagement
Plugin error logs indicating inability to decrypt/parse onion failure replies
Refactor of internal payment command flow to remove auxiliary RPC and capture secrets synchronously before send
Evidence from the diff
The patch removes the internal RPC ‘renesendpay’ and inlines onion construction and shared-secret capture into route_sendpay_request() in routetracker.c. It now calls waitblockheight once per payment, stores blockheight in struct payment, then builds the sphinx onion with create_onion() and immediately records shared_secrets in the route object before issuing sendonion/injectpaymentonion. This eliminates a window where a sendpay_failure notification could arrive before shared_secrets were available, which produced ‘Unable to parse sendpay_failure’ and ‘JSON reply with unknown id’ log errors. The change also removes the sent_routes map and registers routes as pending immediately after send_outreq.
Changed components
plugins/renepay/main.cplugins/renepay/mods.cplugins/renepay/payment.hplugins/renepay/routetracker.cplugins/renepay/routetracker.hplugins/renepay/sendpay.cplugins/renepay/sendpay.hplugins/renepay/MakefileInspect captured patch +269 / −742
diff --git a/plugins/renepay/Makefile b/plugins/renepay/Makefile
index 4d931eca..a52483e8 100644
--- a/plugins/renepay/Makefile
+++ b/plugins/renepay/Makefile
@@ -10,7 +10,6 @@ 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 \
@@ -30,7 +29,6 @@ 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 \
diff --git a/plugins/renepay/main.c b/plugins/renepay/main.c
index e1a47313..04cfe99d 100644
--- a/plugins/renepay/main.c
+++ b/plugins/renepay/main.c
@@ -19,7 +19,6 @@
#include <plugins/renepay/mods.h>
#include <plugins/renepay/payplugin.h>
#include <plugins/renepay/routetracker.h>
-#include <plugins/renepay/sendpay.h>
#include <stdio.h>
// TODO(eduardo): notice that pending attempts performed with another
@@ -452,10 +451,6 @@ static const struct plugin_command commands[] = {
"renepay",
json_renepay
},
- {
- "renesendpay",
- json_renesendpay
- },
};
static const struct plugin_notification notifications[] = {
diff --git a/plugins/renepay/mods.c b/plugins/renepay/mods.c
index e6da2d10..23e2a22c 100644
--- a/plugins/renepay/mods.c
+++ b/plugins/renepay/mods.c
@@ -753,10 +753,30 @@ REGISTER_PAYMENT_MODIFIER(compute_routes, compute_routes_cb);
* request calling sendpay.
*/
-static struct command_result *send_routes_cb(struct payment *payment)
+static struct command_result *waitblockheight_done(struct command *cmd,
+ const char *method UNUSED,
+ const char *buf,
+ const jsmntok_t *result,
+ struct payment *payment)
{
- assert(payment);
- struct routetracker *routetracker = payment->routetracker;
+ const char *err;
+ struct command *aux_cmd;
+ struct route *route;
+ struct routetracker *routetracker;
+
+ err = json_scan(tmpctx, buf, result, "{blockheight:%}",
+ JSON_SCAN(json_to_u32, &payment->blockheight));
+ payment->blockheight += 1;
+
+ if (err) {
+ plugin_err(pay_plugin->plugin,
+ "Failed to read blockheight from waitblockheight "
+ "response: %s",
+ err);
+ return payment_continue(payment);
+ }
+
+ routetracker = payment->routetracker;
assert(routetracker);
if (!routetracker->computed_routes ||
tal_count(routetracker->computed_routes) == 0) {
@@ -765,12 +785,11 @@ static struct command_result *send_routes_cb(struct payment *payment)
__func__);
return payment_continue(payment);
}
- struct command *cmd = payment_command(payment);
- assert(cmd);
for (size_t i = 0; i < tal_count(routetracker->computed_routes); i++) {
- struct route *route = routetracker->computed_routes[i];
+ aux_cmd = aux_command(cmd);
+ route = routetracker->computed_routes[i];
- route_sendpay_request(cmd, take(route), payment);
+ route_sendpay_request(aux_cmd, take(route), payment);
payment_note(payment, LOG_INFORM,
"Sent route request: partid=%" PRIu64
@@ -785,6 +804,22 @@ static struct command_result *send_routes_cb(struct payment *payment)
return payment_continue(payment);
}
+static struct command_result *send_routes_cb(struct payment *payment)
+{
+ struct command *cmd;
+ struct out_req *req;
+ assert(payment);
+ cmd = payment_command(payment);
+ if (!cmd)
+ plugin_err(pay_plugin->plugin,
+ "send_routes_pay_mod: cannot get a valid cmd.");
+ req =
+ jsonrpc_request_start(cmd, "waitblockheight", waitblockheight_done,
+ payment_rpc_failure, payment);
+ json_add_num(req->js, "blockheight", 0);
+ return send_outreq(req);
+}
+
REGISTER_PAYMENT_MODIFIER(send_routes, send_routes_cb);
/*****************************************************************************
diff --git a/plugins/renepay/payment.h b/plugins/renepay/payment.h
index e3d30e30..8a27922a 100644
--- a/plugins/renepay/payment.h
+++ b/plugins/renepay/payment.h
@@ -73,6 +73,9 @@ struct payment {
struct plugin_timer *waitresult_timer;
struct routetracker *routetracker;
+
+ /* use this to build the onions */
+ u32 blockheight;
};
static inline const struct sha256 payment_hash(const struct payment *p)
diff --git a/plugins/renepay/routetracker.c b/plugins/renepay/routetracker.c
index b30a4a54..b8810465 100644
--- a/plugins/renepay/routetracker.c
+++ b/plugins/renepay/routetracker.c
@@ -1,5 +1,7 @@
#include "config.h"
#include <common/json_stream.h>
+#include <common/onion_encode.h>
+#include <common/sphinx.h>
#include <plugins/renepay/json.h>
#include <plugins/renepay/payment.h>
#include <plugins/renepay/payplugin.h>
@@ -23,21 +25,18 @@ struct routetracker *new_routetracker(const tal_t *ctx, struct payment *payment)
struct routetracker *rt = tal(ctx, struct routetracker);
rt->computed_routes = tal_arr(rt, struct route *, 0);
- rt->sent_routes = tal(rt, struct route_map);
rt->finalized_routes = tal_arr(rt, struct route *, 0);
- if (!rt->computed_routes || !rt->sent_routes || !rt->finalized_routes)
+ if (!rt->computed_routes || !rt->finalized_routes)
/* bad allocation */
return tal_free(rt);
- route_map_init(rt->sent_routes);
return rt;
}
bool routetracker_have_results(struct routetracker *routetracker)
{
- return route_map_count(routetracker->sent_routes) == 0 &&
- tal_count(routetracker->finalized_routes) > 0;
+ return tal_count(routetracker->finalized_routes) > 0;
}
void routetracker_cleanup(struct routetracker *routetracker)
@@ -150,7 +149,7 @@ static void remove_route(struct route *route, struct route_map *map)
* - or after listsendpays reveals some pending route that we didn't
* previously know about. */
static void route_pending_register(struct routetracker *routetracker,
- struct route *route)
+ struct route *route TAKES)
{
assert(route);
assert(routetracker);
@@ -165,17 +164,12 @@ static void route_pending_register(struct routetracker *routetracker,
__func__,
fmt_routekey(tmpctx, &route->key));
- if (!route_map_del(routetracker->sent_routes, route))
- plugin_err(pay_plugin->plugin,
- "%s: tracking a route (%s) not computed by this "
- "payment call",
- __func__,
- fmt_routekey(tmpctx, &route->key));
-
uncertainty_commit_htlcs(pay_plugin->uncertainty, route);
- if (!tal_steal(pay_plugin->pending_routes, route) ||
- !route_map_add(pay_plugin->pending_routes, route) ||
+ if (taken(route))
+ tal_steal(pay_plugin->pending_routes, route);
+
+ if (!route_map_add(pay_plugin->pending_routes, route) ||
!tal_add_destructor2(route, remove_route,
pay_plugin->pending_routes))
plugin_err(pay_plugin->plugin, "%s: failed to register route.",
@@ -192,93 +186,6 @@ 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,
- 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");
-
- 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);
-}
-
-/* sendpay really only fails immediately in two ways:
- * 1. We screwed up and misused the API.
- * 2. The first peer is disconnected.
- */
-static struct command_result *sendpay_failed(struct command *cmd,
- const char *method UNUSED,
- const char *buf,
- const jsmntok_t *tok,
- struct route *route)
-{
- assert(route);
- struct payment *payment = route_get_payment_verify(route);
- struct routetracker *routetracker = payment->routetracker;
- assert(routetracker);
-
- enum jsonrpc_errcode errcode;
- const char *msg;
- const char *err;
-
- err = json_scan(tmpctx, buf, tok, "{code:%,message:%}",
- JSON_SCAN(json_to_jsonrpc_errcode, &errcode),
- JSON_SCAN_TAL(tmpctx, json_strdup, &msg));
- if (err)
- plugin_err(pay_plugin->plugin,
- "Unable to parse sendpay error: %s, json: %.*s", err,
- json_tok_full_len(tok), json_tok_full(buf, tok));
-
- payment_note(payment, LOG_INFORM,
- "Sendpay failed: partid=%" PRIu64
- " errorcode:%d message=%s",
- route->key.partid, errcode, msg);
-
- if (errcode != PAY_TRY_OTHER_ROUTE) {
- plugin_log(pay_plugin->plugin, LOG_UNUSUAL,
- "Strange error from sendpay: %.*s",
- json_tok_full_len(tok), json_tok_full(buf, tok));
- }
-
- /* There is no new knowledge from this kind of failure.
- * We just disable this scid. */
- struct short_channel_id_dir scidd_disable = {
- .scid = route->hops[0].scid, .dir = route->hops[0].direction};
- payment_disable_chan(payment, scidd_disable, LOG_INFORM,
- "sendpay didn't like first hop: %s", msg);
-
- if (!route_map_del(routetracker->sent_routes, route))
- plugin_err(pay_plugin->plugin,
- "%s: route (%s) is not marked as sent",
- __func__,
- fmt_routekey(tmpctx, &route->key));
- tal_free(route);
- return command_still_pending(cmd);
-}
-
void payment_collect_results(struct payment *payment,
struct preimage **payment_preimage,
enum jsonrpc_errcode *final_error,
@@ -345,59 +252,230 @@ void payment_collect_results(struct payment *payment,
tal_resize(&routetracker->finalized_routes, 0);
}
+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, const unsigned int blockheight,
+ const struct route_hop *route, const struct sha256 *payment_hash,
+ const u32 final_cltv_delta, const struct blinded_path *blinded_path,
+ const struct secret *payment_secret, const struct amount_msat total_amount,
+ const struct amount_msat deliver_amount, const u8 *metadata,
+ const struct node_id first_node, const size_t first_index,
+ struct secret **shared_secrets)
+{
+ 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(route);
+
+ struct sphinx_path *sp = sphinx_path_new(this_ctx, payment_hash->u.u8,
+ sizeof(payment_hash->u.u8));
+
+ 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, ¤t_node);
+ assert(ret);
+
+ const struct route_hop *hop = &route[i];
+ payload = onion_nonfinal_hop(this_ctx, &hop->scid, hop->amount,
+ hop->delay + blockheight);
+ // FIXME: better handle error here
+ ret = sphinx_add_hop_has_length(sp, &node, take(payload));
+ assert(ret);
+ current_node = route[i].node_id;
+ }
+
+ const u32 final_cltv = final_cltv_delta + blockheight;
+ if (blinded_path) {
+ sphinx_append_blinded_path(this_ctx, sp, blinded_path,
+ deliver_amount, total_amount,
+ final_cltv);
+ } else {
+ sphinx_append_final_hop(this_ctx, sp, payment_secret,
+ ¤t_node, deliver_amount,
+ total_amount, final_cltv, metadata);
+ }
+
+ struct onionpacket *packet =
+ create_onionpacket(this_ctx, sp, ROUTING_INFO_SIZE, shared_secrets);
+ *shared_secrets = tal_steal(ctx, *shared_secrets);
+
+ const u8 *onion = serialize_onionpacket(ctx, packet);
+ tal_free(this_ctx);
+ return onion;
+}
+
+static u32 initial_cltv_delta(const struct route *route,
+ const struct payment_info *pinfo)
+{
+ if (tal_count(route->hops) == 0)
+ return pinfo->final_cltv;
+ return route->hops[0].delay;
+}
+
+static struct command_result *sendonion_done(struct command *aux_cmd,
+ const char *method UNUSED,
+ const char *buffer UNUSED,
+ const jsmntok_t *toks UNUSED,
+ struct payment *payment UNUSED)
+{
+ return aux_command_done(aux_cmd);
+}
+
+static struct command_result *
+sendonion_fail(struct command *aux_cmd, const char *method, const char *buffer,
+ const jsmntok_t *toks, struct payment *payment UNUSED)
+{
+ plugin_log(aux_cmd->plugin, LOG_DBG, "%s failed with: %.*s", method,
+ json_tok_full_len(toks), json_tok_full(buffer, toks));
+ return aux_command_done(aux_cmd);
+}
+
struct command_result *route_sendpay_request(struct command *cmd,
struct route *route TAKES,
struct payment *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);
+ // build onion
+ const u8 *onion;
+ const struct payment_info *pinfo;
+ const struct blinded_path *blinded_path = NULL;
+ struct out_req *req;
+
+ pinfo = &payment->payment_info;
+ if (tal_count(pinfo->blinded_paths) > 0) {
+ assert(route->path_num < tal_count(pinfo->blinded_paths));
+ blinded_path = pinfo->blinded_paths[route->path_num];
+ }
+
+ if (tal_count(route->hops) > 0) {
+ onion = create_onion(
+ route, payment->blockheight, route->hops,
+ &pinfo->payment_hash, pinfo->final_cltv, blinded_path,
+ pinfo->payment_secret, pinfo->amount, route->amount_deliver,
+ /* metadata = */ NULL, route->hops[0].node_id, 1,
+ &route->shared_secrets);
+ } else {
+ /* This is either a self-payment or a payment through a blinded
+ * path that starts at our node. */
+ onion = create_onion(route, payment->blockheight, route->hops,
+ &pinfo->payment_hash, pinfo->final_cltv,
+ blinded_path, pinfo->payment_secret,
+ pinfo->amount, route->amount_deliver,
+ /* metadata = */ NULL, pay_plugin->my_id,
+ 0, &route->shared_secrets);
+ }
+
+ // send onion
+ // FIXME: use injectpaymentonion in both cases
+ if (tal_count(route->hops) > 0) {
+ req = jsonrpc_request_start(cmd, "sendonion", sendonion_done,
+ sendonion_fail, payment);
+ json_add_hex_talarr(req->js, "onion", onion);
+ 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_amount_msat(req->js, "amount_msat",
+ route->amount_deliver);
+ if (pinfo->label)
+ json_add_string(req->js, "label", pinfo->label);
+ if (pinfo->invstr)
+ json_add_string(req->js, "bolt11", pinfo->invstr);
+ if (pinfo->description)
+ json_add_string(req->js, "description",
+ pinfo->description);
+ json_add_node_id(req->js, "destination", &pinfo->destination);
+ json_add_amount_msat(req->js, "total_amount_msat",
+ pinfo->amount);
+
+ json_array_start(req->js, "shared_secrets");
+ for (size_t i = 0; i < tal_count(route->shared_secrets); i++) {
+ json_add_secret(req->js, NULL,
+ &route->shared_secrets[i]);
+ }
+ json_array_end(req->js);
+
+ const struct route_hop *hop = &route->hops[0];
+ json_object_start(req->js, "first_hop");
+ json_add_amount_msat(req->js, "amount_msat", hop->amount);
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_add_num(req->js, "delay",
+ hop->delay + payment->blockheight);
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];
- json_myadd_blinded_path(req->js, "blinded_path", bpath);
+ // FIXME: No localinvreqid is provided
+ } else {
+ req = jsonrpc_request_start(cmd, "injectpaymentonion",
+ sendonion_done, sendonion_fail,
+ payment);
+ json_add_hex_talarr(req->js, "onion", onion);
+ 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_amount_msat(req->js, "amount_msat",
+ route->amount_sent);
+ if (pinfo->label)
+ json_add_string(req->js, "label", pinfo->label);
+ if (pinfo->invstr)
+ json_add_string(req->js, "invstring", pinfo->invstr);
+ json_add_amount_msat(req->js, "destination_msat",
+ route->amount_deliver);
+ json_add_u32(req->js, "cltv_expiry",
+ initial_cltv_delta(route, pinfo) +
+ payment->blockheight);
+ // FIXME: No localinvreqid is provided
}
-
- route_map_add(payment->routetracker->sent_routes, route);
- if (taken(route))
- tal_steal(payment->routetracker->sent_routes, route);
+ route_pending_register(payment->routetracker, route);
return send_outreq(req);
}
diff --git a/plugins/renepay/routetracker.h b/plugins/renepay/routetracker.h
index 2e4c440a..32c18c99 100644
--- a/plugins/renepay/routetracker.h
+++ b/plugins/renepay/routetracker.h
@@ -10,10 +10,6 @@ struct routetracker{
/* Routes that we compute and are kept here before sending them. */
struct route **computed_routes;
- /* Routes that we sendpay and are still waiting for rpc returning
- * success. */
- struct route_map *sent_routes;
-
/* Routes that have concluded (either SENDPAY_FAILED or
* SENDPAY_COMPLETE). */
struct route **finalized_routes;
diff --git a/plugins/renepay/sendpay.c b/plugins/renepay/sendpay.c
deleted file mode 100644
index 3c304990..00000000
--- a/plugins/renepay/sendpay.c
+++ /dev/null
@@ -1,568 +0,0 @@
-#include "config.h"
-#include <common/json_param.h>
-#include <common/json_stream.h>
-#include <common/onion_encode.h>
-#include <plugins/renepay/json.h>
-#include <plugins/renepay/payplugin.h>
-#include <plugins/renepay/sendpay.h>
-
-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 *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 = 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,
- 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,
- 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);
-
- struct sphinx_path *sp =
- sphinx_path_new(this_ctx, renesendpay->payment_hash.u.u8,
- sizeof(renesendpay->payment_hash.u.u8));
-
- 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, ¤t_node);
- assert(ret);
-
- 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;
- 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,
- ¤t_node,
- 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 *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;
- 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);
- json_add_string(response, "status", "pending");
-
- 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)
- 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);
- }
-
- /* FIXME: shall we report the blinded path, secret and route used? */
- 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)
- 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,
- 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;
- struct out_req *req;
-
- if (tal_count(renesendpay->route) > 0) {
- onion = create_onion(tmpctx, renesendpay,
- renesendpay->route[0].node_id, 1);
- req = jsonrpc_request_start(cmd, "sendonion", renesendpay_done,
- rpc_fail, 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);
- 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 {
- /* 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->sent_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);
- }
- send_outreq(req);
- return renesendpay_finished(cmd, renesendpay);
-}
-
-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;
-
- 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;
-
- 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);
- renesendpay->shared_secrets = NULL;
-
- struct out_req *req =
- jsonrpc_request_start(cmd, "waitblockheight", waitblockheight_done,
- rpc_fail, renesendpay);
- json_add_num(req->js, "blockheight", 0);
- return send_outreq(req);
-}
diff --git a/plugins/renepay/sendpay.h b/plugins/renepay/sendpay.h
deleted file mode 100644
index 10120633..00000000
--- a/plugins/renepay/sendpay.h
+++ /dev/null
@@ -1,10 +0,0 @@
-#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 */
Why this scored 47/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.