Skip to content

Commit

Permalink
renepay: decode error onion
Browse files Browse the repository at this point in the history
When paying with injectpaymentonion we need to manually decode the error
from the onion.

Changelog-None.

Signed-off-by: Lagrang3 <[email protected]>
  • Loading branch information
Lagrang3 committed Jan 29, 2025
1 parent d89e58d commit 8147933
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 86 deletions.
175 changes: 101 additions & 74 deletions plugins/renepay/json.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "config.h"
#include <common/json_stream.h>
#include <common/onionreply.h>
#include <common/sphinx.h>
#include <plugins/renepay/json.h>

/* See if this notification is about one of our flows. */
Expand Down Expand Up @@ -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");
Expand All @@ -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
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion plugins/renepay/json.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
25 changes: 16 additions & 9 deletions plugins/renepay/routetracker.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
44 changes: 42 additions & 2 deletions plugins/renepay/sendpay.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit 8147933

Please sign in to comment.