Skip to content

Commit e6d5334

Browse files
committed
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.
1 parent dc846dc commit e6d5334

File tree

6 files changed

+170
-121
lines changed

6 files changed

+170
-121
lines changed

plugins/Makefile

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,14 @@ PLUGIN_LIB_SRC := plugins/libplugin.c
2727
PLUGIN_LIB_HEADER := plugins/libplugin.h
2828
PLUGIN_LIB_OBJS := $(PLUGIN_LIB_SRC:.c=.o)
2929

30-
PLUGIN_PAY_LIB_SRC := plugins/libplugin-pay.c
31-
PLUGIN_PAY_LIB_HEADER := plugins/libplugin-pay.h
30+
PLUGIN_PAY_LIB_SRC := \
31+
plugins/channel_hint.c \
32+
plugins/libplugin-pay.c
33+
34+
PLUGIN_PAY_LIB_HEADER := \
35+
plugins/channel_hint.h \
36+
plugins/libplugin-pay.h
37+
3238
PLUGIN_PAY_LIB_OBJS := $(PLUGIN_PAY_LIB_SRC:.c=.o)
3339

3440
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

plugins/channel_hint.c

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#include "config.h"
2+
#include <plugins/channel_hint.h>
3+
4+
void channel_hint_to_json(const char *name, const struct channel_hint *hint,
5+
struct json_stream *dest)
6+
{
7+
json_object_start(dest, name);
8+
json_add_u32(dest, "timestamp", hint->timestamp);
9+
json_add_short_channel_id_dir(dest, "scid", hint->scid);
10+
json_add_amount_msat(dest, "estimated_capacity_msat",
11+
hint->estimated_capacity);
12+
json_add_amount_msat(dest, "overall_capacity_msat",
13+
hint->overall_capacity);
14+
json_add_bool(dest, "enabled", hint->enabled);
15+
json_object_end(dest);
16+
}
17+
18+
#define PAY_REFILL_TIME 7200
19+
20+
/**
21+
* Update the `channel_hint` in place, return whether it should be kept.
22+
*
23+
* This computes the refill-rate based on the overall capacity, and
24+
* the time elapsed since the last update and relaxes the upper bound
25+
* on the capacity, and resets the enabled flag if appropriate. If the
26+
* hint is no longer useful, i.e., it does not provide any additional
27+
* information on top of the structural information we've learned from
28+
* the gossip, then we return `false` to signal that the
29+
* `channel_hint` may be removed.
30+
*/
31+
bool channel_hint_update(const struct timeabs now, struct channel_hint *hint)
32+
{
33+
/* Precision is not required here, so integer division is good
34+
* enough. But keep the order such that we do not round down
35+
* too much. We do so by first multiplying, before
36+
* dividing. The formula is `current = last + delta_t *
37+
* overall / refill_rate`.
38+
*/
39+
struct amount_msat refill;
40+
u64 seconds = now.ts.tv_sec - hint->timestamp;
41+
if (!amount_msat_mul(&refill, hint->overall_capacity, seconds))
42+
abort();
43+
44+
refill = amount_msat_div(refill, PAY_REFILL_TIME);
45+
if (!amount_msat_add(&hint->estimated_capacity,
46+
hint->estimated_capacity, refill))
47+
abort();
48+
49+
/* Clamp the value to the `overall_capacity` */
50+
if (amount_msat_greater(hint->estimated_capacity,
51+
hint->overall_capacity))
52+
hint->estimated_capacity = hint->overall_capacity;
53+
54+
/* TODO This is rather coarse. We could map the disabled flag
55+
to having 0msat capacity, and then relax from there. But it'd
56+
likely be too slow of a relaxation.*/
57+
if (seconds > 60)
58+
hint->enabled = true;
59+
60+
/* Since we update in-place we should make sure that we can
61+
* just call update again and the result is stable, if no time
62+
* has passed. */
63+
hint->timestamp = now.ts.tv_sec;
64+
65+
/* We report this hint as useless, if the hint does not
66+
* restrict the channel, i.e., if it is enabled and the
67+
* estimate is the same as the overall capacity. */
68+
return !hint->enabled || amount_msat_greater(hint->overall_capacity,
69+
hint->estimated_capacity);
70+
}
71+
72+
/**
73+
* Load a channel_hint from its JSON representation.
74+
*
75+
* @return The initialized `channel_hint` or `NULL` if we encountered a parsing
76+
* error.
77+
*/
78+
/*
79+
struct channel_hint *channel_hint_from_json(const tal_t *ctx,
80+
const char *buffer,
81+
const jsmntok_t *toks)
82+
{
83+
const char *ret;
84+
struct channel_hint *hint = tal(ctx, struct channel_hint);
85+
ret = json_scan(ctx, buffer, toks,
86+
"{timestamp:%,scid:%,estimated_capacity_msat:%,overall_capacity_msat:%,enabled:%}",
87+
JSON_SCAN(json_to_u32, &hint->timestamp),
88+
JSON_SCAN(json_to_short_channel_id_dir, &hint->scid),
89+
JSON_SCAN(json_to_msat, &hint->estimated_capacity),
90+
JSON_SCAN(json_to_bool, &hint->enabled));
91+
92+
if (ret != NULL)
93+
hint = tal_free(hint);
94+
return hint;
95+
}
96+
*/

