Rather than break the API, use total capacity here. ``` Valgrind error file: valgrind-errors.5880 ==5880== Use of uninitialised value of size 8 ==5880== at 0x4A390BB: _itoa_word (_itoa.c:183) ==5880== by 0x4A43C9B: __printf_buffer (vfprintf-process-arg.c:155) ==5880== by 0x4A69D90: vsnprintf (vsnprintf.c:96) ==5880== by 0x1875E6: json_out_addv (json_out.c:239) ==5880== by 0x14471E: json_add_primitive_fmt (json_stream.c:170) ==5880== by 0x144BA2: json_add_u64 (json_stream.c:282) ==5880== by 0x145E33: json_add_amount_msat (json_stream.c:619) ==5880== by 0x11DDE2: channel_hint_to_json (channel_hint.c:33) ==5880== by 0x11FE9F: channel_hint_notify_core (libplugin-pay.c:394) ==5880== by 0x11FF7A: channel_hint_notify (libplugin-pay.c:412) ==5880== by 0x1201EA: channel_hints_update (libplugin-pay.c:455) ==5880== by 0x122DAF: handle_intermediate_failure (libplugin-pay.c:1437) ==5880== ``` Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
234 lines
7.9 KiB
C
234 lines
7.9 KiB
C
#include "config.h"
|
|
#include <common/json_stream.h>
|
|
#include <common/memleak.h>
|
|
#include <plugins/channel_hint.h>
|
|
|
|
size_t channel_hint_hash(const struct short_channel_id_dir *out)
|
|
{
|
|
struct siphash24_ctx ctx;
|
|
siphash24_init(&ctx, siphash_seed());
|
|
siphash24_update(&ctx, &out->scid.u64, sizeof(u64));
|
|
siphash24_update(&ctx, &out->dir, sizeof(int));
|
|
return siphash24_done(&ctx);
|
|
}
|
|
|
|
const struct short_channel_id_dir *channel_hint_keyof(const struct channel_hint *out)
|
|
{
|
|
return &out->scid;
|
|
}
|
|
|
|
bool channel_hint_eq(const struct channel_hint *a,
|
|
const struct short_channel_id_dir *b)
|
|
{
|
|
return short_channel_id_eq(a->scid.scid, b->scid) &&
|
|
a->scid.dir == b->dir;
|
|
}
|
|
|
|
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);
|
|
/* The estimated_capacity is unset if it's not enabled; use total_capacity */
|
|
if (hint->enabled) {
|
|
json_add_amount_msat(dest, "estimated_capacity_msat",
|
|
hint->estimated_capacity);
|
|
} else {
|
|
json_add_amount_msat(dest, "estimated_capacity_msat",
|
|
hint->capacity);
|
|
}
|
|
json_add_amount_msat(dest, "total_capacity_msat", hint->capacity);
|
|
json_add_bool(dest, "enabled", hint->enabled);
|
|
json_object_end(dest);
|
|
}
|
|
|
|
/* How long until even a channel whose estimate is down at 0msat will
|
|
* be considered fully refilled. The refill rate is the inverse of
|
|
* this times the channel size. The refilling is a linear
|
|
* approximation, with a small hysteresis applied in order to prevent
|
|
* a single payment relaxing its own constraints thus causing it to
|
|
* prematurely retry an already attempted channel.
|
|
*/
|
|
#define PAY_REFILL_TIME 7200
|
|
|
|
/* Add an artificial delay before accepting updates. This ensures we
|
|
* don't actually end up relaxing a tight constraint inbetween the
|
|
* attempt that added it and the next retry. If we were to relax right
|
|
* away, then we could end up retrying the exact same path we just
|
|
* failed at. If the `time_between_attempts * refill > 1msat`, we'd
|
|
* end up not actually constraining at all, because we set the
|
|
* estimate to `attempt - 1msat`. This also results in the updates
|
|
* being limited to once every minute, and causes a stairway
|
|
* pattern. The hysteresis has to be >60s otherwise a single payment
|
|
* can already end up retrying a previously excluded channel.
|
|
*/
|
|
#define PAY_REFILL_HYSTERESIS 60
|
|
/**
|
|
* 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;
|
|
struct amount_msat capacity = hint->capacity;
|
|
|
|
if (now.ts.tv_sec < hint->timestamp + PAY_REFILL_HYSTERESIS)
|
|
return true;
|
|
|
|
u64 seconds = now.ts.tv_sec - hint->timestamp;
|
|
if (!amount_msat_mul(&refill, 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, capacity))
|
|
hint->estimated_capacity = 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(capacity, hint->estimated_capacity);
|
|
}
|
|
|
|
struct channel_hint *channel_hint_set_find(const struct channel_hint_set *self,
|
|
const struct short_channel_id_dir *scidd)
|
|
{
|
|
return channel_hint_map_get(self->hints, scidd);
|
|
}
|
|
|
|
/* See header */
|
|
struct channel_hint *
|
|
channel_hint_set_add(struct channel_hint_set *self, u32 timestamp,
|
|
const struct short_channel_id_dir *scidd, bool enabled,
|
|
const struct amount_msat *estimated_capacity,
|
|
const struct amount_msat capacity, u16 *htlc_budget)
|
|
{
|
|
struct channel_hint *copy, *old, *newhint;
|
|
|
|
/* If the channel is marked as enabled it must have an estimate. */
|
|
assert(!enabled || estimated_capacity != NULL);
|
|
|
|
/* If there was no hint, add the new one, if there was one,
|
|
* pick the one with the newer timestamp. */
|
|
old = channel_hint_set_find(self, scidd);
|
|
copy = tal_dup(tmpctx, struct channel_hint, old);
|
|
if (old == NULL) {
|
|
newhint = tal(self, struct channel_hint);
|
|
newhint->enabled = enabled;
|
|
newhint->scid = *scidd;
|
|
newhint->capacity = capacity;
|
|
if (estimated_capacity != NULL)
|
|
newhint->estimated_capacity = *estimated_capacity;
|
|
newhint->local = NULL;
|
|
newhint->timestamp = timestamp;
|
|
channel_hint_map_add(self->hints, newhint);
|
|
return newhint;
|
|
} else if (old->timestamp <= timestamp) {
|
|
/* `local` is kept, since we do not pass in those
|
|
* annotations here. */
|
|
old->enabled = enabled;
|
|
old->timestamp = timestamp;
|
|
if (estimated_capacity != NULL)
|
|
old->estimated_capacity = *estimated_capacity;
|
|
|
|
/* We always pick the larger of the capacities we are
|
|
* being told. This is because in some cases, such as
|
|
* routehints, we're not actually being told the total
|
|
* capacity, just lower values. */
|
|
if (amount_msat_greater(capacity, old->capacity))
|
|
old->capacity = capacity;
|
|
|
|
return copy;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 , *jhint;
|
|
struct channel_hint *hint = tal(ctx, struct channel_hint);
|
|
|
|
/* Deprecated API uses "payload" */
|
|
payload = json_get_member(buffer, toks, "payload");
|
|
/* Modern API includes fields directly */
|
|
if (!payload) {
|
|
jhint = json_get_member(buffer, toks, "channel_hint_update");
|
|
} else {
|
|
jhint = json_get_member(buffer, payload, "channel_hint");
|
|
}
|
|
ret = json_scan(ctx, buffer, jhint,
|
|
"{timestamp:%,scid:%,estimated_capacity_msat:%,total_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_msat, &hint->capacity),
|
|
JSON_SCAN(json_to_bool, &hint->enabled));
|
|
|
|
if (ret != NULL)
|
|
hint = tal_free(hint);
|
|
return hint;
|
|
}
|
|
|
|
struct channel_hint_set *channel_hint_set_new(const tal_t *ctx)
|
|
{
|
|
struct channel_hint_set *set = tal(ctx, struct channel_hint_set);
|
|
set->hints = new_htable(set, channel_hint_map);
|
|
return set;
|
|
}
|
|
|
|
void channel_hint_set_update(struct channel_hint_set *set,
|
|
const struct timeabs now)
|
|
{
|
|
struct channel_hint *hint;
|
|
struct channel_hint_map_iter iter;
|
|
for (hint = channel_hint_map_first(set->hints, &iter);
|
|
hint;
|
|
hint = channel_hint_map_next(set->hints, &iter))
|
|
channel_hint_update(now, hint);
|
|
}
|
|
|
|
size_t channel_hint_set_count(const struct channel_hint_set *set)
|
|
{
|
|
return channel_hint_map_count(set->hints);
|
|
}
|