From 7c7e32b32436f1d85b647d3527f846ed3499ed5d Mon Sep 17 00:00:00 2001 From: Carl Dong Date: Thu, 2 Feb 2023 12:19:09 +1030 Subject: [PATCH] tests: add Carl Dong's example exhaustive zeroconf channel pay test. Modifications from issue #5803 to work here: 1. import json 2. Add xfail 3. Increase channel sizes by 10x so we can open them 4. Fix plugin path Signed-off-by: Rusty Russell --- tests/test_pay.py | 113 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/tests/test_pay.py b/tests/test_pay.py index 7d478caaf..671038ea1 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -10,6 +10,7 @@ from utils import ( ) import copy import os +import json import pytest import random import re @@ -5302,3 +5303,115 @@ def test_payerkey(node_factory): for n, k in zip(nodes, expected_keys): b12 = n.rpc.createinvoicerequest('lnr1qqgz2d7u2smys9dc5q2447e8thjlgq3qqc3xu3s3rg94nj40zfsy866mhu5vxne6tcej5878k2mneuvgjy8ssqvepgz5zsjrg3z3vggzvkm2khkgvrxj27r96c00pwl4kveecdktm29jdd6w0uwu5jgtv5v9qgqxyfhyvyg6pdvu4tcjvpp7kkal9rp57wj7xv4pl3ajku70rzy3pu', False)['bolt12'] assert n.rpc.decode(b12)['invreq_payer_id'] == k + + +@pytest.mark.xfail +@pytest.mark.parametrize("payment_method", ["pay", "getroute_sendpay"]) +@pytest.mark.parametrize("confirm_zeroconf_channel", [False, True], ids=["no_confirm_zeroconf", "yes_confirm_zeroconf"]) +@pytest.mark.parametrize("open_existing_normal_channel", [False, True], ids=["zeroconf_only", "add_existing_normal"]) +def test_cln_sendpay_weirdness(bitcoind, node_factory, open_existing_normal_channel, payment_method, confirm_zeroconf_channel): + # 0. Setup + + # 0.1 Setup the payer node + l1 = node_factory.get_node() + print("CARL: DONE l1 setup") + + # 0.2 Setup the receiver node + zeroconf_plugin = Path(__file__).parent / "plugins" / "zeroconf-selective.py" + l2 = node_factory.get_node( + options={ + 'plugin': zeroconf_plugin, + 'zeroconf-allow': l1.info['id'], + }, + ) + print("CARL: DONE l2 setup") + + # 0.3 Connect the nodes + l1.connect(l2) + + # 0.4 Optionally open a normal channel l1 -> l2 if we're testing that + if open_existing_normal_channel: + normal_sats = 200_000 + print("CARL: Opening normal channel btw l1 and l2") + l1.fundchannel(l2, amount=normal_sats) # This will mine a block! + print("CARL: DONE: Opening normal channel btw l1 and l2") + + # 0.5 Define a helper that syncs nodes to bitcoind and returns the blockheight + def synced_blockheight(nodes): + blockcount = bitcoind.rpc.getblockcount() + print(f"CARL: bitcoind blockcount {blockcount}") + wait_for(lambda: all([node.rpc.getinfo()['blockheight'] == blockcount for node in nodes])) + return blockcount + + + # 1. Open a zeoconf channel l1 -> l2 + zeroconf_sats = 1_000_000 + + # 1.1 Add funds to l1's wallet for the channel open + l1.fundwallet(zeroconf_sats * 2) # This will mine a block! + fundchannel_blockheight = synced_blockheight([l1, l2]) + print(f"CARL: blockheight before fundchannel: {fundchannel_blockheight}") + + # 1.2 Open the zeroconf channel + print("CARL: Opening zeroconf channel btw l1 and l2") + l1.rpc.fundchannel(l2.info['id'], zeroconf_sats, announce=False, mindepth=0) + print("CARL: DONE Opening zeroconf channel btw l1 and l2") + + # 1.2 Optionally generate a block to confirm the zeroconf channel if we're testing that + if confirm_zeroconf_channel: + bitcoind.generate_block(1) + l1.daemon.wait_for_log(r'Funding tx [a-f0-9]{64} depth 1 of 0') + l2.daemon.wait_for_log(r'Funding tx [a-f0-9]{64} depth 1 of 0') + + # 1.3 Wait until the channel becomes active + print("CARL: Waiting until channel becomes active") + num_channels = 4 if open_existing_normal_channel else 2 + wait_for(lambda: len(l1.rpc.listchannels()['channels']) == num_channels) + wait_for(lambda: len(l2.rpc.listchannels()['channels']) == num_channels) + print("CARL: DONE Waiting until channel becomes active") + + # 1.4 Print out channels as seen from both nodes + channels = l1.rpc.listchannels() + print(f"CARL: l1's channels after zeroconf channel open:\n{json.dumps(channels, indent=4)}") + channels = l2.rpc.listchannels() + print(f"CARL: l2's channels after zeroconf channel open:\n{json.dumps(channels, indent=4)}") + + + # 2. Have l2 generate an invoice to be paid + invoice_sats = 500_000 + inv = l2.rpc.invoice(invoice_sats * 1_000, "test", "test") + psecret = l1.rpc.decodepay(inv['bolt11'])['payment_secret'] + rhash = inv['payment_hash'] + + + # 3. Send a payment over the zeroconf channel + riskfactor=0 + + ## 3.1 Sanity check that we're at the block height we expect + payment_blockheight = synced_blockheight([l1, l2]) + print(f"CARL: blockheight before payment: {payment_blockheight}") + if confirm_zeroconf_channel: + assert(fundchannel_blockheight + 1 == payment_blockheight) + else: + assert(fundchannel_blockheight == payment_blockheight) + + if payment_method == "getroute_sendpay": + ## 3.2 Get a route to l2 + route = l1.rpc.getroute(l2.info['id'], 1, riskfactor)['route'] + print(f"CARL: l1's route to l2 for 1sat: {json.dumps(route, indent=4)}") + route = l1.rpc.getroute(l2.info['id'], invoice_sats * 1_000, riskfactor)['route'] + print(f"CARL: l1's route to l2 for invoice amount {invoice_sats * 1_000}msat: {json.dumps(route, indent=4)}") + + ## 3.3 Send the payment via SENDPAY + print("CARL: SENDPAY via l1's route to l2 for invoice") + l1.rpc.sendpay(route, rhash, payment_secret=psecret, bolt11=inv['bolt11']) + result = l1.rpc.waitsendpay(rhash) + assert(result.get('status') == 'complete') + print("CARL: DONE: SENDPAY via l1's route to l2 for invoice") + elif payment_method == "pay": + ## 3.2 Alternatively, send the payment via PAY + print("CARL: PAY via l1's route to l2 for invoice") + l1.rpc.pay(inv['bolt11'], riskfactor=riskfactor) + print("CARL: DONE: PAY via l1's route to l2 for invoice") + else: + raise