mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-03-15 20:09:18 +01:00
pay: Use the global channel_hint_set
and remember across payments
This commit is contained in:
parent
603a70e7e2
commit
50a0321759
10 changed files with 272 additions and 144 deletions
|
@ -71,6 +71,76 @@ bool channel_hint_update(const struct timeabs now, struct channel_hint *hint)
|
|||
amount_msat_greater(capacity, hint->estimated_capacity);
|
||||
}
|
||||
|
||||
struct channel_hint *channel_hint_set_find(struct channel_hint_set *self,
|
||||
const struct short_channel_id_dir *scidd)
|
||||
{
|
||||
for (size_t i=0; i<tal_count(self->hints); i++) {
|
||||
struct channel_hint *hint = &self->hints[i];
|
||||
if (short_channel_id_dir_eq(&hint->scid, scidd))
|
||||
return hint;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* 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_sat capacity, u16 *htlc_budget)
|
||||
{
|
||||
bool modified = false;
|
||||
struct channel_hint *old, *newhint = tal(tmpctx, struct channel_hint);
|
||||
struct timeabs now = time_now();
|
||||
|
||||
/* If the channel is marked as enabled it must have an estimate. */
|
||||
assert(!enabled || estimated_capacity != NULL);
|
||||
newhint->enabled = enabled;
|
||||
newhint->scid = *scidd;
|
||||
newhint->capacity = capacity;
|
||||
if (estimated_capacity != NULL)
|
||||
newhint->estimated_capacity = *estimated_capacity;
|
||||
newhint->local = NULL;
|
||||
newhint->timestamp = timestamp;
|
||||
|
||||
/* Project the channel_hints into the same domain, so we can merge them.
|
||||
*/
|
||||
channel_hint_update(now, newhint);
|
||||
channel_hint_set_update(self, now);
|
||||
|
||||
/* And now we can merge the new hint into the existing ones if there
|
||||
are any. */
|
||||
old = channel_hint_set_find(self, scidd);
|
||||
if (old == NULL) {
|
||||
tal_arr_expand(&self->hints, *newhint);
|
||||
// TODO extend the array
|
||||
return &self->hints[tal_count(self->hints) - 1];
|
||||
} else {
|
||||
/* Prefer to disable a channel. */
|
||||
if (!enabled && old->enabled) {
|
||||
old->enabled = false;
|
||||
modified = true;
|
||||
}
|
||||
/* Prefer the more conservative estimate. */
|
||||
if (estimated_capacity != NULL &&
|
||||
amount_msat_greater(old->estimated_capacity,
|
||||
newhint->estimated_capacity)) {
|
||||
old->estimated_capacity = newhint->estimated_capacity;
|
||||
modified = true;
|
||||
}
|
||||
if (newhint->local) {
|
||||
tal_free(old->local);
|
||||
old->local = tal_steal(old, newhint->local);
|
||||
}
|
||||
}
|
||||
if (modified) {
|
||||
old->timestamp = timestamp;
|
||||
return old;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a channel_hint from its JSON representation.
|
||||
*
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
#include <ccan/time/time.h>
|
||||
#include <common/amount.h>
|
||||
#include <common/json_stream.h>
|
||||
#include <plugins/libplugin-pay.h>
|
||||
#include <plugins/libplugin.h>
|
||||
|
||||
/* Information about channels we inferred from a) looking at our channels, and
|
||||
|
@ -54,7 +53,7 @@ struct channel_hint_set {
|
|||
};
|
||||
|
||||
bool channel_hint_update(const struct timeabs now,
|
||||
struct channel_hint *hint);
|
||||
struct channel_hint *hint);
|
||||
|
||||
void channel_hint_to_json(const char *name, const struct channel_hint *hint,
|
||||
struct json_stream *dest);
|
||||
|
@ -68,4 +67,24 @@ struct channel_hint_set *channel_hint_set_new(const tal_t *ctx);
|
|||
/* Relax all channel_hints in this set, based on the time that has elapsed. */
|
||||
void channel_hint_set_update(struct channel_hint_set *set, const struct timeabs now);
|
||||
|
||||
/**
|
||||
* Look up a `channel_hint` from a `channel_hint_set` for a scidd.
|
||||
*/
|
||||
struct channel_hint *channel_hint_set_find(struct channel_hint_set *self,
|
||||
const struct short_channel_id_dir *scidd);
|
||||
|
||||
/**
|
||||
* Add a new observation to the `channel_hint_set`
|
||||
*
|
||||
* This either adds a new entry, or updates an existing one in the set.
|
||||
* @return A new channel_hint, if the addition resulted in changes.
|
||||
*/
|
||||
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_sat overall_capacity,
|
||||
u16 *htlc_budget);
|
||||
|
||||
#endif /* LIGHTNING_PLUGINS_CHANNEL_HINT_H */
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
static unsigned int maxdelay_default;
|
||||
static struct node_id my_id;
|
||||
static u64 *accepted_extra_tlvs;
|
||||
static struct channel_hint_set *global_hints;
|
||||
|
||||
/*****************************************************************************
|
||||
* Keysend modifier
|
||||
|
@ -159,6 +160,8 @@ static const char *init(struct plugin *p, const char *buf UNUSED,
|
|||
rpc_scan(p, "getinfo", take(json_out_obj(NULL, NULL, NULL)), "{id:%}",
|
||||
JSON_SCAN(json_to_node_id, &my_id));
|
||||
|
||||
global_hints = notleak_with_children(channel_hint_set_new(p));
|
||||
|
||||
accepted_extra_tlvs = notleak(tal_arr(NULL, u64, 0));
|
||||
/* BOLT #4:
|
||||
* ## `max_htlc_cltv` Selection
|
||||
|
@ -241,7 +244,7 @@ static struct command_result *json_keysend(struct command *cmd, const char *buf,
|
|||
NULL))
|
||||
return command_param_failed();
|
||||
|
||||
p = payment_new(cmd, cmd, NULL /* No parent */, pay_mods);
|
||||
p = payment_new(cmd, cmd, NULL /* No parent */, global_hints, pay_mods);
|
||||
p->local_id = &my_id;
|
||||
p->json_buffer = tal_dup_talarr(p, const char, buf);
|
||||
p->json_toks = params;
|
||||
|
|
|
@ -71,6 +71,7 @@ int libplugin_pay_poll(struct pollfd *fds, nfds_t nfds, int timeout)
|
|||
|
||||
struct payment *payment_new(tal_t *ctx, struct command *cmd,
|
||||
struct payment *parent,
|
||||
struct channel_hint_set *channel_hints,
|
||||
struct payment_modifier **mods)
|
||||
{
|
||||
struct payment *p = tal(ctx, struct payment);
|
||||
|
@ -133,7 +134,6 @@ struct payment *payment_new(tal_t *ctx, struct command *cmd,
|
|||
p->partid = 0;
|
||||
p->next_partid = 1;
|
||||
p->plugin = cmd->plugin;
|
||||
p->channel_hints = tal_arr(p, struct channel_hint, 0);
|
||||
p->excluded_nodes = tal_arr(p, struct node_id, 0);
|
||||
p->id = next_id++;
|
||||
p->description = NULL;
|
||||
|
@ -143,6 +143,8 @@ struct payment *payment_new(tal_t *ctx, struct command *cmd,
|
|||
p->groupid = 0;
|
||||
p->mods = NULL;
|
||||
p->chainlag = 0;
|
||||
assert(channel_hints != NULL);
|
||||
p->hints = channel_hints;
|
||||
}
|
||||
|
||||
/* Initialize all modifier data so we can point to the fields when
|
||||
|
@ -158,6 +160,10 @@ struct payment *payment_new(tal_t *ctx, struct command *cmd,
|
|||
tal_arr_expand(&p->modifier_data, NULL);
|
||||
}
|
||||
|
||||
paymod_log(p, LOG_DBG,
|
||||
"Initialized a new (sub-)payment with %zu channel_hints",
|
||||
tal_count(payment_root(p)->hints->hints));
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
|
@ -413,82 +419,39 @@ static void channel_hints_update(struct payment *p,
|
|||
u16 *htlc_budget)
|
||||
{
|
||||
struct payment *root = payment_root(p);
|
||||
struct channel_hint newhint;
|
||||
u32 timestamp = time_now().ts.tv_sec;
|
||||
memcheck(&overall_capacity, sizeof(struct amount_msat));
|
||||
struct short_channel_id_dir *scidd =
|
||||
tal(tmpctx, struct short_channel_id_dir);
|
||||
struct channel_hint *hint;
|
||||
scidd->scid = scid;
|
||||
scidd->dir = direction;
|
||||
|
||||
/* Local channels must have an HTLC budget */
|
||||
assert(!local || htlc_budget != NULL);
|
||||
|
||||
channel_hint_set_add(root->hints, time_now().ts.tv_sec, scidd, enabled,
|
||||
estimated_capacity, overall_capacity, htlc_budget);
|
||||
|
||||
hint = channel_hint_set_find(root->hints, scidd);
|
||||
|
||||
if (local) {
|
||||
hint->local = tal_free(hint->local);
|
||||
hint->local = tal(root->hints, struct local_hint);
|
||||
hint->local->htlc_budget = *htlc_budget;
|
||||
}
|
||||
|
||||
/* If the channel is marked as enabled it must have an estimate. */
|
||||
assert(!enabled || estimated_capacity != NULL);
|
||||
|
||||
/* Try and look for an existing hint: */
|
||||
for (size_t i=0; i<tal_count(root->channel_hints); i++) {
|
||||
struct channel_hint *hint = &root->channel_hints[i];
|
||||
if (short_channel_id_eq(hint->scid.scid, scid) &&
|
||||
hint->scid.dir == direction) {
|
||||
bool modified = false;
|
||||
/* Prefer to disable a channel. */
|
||||
if (!enabled && hint->enabled) {
|
||||
hint->enabled = false;
|
||||
modified = true;
|
||||
}
|
||||
|
||||
/* Prefer the more conservative estimate. */
|
||||
if (estimated_capacity != NULL &&
|
||||
amount_msat_greater(hint->estimated_capacity,
|
||||
*estimated_capacity)) {
|
||||
hint->estimated_capacity = *estimated_capacity;
|
||||
modified = true;
|
||||
}
|
||||
if (htlc_budget != NULL) {
|
||||
assert(hint->local);
|
||||
hint->local->htlc_budget = *htlc_budget;
|
||||
modified = true;
|
||||
}
|
||||
|
||||
if (modified) {
|
||||
hint->timestamp = timestamp;
|
||||
paymod_log(p, LOG_DBG,
|
||||
"Updated a channel hint for %s: "
|
||||
"enabled %s, "
|
||||
"estimated capacity %s",
|
||||
fmt_short_channel_id_dir(tmpctx,
|
||||
&hint->scid),
|
||||
hint->enabled ? "true" : "false",
|
||||
fmt_amount_msat(tmpctx,
|
||||
hint->estimated_capacity));
|
||||
channel_hint_notify(p->plugin, hint);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (hint != NULL) {
|
||||
paymod_log(p, LOG_DBG,
|
||||
"Updated a channel hint for %s: "
|
||||
"enabled %s, "
|
||||
"estimated capacity %s",
|
||||
fmt_short_channel_id_dir(tmpctx, &hint->scid),
|
||||
hint->enabled ? "true" : "false",
|
||||
fmt_amount_msat(tmpctx, hint->estimated_capacity));
|
||||
channel_hint_notify(p->plugin, hint);
|
||||
}
|
||||
|
||||
/* No hint found, create one. */
|
||||
newhint.enabled = enabled;
|
||||
newhint.timestamp = timestamp;
|
||||
newhint.scid.scid = scid;
|
||||
newhint.scid.dir = direction;
|
||||
newhint.capacity = overall_capacity;
|
||||
if (local) {
|
||||
newhint.local = tal(root->channel_hints, struct local_hint);
|
||||
assert(htlc_budget);
|
||||
newhint.local->htlc_budget = *htlc_budget;
|
||||
} else
|
||||
newhint.local = NULL;
|
||||
if (estimated_capacity != NULL)
|
||||
newhint.estimated_capacity = *estimated_capacity;
|
||||
else if (!amount_sat_to_msat(&newhint.estimated_capacity,
|
||||
overall_capacity))
|
||||
abort();
|
||||
|
||||
tal_arr_expand(&root->channel_hints, newhint);
|
||||
|
||||
paymod_log(
|
||||
p, LOG_DBG,
|
||||
"Added a channel hint for %s: enabled %s, estimated capacity %s",
|
||||
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)
|
||||
|
@ -563,15 +526,8 @@ static struct channel_hint *payment_chanhints_get(struct payment *p,
|
|||
struct route_hop *h)
|
||||
{
|
||||
struct payment *root = payment_root(p);
|
||||
struct channel_hint *curhint;
|
||||
for (size_t j = 0; j < tal_count(root->channel_hints); j++) {
|
||||
curhint = &root->channel_hints[j];
|
||||
if (short_channel_id_eq(curhint->scid.scid, h->scid) &&
|
||||
curhint->scid.dir == h->direction) {
|
||||
return curhint;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
struct short_channel_id_dir scidd = {.scid = h->scid, .dir = h->direction};
|
||||
return channel_hint_set_find(root->hints, &scidd);
|
||||
}
|
||||
|
||||
/* Given a route and a couple of channel hints, apply the route to the channel
|
||||
|
@ -697,8 +653,8 @@ payment_get_excluded_channels(const tal_t *ctx, struct payment *p)
|
|||
struct channel_hint *hint;
|
||||
struct short_channel_id_dir *res =
|
||||
tal_arr(ctx, struct short_channel_id_dir, 0);
|
||||
for (size_t i = 0; i < tal_count(root->channel_hints); i++) {
|
||||
hint = &root->channel_hints[i];
|
||||
for (size_t i = 0; i < tal_count(root->hints->hints); i++) {
|
||||
hint = &root->hints->hints[i];
|
||||
|
||||
if (!hint->enabled)
|
||||
tal_arr_expand(&res, hint->scid);
|
||||
|
@ -772,7 +728,7 @@ static bool payment_route_check(const struct gossmap *gossmap,
|
|||
return false;
|
||||
|
||||
scid = gossmap_chan_scid(gossmap, c);
|
||||
hint = find_hint(payment_root(p)->channel_hints, scid, dir);
|
||||
hint = find_hint(payment_root(p)->hints->hints, scid, dir);
|
||||
if (!hint)
|
||||
return true;
|
||||
|
||||
|
@ -2606,7 +2562,7 @@ static inline void retry_step_cb(struct retry_mod_data *rd,
|
|||
/* If the failure was not final, and we tried a route, try again. */
|
||||
if (rdata->retries > 0) {
|
||||
payment_set_step(p, PAYMENT_STEP_RETRY);
|
||||
subpayment = payment_new(p, NULL, p, p->modifiers);
|
||||
subpayment = payment_new(p, NULL, p, NULL, p->modifiers);
|
||||
payment_start(subpayment);
|
||||
subpayment->why =
|
||||
tal_fmt(subpayment, "Still have %d attempts left",
|
||||
|
@ -2694,7 +2650,7 @@ local_channel_hints_listpeerchannels(struct command *cmd, const char *buffer,
|
|||
* otherwise start out as excluded and remain so until
|
||||
* forever. */
|
||||
|
||||
struct channel_hint *hints = payment_root(p)->channel_hints;
|
||||
struct channel_hint *hints = payment_root(p)->hints->hints;
|
||||
for (size_t i = 0; i < tal_count(hints); i++)
|
||||
channel_hint_update(time_now(), &hints[i]);
|
||||
|
||||
|
@ -2837,7 +2793,7 @@ static bool routehint_excluded(struct payment *p,
|
|||
const struct node_id *nodes = payment_get_excluded_nodes(tmpctx, p);
|
||||
const struct short_channel_id_dir *chans =
|
||||
payment_get_excluded_channels(tmpctx, p);
|
||||
const struct channel_hint *hints = payment_root(p)->channel_hints;
|
||||
const struct channel_hint_set *hints = payment_root(p)->hints;
|
||||
|
||||
/* Note that we ignore direction here: in theory, we could have
|
||||
* found that one direction of a channel is unavailable, but they
|
||||
|
@ -2878,12 +2834,12 @@ static bool routehint_excluded(struct payment *p,
|
|||
* channel, which is greater than the destination.
|
||||
*/
|
||||
for (size_t j = 0; j < tal_count(hints); j++) {
|
||||
if (!short_channel_id_eq(hints[j].scid.scid, r->short_channel_id))
|
||||
if (!short_channel_id_eq(hints->hints[j].scid.scid, r->short_channel_id))
|
||||
continue;
|
||||
/* We exclude on equality because we set the estimate
|
||||
* to the smallest failed attempt. */
|
||||
if (amount_msat_greater_eq(needed_capacity,
|
||||
hints[j].estimated_capacity))
|
||||
hints->hints[j].estimated_capacity))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -3526,15 +3482,7 @@ static void direct_pay_override(struct payment *p) {
|
|||
|
||||
/* If we have a channel we need to make sure that it still has
|
||||
* sufficient capacity. Look it up in the channel_hints. */
|
||||
for (size_t i=0; i<tal_count(root->channel_hints); i++) {
|
||||
struct short_channel_id_dir *cur = &root->channel_hints[i].scid;
|
||||
if (short_channel_id_eq(cur->scid, d->chan->scid) &&
|
||||
cur->dir == d->chan->dir) {
|
||||
hint = &root->channel_hints[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
hint = channel_hint_set_find(root->hints, d->chan);
|
||||
if (hint && hint->enabled &&
|
||||
amount_msat_greater(hint->estimated_capacity, p->our_amount)) {
|
||||
/* Now build a route that consists only of this single hop */
|
||||
|
@ -3636,11 +3584,12 @@ REGISTER_PAYMENT_MODIFIER(directpay, struct direct_pay_data *, direct_pay_init,
|
|||
|
||||
static u32 payment_max_htlcs(const struct payment *p)
|
||||
{
|
||||
return 10000;/*
|
||||
const struct payment *root;
|
||||
struct channel_hint *h;
|
||||
u32 res = 0;
|
||||
for (size_t i = 0; i < tal_count(p->channel_hints); i++) {
|
||||
h = &p->channel_hints[i];
|
||||
for (size_t i = 0; i < tal_count(p->hints->hints); i++) {
|
||||
h = &p->hints->hints[i];
|
||||
if (h->local && h->enabled)
|
||||
res += h->local->htlc_budget;
|
||||
}
|
||||
|
@ -3649,7 +3598,7 @@ static u32 payment_max_htlcs(const struct payment *p)
|
|||
root = root->parent;
|
||||
if (res > root->max_htlcs)
|
||||
res = root->max_htlcs;
|
||||
return res;
|
||||
return res;*/
|
||||
}
|
||||
|
||||
/** payment_lower_max_htlcs
|
||||
|
@ -3774,8 +3723,8 @@ static void adaptive_splitter_cb(struct adaptive_split_mod_data *d, struct payme
|
|||
}
|
||||
|
||||
p->step = PAYMENT_STEP_SPLIT;
|
||||
a = payment_new(p, NULL, p, p->modifiers);
|
||||
b = payment_new(p, NULL, p, p->modifiers);
|
||||
a = payment_new(p, NULL, p, NULL, p->modifiers);
|
||||
b = payment_new(p, NULL, p, NULL, p->modifiers);
|
||||
|
||||
a->our_amount.millisatoshis = mid; /* Raw: split. */
|
||||
b->our_amount.millisatoshis -= mid; /* Raw: split. */
|
||||
|
|
|
@ -239,7 +239,7 @@ struct payment {
|
|||
|
||||
/* tal_arr of channel_hints we incrementally learn while performing
|
||||
* payment attempts. */
|
||||
struct channel_hint *channel_hints;
|
||||
struct channel_hint_set *hints;
|
||||
struct node_id *excluded_nodes;
|
||||
|
||||
/* Optional temporarily excluded channels/nodes (i.e. this routehint) */
|
||||
|
@ -446,6 +446,7 @@ REGISTER_PAYMENT_MODIFIER_HEADER(route_exclusions, struct route_exclusions_data)
|
|||
|
||||
struct payment *payment_new(tal_t *ctx, struct command *cmd,
|
||||
struct payment *parent,
|
||||
struct channel_hint_set *channel_hints,
|
||||
struct payment_modifier **mods);
|
||||
|
||||
void payment_start(struct payment *p);
|
||||
|
|
|
@ -24,7 +24,7 @@ static struct node_id my_id;
|
|||
static unsigned int maxdelay_default;
|
||||
static bool exp_offers;
|
||||
static bool disablempp = false;
|
||||
static struct channel_hint *global_hints;
|
||||
static struct channel_hint_set *global_hints;
|
||||
|
||||
static LIST_HEAD(payments);
|
||||
|
||||
|
@ -586,17 +586,15 @@ static const char *init(struct plugin *p,
|
|||
/* FIXME: Typo in spec for CLTV in descripton! But it breaks our spelling check, so we omit it above */
|
||||
maxdelay_default = 2016;
|
||||
|
||||
global_hints =
|
||||
notleak_with_children(tal_arr(p, struct channel_hint, 0));
|
||||
global_hints = notleak_with_children(channel_hint_set_new(p));
|
||||
|
||||
/* max-locktime-blocks deprecated in v24.05, but still grab it! */
|
||||
rpc_scan(p, "listconfigs",
|
||||
take(json_out_obj(NULL, NULL, NULL)),
|
||||
"{configs:"
|
||||
"{max-locktime-blocks?:{value_int:%},"
|
||||
"experimental-offers:{set:%}}}",
|
||||
JSON_SCAN(json_to_number, &maxdelay_default),
|
||||
JSON_SCAN(json_to_bool, &exp_offers));
|
||||
/* max-locktime-blocks deprecated in v24.05, but still grab it! */
|
||||
rpc_scan(p, "listconfigs", take(json_out_obj(NULL, NULL, NULL)),
|
||||
"{configs:"
|
||||
"{max-locktime-blocks?:{value_int:%},"
|
||||
"experimental-offers:{set:%}}}",
|
||||
JSON_SCAN(json_to_number, &maxdelay_default),
|
||||
JSON_SCAN(json_to_bool, &exp_offers));
|
||||
|
||||
plugin_set_memleak_handler(p, memleak_mark_payments);
|
||||
return NULL;
|
||||
|
@ -1261,7 +1259,7 @@ static struct command_result *json_pay(struct command *cmd,
|
|||
NULL))
|
||||
return command_param_failed();
|
||||
|
||||
p = payment_new(cmd, cmd, NULL /* No parent */, paymod_mods);
|
||||
p = payment_new(cmd, cmd, NULL /* No parent */, global_hints, paymod_mods);
|
||||
p->invstring = tal_steal(p, b11str);
|
||||
p->description = tal_steal(p, description);
|
||||
/* Overridded by bolt12 if present */
|
||||
|
|
|
@ -20,14 +20,16 @@ plugins/test/run-route-overlong: \
|
|||
common/gossmap.o \
|
||||
common/node_id.o \
|
||||
common/route.o \
|
||||
gossipd/gossip_store_wiregen.o
|
||||
gossipd/gossip_store_wiregen.o \
|
||||
plugins/channel_hint.o
|
||||
|
||||
plugins/test/run-route-calc: \
|
||||
common/fp16.o \
|
||||
common/gossmap.o \
|
||||
common/node_id.o \
|
||||
common/route.o \
|
||||
gossipd/gossip_store_wiregen.o
|
||||
gossipd/gossip_store_wiregen.o \
|
||||
plugins/channel_hint.o
|
||||
|
||||
$(PLUGIN_TEST_PROGRAMS): $(BITCOIN_OBJS) $(WIRE_OBJS) $(PLUGIN_TEST_COMMON_OBJS)
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "config.h"
|
||||
#include "plugins/channel_hint.h"
|
||||
#define TESTING
|
||||
#include "../../common/dijkstra.c"
|
||||
#include "../libplugin-pay.c"
|
||||
|
@ -18,14 +19,6 @@ u8 **blinded_onion_hops(const tal_t *ctx UNNEEDED,
|
|||
struct amount_msat total_amount UNNEEDED,
|
||||
const struct blinded_path *path UNNEEDED)
|
||||
{ fprintf(stderr, "blinded_onion_hops called!\n"); abort(); }
|
||||
/* Generated stub for channel_hint_to_json */
|
||||
void channel_hint_to_json(const char *name UNNEEDED, const struct channel_hint *hint UNNEEDED,
|
||||
struct json_stream *dest UNNEEDED)
|
||||
{ fprintf(stderr, "channel_hint_to_json called!\n"); abort(); }
|
||||
/* Generated stub for channel_hint_update */
|
||||
bool channel_hint_update(const struct timeabs now UNNEEDED,
|
||||
struct channel_hint *hint UNNEEDED)
|
||||
{ fprintf(stderr, "channel_hint_update called!\n"); abort(); }
|
||||
/* Generated stub for command_finished */
|
||||
struct command_result *command_finished(struct command *cmd UNNEEDED, struct json_stream *response UNNEEDED)
|
||||
{ fprintf(stderr, "command_finished called!\n"); abort(); }
|
||||
|
@ -91,6 +84,16 @@ 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_amount_sat */
|
||||
void json_add_amount_sat(struct json_stream *result UNNEEDED,
|
||||
const char *satfieldname UNNEEDED,
|
||||
struct amount_sat sat)
|
||||
|
||||
{ fprintf(stderr, "json_add_amount_sat 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,
|
||||
|
@ -126,6 +129,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,
|
||||
|
@ -214,6 +222,10 @@ bool json_to_sat(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
|
|||
bool json_to_short_channel_id(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
|
||||
struct short_channel_id *scid UNNEEDED)
|
||||
{ fprintf(stderr, "json_to_short_channel_id called!\n"); abort(); }
|
||||
/* Generated stub for json_to_short_channel_id_dir */
|
||||
bool json_to_short_channel_id_dir(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
|
||||
struct short_channel_id_dir *scidd UNNEEDED)
|
||||
{ fprintf(stderr, "json_to_short_channel_id_dir called!\n"); abort(); }
|
||||
/* Generated stub for json_to_u16 */
|
||||
bool json_to_u16(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
|
||||
uint16_t *num UNNEEDED)
|
||||
|
@ -418,12 +430,14 @@ int main(int argc, char *argv[])
|
|||
{
|
||||
struct payment *p;
|
||||
struct payment_modifier **mods;
|
||||
struct channel_hint_set *hints;
|
||||
|
||||
common_setup(argv[0]);
|
||||
chainparams = chainparams_for_network("regtest");
|
||||
hints = channel_hint_set_new(tmpctx);
|
||||
|
||||
mods = tal_arrz(tmpctx, struct payment_modifier *, 1);
|
||||
p = payment_new(mods, tal(tmpctx, struct command), NULL, mods);
|
||||
p = payment_new(mods, tal(tmpctx, struct command), NULL, hints, mods);
|
||||
|
||||
/* We want to permute order of channels between each node, to
|
||||
* avoid "it works because it chooses the first one!" */
|
||||
|
|
|
@ -16,14 +16,6 @@ u8 **blinded_onion_hops(const tal_t *ctx UNNEEDED,
|
|||
struct amount_msat total_amount UNNEEDED,
|
||||
const struct blinded_path *path UNNEEDED)
|
||||
{ fprintf(stderr, "blinded_onion_hops called!\n"); abort(); }
|
||||
/* Generated stub for channel_hint_to_json */
|
||||
void channel_hint_to_json(const char *name UNNEEDED, const struct channel_hint *hint UNNEEDED,
|
||||
struct json_stream *dest UNNEEDED)
|
||||
{ fprintf(stderr, "channel_hint_to_json called!\n"); abort(); }
|
||||
/* Generated stub for channel_hint_update */
|
||||
bool channel_hint_update(const struct timeabs now UNNEEDED,
|
||||
struct channel_hint *hint UNNEEDED)
|
||||
{ fprintf(stderr, "channel_hint_update called!\n"); abort(); }
|
||||
/* Generated stub for command_finished */
|
||||
struct command_result *command_finished(struct command *cmd UNNEEDED, struct json_stream *response UNNEEDED)
|
||||
{ fprintf(stderr, "command_finished called!\n"); abort(); }
|
||||
|
@ -89,6 +81,16 @@ 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_amount_sat */
|
||||
void json_add_amount_sat(struct json_stream *result UNNEEDED,
|
||||
const char *satfieldname UNNEEDED,
|
||||
struct amount_sat sat)
|
||||
|
||||
{ fprintf(stderr, "json_add_amount_sat 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,
|
||||
|
@ -124,6 +126,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,
|
||||
|
@ -212,6 +219,10 @@ bool json_to_sat(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
|
|||
bool json_to_short_channel_id(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
|
||||
struct short_channel_id *scid UNNEEDED)
|
||||
{ fprintf(stderr, "json_to_short_channel_id called!\n"); abort(); }
|
||||
/* Generated stub for json_to_short_channel_id_dir */
|
||||
bool json_to_short_channel_id_dir(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
|
||||
struct short_channel_id_dir *scidd UNNEEDED)
|
||||
{ fprintf(stderr, "json_to_short_channel_id_dir called!\n"); abort(); }
|
||||
/* Generated stub for json_to_u16 */
|
||||
bool json_to_u16(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
|
||||
uint16_t *num UNNEEDED)
|
||||
|
@ -415,6 +426,7 @@ int main(int argc, char *argv[])
|
|||
struct payment_modifier **mods;
|
||||
char gossip_version = 10;
|
||||
char *gossipfilename;
|
||||
struct channel_hint_set *hints = channel_hint_set_new(tmpctx);
|
||||
|
||||
common_setup(argv[0]);
|
||||
chainparams = chainparams_for_network("regtest");
|
||||
|
@ -431,7 +443,7 @@ int main(int argc, char *argv[])
|
|||
}
|
||||
|
||||
mods = tal_arrz(tmpctx, struct payment_modifier *, 1);
|
||||
p = payment_new(mods, tal(tmpctx, struct command), NULL, mods);
|
||||
p = payment_new(mods, tal(tmpctx, struct command), NULL, hints, mods);
|
||||
|
||||
for (size_t i = 1; i < NUM_NODES; i++) {
|
||||
struct short_channel_id scid;
|
||||
|
@ -480,6 +492,7 @@ int main(int argc, char *argv[])
|
|||
assert(tal_count(r) == 2);
|
||||
}
|
||||
|
||||
tal_free(hints);
|
||||
common_shutdown();
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -5998,7 +5998,7 @@ def test_enableoffer(node_factory):
|
|||
l1.rpc.enableoffer(offer_id=offer1['offer_id'])
|
||||
|
||||
|
||||
def test_pay_remember_hint(node_factory):
|
||||
def diamond_network(node_factory):
|
||||
"""Build a diamond, with a cheap route, that is exhausted. The
|
||||
first payment should try that route first, learn it's exhausted,
|
||||
and then succeed over the other leg. The second, unrelated,
|
||||
|
@ -6007,9 +6007,68 @@ def test_pay_remember_hint(node_factory):
|
|||
|
||||
```mermaid
|
||||
graph LR
|
||||
Sender -- "propfee=1\nexhausted" --> Forwarder1
|
||||
Forwarder1 -- "propfee=1" --> Recipient
|
||||
Sender -- "propfee=50" --> Forwarder2
|
||||
Forwarder2 -- "propfee=1" --> Recipient
|
||||
Sender -- "propfee=1" --> Forwarder1
|
||||
Forwarder1 -- "propfee="1\nexhausted --> Recipient
|
||||
Sender -- "propfee=1" --> Forwarder2
|
||||
Forwarder2 -- "propfee=5" --> Recipient
|
||||
```
|
||||
"""
|
||||
opts = [
|
||||
{'fee-per-satoshi': 0, 'fee-base': 0}, # Sender
|
||||
{'fee-per-satoshi': 0, 'fee-base': 0}, # Low fee, but exhausted channel
|
||||
{'fee-per-satoshi': 5000, 'fee-base': 0}, # Disincentivize using fw2
|
||||
{'fee-per-satoshi': 0, 'fee-base': 0}, # Recipient
|
||||
]
|
||||
|
||||
sender, fw1, fw2, recipient, = node_factory.get_nodes(4, opts=opts)
|
||||
|
||||
# And now wire them all up: notice that all channels, except the
|
||||
# recipent <> fw1 are created in the direction of the planned
|
||||
# from, meaning we won't be able to forward through there, causing
|
||||
# a `channel_hint` to be created, disincentivizing usage of this
|
||||
# channel on the second payment.
|
||||
node_factory.join_nodes(
|
||||
[sender, fw2, recipient, fw1],
|
||||
wait_for_announce=True,
|
||||
announce_channels=True,
|
||||
)
|
||||
# And we complete the diamond by adding the edge from sender to fw1
|
||||
node_factory.join_nodes(
|
||||
[sender, fw1],
|
||||
wait_for_announce=True,
|
||||
announce_channels=True
|
||||
)
|
||||
return [sender, fw1, fw2, recipient]
|
||||
|
||||
|
||||
def test_pay_remember_hint(node_factory):
|
||||
"""Using a diamond graph, with inferred `channel_hint`s, see if we remember
|
||||
"""
|
||||
sender, fw1, fw2, recipient, = diamond_network(node_factory)
|
||||
|
||||
inv = recipient.rpc.invoice(
|
||||
4200000,
|
||||
"lbl1",
|
||||
"desc1",
|
||||
exposeprivatechannels=[], # suppress routehints, so fees alone control route
|
||||
)['bolt11']
|
||||
|
||||
p = sender.rpc.pay(inv)
|
||||
|
||||
# Ensure we failed the first, cheap, path, and then tried the successful one.
|
||||
assert(p['parts'] == 2)
|
||||
|
||||
# Now for the final trick: a new payment should remember the
|
||||
# previous failure, and go directly for the successful route
|
||||
# through fw2
|
||||
|
||||
inv = recipient.rpc.invoice(
|
||||
4200000,
|
||||
"lbl2",
|
||||
"desc2",
|
||||
exposeprivatechannels=[], # suppress routehints, so fees alone control route
|
||||
)['bolt11']
|
||||
|
||||
# We should not have touched fw1, and should succeed after a single call
|
||||
p = sender.rpc.pay(inv)
|
||||
assert(p['parts'] == 1)
|
||||
|
|
Loading…
Add table
Reference in a new issue