diff --git a/lightningd/lightningd.c b/lightningd/lightningd.c index 8ed03a250033..e2ccb1a29fad 100644 --- a/lightningd/lightningd.c +++ b/lightningd/lightningd.c @@ -151,6 +151,7 @@ static struct lightningd *new_lightningd(const tal_t *ctx) ld->dev_hsmd_no_preapprove_check = false; ld->dev_hsmd_fail_preapprove = false; ld->dev_handshake_no_reply = false; + ld->dev_strict_forwarding = false; /*~ We try to ensure enough fds for twice the number of channels * we start with. We have a developer option to change that factor diff --git a/lightningd/lightningd.h b/lightningd/lightningd.h index d13d509b7a58..df7f83f6bd12 100644 --- a/lightningd/lightningd.h +++ b/lightningd/lightningd.h @@ -362,6 +362,10 @@ struct lightningd { /* Tell connectd not to talk after handshake */ bool dev_handshake_no_reply; + /* Remove the freedom to choose select between parallel channels to + * forward a payment. */ + bool dev_strict_forwarding; + /* tor support */ struct wireaddr *proxyaddr; bool always_use_proxy; diff --git a/lightningd/options.c b/lightningd/options.c index 8c62e23e9c16..362940cd8bce 100644 --- a/lightningd/options.c +++ b/lightningd/options.c @@ -956,6 +956,10 @@ static void dev_register_opts(struct lightningd *ld) opt_set_bool, &ld->dev_handshake_no_reply, "Don't send or read init message after connection"); + clnopt_noarg("--dev-strict-forwarding", OPT_DEV, + opt_set_bool, + &ld->dev_strict_forwarding, + "Forward HTLCs along the channel specified"); clnopt_noarg("--dev-throttle-gossip", OPT_DEV, opt_set_bool, &ld->dev_throttle_gossip, diff --git a/lightningd/peer_htlcs.c b/lightningd/peer_htlcs.c index f8f4202c93cd..5cb9350612f1 100644 --- a/lightningd/peer_htlcs.c +++ b/lightningd/peer_htlcs.c @@ -1212,7 +1212,11 @@ static struct channel_id *calc_forwarding_channel(struct lightningd *ld, c = NULL; } - best = best_channel(ld, peer, p->amt_to_forward, c); + if (!ld->dev_strict_forwarding) + best = best_channel(ld, peer, p->amt_to_forward, c); + else + best = c; + if (!c) { if (!best) return NULL; diff --git a/tests/test_pay.py b/tests/test_pay.py index 296750e69316..5040040eb832 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -5996,3 +5996,111 @@ def test_enableoffer(node_factory): # Can't enable unknown. with pytest.raises(RpcError, match="Unknown offer"): l1.rpc.enableoffer(offer_id=offer1['offer_id']) + + +def test_parallel_channels_reserve(node_factory, bitcoind): + """Tests wether we are able to pay through parallel channels concurrently. + To do that we need to enable strict-forwarding.""" + + def direction(node1, node2): + return 0 if node1.info["id"] < node2.info["id"] else 1 + + def get_local_channel_by_id(node, chanid): + peerchannels = node.rpc.listpeerchannels()["channels"] + if not peerchannels: + return None + for c in peerchannels: + if c["channel_id"] == chanid: + return c + return None + + opts = { + "fee-base": 0, + "fee-per-satoshi": 0, + "cltv-delta": 6, + "dev-strict-forwarding": None, + } + l1, l2, l3 = node_factory.get_nodes(3, opts=opts) + + l1.fundwallet(10**7) + l2.fundwallet(10**7) + + scids = [] + + l1.rpc.connect(l2.info["id"], "localhost", l2.port) + l2.rpc.connect(l3.info["id"], "localhost", l3.port) + + c12 = l1.rpc.fundchannel(l2.info["id"], 300_000, minconf=0)["channel_id"] + + c23 = [] + c23.append(l2.rpc.fundchannel(l3.info["id"], 100_000, minconf=0)["channel_id"]) + c23.append(l2.rpc.fundchannel(l3.info["id"], 200_000, minconf=0)["channel_id"]) + + bitcoind.generate_block(6) + sync_blockheight(bitcoind, [l1, l2, l3]) + + scids.append(get_local_channel_by_id(l1, c12)["short_channel_id"]) + scids.append(get_local_channel_by_id(l2, c23[0])["short_channel_id"]) + scids.append(get_local_channel_by_id(l2, c23[1])["short_channel_id"]) + + for l in [l1, l2, l3]: + for c in scids: + l.wait_channel_active(c) + + # we should be able to send these two parts: + nparts = 2 + route_amounts = ["75000sat", "175000sat"] + total_msat = sum([Millisatoshi(a) for a in route_amounts[:nparts]]) + + # Test succeeds if we are able to pay this invoice + inv = l3.rpc.call( + "invoice", + {"amount_msat": total_msat, "label": "inv", "description": "inv", "cltv": 10}, + ) + + # Share data by every route we will construct: l1->l2->l3 + route = [ + { + "id": l2.info["id"], + "direction": direction(l1, l2), + "delay": 16, + "style": "tlv", + }, + { + "id": l3.info["id"], + "direction": direction(l2, l3), + "delay": 10, + "style": "tlv", + }, + ] + + # Send every part with sendpay + for part in range(nparts): + this_part_msat = Millisatoshi(route_amounts[part]) + chan1 = get_local_channel_by_id(l1, c12) + chan2 = get_local_channel_by_id(l2, c23[part]) + + route[0]["channel"] = chan1["short_channel_id"] + route[1]["channel"] = chan2["short_channel_id"] + route[0]["amount_msat"] = route[1]["amount_msat"] = this_part_msat + + assert chan1["spendable_msat"] >= this_part_msat + assert chan2["spendable_msat"] >= this_part_msat + + l1.rpc.call( + "sendpay", + { + "route": route, + "payment_hash": inv["payment_hash"], + "payment_secret": inv["payment_secret"], + "amount_msat": total_msat, + "groupid": 1, + "partid": part + 1, + }, + ) + l1.wait_for_htlcs() + + # Are we happy? + receipt = only_one(l3.rpc.listinvoices("inv")["invoices"]) + assert receipt["status"] == "paid" + assert receipt["amount_received_msat"] == total_msat