From 480dec58f29dfcf80ae999948d2d034bcda2055b Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 8 Apr 2024 15:35:50 +0100 Subject: [PATCH] renepay: add routetracker.c Routetracker is a structure that is used to follow the progress of the route forwarding requests. --- plugins/renepay/routetracker.c | 357 +++++++++++++++++++++++++++++++++ plugins/renepay/routetracker.h | 53 +++++ 2 files changed, 410 insertions(+) create mode 100644 plugins/renepay/routetracker.c create mode 100644 plugins/renepay/routetracker.h diff --git a/plugins/renepay/routetracker.c b/plugins/renepay/routetracker.c new file mode 100644 index 000000000..0e5e32330 --- /dev/null +++ b/plugins/renepay/routetracker.c @@ -0,0 +1,357 @@ +#include +#include +#include +#include +#include +#include + +struct routetracker *new_routetracker(const tal_t *ctx) +{ + struct routetracker *rt = tal(ctx, struct routetracker); + + rt->sent_routes = tal(rt, struct route_map); + route_map_init(rt->sent_routes); + + rt->pending_routes = tal(rt, struct route_map); + route_map_init(rt->pending_routes); + + rt->finalized_routes = tal_arr(rt, struct route *, 0); + return rt; +} + +size_t routetracker_count_sent(struct routetracker *routetracker) +{ + return route_map_count(routetracker->sent_routes); +} + +void routetracker_cleanup(struct routetracker *routetracker) +{ + // TODO +} + +static void routetracker_add_to_final(struct routetracker *routetracker, + struct route *route) +{ + if (!route_map_del(routetracker->pending_routes, route)) + plugin_err(pay_plugin->plugin, + "%s: route with key %s is not in pending_routes", + __PRETTY_FUNCTION__, + fmt_routekey(tmpctx, &route->key)); + tal_arr_expand(&routetracker->finalized_routes, route); + tal_steal(routetracker, route); +} +static void route_is_success(struct route *route) +{ + routetracker_add_to_final(route->payment->routetracker, route); +} +void route_is_failure(struct route *route) +{ + routetracker_add_to_final(route->payment->routetracker, route); +} +static void route_sent(struct route *route) +{ + struct routetracker *routetracker = route->payment->routetracker; + route_map_add(routetracker->sent_routes, route); + tal_steal(routetracker, route); +} +static void route_sendpay_fail(struct route *route TAKES) +{ + struct routetracker *routetracker = route->payment->routetracker; + if (!route_map_del(routetracker->sent_routes, route)) + plugin_log(pay_plugin->plugin, LOG_UNUSUAL, + "%s: route (%s) is not marked as sent", + __PRETTY_FUNCTION__, + fmt_routekey(tmpctx, &route->key)); + if (taken(route)) + tal_free(route); +} + +/* This route is pending, ie. locked in HTLCs. + * Called either: + * - after a sendpay is accepted, + * - or after listsendpays reveals some pending route that we didn't + * previously know about. */ +void route_pending(const struct route *route) +{ + assert(route); + struct payment *payment = route->payment; + assert(payment); + assert(payment->groupid == route->key.groupid); + struct routetracker *routetracker = payment->routetracker; + assert(routetracker); + + /* we already keep track of this route */ + if (route_map_get(routetracker->pending_routes, &route->key)) + return; + + if (!route_map_del(routetracker->sent_routes, route)) + plugin_log(pay_plugin->plugin, LOG_DBG, + "%s: tracking a route (%s) not computed by this " + "payment call", + __PRETTY_FUNCTION__, + fmt_routekey(tmpctx, &route->key)); + + uncertainty_commit_htlcs(pay_plugin->uncertainty, route); + route_map_add(routetracker->pending_routes, route); + tal_steal(routetracker, route); + + if (!amount_msat_add(&payment->total_sent, payment->total_sent, + route_sends(route)) || + !amount_msat_add(&payment->total_delivering, + payment->total_delivering, + route_delivers(route))) { + plugin_err(pay_plugin->plugin, + "%s: amount_msat arithmetic overflow.", + __PRETTY_FUNCTION__); + } +} + +static void route_result_collected(struct route *route TAKES) +{ + assert(route); + assert(route->result); + // TODO: also improve knowledge here? + uncertainty_remove_htlcs(pay_plugin->uncertainty, route); + + assert(route->payment); + struct payment *payment = route->payment; + assert(payment->groupid == route->key.groupid); + + if (route->result->status == SENDPAY_FAILED) { + if (!amount_msat_sub(&payment->total_delivering, + payment->total_delivering, + route_delivers(route)) || + !amount_msat_sub(&payment->total_sent, payment->total_sent, + route_sends(route))) { + plugin_err(pay_plugin->plugin, + "%s: routes do not add up to " + "payment total amount.", + __PRETTY_FUNCTION__); + } + } + if(taken(route)) + tal_free(route); +} + +/* Callback function for sendpay request success. */ +static struct command_result *sendpay_done(struct command *cmd, + const char *buf UNUSED, + const jsmntok_t *result UNUSED, + struct route *route) +{ + assert(route); + route_pending(route); + 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 *buf, + const jsmntok_t *tok, + struct route *route) +{ + assert(route); + assert(route->payment); + struct payment *payment = route->payment; + + 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. */ + payment_disable_chan(payment, route->hops[0].scid, LOG_INFORM, + "sendpay didn't like first hop: %s", msg); + + route_sendpay_fail(take(route)); + return command_still_pending(cmd); +} + +void payment_collect_results(struct payment *payment, + struct preimage **payment_preimage, + enum jsonrpc_errcode *final_error, + const char **final_msg) +{ + assert(payment); + struct routetracker *routetracker = payment->routetracker; + assert(routetracker); + const size_t ncompleted = tal_count(routetracker->finalized_routes); + for (size_t i = 0; i < ncompleted; i++) { + struct route *r = routetracker->finalized_routes[i]; + assert(r); + assert(r->result); + + /* We should never start a new groupid while there are pending + * onions with a different groupid. */ + if (payment->groupid != r->key.groupid) { + plugin_err(pay_plugin->plugin, + "%s: current groupid=%" PRIu64 + ", but recieved a sendpay result with " + "groupid=%" PRIu64, + __PRETTY_FUNCTION__, payment->groupid, + r->key.groupid); + } + + assert(r->result->status == SENDPAY_COMPLETE || + r->result->status == SENDPAY_FAILED); + if (r->result->status == SENDPAY_COMPLETE && payment_preimage) { + assert(r->result->payment_preimage); + *payment_preimage = + tal_dup(payment, struct preimage, + r->result->payment_preimage); + } + + if (r->result->status == SENDPAY_FAILED) { + if (r->final_msg) { + if (final_error) + *final_error = r->final_error; + + if (final_msg) + *final_msg = + tal_strdup(tmpctx, r->final_msg); + } + } + route_result_collected(take(r)); + } + tal_resize(&routetracker->finalized_routes, 0); +} + +struct command_result *route_sendpay_request(struct command *cmd, + struct route *route) +{ + struct out_req *req = + jsonrpc_request_start(pay_plugin->plugin, cmd, "sendpay", + sendpay_done, sendpay_failed, route); + + json_add_route(req->js, route); + + route_sent(route); + return send_outreq(pay_plugin->plugin, req); +} + +struct command_result *notification_sendpay_failure(struct command *cmd, + const char *buf, + const jsmntok_t *params) +{ + plugin_log(pay_plugin->plugin, LOG_DBG, + "sendpay_failure notification: %.*s", + json_tok_full_len(params), json_tok_full(buf, params)); + + // enum jsonrpc_errcode errcode; + const jsmntok_t *sub = json_get_member(buf, params, "sendpay_failure"); + + struct routekey *key = tal_routekey_from_json( + tmpctx, buf, json_get_member(buf, sub, "data")); + if (!key) + plugin_err(pay_plugin->plugin, + "Unable to get routekey from sendpay_failure: %.*s", + json_tok_full_len(sub), json_tok_full(buf, sub)); + + struct payment *payment = + payment_map_get(pay_plugin->payment_map, key->payment_hash); + + if (!payment) { + /* This sendpay is not linked to any route in our database, we + * skip it. */ + return notification_handled(cmd); + } + + assert(payment->routetracker); + struct route *route = + route_map_get(payment->routetracker->pending_routes, key); + if (!route) + plugin_err(pay_plugin->plugin, + "%s: key %s is not found in pending_routes", + __PRETTY_FUNCTION__, fmt_routekey(tmpctx, key)); + + assert(route->result == NULL); + route->result = tal_sendpay_result_from_json(route, buf, sub); + if (route->result == NULL) + plugin_err(pay_plugin->plugin, + "Unable to parse sendpay_failure: %.*s", + json_tok_full_len(sub), json_tok_full(buf, sub)); + + if (route->result->status != SENDPAY_FAILED) { + /* FIXME shouldn't this be always SENDPAY_FAILED? */ + const jsmntok_t *datatok = json_get_member(buf, sub, "data"); + const jsmntok_t *statustok = + json_get_member(buf, datatok, "status"); + const char *status_str = json_strdup(tmpctx, buf, statustok); + + plugin_log(pay_plugin->plugin, LOG_UNUSUAL, + "sendpay_failure notification returned status=%s", + status_str); + route->result->status = SENDPAY_FAILED; + } + return routefail_start(route, route, cmd); +} + +struct command_result *notification_sendpay_success(struct command *cmd, + const char *buf, + const jsmntok_t *params) +{ + plugin_log(pay_plugin->plugin, LOG_DBG, + "sendpay_success notification: %.*s", + json_tok_full_len(params), json_tok_full(buf, params)); + + const jsmntok_t *sub = json_get_member(buf, params, "sendpay_success"); + + struct routekey *key = tal_routekey_from_json(tmpctx, buf, sub); + if (!key) + plugin_err(pay_plugin->plugin, + "Unable to get routekey from sendpay_success: %.*s", + json_tok_full_len(sub), json_tok_full(buf, sub)); + + struct payment *payment = + payment_map_get(pay_plugin->payment_map, key->payment_hash); + + if (!payment) { + /* This sendpay is not linked to any route in our database, we + * skip it. */ + return notification_handled(cmd); + } + + assert(payment->routetracker); + struct route *route = + route_map_get(payment->routetracker->pending_routes, key); + if (!route) + plugin_err(pay_plugin->plugin, + "%s: key %s is not found in pending_routes", + __PRETTY_FUNCTION__, fmt_routekey(tmpctx, key)); + + assert(route->result == NULL); + route->result = tal_sendpay_result_from_json(route, buf, sub); + if (route->result == NULL) + plugin_err(pay_plugin->plugin, + "Unable to parse sendpay_success: %.*s", + json_tok_full_len(sub), json_tok_full(buf, sub)); + + assert(route->result->status == SENDPAY_COMPLETE); + + // FIXME: what happens when several success notification arrive for the + // same payment? Even after the payment has been resolved. + route_is_success(route); + return notification_handled(cmd); +} diff --git a/plugins/renepay/routetracker.h b/plugins/renepay/routetracker.h new file mode 100644 index 000000000..9cc3de42e --- /dev/null +++ b/plugins/renepay/routetracker.h @@ -0,0 +1,53 @@ +#ifndef LIGHTNING_PLUGINS_RENEPAY_ROUTETRACKER_H +#define LIGHTNING_PLUGINS_RENEPAY_ROUTETRACKER_H + +/* This module provides entry points for the management of a route thread. */ + +#include "config.h" +#include + +struct routetracker{ + struct route_map *sent_routes; + struct route_map *pending_routes; + struct route **finalized_routes; +}; + +struct routetracker *new_routetracker(const tal_t *ctx); +// bool routetracker_is_ready(const struct routetracker *routetracker); +void routetracker_cleanup(struct routetracker *routetracker); +size_t routetracker_count_sent(struct routetracker *routetracker); + +/* The payment has a list of route that have "returned". Calling this function + * payment will look through that list and process those routes' results: + * - update the commited amounts, + * - update the uncertainty network, + * - and free the allocated memory. */ +void payment_collect_results(struct payment *payment, + struct preimage **payment_preimage, + enum jsonrpc_errcode *final_error, + const char **final_msg); + +/* Announce that this route is pending and needs to be kept in the waiting list + * for notifications. */ +void route_pending(const struct route *route); + +/* Sends a sendpay request for this route. */ +struct command_result *route_sendpay_request(struct command *cmd, + struct route *route); + +struct command_result *notification_sendpay_failure(struct command *cmd, + const char *buf, + const jsmntok_t *params); + +struct command_result *notification_sendpay_success(struct command *cmd, + const char *buf, + const jsmntok_t *params); + +void route_is_failure(struct route *route); + +// FIXME: double-check that we actually get one notification for each sendpay, +// ie. that after some time we don't have yet pending sendpays for old failed or +// successful payments that we havent processed because we haven't received the +// notification + +#endif /* LIGHTNING_PLUGINS_RENEPAY_ROUTETRACKER_H */