mirror of
https://github.com/ElementsProject/lightning.git
synced 2024-11-19 09:54:16 +01:00
autoformat test_renepay.py
This commit is contained in:
parent
7e0d6d396e
commit
e39e712eed
@ -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")
|
||||
|
Loading…
Reference in New Issue
Block a user