from fixtures import * # noqa: F401,F403 from pyln.client import RpcError, Millisatoshi from utils import ( only_one, wait_for, mine_funding_to_announce, sync_blockheight, TEST_NETWORK, ) import pytest import random import time import json import subprocess import os import re import unittest def test_simple(node_factory): """Testing simply paying a peer.""" l1, l2 = node_factory.line_graph(2) inv = l2.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"] == l2.info["id"] def test_direction_matters(node_factory): """Make sure we use correct delay and fees for the direction we're going.""" l1, l2, l3 = node_factory.line_graph( 3, wait_for_announce=True, opts=[ {}, {"fee-base": 2000, "fee-per-satoshi": 20, "cltv-delta": 20}, {"fee-base": 3000, "fee-per-satoshi": 30, "cltv-delta": 30}, ], ) 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_shadow_routing(node_factory): """ Test the value randomization through shadow routing Note there is a very low (0.5**10) probability that it fails. """ # We need l3 for random walk l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True) amount = 10000 total_amount = 0 n_payments = 10 for i in range(n_payments): inv = l3.rpc.invoice(amount, "{}".format(i), "test")["bolt11"] total_amount += l1.rpc.call( "renepay", {"invstring": inv, "dev_use_shadow": True} )["amount_sent_msat"] assert total_amount > n_payments * amount # Test that the added amount isn't absurd assert total_amount < int((n_payments * amount) * (1 + 0.01)) def test_mpp(node_factory): """Test paying a remote node using two routes. 1----2----4 | | 3----5----6 Try paying 1.2M sats from 1 to 6. """ opts = [ {"disable-mpp": None, "fee-base": 0, "fee-per-satoshi": 0}, ] l1, l2, l3, l4, l5, l6 = node_factory.get_nodes(6, opts=opts * 6) node_factory.join_nodes( [l1, l2, l4, l6], wait_for_announce=True, fundamount=1000000 ) node_factory.join_nodes( [l1, l3, l5, l6], wait_for_announce=True, fundamount=1000000 ) send_amount = Millisatoshi("1200000sat") inv = l6.rpc.invoice(send_amount, "test_renepay", "description")["bolt11"] details = l1.rpc.call("renepay", {"invstring": inv}) assert details["status"] == "complete" assert details["amount_msat"] == send_amount assert details["destination"] == l6.info["id"] def test_errors(node_factory, bitcoind): opts = [ {"disable-mpp": None, "fee-base": 0, "fee-per-satoshi": 0}, ] l1, l2, l3, l4, l5, l6 = node_factory.get_nodes(6, opts=opts * 6) send_amount = Millisatoshi("21sat") inv = l6.rpc.invoice(send_amount, "test_renepay", "description")["bolt11"] inv_deleted = l6.rpc.invoice(send_amount, "test_renepay2", "description2")["bolt11"] l6.rpc.delinvoice("test_renepay2", "unpaid") failmsg = r"We don\'t have any channels" with pytest.raises(RpcError, match=failmsg): l1.rpc.call("renepay", {"invstring": inv}) node_factory.join_nodes([l1, l2, l4], wait_for_announce=True, fundamount=1000000) node_factory.join_nodes([l1, l3, l5], wait_for_announce=True, fundamount=1000000) failmsg = r"Destination is unknown in the network gossip." with pytest.raises(RpcError, match=failmsg): l1.rpc.call("renepay", {"invstring": inv}) l4.rpc.connect(l6.info["id"], "localhost", l6.port) l5.rpc.connect(l6.info["id"], "localhost", l6.port) scid46, _ = l4.fundchannel(l6, 10**6, wait_for_active=False) scid56, _ = l5.fundchannel(l6, 10**6, wait_for_active=False) mine_funding_to_announce(bitcoind, [l1, l2, l3, l4, l5, l6]) l1.daemon.wait_for_logs( [ r"update for channel {}/0 now ACTIVE".format(scid46), r"update for channel {}/1 now ACTIVE".format(scid46), r"update for channel {}/0 now ACTIVE".format(scid56), r"update for channel {}/1 now ACTIVE".format(scid56), ] ) details = l1.rpc.call("renepay", {"invstring": inv}) assert details["status"] == "complete" assert details["amount_msat"] == send_amount assert details["destination"] == l6.info["id"] # Test error from final node. with pytest.raises(RpcError) as err: l1.rpc.call("renepay", {"invstring": inv_deleted}) PAY_DESTINATION_PERM_FAIL = 203 assert err.value.error["code"] == PAY_DESTINATION_PERM_FAIL assert "WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS" in err.value.error["message"] @pytest.mark.openchannel("v1") @pytest.mark.openchannel("v2") def test_pay(node_factory): l1, l2 = node_factory.line_graph(2) inv = l2.rpc.invoice(123000, "test_pay", "description")["bolt11"] before = int(time.time()) details = l1.rpc.call("renepay", {"invstring": inv, "dev_use_shadow": False}) after = time.time() preimage = details["payment_preimage"] assert details["status"] == "complete" assert details["amount_msat"] == Millisatoshi(123000) assert details["destination"] == l2.info["id"] assert details["created_at"] >= before assert details["created_at"] <= after invoices = l2.rpc.listinvoices("test_pay")["invoices"] assert len(invoices) == 1 invoice = invoices[0] assert ( invoice["status"] == "paid" and invoice["paid_at"] >= before and invoice["paid_at"] <= after ) # Repeat payments are NOPs (if valid): we can hand null. l1.rpc.call("renepay", {"invstring": inv, "dev_use_shadow": False}) # This won't work: can't provide an amount (even if correct!) with pytest.raises(RpcError): l1.rpc.call("renepay", {"invstring": inv, "amount_msat": 123000}) with pytest.raises(RpcError): l1.rpc.call("renepay", {"invstring": inv, "amount_msat": 122000}) # Check pay_index is not null outputs = l2.db_query( 'SELECT pay_index IS NOT NULL AS q FROM invoices WHERE label="label";' ) assert len(outputs) == 1 and outputs[0]["q"] != 0 # Check payment of any-amount invoice. for i in range(5): label = "any{}".format(i) inv2 = l2.rpc.invoice("any", label, "description")["bolt11"] # Must provide an amount! with pytest.raises(RpcError): l1.rpc.call("renepay", {"invstring": inv2, "dev_use_shadow": False}) l1.rpc.call( "renepay", { "invstring": inv2, "dev_use_shadow": False, "amount_msat": random.randint(1000, 999999), }, ) # Should see 6 completed payments assert len(l1.rpc.listsendpays()["payments"]) == 6 # Test listsendpays indexed by bolt11. payments = l1.rpc.listsendpays(inv)["payments"] assert len(payments) == 1 and payments[0]["payment_preimage"] == preimage # Make sure they're completely settled, so accounting correct. wait_for(lambda: only_one(l1.rpc.listpeerchannels()["channels"])["htlcs"] == []) # Check channels apy summary view of channel activity apys_1 = l1.rpc.bkpr_channelsapy()["channels_apy"] apys_2 = l2.rpc.bkpr_channelsapy()["channels_apy"] assert ( apys_1[0]["channel_start_balance_msat"] == apys_2[0]["channel_start_balance_msat"] ) assert ( apys_1[0]["channel_start_balance_msat"] == apys_1[0]["our_start_balance_msat"] ) assert apys_2[0]["our_start_balance_msat"] == Millisatoshi(0) assert apys_1[0]["routed_out_msat"] == apys_2[0]["routed_in_msat"] assert apys_1[0]["routed_in_msat"] == apys_2[0]["routed_out_msat"] def test_amounts(node_factory): """ Check that the amount received matches the amount requested in the invoice. """ l1, l2 = node_factory.line_graph(2) inv = l2.rpc.invoice(Millisatoshi(123456), "test_pay_amounts", "description")[ "bolt11" ] invoice = only_one(l2.rpc.listinvoices("test_pay_amounts")["invoices"]) assert invoice["amount_msat"] == Millisatoshi(123456) l1.rpc.call("renepay", {"invstring": inv, "dev_use_shadow": False}) invoice = only_one(l2.rpc.listinvoices("test_pay_amounts")["invoices"]) assert invoice["amount_received_msat"] >= Millisatoshi(123456) def test_limits(node_factory): """ Topology: 1----2----4 | | 3----5----6 Try the error messages when paying when: - the fees are too high, - CLTV delay is too high, - probability of success is too low. """ opts = [ {"disable-mpp": None, "fee-base": 0, "fee-per-satoshi": 100}, ] l1, l2, l3, l4, l5, l6 = node_factory.get_nodes(6, opts=opts * 6) node_factory.join_nodes( [l1, l2, l4, l6], wait_for_announce=True, fundamount=1000000 ) node_factory.join_nodes( [l1, l3, l5, l6], wait_for_announce=True, fundamount=1000000 ) inv = l4.rpc.invoice("any", "any", "description") l2.rpc.call("pay", {"bolt11": inv["bolt11"], "amount_msat": 500000000}) inv = l5.rpc.invoice("any", "any", "description") l3.rpc.call("pay", {"bolt11": inv["bolt11"], "amount_msat": 500000000}) # FIXME: pylightning should define these! # PAY_STOPPED_RETRYING = 210 PAY_ROUTE_TOO_EXPENSIVE = 206 inv = l6.rpc.invoice("any", "any", "description") # Fee too high. failmsg = r"Fee exceeds our fee budget" with pytest.raises(RpcError, match=failmsg) as err: l1.rpc.call( "renepay", {"invstring": inv["bolt11"], "amount_msat": 1000000, "maxfee": 1} ) assert err.value.error["code"] == PAY_ROUTE_TOO_EXPENSIVE # TODO(eduardo): which error code shall we use here? # TODO(eduardo): shall we list attempts in renepay? # status = l1.rpc.call('renepaystatus', {'invstring':inv['bolt11']})['paystatus'][0]['attempts'] failmsg = r"CLTV delay exceeds our CLTV budget" # Delay too high. with pytest.raises(RpcError, match=failmsg) as err: l1.rpc.call( "renepay", {"invstring": inv["bolt11"], "amount_msat": 1000000, "maxdelay": 0}, ) assert err.value.error["code"] == PAY_ROUTE_TOO_EXPENSIVE inv2 = l6.rpc.invoice("800000sat", "inv2", "description") l1.rpc.call("renepay", {"invstring": inv2["bolt11"]}) invoice = only_one(l6.rpc.listinvoices("inv2")["invoices"]) assert invoice["amount_received_msat"] >= Millisatoshi("800000sat") def start_channels(connections): nodes = list() for src, dst, fundamount in connections: nodes.append(src) nodes.append(dst) src.rpc.connect(dst.info["id"], "localhost", dst.port) bitcoind = nodes[0].bitcoin # If we got here, we want to fund channels for src, dst, fundamount in connections: addr = src.rpc.newaddr()["bech32"] bitcoind.rpc.sendtoaddress(addr, (fundamount + 1000000) / 10**8) bitcoind.generate_block(1) sync_blockheight(bitcoind, nodes) txids = [] for src, dst, fundamount in connections: txids.append( src.rpc.fundchannel(dst.info["id"], fundamount, announce=True)["txid"] ) # Confirm all channels and wait for them to become usable bitcoind.generate_block(1, wait_for_mempool=txids) scids = [] for src, dst, fundamount in connections: wait_for(lambda: src.channel_state(dst) == "CHANNELD_NORMAL") scid = src.get_channel_scid(dst) scids.append(scid) # Make sure they have all seen block so they don't complain about # the coming gossip messages sync_blockheight(bitcoind, nodes) bitcoind.generate_block(5) # Make sure everyone sees all channels, all other nodes for n in nodes: for scid in scids: n.wait_channel_active(scid) # Make sure we have all node announcements, too for n in nodes: for n2 in nodes: wait_for( lambda: "alias" in only_one(n.rpc.listnodes(n2.info["id"])["nodes"]) ) def test_hardmpp(node_factory): """ Topology: 1----2----4 | | 3----5----6 This a payment that fails if pending HTLCs are not taken into account when we build the network capacities. """ opts = [ {"disable-mpp": None, "fee-base": 0, "fee-per-satoshi": 0}, ] l1, l2, l3, l4, l5, l6 = node_factory.get_nodes(6, opts=opts * 6) start_channels( [ (l1, l2, 10000000), (l2, l4, 3000000), (l4, l6, 10000000), (l1, l3, 10000000), (l3, l5, 1000000), (l5, l6, 10000000), ] ) with open("/tmp/l1-chans.txt", "w") as f: print(json.dumps(l1.rpc.listchannels()), file=f) inv = l4.rpc.invoice("any", "any", "description") l2.rpc.call("pay", {"bolt11": inv["bolt11"], "amount_msat": 2000000000}) l2.wait_for_htlcs() assert l4.rpc.listinvoices()["invoices"][0]["amount_received_msat"] == 2000000000 with open("/tmp/l2-peerchan.txt", "w") as f: print(json.dumps(l2.rpc.listpeerchannels()), file=f) with open("/tmp/l3-peerchan.txt", "w") as f: print(json.dumps(l3.rpc.listpeerchannels()), file=f) inv2 = l6.rpc.invoice("1800000sat", "inv2", "description") out = subprocess.check_output( [ "cli/lightning-cli", "--network={}".format(TEST_NETWORK), "--lightning-dir={}".format(l1.daemon.lightning_dir), "-k", "renepay", "invstring={}".format(inv2["bolt11"]), ] ).decode("utf-8") lines = out.split("\n") # First comes commentry assert any([l.startswith("#") for l in lines]) # Now comes JSON json.loads("".join([l for l in lines if not l.startswith("#")])) l1.wait_for_htlcs() invoice = only_one(l6.rpc.listinvoices("inv2")["invoices"]) assert invoice["amount_received_msat"] >= Millisatoshi("1800000sat") def test_self_pay(node_factory): l1, l2 = node_factory.line_graph(2, wait_for_announce=True) inv = l1.rpc.invoice(10000, "test", "test")["bolt11"] l1.rpc.call("renepay", {"invstring": inv}) # We can pay twice, no problem! l1.rpc.call("renepay", {"invstring": inv}) inv2 = l1.rpc.invoice(10000, "test2", "test2")["bolt11"] l1.rpc.delinvoice("test2", "unpaid") with pytest.raises(RpcError, match=r"Unknown invoice") as excinfo: l1.rpc.call("renepay", {"invstring": inv2}) assert excinfo.value.error["code"] == 203 def test_fee_allocation(node_factory): """ Topology: 1----2 | | 3----4 This a payment that fails if fee is not allocated as part of the flow constraints. The payment should be straightforward, no failures are expected. """ # We set high fees at 3% and load a plugin that breaks if a sendpay_failure # notification is received. opts = [ { "disable-mpp": None, "fee-base": 1000, "fee-per-satoshi": 30000, "plugin": os.path.join(os.getcwd(), "tests/plugins/no_fail.py"), }, ] l1, l2, l3, l4 = node_factory.get_nodes(4, opts=opts * 4) start_channels( [(l1, l2, 1000000), (l2, l4, 2000000), (l1, l3, 1000000), (l3, l4, 2000000)] ) inv = l4.rpc.invoice("1500000sat", "inv", "description") l1.rpc.call("renepay", {"invstring": inv["bolt11"], "maxfee": "75000sat"}) l1.wait_for_htlcs() invoice = only_one(l4.rpc.listinvoices("inv")["invoices"]) assert invoice["amount_received_msat"] >= Millisatoshi("1500000sat") # is the no_fail.py plugin still running? l1.rpc.call("nofail") 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("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("800000sat") def test_previous_sendpays(node_factory, bitcoind): """ Check that renepay can complete a payment that already started """ opts = [ {"disable-mpp": None, "fee-base": 1000, "fee-per-satoshi": 1000}, ] l1, l2, l3, l4 = node_factory.line_graph(4, wait_for_announce=True, opts=opts * 4) # First case, do not overpay a pending MPP payment invstr = l3.rpc.invoice("100000sat", "inv1", "description")["bolt11"] inv = l1.rpc.decode(invstr) route = l1.rpc.call( "getroute", {"id": inv["payee"], "amount_msat": "50000sat", "riskfactor": 10} ) # we start a MPP payment l1.rpc.call( "sendpay", { "route": route["route"], "payment_hash": inv["payment_hash"], "payment_secret": inv["payment_secret"], "amount_msat": "100000sat", "groupid": 1, "partid": 1, }, ) # while it is pending, we try to complete it with renepay l1.rpc.call("renepay", {"invstring": invstr}) invoice = only_one(l3.rpc.listinvoices("inv1")["invoices"]) # the receive amount should be exact assert invoice["amount_received_msat"] == Millisatoshi("100000sat") # Second case, do not collide with failed sendpays invstr = l3.rpc.invoice("100000sat", "inv2", "description")["bolt11"] inv = l1.rpc.decode(invstr) route = l1.rpc.call( "getroute", {"id": inv["payee"], "amount_msat": "50000sat", "riskfactor": 10} ) # load a plugin that fails all HTLCs l2.rpc.call( "plugin", { "subcommand": "start", "plugin": os.path.join(os.getcwd(), "tests/plugins/fail_htlcs.py"), }, ) l2.daemon.wait_for_log(r"^(?=.*plugin-manager.*fail_htlcs.py).*") # start a MPP payment that will fail at l2 l1.rpc.call( "sendpay", { "route": route["route"], "payment_hash": inv["payment_hash"], "payment_secret": inv["payment_secret"], "amount_msat": "100000sat", "groupid": 1, "partid": 1, }, ) l2.daemon.wait_for_log(r"Failing htlc on purpose") l1.wait_for_htlcs() # another payment that fails l1.rpc.call( "sendpay", { "route": route["route"], "payment_hash": inv["payment_hash"], "payment_secret": inv["payment_secret"], "amount_msat": "100000sat", "groupid": 2, "partid": 1, }, ) l2.daemon.wait_for_log(r"Failing htlc on purpose") l1.wait_for_htlcs() # unload the fail_htlcs plugin l2.rpc.call("plugin", {"subcommand": "stop", "plugin": "fail_htlcs.py"}) l2.daemon.wait_for_log( r"plugin-fail_htlcs.py: Killing plugin: stopped by lightningd via RPC" ) # now renepay should be able to construct new sendpays that do not collide # with the previously failed sendpays l1.rpc.call("renepay", {"invstring": invstr, "dev_use_shadow": False}) invoice = only_one(l3.rpc.listinvoices("inv2")["invoices"]) assert invoice["amount_received_msat"] == Millisatoshi("100000sat") def test_fees(node_factory): """ Check that fees are correctly computed. """ # made up some random fees for every node opts = [ {"disable-mpp": None, "fee-base": 1000, "fee-per-satoshi": 100}, {"disable-mpp": None, "fee-base": 2222, "fee-per-satoshi": 203}, {"disable-mpp": None, "fee-base": 3333, "fee-per-satoshi": 300}, {"disable-mpp": None, "fee-base": 2012, "fee-per-satoshi": 200}, {"disable-mpp": None, "fee-base": 1010, "fee-per-satoshi": 100}, {"disable-mpp": None, "fee-base": 1050, "fee-per-satoshi": 100}, ] nodes = node_factory.line_graph(len(opts), wait_for_announce=True, opts=opts) source = nodes[0] dest = nodes[-1] # check that once gossip is in sync, fees are paid correctly invstr = dest.rpc.invoice("100000sat", "inv1", "description")["bolt11"] source.rpc.call("renepay", {"invstring": invstr}) invoice = only_one(dest.rpc.listinvoices("inv1")["invoices"]) assert invoice["amount_received_msat"] == Millisatoshi("100000sat") # if we update fee policy but gossip is not updated ... nodes[2].rpc.dev_suppress_gossip() nodes[2].rpc.setchannel(nodes[3].info["id"], 4000, 300, enforcedelay=0) nodes[3].rpc.dev_suppress_gossip() nodes[3].rpc.setchannel(nodes[4].info["id"], 3000, 350, enforcedelay=0) invstr = dest.rpc.invoice("150000sat", "inv2", "description")["bolt11"] 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") def test_concurrency(node_factory): l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True, opts=[{}, {}, {}]) inv = l3.rpc.invoice("1000sat", "test_renepay", "description")["bolt11"] p1 = subprocess.Popen( [ "cli/lightning-cli", "--network={}".format(TEST_NETWORK), "--lightning-dir={}".format(l1.daemon.lightning_dir), "-k", "renepay", "invstring={}".format(inv), ], stdout=subprocess.PIPE, ) # make several other spurious requests for i in range(4): subprocess.Popen( [ "cli/lightning-cli", "--network={}".format(TEST_NETWORK), "--lightning-dir={}".format(l1.daemon.lightning_dir), "-k", "renepay", "invstring={}".format(inv), ], stdout=subprocess.PIPE, ) p1.wait(timeout=60) # remove comments from the output before parsing the json out1 = json.loads(re.sub("#.*?\n", "", p1.stdout.read().decode())) assert out1["status"] == "complete" assert out1["amount_msat"] == Millisatoshi("1000sat") invoice = only_one(l3.rpc.listinvoices("test_renepay")["invoices"]) assert invoice["amount_received_msat"] >= Millisatoshi("1000sat") def test_privatechan(node_factory, bitcoind): """ Topology: 1----2----3----4 Tests if a payment can get through a private channel. """ 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": 100}, {"disable-mpp": None, "fee-base": 0, "fee-per-satoshi": 0}, ] l1, l2, l3, l4 = node_factory.get_nodes(4, opts=opts) l1.rpc.connect(l2.info["id"], "localhost", l2.port) l2.rpc.connect(l3.info["id"], "localhost", l3.port) l3.rpc.connect(l4.info["id"], "localhost", l4.port) c12, _ = l1.fundchannel(l2, 10**6) c23, _ = l2.fundchannel(l3, 10**6) c34, _ = l3.fundchannel(l4, 10**6, announce_channel=False) mine_funding_to_announce(bitcoind, [l1, l2, l3, l4]) l1.wait_channel_active(c12) l2.wait_channel_active(c23) l3.wait_local_channel_active(c34) wait_for(lambda: len(l1.rpc.listchannels()["channels"]) == 4) wait_for(lambda: len(l2.rpc.listchannels()["channels"]) == 4) wait_for(lambda: len(l3.rpc.listchannels()["channels"]) == 4) wait_for(lambda: len(l4.rpc.listchannels()["channels"]) == 4) inv = l4.rpc.invoice("1000sat", "inv", "description") l1.rpc.call("renepay", {"invstring": inv["bolt11"]}) l1.wait_for_htlcs() invoice = only_one(l4.rpc.listinvoices("inv")["invoices"]) assert invoice["amount_received_msat"] >= Millisatoshi("1000sat") @unittest.skipIf(TEST_NETWORK == 'liquid-regtest', "broken for some reason") def test_hardmpp2(node_factory, bitcoind): """Credits to @daywalker90 for this test case.""" opts = {"disable-mpp": None, "fee-base": 0, "fee-per-satoshi": 10} l1, l2, l3 = node_factory.get_nodes(3, opts=opts) start_channels( [ (l1, l2, 100_000), (l1, l2, 200_000), (l1, l2, 300_000), (l1, l2, 400_000), (l2, l3, 100_000), (l2, l3, 200_000), (l2, l3, 300_000), (l2, l3, 600_000), ] ) # FIXME: changing the last channel from 600k to 400k will fail the test due # to l2 not accepting to forward any amount above 200k with error: # CHANNEL_ERR_CHANNEL_CAPACITY_EXCEEDED, still investigating inv = l3.rpc.invoice("800000sat", "inv", "description") l1.rpc.call("renepay", {"invstring": inv["bolt11"]}) l1.wait_for_htlcs() receipt = only_one(l3.rpc.listinvoices("inv")["invoices"]) assert receipt["amount_received_msat"] == Millisatoshi("800000sat")