|
@ -367,249 +367,6 @@ class LightningDTests(BaseLightningDTests): |
|
|
l1.daemon.wait_for_log('Got pong 1000 bytes \({}\.\.\.\)' |
|
|
l1.daemon.wait_for_log('Got pong 1000 bytes \({}\.\.\.\)' |
|
|
.format(l2.info['version'])) |
|
|
.format(l2.info['version'])) |
|
|
|
|
|
|
|
|
@unittest.skipIf(not DEVELOPER, "Too slow without --dev-bitcoind-poll") |
|
|
|
|
|
def test_forward(self): |
|
|
|
|
|
# Connect 1 -> 2 -> 3. |
|
|
|
|
|
l1, l2 = self.connect() |
|
|
|
|
|
l3 = self.node_factory.get_node() |
|
|
|
|
|
ret = l2.rpc.connect(l3.info['id'], 'localhost', l3.port) |
|
|
|
|
|
|
|
|
|
|
|
assert ret['id'] == l3.info['id'] |
|
|
|
|
|
|
|
|
|
|
|
l3.daemon.wait_for_log('Handing back peer .* to master') |
|
|
|
|
|
self.fund_channel(l1, l2, 10**6) |
|
|
|
|
|
self.fund_channel(l2, l3, 10**6) |
|
|
|
|
|
|
|
|
|
|
|
# Allow announce messages. |
|
|
|
|
|
l1.bitcoin.generate_block(5) |
|
|
|
|
|
|
|
|
|
|
|
# If they're at different block heights we can get spurious errors. |
|
|
|
|
|
sync_blockheight([l1, l2, l3]) |
|
|
|
|
|
|
|
|
|
|
|
chanid1 = only_one(l1.rpc.getpeer(l2.info['id'])['channels'])['short_channel_id'] |
|
|
|
|
|
chanid2 = only_one(l2.rpc.getpeer(l3.info['id'])['channels'])['short_channel_id'] |
|
|
|
|
|
assert only_one(l2.rpc.getpeer(l1.info['id'])['channels'])['short_channel_id'] == chanid1 |
|
|
|
|
|
assert only_one(l3.rpc.getpeer(l2.info['id'])['channels'])['short_channel_id'] == chanid2 |
|
|
|
|
|
|
|
|
|
|
|
rhash = l3.rpc.invoice(100000000, 'testpayment1', 'desc')['payment_hash'] |
|
|
|
|
|
assert only_one(l3.rpc.listinvoices('testpayment1')['invoices'])['status'] == 'unpaid' |
|
|
|
|
|
|
|
|
|
|
|
# Fee for node2 is 10 millionths, plus 1. |
|
|
|
|
|
amt = 100000000 |
|
|
|
|
|
fee = amt * 10 // 1000000 + 1 |
|
|
|
|
|
|
|
|
|
|
|
baseroute = [{'msatoshi': amt + fee, |
|
|
|
|
|
'id': l2.info['id'], |
|
|
|
|
|
'delay': 12, |
|
|
|
|
|
'channel': chanid1}, |
|
|
|
|
|
{'msatoshi': amt, |
|
|
|
|
|
'id': l3.info['id'], |
|
|
|
|
|
'delay': 6, |
|
|
|
|
|
'channel': chanid2}] |
|
|
|
|
|
|
|
|
|
|
|
# Unknown other peer |
|
|
|
|
|
route = copy.deepcopy(baseroute) |
|
|
|
|
|
route[1]['id'] = '031a8dc444e41bb989653a4501e11175a488a57439b0c4947704fd6e3de5dca607' |
|
|
|
|
|
l1.rpc.sendpay(to_json(route), rhash) |
|
|
|
|
|
self.assertRaises(RpcError, l1.rpc.waitsendpay, rhash) |
|
|
|
|
|
|
|
|
|
|
|
# Delay too short (we always add one internally anyway, so subtract 2 here). |
|
|
|
|
|
route = copy.deepcopy(baseroute) |
|
|
|
|
|
route[0]['delay'] = 8 |
|
|
|
|
|
l1.rpc.sendpay(to_json(route), rhash) |
|
|
|
|
|
self.assertRaises(RpcError, l1.rpc.waitsendpay, rhash) |
|
|
|
|
|
|
|
|
|
|
|
# Final delay too short |
|
|
|
|
|
route = copy.deepcopy(baseroute) |
|
|
|
|
|
route[1]['delay'] = 3 |
|
|
|
|
|
l1.rpc.sendpay(to_json(route), rhash) |
|
|
|
|
|
self.assertRaises(RpcError, l1.rpc.waitsendpay, rhash) |
|
|
|
|
|
|
|
|
|
|
|
# This one works |
|
|
|
|
|
route = copy.deepcopy(baseroute) |
|
|
|
|
|
l1.rpc.sendpay(to_json(route), rhash) |
|
|
|
|
|
l1.rpc.waitsendpay(rhash) |
|
|
|
|
|
|
|
|
|
|
|
@unittest.skipIf(not DEVELOPER, "needs DEVELOPER=1 for --dev-broadcast-interval") |
|
|
|
|
|
def test_forward_different_fees_and_cltv(self): |
|
|
|
|
|
# FIXME: Check BOLT quotes here too |
|
|
|
|
|
# BOLT #7: |
|
|
|
|
|
# ``` |
|
|
|
|
|
# B |
|
|
|
|
|
# / \ |
|
|
|
|
|
# / \ |
|
|
|
|
|
# A C |
|
|
|
|
|
# \ / |
|
|
|
|
|
# \ / |
|
|
|
|
|
# D |
|
|
|
|
|
# ``` |
|
|
|
|
|
# |
|
|
|
|
|
# Each advertises the following `cltv_expiry_delta` on its end of every |
|
|
|
|
|
# channel: |
|
|
|
|
|
# |
|
|
|
|
|
# 1. A: 10 blocks |
|
|
|
|
|
# 2. B: 20 blocks |
|
|
|
|
|
# 3. C: 30 blocks |
|
|
|
|
|
# 4. D: 40 blocks |
|
|
|
|
|
# |
|
|
|
|
|
# C also uses a minimum `cltv_expiry` of 9 (the default) when requesting |
|
|
|
|
|
# payments. |
|
|
|
|
|
# |
|
|
|
|
|
# Also, each node has the same fee scheme which it uses for each of its |
|
|
|
|
|
# channels: |
|
|
|
|
|
# |
|
|
|
|
|
# 1. A: 100 base + 1000 millionths |
|
|
|
|
|
# 1. B: 200 base + 2000 millionths |
|
|
|
|
|
# 1. C: 300 base + 3000 millionths |
|
|
|
|
|
# 1. D: 400 base + 4000 millionths |
|
|
|
|
|
|
|
|
|
|
|
# We don't do D yet. |
|
|
|
|
|
l1 = self.node_factory.get_node(options={'cltv-delta': 10, 'fee-base': 100, 'fee-per-satoshi': 1000}) |
|
|
|
|
|
l2 = self.node_factory.get_node(options={'cltv-delta': 20, 'fee-base': 200, 'fee-per-satoshi': 2000}) |
|
|
|
|
|
l3 = self.node_factory.get_node(options={'cltv-delta': 30, 'cltv-final': 9, 'fee-base': 300, 'fee-per-satoshi': 3000}) |
|
|
|
|
|
|
|
|
|
|
|
ret = l1.rpc.connect(l2.info['id'], 'localhost', l2.port) |
|
|
|
|
|
assert ret['id'] == l2.info['id'] |
|
|
|
|
|
|
|
|
|
|
|
l1.daemon.wait_for_log('Handing back peer .* to master') |
|
|
|
|
|
l2.daemon.wait_for_log('Handing back peer .* to master') |
|
|
|
|
|
|
|
|
|
|
|
ret = l2.rpc.connect(l3.info['id'], 'localhost', l3.port) |
|
|
|
|
|
assert ret['id'] == l3.info['id'] |
|
|
|
|
|
|
|
|
|
|
|
l2.daemon.wait_for_log('Handing back peer .* to master') |
|
|
|
|
|
l3.daemon.wait_for_log('Handing back peer .* to master') |
|
|
|
|
|
|
|
|
|
|
|
c1 = self.fund_channel(l1, l2, 10**6) |
|
|
|
|
|
c2 = self.fund_channel(l2, l3, 10**6) |
|
|
|
|
|
|
|
|
|
|
|
# Make sure l1 has seen announce for all channels. |
|
|
|
|
|
self.wait_for_routes(l1, [c1, c2]) |
|
|
|
|
|
|
|
|
|
|
|
# BOLT #7: |
|
|
|
|
|
# |
|
|
|
|
|
# If B were to send 4,999,999 millisatoshi directly to C, it wouldn't |
|
|
|
|
|
# charge itself a fee nor add its own `cltv_expiry_delta`, so it would |
|
|
|
|
|
# use C's requested `cltv_expiry` of 9. We also assume it adds a |
|
|
|
|
|
# "shadow route" to give an extra CLTV of 42. It could also add extra |
|
|
|
|
|
# cltv deltas at other hops, as these values are a minimum, but we don't |
|
|
|
|
|
# here for simplicity: |
|
|
|
|
|
|
|
|
|
|
|
# FIXME: Add shadow route |
|
|
|
|
|
shadow_route = 0 |
|
|
|
|
|
route = l2.rpc.getroute(l3.info['id'], 4999999, 1)["route"] |
|
|
|
|
|
assert len(route) == 1 |
|
|
|
|
|
|
|
|
|
|
|
# BOLT #7: |
|
|
|
|
|
# |
|
|
|
|
|
# * `amount_msat`: 4999999 |
|
|
|
|
|
# * `cltv_expiry`: current-block-height + 9 + 42 |
|
|
|
|
|
# * `onion_routing_packet`: |
|
|
|
|
|
# * `amt_to_forward` = 4999999 |
|
|
|
|
|
# * `outgoing_cltv_value` = current-block-height + 9 + 42 |
|
|
|
|
|
# |
|
|
|
|
|
assert route[0]['msatoshi'] == 4999999 |
|
|
|
|
|
assert route[0]['delay'] == 9 + shadow_route |
|
|
|
|
|
|
|
|
|
|
|
# BOLT #7: |
|
|
|
|
|
# If A were to send 4,999,999 millisatoshi to C via B, it needs to |
|
|
|
|
|
# pay B the fee it specified in the B->C `channel_update`, calculated as |
|
|
|
|
|
# per [HTLC Fees](#htlc_fees): |
|
|
|
|
|
# |
|
|
|
|
|
# 200 + 4999999 * 2000 / 1000000 = 10199 |
|
|
|
|
|
# |
|
|
|
|
|
# Similarly, it would need to add the `cltv_expiry` from B->C's |
|
|
|
|
|
# `channel_update` (20), plus C's requested minimum (9), plus 42 for the |
|
|
|
|
|
# "shadow route". Thus the `update_add_htlc` message from A to B would |
|
|
|
|
|
# be: |
|
|
|
|
|
# |
|
|
|
|
|
# * `amount_msat`: 5010198 |
|
|
|
|
|
# * `cltv_expiry`: current-block-height + 20 + 9 + 42 |
|
|
|
|
|
# * `onion_routing_packet`: |
|
|
|
|
|
# * `amt_to_forward` = 4999999 |
|
|
|
|
|
# * `outgoing_cltv_value` = current-block-height + 9 + 42 |
|
|
|
|
|
route = l1.rpc.getroute(l3.info['id'], 4999999, 1)["route"] |
|
|
|
|
|
assert len(route) == 2 |
|
|
|
|
|
|
|
|
|
|
|
assert route[0]['msatoshi'] == 5010198 |
|
|
|
|
|
assert route[0]['delay'] == 20 + 9 + shadow_route |
|
|
|
|
|
assert route[1]['msatoshi'] == 4999999 |
|
|
|
|
|
assert route[1]['delay'] == 9 + shadow_route |
|
|
|
|
|
|
|
|
|
|
|
rhash = l3.rpc.invoice(4999999, 'test_forward_different_fees_and_cltv', 'desc')['payment_hash'] |
|
|
|
|
|
assert only_one(l3.rpc.listinvoices('test_forward_different_fees_and_cltv')['invoices'])['status'] == 'unpaid' |
|
|
|
|
|
|
|
|
|
|
|
# This should work. |
|
|
|
|
|
l1.rpc.sendpay(to_json(route), rhash) |
|
|
|
|
|
l1.rpc.waitsendpay(rhash) |
|
|
|
|
|
|
|
|
|
|
|
# We add one to the blockcount for a bit of fuzz (FIXME: Shadowroute would fix this!) |
|
|
|
|
|
shadow_route = 1 |
|
|
|
|
|
l1.daemon.wait_for_log("Adding HTLC 0 msat=5010198 cltv={} gave CHANNEL_ERR_ADD_OK" |
|
|
|
|
|
.format(bitcoind.rpc.getblockcount() + 20 + 9 + shadow_route)) |
|
|
|
|
|
l2.daemon.wait_for_log("Adding HTLC 0 msat=4999999 cltv={} gave CHANNEL_ERR_ADD_OK" |
|
|
|
|
|
.format(bitcoind.rpc.getblockcount() + 9 + shadow_route)) |
|
|
|
|
|
l3.daemon.wait_for_log("test_forward_different_fees_and_cltv: Actual amount 4999999msat, HTLC expiry {}" |
|
|
|
|
|
.format(bitcoind.rpc.getblockcount() + 9 + shadow_route)) |
|
|
|
|
|
assert only_one(l3.rpc.listinvoices('test_forward_different_fees_and_cltv')['invoices'])['status'] == 'paid' |
|
|
|
|
|
|
|
|
|
|
|
# Check that we see all the channels |
|
|
|
|
|
shortids = set(c['short_channel_id'] for c in l2.rpc.listchannels()['channels']) |
|
|
|
|
|
for scid in shortids: |
|
|
|
|
|
c = l1.rpc.listchannels(scid)['channels'] |
|
|
|
|
|
# We get one entry for each direction. |
|
|
|
|
|
assert len(c) == 2 |
|
|
|
|
|
assert c[0]['short_channel_id'] == scid |
|
|
|
|
|
assert c[1]['short_channel_id'] == scid |
|
|
|
|
|
assert c[0]['source'] == c[1]['destination'] |
|
|
|
|
|
assert c[1]['source'] == c[0]['destination'] |
|
|
|
|
|
|
|
|
|
|
|
@unittest.skipIf(not DEVELOPER, "needs DEVELOPER=1 for --dev-broadcast-interval") |
|
|
|
|
|
def test_forward_pad_fees_and_cltv(self): |
|
|
|
|
|
"""Test that we are allowed extra locktime delta, and fees""" |
|
|
|
|
|
|
|
|
|
|
|
l1 = self.node_factory.get_node(options={'cltv-delta': 10, 'fee-base': 100, 'fee-per-satoshi': 1000}) |
|
|
|
|
|
l2 = self.node_factory.get_node(options={'cltv-delta': 20, 'fee-base': 200, 'fee-per-satoshi': 2000}) |
|
|
|
|
|
l3 = self.node_factory.get_node(options={'cltv-delta': 30, 'cltv-final': 9, 'fee-base': 300, 'fee-per-satoshi': 3000}) |
|
|
|
|
|
|
|
|
|
|
|
ret = l1.rpc.connect(l2.info['id'], 'localhost', l2.port) |
|
|
|
|
|
assert ret['id'] == l2.info['id'] |
|
|
|
|
|
|
|
|
|
|
|
l1.daemon.wait_for_log('Handing back peer .* to master') |
|
|
|
|
|
l2.daemon.wait_for_log('Handing back peer .* to master') |
|
|
|
|
|
|
|
|
|
|
|
ret = l2.rpc.connect(l3.info['id'], 'localhost', l3.port) |
|
|
|
|
|
assert ret['id'] == l3.info['id'] |
|
|
|
|
|
|
|
|
|
|
|
l2.daemon.wait_for_log('Handing back peer .* to master') |
|
|
|
|
|
l3.daemon.wait_for_log('Handing back peer .* to master') |
|
|
|
|
|
|
|
|
|
|
|
c1 = self.fund_channel(l1, l2, 10**6) |
|
|
|
|
|
c2 = self.fund_channel(l2, l3, 10**6) |
|
|
|
|
|
|
|
|
|
|
|
# Make sure l1 has seen announce for all channels. |
|
|
|
|
|
self.wait_for_routes(l1, [c1, c2]) |
|
|
|
|
|
|
|
|
|
|
|
route = l1.rpc.getroute(l3.info['id'], 4999999, 1)["route"] |
|
|
|
|
|
assert len(route) == 2 |
|
|
|
|
|
|
|
|
|
|
|
assert route[0]['msatoshi'] == 5010198 |
|
|
|
|
|
assert route[0]['delay'] == 20 + 9 |
|
|
|
|
|
assert route[1]['msatoshi'] == 4999999 |
|
|
|
|
|
assert route[1]['delay'] == 9 |
|
|
|
|
|
|
|
|
|
|
|
# Modify so we overpay, overdo the cltv. |
|
|
|
|
|
route[0]['msatoshi'] += 2000 |
|
|
|
|
|
route[0]['delay'] += 20 |
|
|
|
|
|
route[1]['msatoshi'] += 1000 |
|
|
|
|
|
route[1]['delay'] += 10 |
|
|
|
|
|
|
|
|
|
|
|
# This should work. |
|
|
|
|
|
rhash = l3.rpc.invoice(4999999, 'test_forward_pad_fees_and_cltv', 'desc')['payment_hash'] |
|
|
|
|
|
l1.rpc.sendpay(to_json(route), rhash) |
|
|
|
|
|
l1.rpc.waitsendpay(rhash) |
|
|
|
|
|
assert only_one(l3.rpc.listinvoices('test_forward_pad_fees_and_cltv')['invoices'])['status'] == 'paid' |
|
|
|
|
|
|
|
|
|
|
|
@unittest.skipIf(not DEVELOPER, "needs DEVELOPER=1 for --dev-broadcast-interval") |
|
|
@unittest.skipIf(not DEVELOPER, "needs DEVELOPER=1 for --dev-broadcast-interval") |
|
|
def test_htlc_sig_persistence(self): |
|
|
def test_htlc_sig_persistence(self): |
|
|
"""Interrupt a payment between two peers, then fail and recover funds using the HTLC sig. |
|
|
"""Interrupt a payment between two peers, then fail and recover funds using the HTLC sig. |
|
|