plugins/channel_hint.h

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#ifndef LIGHTNING_PLUGINS_CHANNEL_HINT_H
2+
#define LIGHTNING_PLUGINS_CHANNEL_HINT_H
3+
4+
#include "config.h"
5+
#include <bitcoin/short_channel_id.h>
6+
#include <ccan/short_types/short_types.h>
7+
#include <ccan/time/time.h>
8+
#include <common/amount.h>
9+
#include <common/json_stream.h>
10+
#include <plugins/libplugin-pay.h>
11+
#include <plugins/libplugin.h>
12+
13+
/* Information about channels we inferred from a) looking at our channels, and
14+
* b) from failures encountered during attempts to perform a payment. These
15+
* are attached to the root payment, since that information is
16+
* global. Attempts update the estimated channel capacities when starting, and
17+
* get remove on failure. Success keeps the capacities, since the capacities
18+
* changed due to the successful HTLCs. */
19+
struct channel_hint {
20+
/* The timestamp this observation was made. Used to let the
21+
* constraint expressed by this hint decay over time, until it
22+
* is fully relaxed, at which point we can forget about it
23+
* (the structural information is the best we can do in that
24+
* case).
25+
*/
26+
u32 timestamp;
27+
/* The short_channel_id we're going to use when referring to
28+
* this channel. This can either be the real scid, or the
29+
* local alias. The `pay` algorithm doesn't really care which
30+
* one it is, but we'll prefer the scid as that's likely more
31+
* readable than the alias. */
32+
struct short_channel_id_dir scid;
33+
34+
/* Upper bound on remove channels inferred from payment failures. */
35+
struct amount_msat estimated_capacity;
36+
37+
/* Is the channel enabled? */
38+
bool enabled;
39+
40+
/* Non-null if we are one endpoint of this channel */
41+
struct local_hint *local;
42+
43+
/* The total `amount_msat` that were used to fund the
44+
* channel. This is always smaller gte the
45+
* estimated_capacity */
46+
struct amount_msat overall_capacity;
47+
};
48+
49+
/* A collection of channel_hint instances, allowing us to handle and
50+
* update them more easily. */
51+
struct channel_hint_set {
52+
/* tal_arr of channel_hints. */
53+
struct channel_hint *hints;
54+
};
55+
56+
bool channel_hint_update(const struct timeabs now,
57+
struct channel_hint *hint);
58+
59+
void channel_hint_to_json(const char *name, const struct channel_hint *hint, struct json_stream *dest);
60+
61+
#endif /* LIGHTNING_PLUGINS_CHANNEL_HINT_H */

plugins/libplugin-pay.c

Lines changed: 0 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -387,87 +387,6 @@ void payment_start(struct payment *p)
387387
payment_start_at_blockheight(p, INVALID_BLOCKHEIGHT);
388388
}
389389

