2023-07-31 03:51:22 +02:00
|
|
|
from fixtures import * # noqa: F401,F403
|
|
|
|
from pyln.client import RpcError, Millisatoshi
|
renepay: add command notifications
These show that we should clean up our notes. Here's the result from test_hardmpp:
# we have computed a set of 1 flows with probability 0.328, fees 0msat and delay 23
# No MPP, so added 0msat shadow fee
# Shadow route on flow 0/1 added 0 block delay. now 5
# sendpay flow groupid=1, partid=1, delivering=1800000000msat, probability=0.328
# Update chan knowledge scid=103x2x0, dir=0: [0msat,1799999999msat]
# onion error WIRE_TEMPORARY_CHANNEL_FAILURE from node #1 103x2x0: failed: WIRE_TEMPORARY_CHANNEL_FAILURE (reply from remote)
# we have computed a set of 2 flows with probability 0.115, fees 0msat and delay 23
# Shadow route on flow 0/2 added 0 block delay. now 5
# Shadow route on flow 1/2 added 0 block delay. now 5
# sendpay flow groupid=1, partid=3, delivering=500000000msat, probability=0.475
# sendpay flow groupid=1, partid=2, delivering=1300000000msat, probability=0.242
# Update chan knowledge scid=103x2x0, dir=0: [0msat,1299999999msat]
# onion error WIRE_TEMPORARY_CHANNEL_FAILURE from node #1 103x2x0: failed: WIRE_TEMPORARY_CHANNEL_FAILURE (reply from remote)
# we have computed a set of 2 flows with probability 0.084, fees 0msat and delay 23
# Shadow route on flow 0/2 added 0 block delay. now 5
# Shadow route on flow 1/2 added 0 block delay. now 5
# sendpay flow groupid=1, partid=5, delivering=260000000msat, probability=0.467
# sendpay flow groupid=1, partid=4, delivering=1040000000msat, probability=0.179
# Update chan knowledge scid=103x2x0, dir=0: [0msat,1039999999msat]
# onion error WIRE_TEMPORARY_CHANNEL_FAILURE from node #1 103x2x0: failed: WIRE_TEMPORARY_CHANNEL_FAILURE (reply from remote)
# we have computed a set of 2 flows with probability 0.052, fees 0msat and delay 23
# Shadow route on flow 0/2 added 0 block delay. now 5
# Shadow route on flow 1/2 added 0 block delay. now 5
# sendpay flow groupid=1, partid=7, delivering=120000000msat, probability=0.494
# sendpay flow groupid=1, partid=6, delivering=920000000msat, probability=0.105
Ideally it would look something like:
# Computed 1 flows, probability=0.328:
# Flow 1: 103x2x0 1800000000msat fee=0msat probability=0.328 shadow=+0msat/0blocks
# Flow 1: FAIL: TEMPORARY_CHANNEL_FAILURE for 103x2x0.
# Computed 2 flows, probability=0.115:
# Flow 2: XXX->XXX 1300000000msat fee=XXX, probability=0.475 shadow=+0msat/0blocks
# Flow 3: XXX->XXX 500000000msat fee=XXX, probability=0.475 shadow=+0msat/0blocks
# Flow 2: FAIL: TEMPORARY_CHANNEL_FAILURE from node #1 103x2x0
# Computed 2 flows (3 total), probability=0.084, fee=0msat, delay=23
...
# Flow 4: SUCCESS, 2 in progress should succeed soon.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2023-08-10 03:59:44 +02:00
|
|
|
from utils import only_one, wait_for, mine_funding_to_announce, sync_blockheight, TEST_NETWORK
|
2023-07-31 03:51:22 +02:00
|
|
|
import pytest
|
|
|
|
import random
|
|
|
|
import time
|
2023-07-31 03:51:25 +02:00
|
|
|
import json
|
renepay: add command notifications
These show that we should clean up our notes. Here's the result from test_hardmpp:
# we have computed a set of 1 flows with probability 0.328, fees 0msat and delay 23
# No MPP, so added 0msat shadow fee
# Shadow route on flow 0/1 added 0 block delay. now 5
# sendpay flow groupid=1, partid=1, delivering=1800000000msat, probability=0.328
# Update chan knowledge scid=103x2x0, dir=0: [0msat,1799999999msat]
# onion error WIRE_TEMPORARY_CHANNEL_FAILURE from node #1 103x2x0: failed: WIRE_TEMPORARY_CHANNEL_FAILURE (reply from remote)
# we have computed a set of 2 flows with probability 0.115, fees 0msat and delay 23
# Shadow route on flow 0/2 added 0 block delay. now 5
# Shadow route on flow 1/2 added 0 block delay. now 5
# sendpay flow groupid=1, partid=3, delivering=500000000msat, probability=0.475
# sendpay flow groupid=1, partid=2, delivering=1300000000msat, probability=0.242
# Update chan knowledge scid=103x2x0, dir=0: [0msat,1299999999msat]
# onion error WIRE_TEMPORARY_CHANNEL_FAILURE from node #1 103x2x0: failed: WIRE_TEMPORARY_CHANNEL_FAILURE (reply from remote)
# we have computed a set of 2 flows with probability 0.084, fees 0msat and delay 23
# Shadow route on flow 0/2 added 0 block delay. now 5
# Shadow route on flow 1/2 added 0 block delay. now 5
# sendpay flow groupid=1, partid=5, delivering=260000000msat, probability=0.467
# sendpay flow groupid=1, partid=4, delivering=1040000000msat, probability=0.179
# Update chan knowledge scid=103x2x0, dir=0: [0msat,1039999999msat]
# onion error WIRE_TEMPORARY_CHANNEL_FAILURE from node #1 103x2x0: failed: WIRE_TEMPORARY_CHANNEL_FAILURE (reply from remote)
# we have computed a set of 2 flows with probability 0.052, fees 0msat and delay 23
# Shadow route on flow 0/2 added 0 block delay. now 5
# Shadow route on flow 1/2 added 0 block delay. now 5
# sendpay flow groupid=1, partid=7, delivering=120000000msat, probability=0.494
# sendpay flow groupid=1, partid=6, delivering=920000000msat, probability=0.105
Ideally it would look something like:
# Computed 1 flows, probability=0.328:
# Flow 1: 103x2x0 1800000000msat fee=0msat probability=0.328 shadow=+0msat/0blocks
# Flow 1: FAIL: TEMPORARY_CHANNEL_FAILURE for 103x2x0.
# Computed 2 flows, probability=0.115:
# Flow 2: XXX->XXX 1300000000msat fee=XXX, probability=0.475 shadow=+0msat/0blocks
# Flow 3: XXX->XXX 500000000msat fee=XXX, probability=0.475 shadow=+0msat/0blocks
# Flow 2: FAIL: TEMPORARY_CHANNEL_FAILURE from node #1 103x2x0
# Computed 2 flows (3 total), probability=0.084, fee=0msat, delay=23
...
# Flow 4: SUCCESS, 2 in progress should succeed soon.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2023-08-10 03:59:44 +02:00
|
|
|
import subprocess
|
2023-07-31 03:51:22 +02:00
|
|
|
|
|
|
|
|
|
|
|
def test_simple(node_factory):
|
|
|
|
'''Testing simply paying a peer.'''
|
|
|
|
l1, l2 = node_factory.line_graph(2)
|
|
|
|
inv = l2.rpc.invoice(123000, 'test_renepay', 'description')['bolt11']
|
|
|
|
details = l1.rpc.call('renepay', {'invstring': inv})
|
|
|
|
assert details['status'] == 'complete'
|
|
|
|
assert details['amount_msat'] == Millisatoshi(123000)
|
|
|
|
assert details['destination'] == l2.info['id']
|
|
|
|
|
|
|
|
|
2023-08-29 07:29:07 +02:00
|
|
|
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']
|
|
|
|
|
|
|
|
|
2023-08-29 07:29:16 +02:00
|
|
|
def test_shadow(node_factory):
|
|
|
|
'''Make sure we shadow correctly.'''
|
|
|
|
l1, l2, l3 = node_factory.line_graph(3,
|
|
|
|
wait_for_announce=True,
|
|
|
|
opts=[{},
|
pytest: fix renepay test_shadow flake.
Sometimes we don't shadow because it would break our budget:
```
def test_shadow(node_factory):
'''Make sure we shadow correctly.'''
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}])
# Shadow doesn't always happen (50% chance)!
for i in range(20):
inv = l2.rpc.invoice(123000, f'test_renepay{i}', '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']
> line = l1.daemon.wait_for_log("No MPP, so added .*msat shadow fee")
tests/test_renepay.py:51:
```
```
lightningd-1 2023-09-12T05:08:39.699Z UNUSUAL plugin-cln-renepay: No shadow fee for flow 0/1: fee would add 5005msat to 123000msat, exceeding budget 128000msat.
```
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2023-09-13 07:29:26 +02:00
|
|
|
{'fee-base': 20, 'fee-per-satoshi': 20, 'cltv-delta': 20},
|
|
|
|
{'fee-base': 30, 'fee-per-satoshi': 30, 'cltv-delta': 30}])
|
2023-08-29 07:29:16 +02:00
|
|
|
|
|
|
|
# Shadow doesn't always happen (50% chance)!
|
|
|
|
for i in range(20):
|
|
|
|
inv = l2.rpc.invoice(123000, f'test_renepay{i}', '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']
|
|
|
|
|
|
|
|
line = l1.daemon.wait_for_log("No MPP, so added .*msat shadow fee")
|
|
|
|
if 'added 0msat' not in line:
|
|
|
|
break
|
|
|
|
assert i != 19
|
|
|
|
|
|
|
|
|
2023-07-31 03:51:22 +02:00
|
|
|
def test_mpp(node_factory):
|
|
|
|
'''Test paying a remote node using two routes.
|
|
|
|
1----2----4
|
|
|
|
| |
|
|
|
|
3----5----6
|
|
|
|
Try paying 1.2M sats from 1 to 6.
|
|
|
|
'''
|
|
|
|
opts = [
|
|
|
|
{'disable-mpp': None, 'fee-base': 0, 'fee-per-satoshi': 0},
|
|
|
|
]
|
|
|
|
l1, l2, l3, l4, l5, l6 = node_factory.get_nodes(6, opts=opts * 6)
|
|
|
|
node_factory.join_nodes([l1, l2, l4, l6],
|
|
|
|
wait_for_announce=True, fundamount=1000000)
|
|
|
|
node_factory.join_nodes([l1, l3, l5, l6],
|
|
|
|
wait_for_announce=True, fundamount=1000000)
|
|
|
|
|
|
|
|
send_amount = Millisatoshi('1200000sat')
|
|
|
|
inv = l6.rpc.invoice(send_amount, 'test_renepay', 'description')['bolt11']
|
|
|
|
details = l1.rpc.call('renepay', {'invstring': inv})
|
|
|
|
assert details['status'] == 'complete'
|
|
|
|
assert details['amount_msat'] == send_amount
|
|
|
|
assert details['destination'] == l6.info['id']
|
|
|
|
|
|
|
|
|
|
|
|
def test_errors(node_factory, bitcoind):
|
|
|
|
opts = [
|
|
|
|
{'disable-mpp': None, 'fee-base': 0, 'fee-per-satoshi': 0},
|
|
|
|
]
|
|
|
|
l1, l2, l3, l4, l5, l6 = node_factory.get_nodes(6, opts=opts * 6)
|
|
|
|
send_amount = Millisatoshi('21sat')
|
|
|
|
inv = l6.rpc.invoice(send_amount, 'test_renepay', 'description')['bolt11']
|
2023-08-10 02:01:21 +02:00
|
|
|
inv_deleted = l6.rpc.invoice(send_amount, 'test_renepay2', 'description2')['bolt11']
|
|
|
|
l6.rpc.delinvoice('test_renepay2', 'unpaid')
|
2023-07-31 03:51:22 +02:00
|
|
|
|
|
|
|
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)
|
|
|
|
|
2023-08-10 03:59:44 +02:00
|
|
|
failmsg = r'Destination is unknown in the network gossip.'
|
2023-07-31 03:51:22 +02:00
|
|
|
with pytest.raises(RpcError, match=failmsg):
|
|
|
|
l1.rpc.call('renepay', {'invstring': inv})
|
|
|
|
|
|
|
|
l4.rpc.connect(l6.info['id'], 'localhost', l6.port)
|
|
|
|
l5.rpc.connect(l6.info['id'], 'localhost', l6.port)
|
|
|
|
|
|
|
|
scid46, _ = l4.fundchannel(l6, 10**6, wait_for_active=False)
|
|
|
|
scid56, _ = l5.fundchannel(l6, 10**6, wait_for_active=False)
|
|
|
|
mine_funding_to_announce(bitcoind, [l1, l2, l3, l4, l5, l6])
|
|
|
|
|
|
|
|
l1.daemon.wait_for_logs([r'update for channel {}/0 now ACTIVE'
|
|
|
|
.format(scid46),
|
|
|
|
r'update for channel {}/1 now ACTIVE'
|
|
|
|
.format(scid46),
|
|
|
|
r'update for channel {}/0 now ACTIVE'
|
|
|
|
.format(scid56),
|
|
|
|
r'update for channel {}/1 now ACTIVE'
|
|
|
|
.format(scid56)])
|
2023-07-31 03:51:25 +02:00
|
|
|
details = l1.rpc.call('renepay', {'invstring': inv})
|
2023-07-31 03:51:22 +02:00
|
|
|
assert details['status'] == 'complete'
|
|
|
|
assert details['amount_msat'] == send_amount
|
|
|
|
assert details['destination'] == l6.info['id']
|
|
|
|
|
2023-08-10 02:01:21 +02:00
|
|
|
# Test error from final node.
|
|
|
|
with pytest.raises(RpcError) as err:
|
|
|
|
l1.rpc.call('renepay', {'invstring': inv_deleted})
|
|
|
|
|
|
|
|
PAY_DESTINATION_PERM_FAIL = 203
|
|
|
|
assert err.value.error['code'] == PAY_DESTINATION_PERM_FAIL
|
|
|
|
assert 'WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS' in err.value.error['message']
|
|
|
|
|
2023-07-31 03:51:22 +02:00
|
|
|
|
|
|
|
@pytest.mark.openchannel('v1')
|
|
|
|
@pytest.mark.openchannel('v2')
|
|
|
|
def test_pay(node_factory):
|
|
|
|
l1, l2 = node_factory.line_graph(2)
|
|
|
|
|
|
|
|
inv = l2.rpc.invoice(123000, 'test_pay', 'description')['bolt11']
|
|
|
|
before = int(time.time())
|
2023-09-21 07:36:28 +02:00
|
|
|
details = l1.rpc.call('renepay', {'invstring': inv, 'dev_use_shadow': False})
|
2023-07-31 03:51:22 +02:00
|
|
|
after = time.time()
|
|
|
|
preimage = details['payment_preimage']
|
|
|
|
assert details['status'] == 'complete'
|
|
|
|
assert details['amount_msat'] == Millisatoshi(123000)
|
|
|
|
assert details['destination'] == l2.info['id']
|
|
|
|
assert details['created_at'] >= before
|
|
|
|
assert details['created_at'] <= after
|
|
|
|
|
|
|
|
invoices = l2.rpc.listinvoices('test_pay')['invoices']
|
|
|
|
assert len(invoices) == 1
|
|
|
|
invoice = invoices[0]
|
|
|
|
assert invoice['status'] == 'paid' and invoice['paid_at'] >= before and invoice['paid_at'] <= after
|
|
|
|
|
|
|
|
# Repeat payments are NOPs (if valid): we can hand null.
|
2023-09-21 07:36:28 +02:00
|
|
|
l1.rpc.call('renepay', {'invstring': inv, 'dev_use_shadow': False})
|
2023-07-31 03:51:22 +02:00
|
|
|
# This won't work: can't provide an amount (even if correct!)
|
|
|
|
with pytest.raises(RpcError):
|
|
|
|
l1.rpc.call('renepay', {'invstring': inv, 'amount_msat': 123000})
|
|
|
|
with pytest.raises(RpcError):
|
|
|
|
l1.rpc.call('renepay', {'invstring': inv, 'amount_msat': 122000})
|
|
|
|
|
|
|
|
# Check pay_index is not null
|
|
|
|
outputs = l2.db_query(
|
|
|
|
'SELECT pay_index IS NOT NULL AS q FROM invoices WHERE label="label";')
|
|
|
|
assert len(outputs) == 1 and outputs[0]['q'] != 0
|
|
|
|
|
|
|
|
# Check payment of any-amount invoice.
|
|
|
|
for i in range(5):
|
|
|
|
label = "any{}".format(i)
|
|
|
|
inv2 = l2.rpc.invoice("any", label, 'description')['bolt11']
|
|
|
|
# Must provide an amount!
|
|
|
|
with pytest.raises(RpcError):
|
2023-09-21 07:36:28 +02:00
|
|
|
l1.rpc.call('renepay', {'invstring': inv2, 'dev_use_shadow': False})
|
2023-07-31 03:51:22 +02:00
|
|
|
|
2023-09-21 07:36:28 +02:00
|
|
|
l1.rpc.call('renepay', {'invstring': inv2, 'dev_use_shadow': False,
|
2023-07-31 03:51:22 +02:00
|
|
|
'amount_msat': random.randint(1000, 999999)})
|
|
|
|
|
|
|
|
# Should see 6 completed payments
|
|
|
|
assert len(l1.rpc.listsendpays()['payments']) == 6
|
|
|
|
|
|
|
|
# Test listsendpays indexed by bolt11.
|
|
|
|
payments = l1.rpc.listsendpays(inv)['payments']
|
|
|
|
assert len(payments) == 1 and payments[0]['payment_preimage'] == preimage
|
|
|
|
|
|
|
|
# Make sure they're completely settled, so accounting correct.
|
|
|
|
wait_for(lambda: only_one(l1.rpc.listpeerchannels()['channels'])['htlcs'] == [])
|
|
|
|
|
|
|
|
# Check channels apy summary view of channel activity
|
|
|
|
apys_1 = l1.rpc.bkpr_channelsapy()['channels_apy']
|
|
|
|
apys_2 = l2.rpc.bkpr_channelsapy()['channels_apy']
|
|
|
|
|
|
|
|
assert apys_1[0]['channel_start_balance_msat'] == apys_2[0]['channel_start_balance_msat']
|
|
|
|
assert apys_1[0]['channel_start_balance_msat'] == apys_1[0]['our_start_balance_msat']
|
|
|
|
assert apys_2[0]['our_start_balance_msat'] == Millisatoshi(0)
|
|
|
|
assert apys_1[0]['routed_out_msat'] == apys_2[0]['routed_in_msat']
|
|
|
|
assert apys_1[0]['routed_in_msat'] == apys_2[0]['routed_out_msat']
|
|
|
|
|
|
|
|
|
|
|
|
def test_amounts(node_factory):
|
|
|
|
'''
|
|
|
|
Check that the amount received matches the amount requested in the invoice.
|
|
|
|
'''
|
|
|
|
l1, l2 = node_factory.line_graph(2)
|
|
|
|
inv = l2.rpc.invoice(Millisatoshi(
|
|
|
|
123456), 'test_pay_amounts', 'description')['bolt11']
|
|
|
|
|
|
|
|
invoice = only_one(l2.rpc.listinvoices('test_pay_amounts')['invoices'])
|
|
|
|
|
|
|
|
assert isinstance(invoice['amount_msat'], Millisatoshi)
|
|
|
|
assert invoice['amount_msat'] == Millisatoshi(123456)
|
|
|
|
|
2023-09-21 07:36:28 +02:00
|
|
|
l1.rpc.call('renepay', {'invstring': inv, 'dev_use_shadow': False})
|
2023-07-31 03:51:22 +02:00
|
|
|
|
|
|
|
invoice = only_one(l2.rpc.listinvoices('test_pay_amounts')['invoices'])
|
|
|
|
assert isinstance(invoice['amount_received_msat'], Millisatoshi)
|
|
|
|
assert invoice['amount_received_msat'] >= Millisatoshi(123456)
|
|
|
|
|
|
|
|
|
|
|
|
def test_limits(node_factory):
|
|
|
|
'''
|
|
|
|
Topology:
|
|
|
|
1----2----4
|
|
|
|
| |
|
|
|
|
3----5----6
|
|
|
|
Try the error messages when paying when:
|
|
|
|
- the fees are too high,
|
|
|
|
- CLTV delay is too high,
|
|
|
|
- probability of success is too low.
|
|
|
|
'''
|
|
|
|
opts = [
|
|
|
|
{'disable-mpp': None, 'fee-base': 0, 'fee-per-satoshi': 100},
|
|
|
|
]
|
|
|
|
l1, l2, l3, l4, l5, l6 = node_factory.get_nodes(6, opts=opts * 6)
|
|
|
|
node_factory.join_nodes([l1, l2, l4, l6],
|
|
|
|
wait_for_announce=True, fundamount=1000000)
|
|
|
|
node_factory.join_nodes([l1, l3, l5, l6],
|
|
|
|
wait_for_announce=True, fundamount=1000000)
|
|
|
|
|
|
|
|
inv = l4.rpc.invoice('any', 'any', 'description')
|
|
|
|
l2.rpc.call('pay', {'bolt11': inv['bolt11'], 'amount_msat': 500000000})
|
|
|
|
inv = l5.rpc.invoice('any', 'any', 'description')
|
|
|
|
l3.rpc.call('pay', {'bolt11': inv['bolt11'], 'amount_msat': 500000000})
|
|
|
|
|
|
|
|
# FIXME: pylightning should define these!
|
|
|
|
# PAY_STOPPED_RETRYING = 210
|
2023-08-10 03:59:44 +02:00
|
|
|
PAY_ROUTE_TOO_EXPENSIVE = 206
|
2023-07-31 03:51:22 +02:00
|
|
|
|
|
|
|
inv = l6.rpc.invoice("any", "any", 'description')
|
|
|
|
|
|
|
|
# Fee too high.
|
|
|
|
failmsg = r'Fee exceeds our fee budget'
|
|
|
|
with pytest.raises(RpcError, match=failmsg) as err:
|
|
|
|
l1.rpc.call(
|
|
|
|
'renepay', {'invstring': inv['bolt11'], 'amount_msat': 1000000, 'maxfee': 1})
|
2023-08-10 03:59:44 +02:00
|
|
|
assert err.value.error['code'] == PAY_ROUTE_TOO_EXPENSIVE
|
2023-07-31 03:51:22 +02:00
|
|
|
# TODO(eduardo): which error code shall we use here?
|
|
|
|
|
|
|
|
# TODO(eduardo): shall we list attempts in renepay?
|
|
|
|
# status = l1.rpc.call('renepaystatus', {'invstring':inv['bolt11']})['paystatus'][0]['attempts']
|
|
|
|
|
|
|
|
failmsg = r'CLTV delay exceeds our CLTV budget'
|
|
|
|
# Delay too high.
|
|
|
|
with pytest.raises(RpcError, match=failmsg) as err:
|
|
|
|
l1.rpc.call(
|
|
|
|
'renepay', {'invstring': inv['bolt11'], 'amount_msat': 1000000, 'maxdelay': 0})
|
2023-08-10 03:59:44 +02:00
|
|
|
assert err.value.error['code'] == PAY_ROUTE_TOO_EXPENSIVE
|
2023-07-31 03:51:22 +02:00
|
|
|
|
|
|
|
inv2 = l6.rpc.invoice("800000sat", "inv2", 'description')
|
|
|
|
l1.rpc.call(
|
2023-07-31 03:51:25 +02:00
|
|
|
'renepay', {'invstring': inv2['bolt11']})
|
2023-07-31 03:51:22 +02:00
|
|
|
invoice = only_one(l6.rpc.listinvoices('inv2')['invoices'])
|
|
|
|
assert isinstance(invoice['amount_received_msat'], Millisatoshi)
|
|
|
|
assert invoice['amount_received_msat'] >= Millisatoshi('800000sat')
|
2023-07-31 03:51:25 +02:00
|
|
|
|
|
|
|
|
|
|
|
def start_channels(connections):
|
|
|
|
nodes = list()
|
|
|
|
for src, dst, fundamount in connections:
|
|
|
|
nodes.append(src)
|
|
|
|
nodes.append(dst)
|
|
|
|
src.rpc.connect(dst.info['id'], 'localhost', dst.port)
|
|
|
|
|
|
|
|
bitcoind = nodes[0].bitcoin
|
|
|
|
# If we got here, we want to fund channels
|
|
|
|
for src, dst, fundamount in connections:
|
|
|
|
addr = src.rpc.newaddr()['bech32']
|
|
|
|
bitcoind.rpc.sendtoaddress(addr, (fundamount + 1000000) / 10**8)
|
|
|
|
|
|
|
|
bitcoind.generate_block(1)
|
|
|
|
sync_blockheight(bitcoind, nodes)
|
|
|
|
txids = []
|
|
|
|
for src, dst, fundamount in connections:
|
|
|
|
txids.append(src.rpc.fundchannel(dst.info['id'], fundamount,
|
|
|
|
announce=True)['txid'])
|
|
|
|
|
|
|
|
# Confirm all channels and wait for them to become usable
|
|
|
|
bitcoind.generate_block(1, wait_for_mempool=txids)
|
|
|
|
scids = []
|
|
|
|
for src, dst, fundamount in connections:
|
|
|
|
wait_for(lambda: src.channel_state(dst) == 'CHANNELD_NORMAL')
|
|
|
|
scid = src.get_channel_scid(dst)
|
|
|
|
scids.append(scid)
|
|
|
|
|
|
|
|
bitcoind.generate_block(5)
|
|
|
|
|
|
|
|
# Make sure everyone sees all channels, all other nodes
|
|
|
|
for n in nodes:
|
|
|
|
for scid in scids:
|
|
|
|
n.wait_channel_active(scid)
|
|
|
|
|
|
|
|
# Make sure we have all node announcements, too
|
|
|
|
for n in nodes:
|
|
|
|
for n2 in nodes:
|
|
|
|
wait_for(lambda: 'alias' in only_one(n.rpc.listnodes(n2.info['id'])['nodes']))
|
|
|
|
|
|
|
|
|
|
|
|
def test_hardmpp(node_factory):
|
|
|
|
'''
|
|
|
|
Topology:
|
|
|
|
1----2----4
|
|
|
|
| |
|
|
|
|
3----5----6
|
|
|
|
This a payment that fails if pending HTLCs are not taken into account when
|
|
|
|
we build the network capacities.
|
|
|
|
'''
|
|
|
|
opts = [
|
|
|
|
{'disable-mpp': None, 'fee-base': 0, 'fee-per-satoshi': 0},
|
|
|
|
]
|
|
|
|
l1, l2, l3, l4, l5, l6 = node_factory.get_nodes(6, opts=opts * 6)
|
|
|
|
start_channels([(l1, l2, 10000000), (l2, l4, 3000000), (l4, l6, 10000000),
|
|
|
|
(l1, l3, 10000000), (l3, l5, 1000000), (l5, l6, 10000000)])
|
|
|
|
|
|
|
|
with open('/tmp/l1-chans.txt', 'w') as f:
|
|
|
|
print(json.dumps(l1.rpc.listchannels()), file=f)
|
|
|
|
|
|
|
|
inv = l4.rpc.invoice('any', 'any', 'description')
|
|
|
|
l2.rpc.call('pay', {'bolt11': inv['bolt11'], 'amount_msat': 2000000000})
|
|
|
|
l2.wait_for_htlcs()
|
|
|
|
assert l4.rpc.listinvoices()["invoices"][0]["amount_received_msat"] == 2000000000
|
|
|
|
|
|
|
|
with open('/tmp/l2-peerchan.txt', 'w') as f:
|
|
|
|
print(json.dumps(l2.rpc.listpeerchannels()), file=f)
|
|
|
|
with open('/tmp/l3-peerchan.txt', 'w') as f:
|
|
|
|
print(json.dumps(l3.rpc.listpeerchannels()), file=f)
|
|
|
|
|
|
|
|
inv2 = l6.rpc.invoice("1800000sat", "inv2", 'description')
|
renepay: add command notifications
These show that we should clean up our notes. Here's the result from test_hardmpp:
# we have computed a set of 1 flows with probability 0.328, fees 0msat and delay 23
# No MPP, so added 0msat shadow fee
# Shadow route on flow 0/1 added 0 block delay. now 5
# sendpay flow groupid=1, partid=1, delivering=1800000000msat, probability=0.328
# Update chan knowledge scid=103x2x0, dir=0: [0msat,1799999999msat]
# onion error WIRE_TEMPORARY_CHANNEL_FAILURE from node #1 103x2x0: failed: WIRE_TEMPORARY_CHANNEL_FAILURE (reply from remote)
# we have computed a set of 2 flows with probability 0.115, fees 0msat and delay 23
# Shadow route on flow 0/2 added 0 block delay. now 5
# Shadow route on flow 1/2 added 0 block delay. now 5
# sendpay flow groupid=1, partid=3, delivering=500000000msat, probability=0.475
# sendpay flow groupid=1, partid=2, delivering=1300000000msat, probability=0.242
# Update chan knowledge scid=103x2x0, dir=0: [0msat,1299999999msat]
# onion error WIRE_TEMPORARY_CHANNEL_FAILURE from node #1 103x2x0: failed: WIRE_TEMPORARY_CHANNEL_FAILURE (reply from remote)
# we have computed a set of 2 flows with probability 0.084, fees 0msat and delay 23
# Shadow route on flow 0/2 added 0 block delay. now 5
# Shadow route on flow 1/2 added 0 block delay. now 5
# sendpay flow groupid=1, partid=5, delivering=260000000msat, probability=0.467
# sendpay flow groupid=1, partid=4, delivering=1040000000msat, probability=0.179
# Update chan knowledge scid=103x2x0, dir=0: [0msat,1039999999msat]
# onion error WIRE_TEMPORARY_CHANNEL_FAILURE from node #1 103x2x0: failed: WIRE_TEMPORARY_CHANNEL_FAILURE (reply from remote)
# we have computed a set of 2 flows with probability 0.052, fees 0msat and delay 23
# Shadow route on flow 0/2 added 0 block delay. now 5
# Shadow route on flow 1/2 added 0 block delay. now 5
# sendpay flow groupid=1, partid=7, delivering=120000000msat, probability=0.494
# sendpay flow groupid=1, partid=6, delivering=920000000msat, probability=0.105
Ideally it would look something like:
# Computed 1 flows, probability=0.328:
# Flow 1: 103x2x0 1800000000msat fee=0msat probability=0.328 shadow=+0msat/0blocks
# Flow 1: FAIL: TEMPORARY_CHANNEL_FAILURE for 103x2x0.
# Computed 2 flows, probability=0.115:
# Flow 2: XXX->XXX 1300000000msat fee=XXX, probability=0.475 shadow=+0msat/0blocks
# Flow 3: XXX->XXX 500000000msat fee=XXX, probability=0.475 shadow=+0msat/0blocks
# Flow 2: FAIL: TEMPORARY_CHANNEL_FAILURE from node #1 103x2x0
# Computed 2 flows (3 total), probability=0.084, fee=0msat, delay=23
...
# Flow 4: SUCCESS, 2 in progress should succeed soon.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2023-08-10 03:59:44 +02:00
|
|
|
|
|
|
|
out = subprocess.check_output(['cli/lightning-cli',
|
|
|
|
'--network={}'.format(TEST_NETWORK),
|
|
|
|
'--lightning-dir={}'
|
|
|
|
.format(l1.daemon.lightning_dir),
|
|
|
|
'-k',
|
|
|
|
'renepay', 'invstring={}'.format(inv2['bolt11'])]).decode('utf-8')
|
|
|
|
lines = out.split('\n')
|
|
|
|
# First comes commentry
|
|
|
|
assert any([l.startswith('#') for l in lines])
|
|
|
|
|
|
|
|
# Now comes JSON
|
|
|
|
json.loads("".join([l for l in lines if not l.startswith('#')]))
|
2023-07-31 03:51:25 +02:00
|
|
|
l1.wait_for_htlcs()
|
|
|
|
invoice = only_one(l6.rpc.listinvoices('inv2')['invoices'])
|
|
|
|
assert isinstance(invoice['amount_received_msat'], Millisatoshi)
|
|
|
|
assert invoice['amount_received_msat'] >= Millisatoshi('1800000sat')
|