askrene: remember individual reservations, for better debugging.

Suggested-by: Lagrang3 <lagrang3@protonmail.com>
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2024-10-04 08:56:53 +09:30
parent 10dc40a895
commit 0398d2ff73
4 changed files with 77 additions and 141 deletions

View File

@ -447,7 +447,6 @@ void get_constraints(const struct route_query *rq,
struct amount_msat *max)
{
struct short_channel_id_dir scidd;
const struct reserve *reserve;
size_t idx = gossmap_chan_idx(rq->gossmap, chan);
*min = AMOUNT_MSAT(0);
@ -494,14 +493,8 @@ void get_constraints(const struct route_query *rq,
}
/* Finally, if any is in use, subtract that! */
reserve = find_reserve(rq->reserved, &scidd);
if (reserve) {
/* They can definitely *try* to push too much through a channel! */
if (!amount_msat_sub(min, *min, reserve->amount))
*min = AMOUNT_MSAT(0);
if (!amount_msat_sub(max, *max, reserve->amount))
*max = AMOUNT_MSAT(0);
}
reserve_sub(rq->reserved, &scidd, min);
reserve_sub(rq->reserved, &scidd, max);
}
struct getroutes_info {
@ -687,7 +680,6 @@ static struct command_result *json_askrene_reserve(struct command *cmd,
{
struct reserve_hop *path;
struct json_stream *response;
size_t num;
struct askrene *askrene = get_askrene(cmd->plugin);
if (!param(cmd, buffer, params,
@ -695,16 +687,8 @@ static struct command_result *json_askrene_reserve(struct command *cmd,
NULL))
return command_param_failed();
num = reserves_add(askrene->reserved, path, tal_count(path));
if (num != tal_count(path)) {
const struct reserve *r = find_reserve(askrene->reserved, &path[num].scidd);
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Overflow reserving %zu: %s amount %s (%s reserved already)",
num,
fmt_short_channel_id_dir(tmpctx, &path[num].scidd),
fmt_amount_msat(tmpctx, path[num].amount),
r ? fmt_amount_msat(tmpctx, r->amount) : "none");
}
for (size_t i = 0; i < tal_count(path); i++)
reserve_add(askrene->reserved, &path[i], cmd->id);
response = jsonrpc_stream_success(cmd);
return command_finished(cmd, response);
@ -716,7 +700,6 @@ static struct command_result *json_askrene_unreserve(struct command *cmd,
{
struct reserve_hop *path;
struct json_stream *response;
size_t num;
struct askrene *askrene = get_askrene(cmd->plugin);
if (!param(cmd, buffer, params,
@ -724,17 +707,14 @@ static struct command_result *json_askrene_unreserve(struct command *cmd,
NULL))
return command_param_failed();
num = reserves_remove(askrene->reserved, path, tal_count(path));
if (num != tal_count(path)) {
const struct reserve *r = find_reserve(askrene->reserved, &path[num].scidd);
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Underflow unreserving %zu: %s amount %s (%zu reserved, amount %s)",
num,
fmt_short_channel_id_dir(tmpctx, &path[num].scidd),
fmt_amount_msat(tmpctx, path[num].amount),
r ? r->num_htlcs : 0,
r ? fmt_amount_msat(tmpctx, r->amount) : "none");
}
for (size_t i = 0; i < tal_count(path); i++) {
if (!reserve_remove(askrene->reserved, &path[i])) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Unknown reservation for %s",
fmt_short_channel_id_dir(tmpctx,
&path[i].scidd));
}
}
response = jsonrpc_stream_success(cmd);
return command_finished(cmd, response);

View File

@ -22,10 +22,8 @@ static void get_scidd(const struct gossmap *gossmap,
static void destroy_reservations(struct reserve_hop *rhops, struct askrene *askrene)
{
if (reserves_remove(askrene->reserved, rhops, tal_count(rhops))
!= tal_count(rhops)) {
plugin_err(askrene->plugin, "Failed to remove reservations?");
}
for (size_t i = 0; i < tal_count(rhops); i++)
reserve_remove(askrene->reserved, &rhops[i]);
}
static struct reserve_hop *new_reservations(const tal_t *ctx,
@ -53,14 +51,7 @@ static void add_reservation(struct reserve_hop **reservations,
get_scidd(rq->gossmap, flow, i, &rhop.scidd);
rhop.amount = amt;
/* This should not happen, but simply don't reserve if it does */
if (!reserves_add(askrene->reserved, &rhop, 1)) {
plugin_log(rq->plugin, LOG_BROKEN,
"Failed to reserve %s in %s",
fmt_amount_msat(tmpctx, amt),
fmt_short_channel_id_dir(tmpctx, &rhop.scidd));
return;
}
reserve_add(askrene->reserved, &rhop, rq->cmd->id);
/* Set capacities entry to 0 so it get_constraints() looks in reserve. */
idx = gossmap_chan_idx(rq->gossmap, flow->path[i]);

View File

@ -1,16 +1,27 @@
#include "config.h"
#include <assert.h>
#include <ccan/htable/htable_type.h>
#include <ccan/tal/str/str.h>
#include <common/gossmap.h>
#include <common/memleak.h>
#include <plugins/askrene/askrene.h>
#include <plugins/askrene/reserve.h>
/* Note! We can have multiple of these! */
struct reserve {
/* What */
struct reserve_hop rhop;
/* When */
struct timemono timestamp;
/* ID of command which reserved it */
const char *cmd_id;
};
/* Hash table for reservations */
static const struct short_channel_id_dir *
reserve_scidd(const struct reserve *r)
{
return &r->scidd;
return &r->rhop.scidd;
}
static size_t hash_scidd(const struct short_channel_id_dir *scidd)
@ -22,7 +33,7 @@ static size_t hash_scidd(const struct short_channel_id_dir *scidd)
static bool reserve_eq_scidd(const struct reserve *r,
const struct short_channel_id_dir *scidd)
{
return short_channel_id_dir_eq(scidd, &r->scidd);
return short_channel_id_dir_eq(scidd, &r->rhop.scidd);
}
HTABLE_DEFINE_TYPE(struct reserve, reserve_scidd, hash_scidd,
@ -35,88 +46,37 @@ struct reserve_htable *new_reserve_htable(const tal_t *ctx)
return reserved;
}
/* Find a reservation for this scidd (if any!) */
const struct reserve *find_reserve(const struct reserve_htable *reserved,
const struct short_channel_id_dir *scidd)
{
return reserve_htable_get(reserved, scidd);
}
/* Create a new (empty) reservation */
static struct reserve *new_reserve(struct reserve_htable *reserved,
const struct short_channel_id_dir *scidd)
void reserve_add(struct reserve_htable *reserved,
const struct reserve_hop *rhop,
const char *cmd_id TAKES)
{
struct reserve *r = tal(reserved, struct reserve);
r->num_htlcs = 0;
r->amount = AMOUNT_MSAT(0);
r->scidd = *scidd;
r->rhop = *rhop;
r->timestamp = time_mono();
r->cmd_id = tal_strdup(r, cmd_id);
reserve_htable_add(reserved, r);
return r;
}
static void del_reserve(struct reserve_htable *reserved, struct reserve *r)
bool reserve_remove(struct reserve_htable *reserved,
const struct reserve_hop *rhop)
{
assert(r->num_htlcs == 0);
struct reserve *r;
struct reserve_htable_iter rit;
reserve_htable_del(reserved, r);
tal_free(r);
}
/* Note! This may remove the "wrong" one, but since they're only
* differentiated for debugging, that's OK */
for (r = reserve_htable_getfirst(reserved, &rhop->scidd, &rit);
r;
r = reserve_htable_getnext(reserved, &rhop->scidd, &rit)) {
if (!amount_msat_eq(r->rhop.amount, rhop->amount))
continue;
/* Add to existing reservation (false if would overflow). */
static bool add(struct reserve *r, struct amount_msat amount)
{
if (!amount_msat_accumulate(&r->amount, amount))
return false;
r->num_htlcs++;
return true;
}
static bool remove(struct reserve *r, struct amount_msat amount)
{
if (r->num_htlcs == 0)
return false;
if (!amount_msat_sub(&r->amount, r->amount, amount))
return false;
r->num_htlcs--;
return true;
}
/* Atomically add to reserves, or fail.
* Returns offset of failure, or num on success */
size_t reserves_add(struct reserve_htable *reserved,
const struct reserve_hop *hops,
size_t num)
{
for (size_t i = 0; i < num; i++) {
struct reserve *r = reserve_htable_get(reserved, &hops[i].scidd);
if (!r)
r = new_reserve(reserved, &hops[i].scidd);
if (!add(r, hops[i].amount)) {
reserves_remove(reserved, hops, i);
return i;
}
reserve_htable_del(reserved, r);
tal_free(r);
return true;
}
return num;
}
/* Atomically remove from reserves, to fail.
* Returns offset of failure or tal_count(scidds) */
size_t reserves_remove(struct reserve_htable *reserved,
const struct reserve_hop *hops,
size_t num)
{
for (size_t i = 0; i < num; i++) {
struct reserve *r = reserve_htable_get(reserved, &hops[i].scidd);
if (!r || !remove(r, hops[i].amount)) {
reserves_add(reserved, hops, i);
return i;
}
if (r->num_htlcs == 0)
del_reserve(reserved, r);
}
return num;
return false;
}
void reserves_clear_capacities(struct reserve_htable *reserved,
@ -129,7 +89,7 @@ void reserves_clear_capacities(struct reserve_htable *reserved,
for (r = reserve_htable_first(reserved, &rit);
r;
r = reserve_htable_next(reserved, &rit)) {
struct gossmap_chan *c = gossmap_find_chan(gossmap, &r->scidd.scid);
struct gossmap_chan *c = gossmap_find_chan(gossmap, &r->rhop.scidd.scid);
size_t idx;
if (!c)
continue;
@ -139,6 +99,21 @@ void reserves_clear_capacities(struct reserve_htable *reserved,
}
}
void reserve_sub(const struct reserve_htable *reserved,
const struct short_channel_id_dir *scidd,
struct amount_msat *amount)
{
struct reserve *r;
struct reserve_htable_iter rit;
for (r = reserve_htable_getfirst(reserved, scidd, &rit);
r;
r = reserve_htable_getnext(reserved, scidd, &rit)) {
if (!amount_msat_sub(amount, *amount, r->rhop.amount))
*amount = AMOUNT_MSAT(0);
}
}
void reserve_memleak_mark(struct askrene *askrene, struct htable *memtable)
{
memleak_scan_htable(memtable, &askrene->reserved->raw);

View File

@ -8,43 +8,33 @@
#include <common/amount.h>
#include <common/fp16.h>
/* We reserve a path being used. This records how many and how much */
struct reserve {
size_t num_htlcs;
struct short_channel_id_dir scidd;
struct amount_msat amount;
};
/* Initialize hash table for reservations */
struct reserve_htable *new_reserve_htable(const tal_t *ctx);
/* Find a reservation for this scidd (if any!) */
const struct reserve *find_reserve(const struct reserve_htable *reserved,
const struct short_channel_id_dir *scidd);
struct reserve_hop {
struct short_channel_id_dir scidd;
struct amount_msat amount;
};
/* Atomically add to reserves, or fail.
* Returns offset of failure, or num on success */
size_t reserves_add(struct reserve_htable *reserved,
const struct reserve_hop *hops,
size_t num);
/* Add a reservation */
void reserve_add(struct reserve_htable *reserved,
const struct reserve_hop *rhop,
const char *cmd_id TAKES);
/* Atomically remove from reserves, to fail.
* Returns offset of failure or tal_count(scidds) */
size_t reserves_remove(struct reserve_htable *reserved,
const struct reserve_hop *hops,
size_t num);
/* Try to remove a reservation, if it exists. */
bool reserve_remove(struct reserve_htable *reserved,
const struct reserve_hop *rhop);
/* Clear capacities array where we have reserves */
void reserves_clear_capacities(struct reserve_htable *reserved,
const struct gossmap *gossmap,
fp16_t *capacities);
/* Subtract any reserves for scidd from this amount */
void reserve_sub(const struct reserve_htable *reserved,
const struct short_channel_id_dir *scidd,
struct amount_msat *amount);
/* Scan for memleaks */
void reserve_memleak_mark(struct askrene *askrene, struct htable *memtable);
#endif /* LIGHTNING_PLUGINS_ASKRENE_RESERVE_H */