autoformat test_renepay.py

This commit is contained in:
Lagrang3 2024-02-14 17:13:43 +01:00 committed by Christian Decker
parent 7e0d6d396e
commit e39e712eed

View File

@ -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,
"""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']
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")