From 9f460296b5abc9c7cae934778d208b023091e341 Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Tue, 23 Jul 2024 15:06:52 +0200 Subject: [PATCH 1/4] libplugin: `pay` now emits `channel_hint` as we learn about the net The `pay` plugin, as well as other plugins making use of the tree-pay executor, will now emit their observations as they see them. The notifications are sent on the `channel_hint_updated` topic, and any subscriber will get them. We also added a `timestamp` to the `struct channel_hint`, as these observations now outlive the `pay` call, and have to be attenuated / relaxed as they age, until we can eliminate them completely (when the restriction is equal to the structural information gathered from the gossip). Changelog-Added: pay: Payments now emit `channel_hint_updated` notification to share inferred balances and observations across multiple payments. --- plugins/libplugin-pay.c | 31 ++++++++++++++++++++++++++++++- plugins/libplugin-pay.h | 7 +++++++ plugins/pay.c | 1 + 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/plugins/libplugin-pay.c b/plugins/libplugin-pay.c index c7db43a50e97..63e2c9c11b55 100644 --- a/plugins/libplugin-pay.c +++ b/plugins/libplugin-pay.c @@ -386,6 +386,29 @@ void payment_start(struct payment *p) payment_start_at_blockheight(p, INVALID_BLOCKHEIGHT); } +/** + * Notify subscribers of the `channel_hint` topic about a changed hint + * + * We share the channel_hints across payments, and across plugins, in order to + * maximize the context they have when performing payments. + */ + /** + * Notify subscribers of the `channel_hint` topic about a changed hint + * + * We share the channel_hints across payments, and across plugins, in order + * to maximize the context they have when performing payments. + */ +static void channel_hint_notify(struct plugin *plugin, + const struct channel_hint *hint) +{ + struct json_stream *js = + plugin_notification_start(plugin, "channel_hint_update"); + + /* The timestamp used to decay the observation over time. */ + json_add_u32(js, "timestamp", hint->timestamp); + plugin_notification_end(plugin, js); +} + static void channel_hints_update(struct payment *p, const struct short_channel_id scid, int direction, bool enabled, bool local, @@ -394,6 +417,7 @@ static void channel_hints_update(struct payment *p, { struct payment *root = payment_root(p); struct channel_hint newhint; + u32 timestamp = time_now().ts.tv_sec; /* If the channel is marked as enabled it must have an estimate. */ assert(!enabled || estimated_capacity != NULL); @@ -423,7 +447,8 @@ static void channel_hints_update(struct payment *p, modified = true; } - if (modified) + if (modified) { + hint->timestamp = timestamp; paymod_log(p, LOG_DBG, "Updated a channel hint for %s: " "enabled %s, " @@ -433,12 +458,15 @@ static void channel_hints_update(struct payment *p, hint->enabled ? "true" : "false", fmt_amount_msat(tmpctx, hint->estimated_capacity)); + channel_hint_notify(p->plugin, hint); + } return; } } /* No hint found, create one. */ newhint.enabled = enabled; + newhint.timestamp = timestamp; newhint.scid.scid = scid; newhint.scid.dir = direction; if (local) { @@ -458,6 +486,7 @@ static void channel_hints_update(struct payment *p, fmt_short_channel_id_dir(tmpctx, &newhint.scid), newhint.enabled ? "true" : "false", fmt_amount_msat(tmpctx, newhint.estimated_capacity)); + channel_hint_notify(p->plugin, &newhint); } static void payment_exclude_most_expensive(struct payment *p) diff --git a/plugins/libplugin-pay.h b/plugins/libplugin-pay.h index c2f91839906e..ca925b7528d8 100644 --- a/plugins/libplugin-pay.h +++ b/plugins/libplugin-pay.h @@ -68,6 +68,13 @@ struct local_hint { * 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 diff --git a/plugins/pay.c b/plugins/pay.c index 3a33f3d88c6f..42de850d7ce8 100644 --- a/plugins/pay.c +++ b/plugins/pay.c @@ -1522,6 +1522,7 @@ static const struct plugin_command commands[] = { static const char *notification_topics[] = { "pay_success", "pay_failure", + "channel_hint_update", }; int main(int argc, char *argv[]) From 1aea198465d7baa7bc4dfb2409636bb1bec96b86 Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Thu, 25 Jul 2024 13:11:57 +0200 Subject: [PATCH 2/4] common: Add some more JSON primitives We were missing the `short_channel_id_dir` helpers. --- common/json_parse.c | 20 +++++++++++++++++++- common/json_parse.h | 5 +++++ common/json_stream.c | 11 ++++++++++- common/json_stream.h | 6 ++++++ plugins/offers_offer.c | 19 ------------------- 5 files changed, 40 insertions(+), 21 deletions(-) diff --git a/common/json_parse.c b/common/json_parse.c index 9fc9882e22e7..639c35b2e70b 100644 --- a/common/json_parse.c +++ b/common/json_parse.c @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -574,6 +573,25 @@ bool json_to_short_channel_id(const char *buffer, const jsmntok_t *tok, tok->end - tok->start, scid)); } +bool json_to_short_channel_id_dir(const char *buffer, const jsmntok_t *tok, + struct short_channel_id_dir *scidd) +{ + jsmntok_t scidtok, numtok; + u32 dir; + + if (!split_tok(buffer, tok, '/', &scidtok, &numtok)) + return false; + + if (!json_to_short_channel_id(buffer, &scidtok, &scidd->scid)) + return false; + + if (!json_to_u32(buffer, &numtok, &dir) || (dir > 1)) + return false; + + scidd->dir = dir; + return true; +} + bool json_to_txid(const char *buffer, const jsmntok_t *tok, struct bitcoin_txid *txid) { diff --git a/common/json_parse.h b/common/json_parse.h index 1569a4a4e56e..7eca7284c2d6 100644 --- a/common/json_parse.h +++ b/common/json_parse.h @@ -1,6 +1,7 @@ #ifndef LIGHTNING_COMMON_JSON_PARSE_H #define LIGHTNING_COMMON_JSON_PARSE_H #include "config.h" +#include #include #include #include @@ -110,6 +111,10 @@ bool json_to_outpoint(const char *buffer, const jsmntok_t *tok, bool json_to_channel_id(const char *buffer, const jsmntok_t *tok, struct channel_id *cid); +/* Extract a channel id + dir from this */ +bool json_to_short_channel_id_dir(const char *buffer, const jsmntok_t *tok, + struct short_channel_id_dir *scidd); + /* Extract a coin movement 'tag' from this */ bool json_to_coin_mvt_tag(const char *buffer, const jsmntok_t *tok, enum mvt_tag *tag); diff --git a/common/json_stream.c b/common/json_stream.c index f0667b37dc24..31a5367b15e4 100644 --- a/common/json_stream.c +++ b/common/json_stream.c @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -485,6 +484,16 @@ void json_add_short_channel_id(struct json_stream *response, short_channel_id_outnum(scid)); } +void json_add_short_channel_id_dir(struct json_stream *response, + const char *fieldname, + struct short_channel_id_dir scidd) +{ + json_add_str_fmt(response, fieldname, "%dx%dx%d/%d", + short_channel_id_blocknum(scidd.scid), + short_channel_id_txnum(scidd.scid), + short_channel_id_outnum(scidd.scid), scidd.dir); +} + static void json_add_address_fields(struct json_stream *response, const struct wireaddr *addr, const char *typefield) diff --git a/common/json_stream.h b/common/json_stream.h index b2963dfedc15..eeca272c3e09 100644 --- a/common/json_stream.h +++ b/common/json_stream.h @@ -5,6 +5,7 @@ #define LIGHTNING_COMMON_JSON_STREAM_H #include "config.h" +#include #define JSMN_STRICT 1 # include @@ -313,6 +314,11 @@ void json_add_short_channel_id(struct json_stream *response, const char *fieldname, struct short_channel_id id); +/* '"fieldname" : "1234:5:6/1"' */ +void json_add_short_channel_id_dir(struct json_stream *response, + const char *fieldname, + struct short_channel_id_dir idd); + /* JSON serialize a network address for a node */ void json_add_address(struct json_stream *response, const char *fieldname, const struct wireaddr *addr); diff --git a/plugins/offers_offer.c b/plugins/offers_offer.c index 6c75ce17e175..d8d75f93e284 100644 --- a/plugins/offers_offer.c +++ b/plugins/offers_offer.c @@ -350,25 +350,6 @@ static struct command_result *currency_done(struct command *cmd, return maybe_add_path(cmd, offinfo); } -static bool json_to_short_channel_id_dir(const char *buffer, const jsmntok_t *tok, - struct short_channel_id_dir *scidd) -{ - jsmntok_t scidtok, numtok; - u32 dir; - - if (!split_tok(buffer, tok, '/', &scidtok, &numtok)) - return false; - - if (!json_to_short_channel_id(buffer, &scidtok, &scidd->scid)) - return false; - - if (!json_to_u32(buffer, &numtok, &dir) || (dir > 1)) - return false; - - scidd->dir = dir; - return true; -} - static bool json_to_sciddir_or_pubkey(const char *buffer, const jsmntok_t *tok, struct sciddir_or_pubkey *sciddir_or_pubkey) { From ab61843f50d720d861696917aaf3cf384ab15813 Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Thu, 25 Jul 2024 13:13:11 +0200 Subject: [PATCH 3/4] libplugin: Add primitive to serialize `channel_hint` to JSON --- plugins/libplugin-pay.c | 12 +++++++++++- plugins/test/run-route-calc.c | 9 +++++++++ plugins/test/run-route-overlong.c | 9 +++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/plugins/libplugin-pay.c b/plugins/libplugin-pay.c index 63e2c9c11b55..91101719f51f 100644 --- a/plugins/libplugin-pay.c +++ b/plugins/libplugin-pay.c @@ -386,6 +386,16 @@ 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, "capacity_msat", hint->estimated_capacity); + json_add_bool(dest, "enabled", hint->enabled); + json_object_end(dest); +} + /** * Notify subscribers of the `channel_hint` topic about a changed hint * @@ -405,7 +415,7 @@ static void channel_hint_notify(struct plugin *plugin, plugin_notification_start(plugin, "channel_hint_update"); /* The timestamp used to decay the observation over time. */ - json_add_u32(js, "timestamp", hint->timestamp); + channel_hint_to_json("channel_hint", hint, js); plugin_notification_end(plugin, js); } diff --git a/plugins/test/run-route-calc.c b/plugins/test/run-route-calc.c index ee4a1ab74e60..910dc60a1f3b 100644 --- a/plugins/test/run-route-calc.c +++ b/plugins/test/run-route-calc.c @@ -81,6 +81,10 @@ void json_add_amount_msat(struct json_stream *result UNNEEDED, struct amount_msat msat) { fprintf(stderr, "json_add_amount_msat called!\n"); abort(); } +/* Generated stub for json_add_bool */ +void json_add_bool(struct json_stream *result UNNEEDED, const char *fieldname UNNEEDED, + bool value UNNEEDED) +{ fprintf(stderr, "json_add_bool called!\n"); abort(); } /* Generated stub for json_add_hex_talarr */ void json_add_hex_talarr(struct json_stream *result UNNEEDED, const char *fieldname UNNEEDED, @@ -116,6 +120,11 @@ void json_add_short_channel_id(struct json_stream *response UNNEEDED, const char *fieldname UNNEEDED, struct short_channel_id id UNNEEDED) { fprintf(stderr, "json_add_short_channel_id called!\n"); abort(); } +/* Generated stub for json_add_short_channel_id_dir */ +void json_add_short_channel_id_dir(struct json_stream *response UNNEEDED, + const char *fieldname UNNEEDED, + struct short_channel_id_dir idd UNNEEDED) +{ fprintf(stderr, "json_add_short_channel_id_dir called!\n"); abort(); } /* Generated stub for json_add_string */ void json_add_string(struct json_stream *js UNNEEDED, const char *fieldname UNNEEDED, diff --git a/plugins/test/run-route-overlong.c b/plugins/test/run-route-overlong.c index 8f9ce9bdee86..0f9703ea2aa9 100644 --- a/plugins/test/run-route-overlong.c +++ b/plugins/test/run-route-overlong.c @@ -78,6 +78,10 @@ void json_add_amount_msat(struct json_stream *result UNNEEDED, struct amount_msat msat) { fprintf(stderr, "json_add_amount_msat called!\n"); abort(); } +/* Generated stub for json_add_bool */ +void json_add_bool(struct json_stream *result UNNEEDED, const char *fieldname UNNEEDED, + bool value UNNEEDED) +{ fprintf(stderr, "json_add_bool called!\n"); abort(); } /* Generated stub for json_add_hex_talarr */ void json_add_hex_talarr(struct json_stream *result UNNEEDED, const char *fieldname UNNEEDED, @@ -113,6 +117,11 @@ void json_add_short_channel_id(struct json_stream *response UNNEEDED, const char *fieldname UNNEEDED, struct short_channel_id id UNNEEDED) { fprintf(stderr, "json_add_short_channel_id called!\n"); abort(); } +/* Generated stub for json_add_short_channel_id_dir */ +void json_add_short_channel_id_dir(struct json_stream *response UNNEEDED, + const char *fieldname UNNEEDED, + struct short_channel_id_dir idd UNNEEDED) +{ fprintf(stderr, "json_add_short_channel_id_dir called!\n"); abort(); } /* Generated stub for json_add_string */ void json_add_string(struct json_stream *js UNNEEDED, const char *fieldname UNNEEDED, From a8504b7710ca205e3530b7d8012b2c2913fca3c5 Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Thu, 25 Jul 2024 13:15:10 +0200 Subject: [PATCH 4/4] libplugin: Add parser for `channel_hint` from JSON --- plugins/libplugin-pay.c | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/plugins/libplugin-pay.c b/plugins/libplugin-pay.c index 91101719f51f..81a9fc0f512c 100644 --- a/plugins/libplugin-pay.c +++ b/plugins/libplugin-pay.c @@ -397,11 +397,30 @@ static void channel_hint_to_json(const char *name, const struct channel_hint *hi } /** - * Notify subscribers of the `channel_hint` topic about a changed hint + * Load a channel_hint from its JSON representation. * - * We share the channel_hints across payments, and across plugins, in order to - * maximize the context they have when performing payments. + * @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:%,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 *