From 37a204df41ad299d1f57cc4cc23c76cf9b250463 Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Fri, 2 Aug 2024 18:28:45 +0200 Subject: [PATCH] plugin: Split out the `struct channel_hint` handling We're getting serious about how we manage the channel_hints, so let's give them a proper home. --- plugins/Makefile | 10 ++++- plugins/channel_hint.c | 99 +++++++++++++++++++++++++++++++++++++++++ plugins/channel_hint.h | 61 +++++++++++++++++++++++++ plugins/libplugin-pay.c | 81 --------------------------------- plugins/libplugin-pay.h | 37 +-------------- 5 files changed, 169 insertions(+), 119 deletions(-) create mode 100644 plugins/channel_hint.c create mode 100644 plugins/channel_hint.h diff --git a/plugins/Makefile b/plugins/Makefile index e236af317..23c8d1492 100644 --- a/plugins/Makefile +++ b/plugins/Makefile @@ -27,8 +27,14 @@ PLUGIN_LIB_SRC := plugins/libplugin.c PLUGIN_LIB_HEADER := plugins/libplugin.h PLUGIN_LIB_OBJS := $(PLUGIN_LIB_SRC:.c=.o) -PLUGIN_PAY_LIB_SRC := plugins/libplugin-pay.c -PLUGIN_PAY_LIB_HEADER := plugins/libplugin-pay.h +PLUGIN_PAY_LIB_SRC := \ + plugins/channel_hint.c \ + plugins/libplugin-pay.c + +PLUGIN_PAY_LIB_HEADER := \ + plugins/channel_hint.h \ + plugins/libplugin-pay.h + PLUGIN_PAY_LIB_OBJS := $(PLUGIN_PAY_LIB_SRC:.c=.o) PLUGIN_OFFERS_SRC := plugins/offers.c plugins/offers_offer.c plugins/offers_invreq_hook.c plugins/offers_inv_hook.c plugins/establish_onion_path.c plugins/fetchinvoice.c diff --git a/plugins/channel_hint.c b/plugins/channel_hint.c new file mode 100644 index 000000000..8bb6c0fcb --- /dev/null +++ b/plugins/channel_hint.c @@ -0,0 +1,99 @@ +#include "config.h" +#include + +void channel_hint_to_json(const char *name, const struct channel_hint *hint, + struct json_stream *dest) +{ + json_object_start(dest, name); + json_add_u32(dest, "timestamp", hint->timestamp); + json_add_short_channel_id_dir(dest, "scid", hint->scid); + json_add_amount_msat(dest, "estimated_capacity_msat", + hint->estimated_capacity); + json_add_amount_msat(dest, "overall_capacity_msat", + hint->overall_capacity); + json_add_bool(dest, "enabled", hint->enabled); + json_object_end(dest); +} + +#define PAY_REFILL_TIME 7200 + +/** + * Update the `channel_hint` in place, return whether it should be kept. + * + * This computes the refill-rate based on the overall capacity, and + * the time elapsed since the last update and relaxes the upper bound + * on the capacity, and resets the enabled flag if appropriate. If the + * hint is no longer useful, i.e., it does not provide any additional + * information on top of the structural information we've learned from + * the gossip, then we return `false` to signal that the + * `channel_hint` may be removed. + */ +bool channel_hint_update(const struct timeabs now, struct channel_hint *hint) +{ + /* Precision is not required here, so integer division is good + * enough. But keep the order such that we do not round down + * too much. We do so by first multiplying, before + * dividing. The formula is `current = last + delta_t * + * overall / refill_rate`. + */ + struct amount_msat refill; + u64 seconds = now.ts.tv_sec - hint->timestamp; + if (!amount_msat_mul(&refill, hint->overall_capacity, seconds)) + abort(); + + refill = amount_msat_div(refill, PAY_REFILL_TIME); + if (!amount_msat_add(&hint->estimated_capacity, + hint->estimated_capacity, refill)) + abort(); + + /* Clamp the value to the `overall_capacity` */ + if (amount_msat_greater(hint->estimated_capacity, + hint->overall_capacity)) + hint->estimated_capacity = hint->overall_capacity; + + /* TODO This is rather coarse. We could map the disabled flag + to having 0msat capacity, and then relax from there. But it'd + likely be too slow of a relaxation.*/ + if (seconds > 60) + hint->enabled = true; + + /* Since we update in-place we should make sure that we can + * just call update again and the result is stable, if no time + * has passed. */ + hint->timestamp = now.ts.tv_sec; + + /* We report this hint as useless, if the hint does not + * restrict the channel, i.e., if it is enabled and the + * estimate is the same as the overall capacity. */ + return !hint->enabled || amount_msat_greater(hint->overall_capacity, + hint->estimated_capacity); +} + +/** + * Load a channel_hint from its JSON representation. + * + * @return The initialized `channel_hint` or `NULL` if we encountered a parsing + * error. + */ +/* +struct channel_hint *channel_hint_from_json(const tal_t *ctx, + const char *buffer, + const jsmntok_t *toks) +{ + const char *ret; + const jsmntok_t *payload = json_get_member(buffer, toks, "payload"), + *jhint = + json_get_member(buffer, payload, "channel_hint"); + struct channel_hint *hint = tal(ctx, struct channel_hint); + ret = json_scan(ctx, buffer, toks, + "{timestamp:%,scid:%,estimated_capacity_msat:%,overall_capacity_msat:%,enabled:%}", + JSON_SCAN(json_to_u32, &hint->timestamp), + JSON_SCAN(json_to_short_channel_id_dir, &hint->scid), + JSON_SCAN(json_to_msat, &hint->estimated_capacity), + JSON_SCAN(json_to_bool, &hint->enabled)); + + if (ret != NULL) + hint = tal_free(hint); + return hint; +} +*/ diff --git a/plugins/channel_hint.h b/plugins/channel_hint.h new file mode 100644 index 000000000..f41684196 --- /dev/null +++ b/plugins/channel_hint.h @@ -0,0 +1,61 @@ +#ifndef LIGHTNING_PLUGINS_CHANNEL_HINT_H +#define LIGHTNING_PLUGINS_CHANNEL_HINT_H + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include + +/* Information about channels we inferred from a) looking at our channels, and + * b) from failures encountered during attempts to perform a payment. These + * are attached to the root payment, since that information is + * global. Attempts update the estimated channel capacities when starting, and + * get remove on failure. Success keeps the capacities, since the capacities + * changed due to the successful HTLCs. */ +struct channel_hint { + /* The timestamp this observation was made. Used to let the + * constraint expressed by this hint decay over time, until it + * is fully relaxed, at which point we can forget about it + * (the structural information is the best we can do in that + * case). + */ + u32 timestamp; + /* The short_channel_id we're going to use when referring to + * this channel. This can either be the real scid, or the + * local alias. The `pay` algorithm doesn't really care which + * one it is, but we'll prefer the scid as that's likely more + * readable than the alias. */ + struct short_channel_id_dir scid; + + /* Upper bound on remove channels inferred from payment failures. */ + struct amount_msat estimated_capacity; + + /* Is the channel enabled? */ + bool enabled; + + /* Non-null if we are one endpoint of this channel */ + struct local_hint *local; + + /* The total `amount_msat` that were used to fund the + * channel. This is always smaller gte the + * estimated_capacity */ + struct amount_msat overall_capacity; +}; + +/* A collection of channel_hint instances, allowing us to handle and + * update them more easily. */ +struct channel_hint_set { + /* tal_arr of channel_hints. */ + struct channel_hint *hints; +}; + +bool channel_hint_update(const struct timeabs now, + struct channel_hint *hint); + +void channel_hint_to_json(const char *name, const struct channel_hint *hint, struct json_stream *dest); + +#endif /* LIGHTNING_PLUGINS_CHANNEL_HINT_H */ diff --git a/plugins/libplugin-pay.c b/plugins/libplugin-pay.c index c93024d84..6a704cda2 100644 --- a/plugins/libplugin-pay.c +++ b/plugins/libplugin-pay.c @@ -388,87 +388,6 @@ void payment_start(struct payment *p) payment_start_at_blockheight(p, INVALID_BLOCKHEIGHT); } -static void channel_hint_to_json(const char *name, const struct channel_hint *hint, struct json_stream *dest) -{ - json_object_start(dest, name); - json_add_u32(dest, "timestamp", hint->timestamp); - json_add_short_channel_id_dir(dest, "scid", hint->scid); - json_add_amount_msat(dest, "estimated_capacity_msat", hint->estimated_capacity); - json_add_amount_msat(dest, "overall_capacity_msat", hint->overall_capacity); - json_add_bool(dest, "enabled", hint->enabled); - json_object_end(dest); -} - -#define PAY_REFILL_TIME 7200 - -/** - * Update the `channel_hint` in place, return whether it should be kept. - * - * This computes the refill-rate based on the overall capacity, and - * the time elapsed since the last update and relaxes the upper bound - * on the capacity, and resets the enabled flag if appropriate. If the - * hint is no longer useful, i.e., it does not provide any additional - * information on top of the structural information we've learned from - * the gossip, then we return `false` to signal that the - * `channel_hint` may be removed. - */ -static bool channel_hint_update(const struct timeabs now, - struct channel_hint *hint) -{ - /* Precision is not required here, so integer division is good enough. - */ - u64 refill_rate = hint->overall_capacity.millisatoshis / PAY_REFILL_TIME; /* Raw: just simpler */ - u64 seconds = now.ts.tv_sec - hint->timestamp; - hint->estimated_capacity.millisatoshis += refill_rate * seconds; /* Raw: simpler */ - - /* Clamp the value to the `overall_capacity` */ - if (amount_msat_greater(hint->estimated_capacity, - hint->overall_capacity)) - hint->estimated_capacity = hint->overall_capacity; - - /* TODO This is rather coarse. We could map the disabled flag - to having 0msat capacity, and then relax from there. But it'd - likely be too slow of a relaxation.*/ - if (seconds > 60) - hint->enabled = true; - - /* Since we update in-place we should make sure that we can - * just call update again and the result is stable, if no time - * has passed. */ - hint->timestamp = now.ts.tv_sec; - - /* We report this hint as useless, if the hint does not - * restrict the channel, i.e., if it is enabled and the - * estimate is the same as the overall capacity. */ - return !hint->enabled || amount_msat_greater(hint->overall_capacity, - hint->estimated_capacity); -} - -/** - * Load a channel_hint from its JSON representation. - * - * @return The initialized `channel_hint` or `NULL` if we encountered a parsing - * error. - */ -/* -static struct channel_hint *channel_hint_from_json(const tal_t *ctx, - const char *buffer, - const jsmntok_t *toks) -{ - const char *ret; - struct channel_hint *hint = tal(ctx, struct channel_hint); - ret = json_scan(ctx, buffer, toks, - "{timestamp:%,scid:%,estimated_capacity_msat:%,overall_capacity_msat:%,enabled:%}", - JSON_SCAN(json_to_u32, &hint->timestamp), - JSON_SCAN(json_to_short_channel_id_dir, &hint->scid), - JSON_SCAN(json_to_msat, &hint->estimated_capacity), - JSON_SCAN(json_to_bool, &hint->enabled)); - - if (ret != NULL) - hint = tal_free(hint); - return hint; -} -*/ /** * Notify subscribers of the `channel_hint` topic about a changed hint * diff --git a/plugins/libplugin-pay.h b/plugins/libplugin-pay.h index 5f830cc42..90d4e6ab7 100644 --- a/plugins/libplugin-pay.h +++ b/plugins/libplugin-pay.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -61,42 +62,6 @@ struct local_hint { u16 htlc_budget; }; -/* Information about channels we inferred from a) looking at our channels, and - * b) from failures encountered during attempts to perform a payment. These - * are attached to the root payment, since that information is - * global. Attempts update the estimated channel capacities when starting, and - * get remove on failure. Success keeps the capacities, since the capacities - * changed due to the successful HTLCs. */ -struct channel_hint { - /* The timestamp this observation was made. Used to let the - * constraint expressed by this hint decay over time, until it - * is fully relaxed, at which point we can forget about it - * (the structural information is the best we can do in that - * case). - */ - u32 timestamp; - /* The short_channel_id we're going to use when referring to - * this channel. This can either be the real scid, or the - * local alias. The `pay` algorithm doesn't really care which - * one it is, but we'll prefer the scid as that's likely more - * readable than the alias. */ - struct short_channel_id_dir scid; - - /* Upper bound on remove channels inferred from payment failures. */ - struct amount_msat estimated_capacity; - - /* Is the channel enabled? */ - bool enabled; - - /* Non-null if we are one endpoint of this channel */ - struct local_hint *local; - - /* The total `amount_msat` that were used to fund the - * channel. This is always smaller gte the - * estimated_capacity */ - struct amount_msat overall_capacity; -}; - /* Each payment goes through a number of steps that are always processed in * the same order, and some modifiers are called with the payment, and the * modifier's data before and after certain steps, allowing customization. The