diff --git a/common/gossmods_listpeerchannels.c b/common/gossmods_listpeerchannels.c index 731c63ae7da1..73864241302e 100644 --- a/common/gossmods_listpeerchannels.c +++ b/common/gossmods_listpeerchannels.c @@ -9,8 +9,9 @@ void gossmod_add_localchan(struct gossmap_localmods *mods, const struct node_id *self, const struct node_id *peer, const struct short_channel_id_dir *scidd, - struct amount_msat min, - struct amount_msat max, + struct amount_msat htlcmin, + struct amount_msat htlcmax, + struct amount_msat spendable, struct amount_msat fee_base, u32 fee_proportional, u32 cltv_delta, @@ -19,6 +20,11 @@ void gossmod_add_localchan(struct gossmap_localmods *mods, const jsmntok_t *chantok UNUSED, void *cbarg UNUSED) { + struct amount_msat min = htlcmin, max = htlcmax; + + if (amount_msat_less(spendable, max)) + max = spendable; + /* FIXME: features? */ gossmap_local_addchan(mods, self, peer, scidd->scid, NULL); @@ -40,8 +46,9 @@ gossmods_from_listpeerchannels_(const tal_t *ctx, const struct node_id *self, const struct node_id *peer, const struct short_channel_id_dir *scidd, - struct amount_msat min, - struct amount_msat max, + struct amount_msat htlcmin, + struct amount_msat htlcmax, + struct amount_msat sr_able, struct amount_msat fee_base, u32 fee_proportional, u32 cltv_delta, @@ -130,10 +137,6 @@ gossmods_from_listpeerchannels_(const tal_t *ctx, && !streq(state, "CHANNELD_AWAITING_SPLICE")) enabled = false; - /* Cut htlc max to spendable. */ - if (amount_msat_less(spendable, htlc_max[LOCAL])) - htlc_max[LOCAL] = spendable; - /* We route better if we know we won't charge * ourselves fees (though if fees are a signal on what * channel we prefer to use, this ignores that @@ -146,8 +149,8 @@ gossmods_from_listpeerchannels_(const tal_t *ctx, /* We add both directions */ cb(mods, self, &dst, &scidd, htlc_min[LOCAL], htlc_max[LOCAL], - fee_base[LOCAL], fee_proportional[LOCAL], cltv_delta[LOCAL], - enabled, buf, channel, cbarg); + spendable, fee_base[LOCAL], fee_proportional[LOCAL], + cltv_delta[LOCAL], enabled, buf, channel, cbarg); /* If we didn't have a remote update, it's not usable yet */ if (fee_proportional[REMOTE] == -1U) @@ -155,13 +158,9 @@ gossmods_from_listpeerchannels_(const tal_t *ctx, scidd.dir = !scidd.dir; - /* Cut htlc max to receivable. */ - if (amount_msat_less(receivable, htlc_max[REMOTE])) - htlc_max[REMOTE] = receivable; - cb(mods, self, &dst, &scidd, htlc_min[REMOTE], htlc_max[REMOTE], - fee_base[REMOTE], fee_proportional[REMOTE], cltv_delta[REMOTE], - enabled, buf, channel, cbarg); + receivable, fee_base[REMOTE], fee_proportional[REMOTE], + cltv_delta[REMOTE], enabled, buf, channel, cbarg); } return mods; diff --git a/common/gossmods_listpeerchannels.h b/common/gossmods_listpeerchannels.h index edabacc10d9f..a9847c882645 100644 --- a/common/gossmods_listpeerchannels.h +++ b/common/gossmods_listpeerchannels.h @@ -30,8 +30,9 @@ struct gossmap_localmods *gossmods_from_listpeerchannels_(const tal_t *ctx, const struct node_id *self_, const struct node_id *peer, const struct short_channel_id_dir *scidd, - struct amount_msat min, - struct amount_msat max, + struct amount_msat htlcmin, + struct amount_msat htlcmax, + struct amount_msat spendable, struct amount_msat fee_base, u32 fee_proportional, u32 cltv_delta, @@ -51,6 +52,7 @@ struct gossmap_localmods *gossmods_from_listpeerchannels_(const tal_t *ctx, struct amount_msat, \ struct amount_msat, \ struct amount_msat, \ + struct amount_msat, \ u32, \ u32, \ bool, \ @@ -63,8 +65,9 @@ void gossmod_add_localchan(struct gossmap_localmods *mods, const struct node_id *self, const struct node_id *peer, const struct short_channel_id_dir *scidd, - struct amount_msat min, - struct amount_msat max, + struct amount_msat htlcmin, + struct amount_msat htlcmax, + struct amount_msat spendable, struct amount_msat fee_base, u32 fee_proportional, u32 cltv_delta, diff --git a/plugins/renepay/mcf.c b/plugins/renepay/mcf.c index 8537b2cae8e7..deea4d66c5d0 100644 --- a/plugins/renepay/mcf.c +++ b/plugins/renepay/mcf.c @@ -483,11 +483,18 @@ static bool linearize_channel(const struct pay_parameters *params, a = MAX(a,0); b = MAX(a+1,b); + /* An extra bound on capacity, here we use it to reduce the flow such + * that it does not exceed htlcmax. */ + s64 cap_on_capacity = + channel_htlc_max(c, dir).millisatoshis/1000; /* Raw: linearize_channel */ + capacity[0]=a; cost[0]=0; for(size_t i=1;icap_fraction[i]*(b-a); + capacity[i] = MIN(params->cap_fraction[i]*(b-a), cap_on_capacity); + cap_on_capacity -= capacity[i]; + assert(cap_on_capacity>=0); cost[i] = params->cost_fraction[i] *params->amount.millisatoshis /* Raw: linearize_channel */ diff --git a/plugins/renepay/pay.c b/plugins/renepay/pay.c index e20b3b0a8c75..30ff14758f7d 100644 --- a/plugins/renepay/pay.c +++ b/plugins/renepay/pay.c @@ -458,8 +458,9 @@ static void gossmod_cb(struct gossmap_localmods *mods, const struct node_id *self, const struct node_id *peer, const struct short_channel_id_dir *scidd, - struct amount_msat min, - struct amount_msat max, + struct amount_msat htlcmin, + struct amount_msat htlcmax, + struct amount_msat spendable, struct amount_msat fee_base, u32 fee_proportional, u32 cltv_delta, @@ -468,9 +469,27 @@ static void gossmod_cb(struct gossmap_localmods *mods, const jsmntok_t *chantok, struct payment *payment) { - /* Add to gossmap like normal */ - gossmod_add_localchan(mods, self, peer, scidd, min, max, - fee_base, fee_proportional, cltv_delta, enabled, buf, chantok, NULL); + struct amount_msat min, max; + + if (scidd->dir == node_id_idx(self, peer)) { + /* our side of the channel can send up to what's spendable */ + min = AMOUNT_MSAT(0); + max = spendable; + } else { + /* the remote side can send up to no more than spendable */ + min = htlcmin; + max = amount_msat_min(spendable, htlcmax); + } + + /* FIXME: features? */ + gossmap_local_addchan(mods, self, peer, scidd->scid, NULL); + + gossmap_local_updatechan(mods, scidd->scid, min, max, + fee_base.millisatoshis, /* Raw: gossmap */ + fee_proportional, + cltv_delta, + enabled, + scidd->dir); /* Also update uncertainty map */ uncertainty_network_update_from_listpeerchannels(payment, scidd, max, enabled, diff --git a/plugins/test/run-route-calc.c b/plugins/test/run-route-calc.c index a9d97c245156..5a8f74a0a9de 100644 --- a/plugins/test/run-route-calc.c +++ b/plugins/test/run-route-calc.c @@ -42,8 +42,9 @@ void gossmod_add_localchan(struct gossmap_localmods *mods UNNEEDED, const struct node_id *self UNNEEDED, const struct node_id *peer UNNEEDED, const struct short_channel_id_dir *scidd UNNEEDED, - struct amount_msat min UNNEEDED, - struct amount_msat max UNNEEDED, + struct amount_msat htlcmin UNNEEDED, + struct amount_msat htlcmax UNNEEDED, + struct amount_msat spendable UNNEEDED, struct amount_msat fee_base UNNEEDED, u32 fee_proportional UNNEEDED, u32 cltv_delta UNNEEDED, @@ -62,8 +63,9 @@ struct gossmap_localmods *gossmods_from_listpeerchannels_(const tal_t *ctx UNNEE const struct node_id *self_ UNNEEDED, const struct node_id *peer UNNEEDED, const struct short_channel_id_dir *scidd UNNEEDED, - struct amount_msat min UNNEEDED, - struct amount_msat max UNNEEDED, + struct amount_msat htlcmin UNNEEDED, + struct amount_msat htlcmax UNNEEDED, + struct amount_msat spendable UNNEEDED, struct amount_msat fee_base UNNEEDED, u32 fee_proportional UNNEEDED, u32 cltv_delta UNNEEDED, diff --git a/plugins/test/run-route-overlong.c b/plugins/test/run-route-overlong.c index 39b74377715c..a6e0a5a00e22 100644 --- a/plugins/test/run-route-overlong.c +++ b/plugins/test/run-route-overlong.c @@ -39,8 +39,9 @@ void gossmod_add_localchan(struct gossmap_localmods *mods UNNEEDED, const struct node_id *self UNNEEDED, const struct node_id *peer UNNEEDED, const struct short_channel_id_dir *scidd UNNEEDED, - struct amount_msat min UNNEEDED, - struct amount_msat max UNNEEDED, + struct amount_msat htlcmin UNNEEDED, + struct amount_msat htlcmax UNNEEDED, + struct amount_msat spendable UNNEEDED, struct amount_msat fee_base UNNEEDED, u32 fee_proportional UNNEEDED, u32 cltv_delta UNNEEDED, @@ -59,8 +60,9 @@ struct gossmap_localmods *gossmods_from_listpeerchannels_(const tal_t *ctx UNNEE const struct node_id *self_ UNNEEDED, const struct node_id *peer UNNEEDED, const struct short_channel_id_dir *scidd UNNEEDED, - struct amount_msat min UNNEEDED, - struct amount_msat max UNNEEDED, + struct amount_msat htlcmin UNNEEDED, + struct amount_msat htlcmax UNNEEDED, + struct amount_msat spendable UNNEEDED, struct amount_msat fee_base UNNEEDED, u32 fee_proportional UNNEEDED, u32 cltv_delta UNNEEDED, diff --git a/plugins/topology.c b/plugins/topology.c index f8593043e31c..ecd97443e9ea 100644 --- a/plugins/topology.c +++ b/plugins/topology.c @@ -364,6 +364,7 @@ static void gossmod_add_unknown_localchan(struct gossmap_localmods *mods, const struct short_channel_id_dir *scidd, struct amount_msat min, struct amount_msat max, + struct amount_msat spendable, struct amount_msat fee_base, u32 fee_proportional, u32 cltv_delta, @@ -375,7 +376,7 @@ static void gossmod_add_unknown_localchan(struct gossmap_localmods *mods, if (gossmap_find_chan(gossmap, &scidd->scid)) return; - gossmod_add_localchan(mods, self, peer, scidd, min, max, + gossmod_add_localchan(mods, self, peer, scidd, min, max, spendable, fee_base, fee_proportional, cltv_delta, enabled, buf, chantok, gossmap); } diff --git a/tests/test_renepay.py b/tests/test_renepay.py index c5831885eab2..99f654a9a859 100644 --- a/tests/test_renepay.py +++ b/tests/test_renepay.py @@ -489,12 +489,12 @@ def test_htlc_max(node_factory): ] ) - inv = l6.rpc.invoice("1800000sat", "inv", "description") + inv = l6.rpc.invoice("800000sat", "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") + assert invoice["amount_received_msat"] >= Millisatoshi("800000sat") def test_previous_sendpays(node_factory, bitcoind): @@ -624,3 +624,60 @@ def test_fees(node_factory): source.rpc.call("renepay", {"invstring": invstr}) invoice = only_one(dest.rpc.listinvoices("inv2")["invoices"]) assert invoice["amount_received_msat"] == Millisatoshi("150000sat") + + +def test_local_htlcmax0(node_factory): + """Testing a simple pay route when local channels have htlcmax=0.""" + l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True) + l1.rpc.setchannel(l2.info["id"], htlcmax=0) + inv = l3.rpc.invoice(123000, "test_renepay", "description")["bolt11"] + details = l1.rpc.call("renepay", {"invstring": inv}) + assert details["status"] == "complete" + assert details["amount_msat"] == Millisatoshi(123000) + assert details["destination"] == l3.info["id"] + + +def test_htlcmax0(node_factory): + """ + Topology: + 1----2----4 + | | + 3----5----6 + Tests the plugin when some routes have htlc_max=0. + """ + 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": 0, + }, + { + "disable-mpp": None, + "fee-base": 0, + "fee-per-satoshi": 0, + "htlc-maximum-msat": 800000000, + }, + {"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, 1000000), + (l1, l3, 10000000), + (l3, l5, 1000000), + (l5, l6, 1000000), + ] + ) + + inv = l6.rpc.invoice("600000sat", "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("600000sat")