diff --git a/tests/test_renepay.py b/tests/test_renepay.py index da07c873b..1cc6b7739 100644 --- a/tests/test_renepay.py +++ b/tests/test_renepay.py @@ -1,6 +1,12 @@ 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 +from utils import ( + only_one, + wait_for, + mine_funding_to_announce, + sync_blockheight, + TEST_NETWORK, +) import pytest import random import time @@ -10,27 +16,31 @@ import os def test_simple(node_factory): - '''Testing simply paying a peer.''' + """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'] + 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'] + """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): @@ -39,7 +49,7 @@ def test_shadow_routing(node_factory): Note there is a very low (0.5**10) probability that it fails. """ -# We need l3 for random walk + # We need l3 for random walk l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True) amount = 10000 @@ -47,8 +57,9 @@ def test_shadow_routing(node_factory): 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"] + 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 @@ -56,166 +67,183 @@ def test_shadow_routing(node_factory): def test_mpp(node_factory): - '''Test paying a remote node using two routes. + """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}, + {"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) + 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'] + 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}, + {"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') + 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' + 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) + 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.' + failmsg = r"Destination is unknown in the network gossip." with pytest.raises(RpcError, match=failmsg): - l1.rpc.call('renepay', {'invstring': inv}) + l1.rpc.call("renepay", {"invstring": inv}) - l4.rpc.connect(l6.info['id'], 'localhost', l6.port) - l5.rpc.connect(l6.info['id'], 'localhost', l6.port) + 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'] + 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}) + 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'] + 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') +@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'] + inv = l2.rpc.invoice(123000, "test_pay", "description")["bolt11"] before = int(time.time()) - details = l1.rpc.call('renepay', {'invstring': inv, 'dev_use_shadow': False}) + 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 + 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'] + 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 + 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}) + 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}) + l1.rpc.call("renepay", {"invstring": inv, "amount_msat": 123000}) with pytest.raises(RpcError): - l1.rpc.call('renepay', {'invstring': inv, 'amount_msat': 122000}) + 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 + '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'] + 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}) - l1.rpc.call('renepay', {'invstring': inv2, 'dev_use_shadow': False, - 'amount_msat': random.randint(1000, 999999)}) + 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 + 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 + 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'] == []) + 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'] + 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'] + 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'] + inv = l2.rpc.invoice(Millisatoshi(123456), "test_pay_amounts", "description")[ + "bolt11" + ] - invoice = only_one(l2.rpc.listinvoices('test_pay_amounts')['invoices']) + invoice = only_one(l2.rpc.listinvoices("test_pay_amounts")["invoices"]) - assert invoice['amount_msat'] == Millisatoshi(123456) + assert invoice["amount_msat"] == Millisatoshi(123456) - l1.rpc.call('renepay', {'invstring': inv, 'dev_use_shadow': False}) + 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) + 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 | | @@ -224,50 +252,54 @@ def test_limits(node_factory): - 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}, + {"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) + 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}) + 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') + inv = l6.rpc.invoice("any", "any", "description") # Fee too high. - failmsg = r'Fee exceeds our fee budget' + 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 + "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' + 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 + "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') + 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): @@ -275,26 +307,27 @@ def start_channels(connections): for src, dst, fundamount in connections: nodes.append(src) nodes.append(dst) - src.rpc.connect(dst.info['id'], 'localhost', dst.port) + 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'] + 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']) + 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') + wait_for(lambda: src.channel_state(dst) == "CHANNELD_NORMAL") scid = src.get_channel_scid(dst) scids.append(scid) @@ -312,203 +345,246 @@ def start_channels(connections): # 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'])) + 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}, + {"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)]) + 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: + 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}) + 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: + 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: + with open("/tmp/l3-peerchan.txt", "w") as f: print(json.dumps(l3.rpc.listpeerchannels()), file=f) - inv2 = l6.rpc.invoice("1800000sat", "inv2", 'description') + 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') + 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]) + assert any([l.startswith("#") for l in lines]) # Now comes JSON - json.loads("".join([l for l in lines if not l.startswith('#')])) + 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') + 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}) + 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}) + l1.rpc.call("renepay", {"invstring": inv}) - inv2 = l1.rpc.invoice(10000, 'test2', 'test2')['bolt11'] - l1.rpc.delinvoice('test2', 'unpaid') + 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 + 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. - ''' + """ # High fees at 3% opts = [ - {'disable-mpp': None, 'fee-base': 1000, 'fee-per-satoshi': 30000}, + {"disable-mpp": None, "fee-base": 1000, "fee-per-satoshi": 30000}, ] 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)]) + 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'}) + 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') + 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}, + {"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)]) + 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') + inv = l6.rpc.invoice("1800000sat", "inv", "description") - l1.rpc.call('renepay', {'invstring': inv['bolt11']}) + 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') + invoice = only_one(l6.rpc.listinvoices("inv")["invoices"]) + assert invoice["amount_received_msat"] >= Millisatoshi("1800000sat") 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}, + {"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) + 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'] + 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}) + 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}) + 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']) + 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') + assert invoice["amount_received_msat"] == Millisatoshi("100000sat") # Second case, do not collide with failed sendpays - invstr = l3.rpc.invoice("100000sat", 'inv2', 'description')['bolt11'] + 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}) + 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).*') + 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.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.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') + 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') + 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")