core-lightning/plugins/renepay/routetracker.c
Lagrang3 a339e553e8 renepay: listsendpay before computing routes
It feels unsafe to rely on the internal state of the plugin's database
to tell how many pending sendpays there are for the current payment.
The safest way is to assume lightningd knows and thus use listsendpay
before computing routes.

Signed-off-by: Lagrang3 <lagrang3@protonmail.com>
2024-08-12 13:27:34 -07:00

387 lines
12 KiB
C

#include "config.h"
#include <common/json_stream.h>
#include <plugins/renepay/json.h>
#include <plugins/renepay/payment.h>
#include <plugins/renepay/payplugin.h>
#include <plugins/renepay/routefail.h>
#include <plugins/renepay/routetracker.h>
static struct payment *route_get_payment_verify(struct route *route)
{
struct payment *payment =
payment_map_get(pay_plugin->payment_map, route->key.payment_hash);
if (!payment)
plugin_err(pay_plugin->plugin,
"%s: no payment associated with routekey %s",
__PRETTY_FUNCTION__,
fmt_routekey(tmpctx, &route->key));
return payment;
}
struct routetracker *new_routetracker(const tal_t *ctx, struct payment *payment)
{
struct routetracker *rt = tal(ctx, struct routetracker);
rt->sent_routes = tal(rt, struct route_map);
route_map_init(rt->sent_routes);
rt->finalized_routes = tal_arr(rt, struct route *, 0);
return rt;
}
bool routetracker_have_results(struct routetracker *routetracker)
{
return route_map_count(routetracker->sent_routes) == 0 &&
tal_count(routetracker->finalized_routes) > 0;
}
void routetracker_cleanup(struct routetracker *routetracker)
{
// TODO
}
static void routetracker_add_to_final(struct routetracker *routetracker,
struct route *route)
{
tal_arr_expand(&routetracker->finalized_routes, route);
tal_steal(routetracker, route);
}
static void route_success_register(struct routetracker *routetracker,
struct route *route)
{
routetracker_add_to_final(routetracker, route);
}
void route_failure_register(struct routetracker *routetracker,
struct route *route)
{
routetracker_add_to_final(routetracker, route);
}
static void remove_route(struct route *route, struct route_map *map)
{
route_map_del(map, route);
}
static void route_sent_register(struct routetracker *routetracker,
struct route *route STEALS)
{
route_map_add(routetracker->sent_routes, route);
tal_steal(routetracker, route);
}
static void route_sendpay_fail(struct routetracker *routetracker,
struct route *route TAKES)
{
if (!route_map_del(routetracker->sent_routes, route))
plugin_err(pay_plugin->plugin,
"%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. */
static void route_pending_register(struct routetracker *routetracker,
struct route *route)
{
assert(route);
assert(routetracker);
struct payment *payment = route_get_payment_verify(route);
assert(payment);
assert(payment->groupid == route->key.groupid);
/* we already keep track of this route */
if (route_map_get(pay_plugin->pending_routes, &route->key))
plugin_err(pay_plugin->plugin,
"%s: tracking a route (%s) duplicate?",
__PRETTY_FUNCTION__,
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",
__PRETTY_FUNCTION__,
fmt_routekey(tmpctx, &route->key));
uncertainty_commit_htlcs(pay_plugin->uncertainty, route);
if (!tal_steal(pay_plugin, route) ||
!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.",
__PRETTY_FUNCTION__);
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 routetracker *routetracker,
struct route *route TAKES)
{
assert(route);
assert(routetracker);
assert(route->result);
struct payment *payment = route_get_payment_verify(route);
assert(payment);
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);
struct payment *payment = route_get_payment_verify(route);
route_pending_register(payment->routetracker, 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);
struct payment *payment = route_get_payment_verify(route);
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(payment->routetracker, 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(tmpctx, 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(routetracker, take(r));
}
tal_resize(&routetracker->finalized_routes, 0);
}
struct command_result *route_sendpay_request(struct command *cmd,
struct route *route,
struct payment *payment)
{
struct out_req *req =
jsonrpc_request_start(pay_plugin->plugin, cmd, "sendpay",
sendpay_done, sendpay_failed, route);
json_add_route(req->js, route, payment);
route_sent_register(payment->routetracker, 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(pay_plugin->pending_routes, key);
if (!route) {
/* This can happen if payment is first tried with renepay and
* then retried using another payment plugin. */
plugin_log(pay_plugin->plugin, LOG_UNUSUAL,
"%s: key %s is not found in pending_routes",
__PRETTY_FUNCTION__, fmt_routekey(tmpctx, key));
return notification_handled(cmd);
}
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(pay_plugin->pending_routes, key);
if (!route) {
/* This can happen if payment is first tried with renepay and
* then retried using another payment plugin. */
plugin_log(pay_plugin->plugin, LOG_UNUSUAL,
"%s: key %s is not found in pending_routes",
__PRETTY_FUNCTION__, fmt_routekey(tmpctx, key));
return notification_handled(cmd);
}
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_success_register(payment->routetracker, route);
return notification_handled(cmd);
}