askrene: reservation implementation.
They tell us what paths they're using, so we can adjust capacity estimates accordingly. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au> Header from folded patch 'reserve-fixup.patch': fixup! askrene: reservation implementation.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
PLUGIN_ASKRENE_SRC := plugins/askrene/askrene.c plugins/askrene/layer.c
|
||||
PLUGIN_ASKRENE_HEADER := plugins/askrene/askrene.h plugins/askrene/layer.h
|
||||
PLUGIN_ASKRENE_SRC := plugins/askrene/askrene.c plugins/askrene/layer.c plugins/askrene/reserve.c
|
||||
PLUGIN_ASKRENE_HEADER := plugins/askrene/askrene.h plugins/askrene/layer.h plugins/askrene/reserve.h
|
||||
PLUGIN_ASKRENE_OBJS := $(PLUGIN_ASKRENE_SRC:.c=.o)
|
||||
|
||||
$(PLUGIN_ASKRENE_OBJS): $(PLUGIN_ASKRENE_HEADER)
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <errno.h>
|
||||
#include <plugins/askrene/askrene.h>
|
||||
#include <plugins/askrene/layer.h>
|
||||
#include <plugins/askrene/reserve.h>
|
||||
#include <plugins/libplugin.h>
|
||||
|
||||
static struct askrene *get_askrene(struct plugin *plugin)
|
||||
@@ -209,12 +210,26 @@ static struct command_result *json_askrene_reserve(struct command *cmd,
|
||||
{
|
||||
struct reserve_path *path;
|
||||
struct json_stream *response;
|
||||
size_t num;
|
||||
struct askrene *askrene = get_askrene(cmd->plugin);
|
||||
|
||||
if (!param(cmd, buffer, params,
|
||||
p_req("path", param_reserve_path, &path),
|
||||
NULL))
|
||||
return command_param_failed();
|
||||
|
||||
num = reserves_add(askrene->reserved, path->scidds, path->amounts,
|
||||
tal_count(path->scidds));
|
||||
if (num != tal_count(path->scidds)) {
|
||||
const struct reserve *r = find_reserve(askrene->reserved, &path->scidds[num]);
|
||||
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
|
||||
"Overflow reserving %zu: %s amount %s (%s reserved already)",
|
||||
num,
|
||||
fmt_short_channel_id_dir(tmpctx, &path->scidds[num]),
|
||||
fmt_amount_msat(tmpctx, path->amounts[num]),
|
||||
r ? fmt_amount_msat(tmpctx, r->amount) : "none");
|
||||
}
|
||||
|
||||
response = jsonrpc_stream_success(cmd);
|
||||
return command_finished(cmd, response);
|
||||
}
|
||||
@@ -225,12 +240,27 @@ static struct command_result *json_askrene_unreserve(struct command *cmd,
|
||||
{
|
||||
struct reserve_path *path;
|
||||
struct json_stream *response;
|
||||
size_t num;
|
||||
struct askrene *askrene = get_askrene(cmd->plugin);
|
||||
|
||||
if (!param(cmd, buffer, params,
|
||||
p_req("path", param_reserve_path, &path),
|
||||
NULL))
|
||||
return command_param_failed();
|
||||
|
||||
num = reserves_remove(askrene->reserved, path->scidds, path->amounts,
|
||||
tal_count(path->scidds));
|
||||
if (num != tal_count(path->scidds)) {
|
||||
const struct reserve *r = find_reserve(askrene->reserved, &path->scidds[num]);
|
||||
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->scidds[num]),
|
||||
fmt_amount_msat(tmpctx, path->amounts[num]),
|
||||
r ? r->num_htlcs : 0,
|
||||
r ? fmt_amount_msat(tmpctx, r->amount) : "none");
|
||||
}
|
||||
|
||||
response = jsonrpc_stream_success(cmd);
|
||||
return command_finished(cmd, response);
|
||||
}
|
||||
@@ -456,6 +486,7 @@ static const char *init(struct plugin *plugin,
|
||||
struct askrene *askrene = tal(plugin, struct askrene);
|
||||
askrene->plugin = plugin;
|
||||
list_head_init(&askrene->layers);
|
||||
askrene->reserved = new_reserve_hash(askrene);
|
||||
askrene->gossmap = gossmap_load(askrene, GOSSIP_STORE_FILENAME, NULL);
|
||||
|
||||
if (!askrene->gossmap)
|
||||
|
||||
@@ -5,13 +5,6 @@
|
||||
#include <ccan/list/list.h>
|
||||
#include <common/amount.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 sciddir;
|
||||
struct amount_msat amount;
|
||||
};
|
||||
|
||||
/* A single route. */
|
||||
struct route {
|
||||
/* Actual path to take */
|
||||
@@ -26,6 +19,8 @@ struct askrene {
|
||||
struct gossmap *gossmap;
|
||||
/* List of layers */
|
||||
struct list_head layers;
|
||||
/* In-flight payment attempts */
|
||||
struct reserve_hash *reserved;
|
||||
};
|
||||
|
||||
#endif /* LIGHTNING_PLUGINS_ASKRENE_ASKRENE_H */
|
||||
|
||||
119
plugins/askrene/reserve.c
Normal file
119
plugins/askrene/reserve.c
Normal file
@@ -0,0 +1,119 @@
|
||||
#include "config.h"
|
||||
#include <assert.h>
|
||||
#include <ccan/htable/htable_type.h>
|
||||
#include <plugins/askrene/reserve.h>
|
||||
|
||||
/* Hash table for reservations */
|
||||
static const struct short_channel_id_dir *
|
||||
reserve_scidd(const struct reserve *r)
|
||||
{
|
||||
return &r->scidd;
|
||||
}
|
||||
|
||||
static size_t hash_scidd(const struct short_channel_id_dir *scidd)
|
||||
{
|
||||
/* scids cost money to generate, so simple hash works here */
|
||||
return (scidd->scid.u64 >> 32) ^ (scidd->scid.u64 >> 16) ^ (scidd->scid.u64 << 1) ^ scidd->dir;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
HTABLE_DEFINE_TYPE(struct reserve, reserve_scidd, hash_scidd,
|
||||
reserve_eq_scidd, reserve_hash);
|
||||
|
||||
struct reserve_hash *new_reserve_hash(const tal_t *ctx)
|
||||
{
|
||||
struct reserve_hash *reserved = tal(ctx, struct reserve_hash);
|
||||
reserve_hash_init(reserved);
|
||||
return reserved;
|
||||
}
|
||||
|
||||
/* Find a reservation for this scidd (if any!) */
|
||||
const struct reserve *find_reserve(const struct reserve_hash *reserved,
|
||||
const struct short_channel_id_dir *scidd)
|
||||
{
|
||||
return reserve_hash_get(reserved, scidd);
|
||||
}
|
||||
|
||||
/* Create a new (empty) reservation */
|
||||
static struct reserve *new_reserve(struct reserve_hash *reserved,
|
||||
const struct short_channel_id_dir *scidd)
|
||||
{
|
||||
struct reserve *r = tal(reserved, struct reserve);
|
||||
|
||||
r->num_htlcs = 0;
|
||||
r->amount = AMOUNT_MSAT(0);
|
||||
r->scidd = *scidd;
|
||||
|
||||
reserve_hash_add(reserved, r);
|
||||
return r;
|
||||
}
|
||||
|
||||
static void del_reserve(struct reserve_hash *reserved, struct reserve *r)
|
||||
{
|
||||
assert(r->num_htlcs == 0);
|
||||
|
||||
reserve_hash_del(reserved, r);
|
||||
tal_free(r);
|
||||
}
|
||||
|
||||
/* Add to existing reservation (false if would overflow). */
|
||||
static bool add(struct reserve *r, struct amount_msat amount)
|
||||
{
|
||||
if (!amount_msat_add(&r->amount, 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_hash *reserved,
|
||||
const struct short_channel_id_dir *scidds,
|
||||
const struct amount_msat *amounts,
|
||||
size_t num)
|
||||
{
|
||||
for (size_t i = 0; i < num; i++) {
|
||||
struct reserve *r = reserve_hash_get(reserved, &scidds[i]);
|
||||
if (!r)
|
||||
r = new_reserve(reserved, &scidds[i]);
|
||||
if (!add(r, amounts[i])) {
|
||||
reserves_remove(reserved, scidds, amounts, i);
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
/* Atomically remove from reserves, to fail.
|
||||
* Returns offset of failure or tal_count(scidds) */
|
||||
size_t reserves_remove(struct reserve_hash *reserved,
|
||||
const struct short_channel_id_dir *scidds,
|
||||
const struct amount_msat *amounts,
|
||||
size_t num)
|
||||
{
|
||||
for (size_t i = 0; i < num; i++) {
|
||||
struct reserve *r = reserve_hash_get(reserved, &scidds[i]);
|
||||
if (!r || !remove(r, amounts[i])) {
|
||||
reserves_add(reserved, scidds, amounts, i);
|
||||
return i;
|
||||
}
|
||||
if (r->num_htlcs == 0)
|
||||
del_reserve(reserved, r);
|
||||
}
|
||||
return num;
|
||||
}
|
||||
38
plugins/askrene/reserve.h
Normal file
38
plugins/askrene/reserve.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef LIGHTNING_PLUGINS_ASKRENE_RESERVE_H
|
||||
#define LIGHTNING_PLUGINS_ASKRENE_RESERVE_H
|
||||
/* We have to know what payments are in progress, so we can take into
|
||||
* account the reduced capacity of channels. We do this by telling
|
||||
* everyone to reserve / unreserve paths as they use them. */
|
||||
#include "config.h"
|
||||
#include <bitcoin/short_channel_id.h>
|
||||
#include <common/amount.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_hash *new_reserve_hash(const tal_t *ctx);
|
||||
|
||||
/* Find a reservation for this scidd (if any!) */
|
||||
const struct reserve *find_reserve(const struct reserve_hash *reserved,
|
||||
const struct short_channel_id_dir *scidd);
|
||||
|
||||
/* Atomically add to reserves, or fail.
|
||||
* Returns offset of failure, or num on success */
|
||||
size_t reserves_add(struct reserve_hash *reserved,
|
||||
const struct short_channel_id_dir *scidds,
|
||||
const struct amount_msat *amounts,
|
||||
size_t num);
|
||||
|
||||
/* Atomically remove from reserves, to fail.
|
||||
* Returns offset of failure or tal_count(scidds) */
|
||||
size_t reserves_remove(struct reserve_hash *reserved,
|
||||
const struct short_channel_id_dir *scidds,
|
||||
const struct amount_msat *amounts,
|
||||
size_t num);
|
||||
|
||||
#endif /* LIGHTNING_PLUGINS_ASKRENE_RESERVE_H */
|
||||
Reference in New Issue
Block a user