pay: Use the global channel_hint_set and remember across payments

This commit is contained in:
Christian Decker 2024-08-22 18:34:38 +02:00
parent 603a70e7e2
commit 50a0321759
10 changed files with 272 additions and 144 deletions

View file

@ -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.
*

View file

@ -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 */

View file

@ -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;

View file

@ -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. */

View file

@ -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);

View file

@ -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 */

View file

@ -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)

View file

@ -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!" */

View file

@ -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;
}

View file

@ -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)