We have the issue of aliases: xpay uses scids like 0x0x0 for routehints and blinded paths, and then can apply reservations to them. But generally, reservations are *global*, so we need to differentiate. Changelog-Added: Plugins: `askrene-reserve` and `askrene-unreserve` can take an optional `layer` inside `path` elements. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
216 lines
5.6 KiB
C
216 lines
5.6 KiB
C
#include "config.h"
|
|
#include <assert.h>
|
|
#include <ccan/htable/htable_type.h>
|
|
#include <ccan/tal/str/str.h>
|
|
#include <common/gossmap.h>
|
|
#include <common/json_stream.h>
|
|
#include <common/memleak.h>
|
|
#include <inttypes.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->rhop.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->rhop.scidd);
|
|
}
|
|
|
|
HTABLE_DEFINE_DUPS_TYPE(struct reserve, reserve_scidd, hash_scidd,
|
|
reserve_eq_scidd, reserve_htable);
|
|
|
|
struct reserve_htable *new_reserve_htable(const tal_t *ctx)
|
|
{
|
|
return new_htable(ctx, reserve_htable);
|
|
}
|
|
|
|
static void destroy_reserve(struct reserve *r, struct reserve_htable *reserved)
|
|
{
|
|
if (!reserve_htable_del(reserved, r))
|
|
abort();
|
|
}
|
|
|
|
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->rhop = *rhop;
|
|
r->timestamp = time_mono();
|
|
r->cmd_id = tal_strdup(r, cmd_id);
|
|
|
|
/* If owned by a layer, clean up when layer destroyed */
|
|
if (rhop->layer) {
|
|
tal_steal(rhop->layer, r);
|
|
tal_add_destructor2(r, destroy_reserve, reserved);
|
|
}
|
|
reserve_htable_add(reserved, r);
|
|
}
|
|
|
|
bool reserve_remove(struct reserve_htable *reserved,
|
|
const struct reserve_hop *rhop)
|
|
{
|
|
struct reserve *r;
|
|
struct reserve_htable_iter rit;
|
|
|
|
/* 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;
|
|
if (r->rhop.layer != rhop->layer)
|
|
continue;
|
|
|
|
/* hops on layers have a destructor which does this. */
|
|
if (r->rhop.layer == NULL)
|
|
reserve_htable_del(reserved, r);
|
|
tal_free(r);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void reserves_clear_capacities(struct reserve_htable *reserved,
|
|
const struct gossmap *gossmap,
|
|
fp16_t *capacities)
|
|
{
|
|
struct reserve *r;
|
|
struct reserve_htable_iter rit;
|
|
|
|
for (r = reserve_htable_first(reserved, &rit);
|
|
r;
|
|
r = reserve_htable_next(reserved, &rit)) {
|
|
struct gossmap_chan *c = gossmap_find_chan(gossmap, &r->rhop.scidd.scid);
|
|
size_t idx;
|
|
if (!c)
|
|
continue;
|
|
idx = gossmap_chan_idx(gossmap, c);
|
|
if (idx < tal_count(capacities))
|
|
capacities[idx] = 0;
|
|
}
|
|
}
|
|
|
|
static bool layer_in(const struct layer *layer,
|
|
const struct layer **layers)
|
|
{
|
|
for (size_t i = 0; i < tal_count(layers); i++)
|
|
if (layer == layers[i])
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
void reserve_sub(const struct reserve_htable *reserved,
|
|
const struct short_channel_id_dir *scidd,
|
|
const struct layer **layers,
|
|
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 (r->rhop.layer && !layer_in(r->rhop.layer, layers))
|
|
continue;
|
|
if (!amount_msat_deduct(amount, r->rhop.amount))
|
|
*amount = AMOUNT_MSAT(0);
|
|
}
|
|
}
|
|
|
|
bool reserve_accumulate(const struct reserve_htable *reserved,
|
|
const struct short_channel_id_dir *scidd,
|
|
const struct layer *layer,
|
|
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)) {
|
|
/* Non-layer ones always get counted. Layered ones have
|
|
* to match this layer. */
|
|
if (r->rhop.layer && r->rhop.layer != layer)
|
|
continue;
|
|
if (!amount_msat_add(amount, *amount, r->rhop.amount))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void json_add_reservations(struct json_stream *js,
|
|
const struct reserve_htable *reserved,
|
|
const char *fieldname,
|
|
const struct layer **layers)
|
|
{
|
|
struct reserve *r;
|
|
struct reserve_htable_iter rit;
|
|
|
|
json_array_start(js, fieldname);
|
|
for (r = reserve_htable_first(reserved, &rit);
|
|
r;
|
|
r = reserve_htable_next(reserved, &rit)) {
|
|
if (r->rhop.layer && !layer_in(r->rhop.layer, layers))
|
|
continue;
|
|
json_object_start(js, NULL);
|
|
json_add_short_channel_id_dir(js,
|
|
"short_channel_id_dir",
|
|
r->rhop.scidd);
|
|
json_add_amount_msat(js,
|
|
"amount_msat",
|
|
r->rhop.amount);
|
|
json_add_u64(js, "age_in_seconds",
|
|
timemono_between(time_mono(), r->timestamp).ts.tv_sec);
|
|
json_add_string(js, "command_id", r->cmd_id);
|
|
json_object_end(js);
|
|
}
|
|
json_array_end(js);
|
|
}
|
|
|
|
const char *fmt_reservations(const tal_t *ctx,
|
|
const struct reserve_htable *reserved,
|
|
const struct short_channel_id_dir *scidd,
|
|
const struct layer **layers)
|
|
{
|
|
struct reserve *r;
|
|
struct reserve_htable_iter rit;
|
|
char *ret = NULL;
|
|
|
|
for (r = reserve_htable_getfirst(reserved, scidd, &rit);
|
|
r;
|
|
r = reserve_htable_getnext(reserved, scidd, &rit)) {
|
|
u64 seconds;
|
|
if (r->rhop.layer && !layer_in(r->rhop.layer, layers))
|
|
continue;
|
|
if (!ret)
|
|
ret = tal_strdup(ctx, "");
|
|
else
|
|
tal_append_fmt(&ret, ", ");
|
|
tal_append_fmt(&ret, "%s by command %s",
|
|
fmt_amount_msat(tmpctx, r->rhop.amount), r->cmd_id);
|
|
seconds = timemono_between(time_mono(), r->timestamp).ts.tv_sec;
|
|
/* Add a note if it's old */
|
|
if (seconds > 0)
|
|
tal_append_fmt(&ret, " (%"PRIu64" seconds ago)", seconds);
|
|
}
|
|
return ret;
|
|
}
|