diff --git a/tests/test_pay.py b/tests/test_pay.py index dddabdb05..f5549ebf9 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -6,7 +6,7 @@ from pyln.client import RpcError, Millisatoshi from pyln.proto.onion import TlvPayload from utils import ( DEVELOPER, wait_for, only_one, sync_blockheight, TIMEOUT, - EXPERIMENTAL_FEATURES, env + EXPERIMENTAL_FEATURES, env, VALGRIND ) import copy import os @@ -3373,3 +3373,100 @@ def test_mpp_waitblockheight_routehint_conflict(node_factory, bitcoind, executor # pay command should complete without error fut.result(TIMEOUT) + + +@unittest.skipIf(VALGRIND, "runs 7 nodes") +@unittest.skipIf(not DEVELOPER, "channel setup very slow (~10 minutes) if not DEVELOPER") +@pytest.mark.slow_test +@pytest.mark.xfail(strict=True) +def test_mpp_interference_2(node_factory, bitcoind, executor): + ''' + We create a "public network" that looks like so. + Each channel is perfectly balanced, with 7 * unit + funds on each side. + + 4 -- 5 + | /| + | / | + | / | + |/ | + 6 -- 7 + + l1 is the payee, who will later issue some invoices. + It arranges unpublished channels from the above public + network: + + l5->l1: 7 * unit + l6->l1: 5 * unit + l4->l1: 3 * unit + l7->l1: 2 * unit + + l2 and l3 are payers. + They create some unpublished channels to the public network: + + l2->l4, l2->l6: 6 * unit each + l3->l7, l3->l6: 6 * unit each + + Finally, l1 issues 6 * unit invoices, simultaneously, to l1 and l2. + Both of them perform `pay` simultaneously, in order to test if + they interfere with each other. + + This test then tries to check if both of them can pay, given + that there is sufficient incoming capacity, and then some, + to the payee, and the public network is perfectly balanced + with more than sufficient capacity, as well. + ''' + l1, l2, l3, l4, l5, l6, l7 = node_factory.get_nodes(7, opts={'feerates': (1000, 1000, 1000, 1000)}) + + # Unit + unit = Millisatoshi(11000 * 1000) + + # Build the public network. + public_network = [l4.fundbalancedchannel(l5, unit * 14), + l4.fundbalancedchannel(l6, unit * 14), + l5.fundbalancedchannel(l6, unit * 14), + l5.fundbalancedchannel(l7, unit * 14), + l6.fundbalancedchannel(l7, unit * 14)] + + # Build unpublished channels to the merchant l1. + l4.rpc.connect(l1.info['id'], 'localhost', l1.port) + l5.rpc.connect(l1.info['id'], 'localhost', l1.port) + l6.rpc.connect(l1.info['id'], 'localhost', l1.port) + l7.rpc.connect(l1.info['id'], 'localhost', l1.port) + # The order in which the routes are built should not matter so + # shuffle them. + incoming_builders = [lambda: l5.fund_channel(l1, int((unit * 7).to_satoshi()), announce_channel=False), + lambda: l6.fund_channel(l1, int((unit * 5).to_satoshi()), announce_channel=False), + lambda: l4.fund_channel(l1, int((unit * 3).to_satoshi()), announce_channel=False), + lambda: l7.fund_channel(l1, int((unit * 2).to_satoshi()), announce_channel=False)] + random.shuffle(incoming_builders) + for b in incoming_builders: + b() + + # Build unpublished channels from the buyers l2 and l3. + l2.rpc.connect(l4.info['id'], 'localhost', l4.port) + l2.rpc.connect(l6.info['id'], 'localhost', l6.port) + l3.rpc.connect(l7.info['id'], 'localhost', l7.port) + l3.rpc.connect(l6.info['id'], 'localhost', l6.port) + l2.fund_channel(l4, int((unit * 6).to_satoshi()), announce_channel=False) + l2.fund_channel(l6, int((unit * 6).to_satoshi()), announce_channel=False) + l3.fund_channel(l7, int((unit * 6).to_satoshi()), announce_channel=False) + l3.fund_channel(l6, int((unit * 6).to_satoshi()), announce_channel=False) + + # Now wait for the buyers to learn the entire public network. + bitcoind.generate_block(5) + for channel in public_network: + wait_for(lambda: len(l2.rpc.listchannels(channel)['channels']) >= 2) + wait_for(lambda: len(l3.rpc.listchannels(channel)['channels']) >= 2) + + # Buyers check out some purchaseable stuff from the merchant. + i2 = l1.rpc.invoice(unit * 6, 'i2', 'i2')['bolt11'] + i3 = l1.rpc.invoice(unit * 6, 'i3', 'i3')['bolt11'] + + # Pay simultaneously! + p2 = executor.submit(l2.rpc.pay, i2) + p3 = executor.submit(l3.rpc.pay, i3) + + # Both payments should succeed. + p2.result(TIMEOUT) + p3.result(TIMEOUT)