diff --git a/tests/test_lightningd.py b/tests/test_lightningd.py index 465da18aa..a41cced1f 100644 --- a/tests/test_lightningd.py +++ b/tests/test_lightningd.py @@ -367,249 +367,6 @@ class LightningDTests(BaseLightningDTests): l1.daemon.wait_for_log('Got pong 1000 bytes \({}\.\.\.\)' .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") def test_htlc_sig_persistence(self): """Interrupt a payment between two peers, then fail and recover funds using the HTLC sig. diff --git a/tests/test_pay.py b/tests/test_pay.py index 8f74b7cb3..73b94e736 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -1,6 +1,6 @@ from fixtures import * # noqa: F401,F403 from lightning import RpcError -from utils import DEVELOPER, wait_for +from utils import DEVELOPER, wait_for, only_one, sync_blockheight import copy @@ -724,3 +724,248 @@ def test_decodepay(node_factory): with pytest.raises(RpcError): l1.rpc.decodepay('1111111') + + +@unittest.skipIf(not DEVELOPER, "Too slow without --dev-bitcoind-poll") +def test_forward(node_factory, bitcoind): + # Connect 1 -> 2 -> 3. + l1, l2, l3 = node_factory.line_graph(3, fundchannel=True) + + # Allow announce messages. + l1.bitcoin.generate_block(5) + + # If they're at different block heights we can get spurious errors. + sync_blockheight(bitcoind, [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(route, rhash) + with pytest.raises(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(route, rhash) + with pytest.raises(RpcError): + l1.rpc.waitsendpay(rhash) + + # Final delay too short + route = copy.deepcopy(baseroute) + route[1]['delay'] = 3 + l1.rpc.sendpay(route, rhash) + with pytest.raises(RpcError): + l1.rpc.waitsendpay(rhash) + + # This one works + route = copy.deepcopy(baseroute) + l1.rpc.sendpay(route, rhash) + l1.rpc.waitsendpay(rhash) + + +@unittest.skipIf(not DEVELOPER, "needs DEVELOPER=1 for --dev-broadcast-interval") +def test_forward_different_fees_and_cltv(node_factory, bitcoind): + # 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 = node_factory.get_node(options={'cltv-delta': 10, 'fee-base': 100, 'fee-per-satoshi': 1000}) + l2 = node_factory.get_node(options={'cltv-delta': 20, 'fee-base': 200, 'fee-per-satoshi': 2000}) + l3 = 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 = l1.fund_channel(l2, 10**6) + c2 = l2.fund_channel(l3, 10**6) + bitcoind.generate_block(5) + + # Make sure l1 has seen announce for all channels. + l1.wait_channel_active(c1) + l1.wait_channel_active(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(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(node_factory, bitcoind): + """Test that we are allowed extra locktime delta, and fees""" + + l1 = node_factory.get_node(options={'cltv-delta': 10, 'fee-base': 100, 'fee-per-satoshi': 1000}) + l2 = node_factory.get_node(options={'cltv-delta': 20, 'fee-base': 200, 'fee-per-satoshi': 2000}) + l3 = 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 = l1.fund_channel(l2, 10**6) + c2 = l2.fund_channel(l3, 10**6) + bitcoind.generate_block(5) + + # Make sure l1 has seen announce for all channels. + l1.wait_channel_active(c1) + l1.wait_channel_active(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(route, rhash) + l1.rpc.waitsendpay(rhash) + assert only_one(l3.rpc.listinvoices('test_forward_pad_fees_and_cltv')['invoices'])['status'] == 'paid'