390-
static void channel_hint_to_json(const char *name, const struct channel_hint *hint, struct json_stream *dest)
391-
{
392-
json_object_start(dest, name);
393-
json_add_u32(dest, "timestamp", hint->timestamp);
394-
json_add_short_channel_id_dir(dest, "scid", hint->scid);
395-
json_add_amount_msat(dest, "estimated_capacity_msat", hint->estimated_capacity);
396-
json_add_amount_msat(dest, "overall_capacity_msat", hint->overall_capacity);
397-
json_add_bool(dest, "enabled", hint->enabled);
398-
json_object_end(dest);
399-
}
400-
401-
#define PAY_REFILL_TIME 7200
402-
403-
/**
404-
* Update the `channel_hint` in place, return whether it should be kept.
405-
*
406-
* This computes the refill-rate based on the overall capacity, and
407-
* the time elapsed since the last update and relaxes the upper bound
408-
* on the capacity, and resets the enabled flag if appropriate. If the
409-
* hint is no longer useful, i.e., it does not provide any additional
410-
* information on top of the structural information we've learned from
411-
* the gossip, then we return `false` to signal that the
412-
* `channel_hint` may be removed.
413-
*/
414-
static bool channel_hint_update(const struct timeabs now,
415-
struct channel_hint *hint)
416-
{
417-
/* Precision is not required here, so integer division is good enough.
418-
*/
419-
u64 refill_rate = hint->overall_capacity.millisatoshis / PAY_REFILL_TIME; /* Raw: just simpler */
420-
u64 seconds = now.ts.tv_sec - hint->timestamp;
421-
hint->estimated_capacity.millisatoshis += refill_rate * seconds; /* Raw: simpler */
422-
423-
/* Clamp the value to the `overall_capacity` */
424-
if (amount_msat_greater(hint->estimated_capacity,
425-
hint->overall_capacity))
426-
hint->estimated_capacity = hint->overall_capacity;
427-
428-
/* TODO This is rather coarse. We could map the disabled flag
429-
to having 0msat capacity, and then relax from there. But it'd
430-
likely be too slow of a relaxation.*/
431-
if (seconds > 60)
432-
hint->enabled = true;
433-
434-
/* Since we update in-place we should make sure that we can
435-
* just call update again and the result is stable, if no time
436-
* has passed. */
437-
hint->timestamp = now.ts.tv_sec;
438-
439-
/* We report this hint as useless, if the hint does not
440-
* restrict the channel, i.e., if it is enabled and the
441-
* estimate is the same as the overall capacity. */
442-
return !hint->enabled || amount_msat_greater(hint->overall_capacity,
443-
hint->estimated_capacity);
444-
}
445-
446-
/**
447-
* Load a channel_hint from its JSON representation.
448-
*
449-
* @return The initialized `channel_hint` or `NULL` if we encountered a parsing
450-
* error.
451-
*/
452-
/*
453-
static struct channel_hint *channel_hint_from_json(const tal_t *ctx,
454-
const char *buffer,
455-
const jsmntok_t *toks)
456-
{
457-
const char *ret;
458-
struct channel_hint *hint = tal(ctx, struct channel_hint);
459-
ret = json_scan(ctx, buffer, toks,
460-
"{timestamp:%,scid:%,estimated_capacity_msat:%,overall_capacity_msat:%,enabled:%}",
461-
JSON_SCAN(json_to_u32, &hint->timestamp),
462-
JSON_SCAN(json_to_short_channel_id_dir, &hint->scid),
463-
JSON_SCAN(json_to_msat, &hint->estimated_capacity),
464-
JSON_SCAN(json_to_bool, &hint->enabled));
465-
466-
if (ret != NULL)
467-
hint = tal_free(hint);
468-
return hint;
469-
}
470-
*/
471390
/**
472391
* Notify subscribers of the `channel_hint` topic about a changed hint
473392
*

plugins/libplugin-pay.h

Lines changed: 1 addition & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <ccan/io/io.h>
66
#include <common/bolt11.h>
77
#include <common/route.h>
8+
#include <plugins/channel_hint.h>
89
#include <plugins/libplugin.h>
910
#include <wire/onion_wire.h>
1011

@@ -61,42 +62,6 @@ struct local_hint {
6162
u16 htlc_budget;
6263
};
6364

64-
/* Information about channels we inferred from a) looking at our channels, and
65-
* b) from failures encountered during attempts to perform a payment. These
66-
* are attached to the root payment, since that information is
67-
* global. Attempts update the estimated channel capacities when starting, and
68-
* get remove on failure. Success keeps the capacities, since the capacities
69-
* changed due to the successful HTLCs. */
70-
struct channel_hint {
71-
/* The timestamp this observation was made. Used to let the
72-
* constraint expressed by this hint decay over time, until it
73-
* is fully relaxed, at which point we can forget about it
74-
* (the structural information is the best we can do in that
75-
* case).
76-
*/
77-
u32 timestamp;
78-
/* The short_channel_id we're going to use when referring to
79-
* this channel. This can either be the real scid, or the
80-
* local alias. The `pay` algorithm doesn't really care which
81-
* one it is, but we'll prefer the scid as that's likely more
82-
* readable than the alias. */
83-
struct short_channel_id_dir scid;
84-
85-
/* Upper bound on remove channels inferred from payment failures. */
86-
struct amount_msat estimated_capacity;
87-
88-
/* Is the channel enabled? */
89-
bool enabled;
90-
91-
/* Non-null if we are one endpoint of this channel */
92-
struct local_hint *local;
93-
94-
/* The total `amount_msat` that were used to fund the
95-
* channel. This is always smaller gte the
96-
* estimated_capacity */
97-
struct amount_msat overall_capacity;
98-
};
99-
10065
/* Each payment goes through a number of steps that are always processed in
10166
* the same order, and some modifiers are called with the payment, and the
10267
* modifier's data before and after certain steps, allowing customization. The

plugins/test/Makefile

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,16 @@ plugins/test/run-route-overlong: \
2020
common/gossmap.o \
2121
common/node_id.o \
2222
common/route.o \
23-
gossipd/gossip_store_wiregen.o
23+
gossipd/gossip_store_wiregen.o \
24+
plugins/channel_hint.o
2425

2526
plugins/test/run-route-calc: \
2627
common/fp16.o \
2728
common/gossmap.o \
2829
common/node_id.o \
2930
common/route.o \
30-
gossipd/gossip_store_wiregen.o
31+
gossipd/gossip_store_wiregen.o \
32+
plugins/channel_hint.o
3133

3234
$(PLUGIN_TEST_PROGRAMS): $(BITCOIN_OBJS) $(WIRE_OBJS) $(PLUGIN_TEST_COMMON_OBJS)
3335

0 commit comments

Comments
 (0)