From bb672a509c6846d0694186c600c16300d9edd93d Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 29 Jan 2024 08:57:05 +0100 Subject: [PATCH 1/2] renepay: flow routes limited by htlc_max/min The flow routes returned by minflow are bounded by the htlc_min and htlc_max along the route whenever possible. --- plugins/renepay/flow.h | 12 +++++++++ plugins/renepay/mcf.c | 59 +++++++++++++++++++++++++++++++++++++----- tests/test_renepay.py | 29 +++++++++++++++++++++ 3 files changed, 94 insertions(+), 6 deletions(-) diff --git a/plugins/renepay/flow.h b/plugins/renepay/flow.h index 6295afa8db00..dcd8aaacedba 100644 --- a/plugins/renepay/flow.h +++ b/plugins/renepay/flow.h @@ -272,4 +272,16 @@ bool flows_fit_amount(const tal_t *ctx, struct amount_msat *amount_allocated, const struct gossmap *gossmap, struct chan_extra_map *chan_extra_map, char **fail); +/* Helpers to get the htlc_max and htlc_min of a channel. */ +static inline struct amount_msat +channel_htlc_max(const struct gossmap_chan *chan, const int dir) +{ + return amount_msat(fp16_to_u64(chan->half[dir].htlc_max)); +} +static inline struct amount_msat +channel_htlc_min(const struct gossmap_chan *chan, const int dir) +{ + return amount_msat(fp16_to_u64(chan->half[dir].htlc_min)); +} + #endif /* LIGHTNING_PLUGINS_RENEPAY_FLOW_H */ diff --git a/plugins/renepay/mcf.c b/plugins/renepay/mcf.c index e5e12a67feb1..041d7c49427e 100644 --- a/plugins/renepay/mcf.c +++ b/plugins/renepay/mcf.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -191,7 +192,8 @@ static const double CHANNEL_PIVOTS[]={0,0.5,0.8,0.95}; static const s64 INFINITE = INT64_MAX; -static const u32 INVALID_INDEX=0xffffffff; +static const u64 INFINITE_MSAT = UINT64_MAX; +static const u32 INVALID_INDEX = 0xffffffff; static const s64 MU_MAX = 128; /* Let's try this encoding of arcs: @@ -1226,6 +1228,12 @@ struct list_data struct flow *flow_path; }; +static inline uint64_t pseudorand_interval(uint64_t a, uint64_t b) +{ + assert(b > a); + return a + pseudorand(b - a); +} + /* Given a flow in the residual network, build a set of payment flows in the * gossmap that corresponds to this flow. */ static struct flow ** @@ -1310,6 +1318,13 @@ get_flow_paths(const tal_t *ctx, const struct gossmap *gossmap, u32 final_idx = find_positive_balance(gossmap,chan_flow,node_idx,balance, prev_chan,prev_dir,prev_idx); + /* For each route we will compute the highest htlc_min + * and the smallest htlc_max and use those to constraint + * the flow we will allocate. */ + struct amount_msat sup_htlc_min = AMOUNT_MSAT_INIT(0), + inf_htlc_max = + AMOUNT_MSAT_INIT(INFINITE_MSAT); + s64 delta=-balance[node_idx]; int length = 0; delta = MIN(delta,balance[final_idx]); @@ -1329,13 +1344,45 @@ get_flow_paths(const tal_t *ctx, const struct gossmap *gossmap, delta=MIN(delta,chan_flow[c_idx].half[dir]); length++; - // TODO(eduardo) does htlc_max has any relevance - // here? - // HINT: delta=MIN(delta,htlc_max); - // however this might not work because often we - // move delta+fees + /* obtain the supremum htlc_min along the route + */ + sup_htlc_min = amount_msat_max( + sup_htlc_min, channel_htlc_min(c, dir)); + + /* obtain the infimum htlc_max along the route + */ + inf_htlc_max = amount_msat_min( + inf_htlc_max, channel_htlc_max(c, dir)); + } + + s64 htlc_max = inf_htlc_max.millisatoshis / + 1000; /* Raw: need htlc_max in sats to do + arithmetic operations. */ + s64 htlc_min = (sup_htlc_min.millisatoshis + 999) / + 1000; /* Raw: need htlc_min in sats to do + arithmetic operations. */ + + if (htlc_min > htlc_max) { + /* htlc_min is too big or htlc_max is too small, + * we cannot send `delta` along this route. + * + * FIXME: We try anyways because failing + * channels will be blacklisted downstream. */ + htlc_min = 0; } + /* If we divide this route into different flows make it + * random to avoid routing nodes making correlations. */ + if (delta > htlc_max) { + // FIXME: choosing a number in the range + // [htlc_min, htlc_max] or + // [0.5 htlc_max, htlc_max] + // The choice of the fraction was completely + // arbitrary. + delta = pseudorand_interval( + MAX(htlc_min, (htlc_max * 50) / 100), + htlc_max); + } struct flow *fp = tal(this_ctx,struct flow); fp->path = tal_arr(fp,const struct gossmap_chan *,length); diff --git a/tests/test_renepay.py b/tests/test_renepay.py index b5bf3f4a3441..45caa1ec06dc 100644 --- a/tests/test_renepay.py +++ b/tests/test_renepay.py @@ -397,3 +397,32 @@ def test_fee_allocation(node_factory): l1.wait_for_htlcs() invoice = only_one(l4.rpc.listinvoices('inv')['invoices']) assert invoice['amount_received_msat'] >= Millisatoshi('1500000sat') + + +def test_htlc_max(node_factory): + ''' + Topology: + 1----2----4 + | | + 3----5----6 + ''' + opts = [ + {'disable-mpp': None, 'fee-base': 0, 'fee-per-satoshi': 0}, + {'disable-mpp': None, 'fee-base': 0, 'fee-per-satoshi': 0}, + {'disable-mpp': None, 'fee-base': 0, 'fee-per-satoshi': 0}, + {'disable-mpp': None, 'fee-base': 0, 'fee-per-satoshi': 0, + 'htlc-maximum-msat': 500000000}, + {'disable-mpp': None, 'fee-base': 0, 'fee-per-satoshi': 0, + 'htlc-maximum-msat': 500000000}, + {'disable-mpp': None, 'fee-base': 0, 'fee-per-satoshi': 0}, + ] + l1, l2, l3, l4, l5, l6 = node_factory.get_nodes(6, opts=opts) + start_channels([(l1, l2, 10000000), (l2, l4, 1000000), (l4, l6, 2000000), + (l1, l3, 10000000), (l3, l5, 1000000), (l5, l6, 2000000)]) + + inv = l6.rpc.invoice("1800000sat", "inv", 'description') + + l1.rpc.call('renepay', {'invstring': inv['bolt11']}) + l1.wait_for_htlcs() + invoice = only_one(l6.rpc.listinvoices('inv')['invoices']) + assert invoice['amount_received_msat'] >= Millisatoshi('1800000sat') From aedb565ce5d53167d52ade82c301ddadabc7393f Mon Sep 17 00:00:00 2001 From: Lagrang3 Date: Mon, 29 Jan 2024 10:52:26 +0100 Subject: [PATCH 2/2] fixed source (using raw millisatohis) --- plugins/renepay/mcf.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/plugins/renepay/mcf.c b/plugins/renepay/mcf.c index 041d7c49427e..c3f54b6b9634 100644 --- a/plugins/renepay/mcf.c +++ b/plugins/renepay/mcf.c @@ -1355,12 +1355,8 @@ get_flow_paths(const tal_t *ctx, const struct gossmap *gossmap, inf_htlc_max, channel_htlc_max(c, dir)); } - s64 htlc_max = inf_htlc_max.millisatoshis / - 1000; /* Raw: need htlc_max in sats to do - arithmetic operations. */ - s64 htlc_min = (sup_htlc_min.millisatoshis + 999) / - 1000; /* Raw: need htlc_min in sats to do - arithmetic operations. */ + s64 htlc_max=inf_htlc_max.millisatoshis/1000;/* Raw: need htlc_max in sats to do arithmetic operations.*/ + s64 htlc_min=(sup_htlc_min.millisatoshis+999)/1000;/* Raw: need htlc_min in sats to do arithmetic operations.*/ if (htlc_min > htlc_max) { /* htlc_min is too big or htlc_max is too small,