From fe8fdf545cb20d9634b20ae769f083c2ba0686e5 Mon Sep 17 00:00:00 2001 From: daywalker90 Date: Fri, 6 Oct 2023 18:04:37 +0200 Subject: [PATCH] small test fixes and new test for force close --- plugins/holdinvoice/tests/holdinvoicetest.py | 130 ++++++++++++++++++- plugins/holdinvoice/tests/stresstest.py | 24 ++-- plugins/holdinvoice/tests/util.py | 6 +- 3 files changed, 143 insertions(+), 17 deletions(-) diff --git a/plugins/holdinvoice/tests/holdinvoicetest.py b/plugins/holdinvoice/tests/holdinvoicetest.py index a047e4015ba8..a2a44e164e44 100644 --- a/plugins/holdinvoice/tests/holdinvoicetest.py +++ b/plugins/holdinvoice/tests/holdinvoicetest.py @@ -1,7 +1,8 @@ #!/usr/bin/python from pyln.testing.fixtures import * -from pyln.testing.utils import only_one, mine_funding_to_announce +from pyln.testing.utils import only_one, wait_for +from pyln.client import Millisatoshi import secrets import threading import time @@ -15,7 +16,7 @@ def test_inputs(node_factory): node = node_factory.get_node( options={ 'important-plugin': os.path.join( - os.getcwd(), '../../target/release/holdinvoice' + os.getcwd(), 'target/release/holdinvoice' ) } ) @@ -156,7 +157,7 @@ def test_valid_hold_then_settle(node_factory, bitcoind): opts={ 'important-plugin': os.path.join( os.getcwd(), - '../../target/release/holdinvoice' + 'target/release/holdinvoice' ) } ) @@ -194,7 +195,7 @@ def test_valid_hold_then_settle(node_factory, bitcoind): assert result_settle["message"] == expected_message threading.Thread(target=pay_with_thread, args=( - l1.rpc, invoice["bolt11"])).start() + l1, invoice["bolt11"])).start() timeout = 10 start_time = time.time() @@ -247,12 +248,129 @@ def test_valid_hold_then_settle(node_factory, bitcoind): assert result_cancel_settled["message"] == expected_message +def test_fc_hold_then_settle(node_factory, bitcoind): + l1, l2 = node_factory.get_nodes(2, + opts={ + 'important-plugin': os.path.join( + os.getcwd(), + 'target/release/holdinvoice' + ) + } + ) + l1.rpc.connect(l2.info['id'], 'localhost', l2.port) + cl1, _ = l1.fundchannel(l2, 1_000_000) + + l1.wait_channel_active(cl1) + + fundsres = l2.rpc.call("listfunds")["outputs"] + total_funds = 0 + for utxo in fundsres: + total_funds += utxo["amount_msat"] + assert total_funds == Millisatoshi(0) + + invoice_amt = 10_000_000 + + invoice = l2.rpc.call("holdinvoice", { + "amount_msat": invoice_amt, + "description": "test_valid_hold_then_settle", + "label": generate_random_label(), + "cltv": 50} + ) + assert invoice is not None + assert isinstance(invoice, dict) is True + assert "payment_hash" in invoice + + threading.Thread(target=pay_with_thread, args=( + l1, invoice["bolt11"])).start() + + timeout = 10 + start_time = time.time() + + while time.time() - start_time < timeout: + result_lookup = l2.rpc.call("holdinvoicelookup", { + "payment_hash": invoice["payment_hash"]}) + assert result_lookup is not None + assert isinstance(result_lookup, dict) is True + + if result_lookup["state"] == "accepted": + break + else: + time.sleep(1) + + assert result_lookup["state"] == "accepted" + assert "htlc_expiry" in result_lookup + + # test that it's actually holding the htlcs + # and not letting them through + doublecheck = only_one(l2.rpc.call("listinvoices", { + "payment_hash": invoice["payment_hash"]})["invoices"]) + assert doublecheck["status"] == "unpaid" + + fc = l1.rpc.close(cl1, 1) + bitcoind.generate_block(1, wait_for_mempool=fc['txid']) + wait_for(lambda: l1.rpc.listpeerchannels(l2.info['id'])[ + 'channels'][0]["state"] == 'ONCHAIN') + wait_for(lambda: l2.rpc.listpeerchannels(l1.info['id'])[ + 'channels'][0]["state"] == 'ONCHAIN') + assert l2.channel_state(l1) == 'ONCHAIN' + + result_settle = l2.rpc.call("holdinvoicesettle", { + "payment_hash": invoice["payment_hash"]}) + assert result_settle is not None + assert isinstance(result_settle, dict) is True + assert result_settle["state"] == "settled" + + result_lookup = l2.rpc.call("holdinvoicelookup", { + "payment_hash": invoice["payment_hash"]}) + assert result_lookup is not None + assert isinstance(result_lookup, dict) is True + assert result_lookup["state"] == "settled" + assert "htlc_expiry" not in result_lookup + + # ask cln if the invoice is actually paid + # should not be necessary because lookup does this aswell + doublecheck = only_one(l2.rpc.call("listinvoices", { + "payment_hash": invoice["payment_hash"]})["invoices"]) + assert doublecheck["status"] == "paid" + + # payres = only_one(l1.rpc.call( + # "listpays", {"payment_hash": invoice["payment_hash"]})["pays"]) + # assert payres["status"] == "complete" + + fundsres = l2.rpc.call("listfunds")["outputs"] + total_funds = 0 + for utxo in fundsres: + total_funds += utxo["amount_msat"] + assert total_funds == Millisatoshi(0) + + for _ in range(15): + bitcoind.generate_block(5) + time.sleep(0.2) + + wait_for(lambda: any('ONCHAIN:All outputs resolved' in status_str + for status_str in l1.rpc.listpeerchannels( + l2.info['id'])['channels'][0]["status"])) + wait_for(lambda: any('ONCHAIN:All outputs resolved' in status_str + for status_str in l2.rpc.listpeerchannels( + l1.info['id'])['channels'][0]["status"])) + + payres = only_one(l1.rpc.call( + "listpays", {"payment_hash": invoice["payment_hash"]})["pays"]) + assert payres["status"] == "complete" + + fundsres = l2.rpc.call("listfunds")["outputs"] + total_funds = 0 + for utxo in fundsres: + total_funds += utxo["amount_msat"] + assert total_funds > Millisatoshi(0) + + def test_valid_hold_then_cancel(node_factory, bitcoind): l1, l2 = node_factory.get_nodes(2, opts={ 'important-plugin': os.path.join( os.getcwd(), - '../../target/release/holdinvoice' + 'target/release/holdinvoice' ) } ) @@ -282,7 +400,7 @@ def test_valid_hold_then_cancel(node_factory, bitcoind): assert "htlc_expiry" not in result_lookup threading.Thread(target=pay_with_thread, args=( - l1.rpc, invoice["bolt11"])).start() + l1, invoice["bolt11"])).start() timeout = 10 start_time = time.time() diff --git a/plugins/holdinvoice/tests/stresstest.py b/plugins/holdinvoice/tests/stresstest.py index ff2790833a04..59ba8624831b 100755 --- a/plugins/holdinvoice/tests/stresstest.py +++ b/plugins/holdinvoice/tests/stresstest.py @@ -1,7 +1,7 @@ #!/usr/bin/python from pyln.testing.fixtures import * -from pyln.testing.utils import only_one, mine_funding_to_announce +from pyln.testing.utils import wait_for, mine_funding_to_announce import time import threading import os @@ -10,9 +10,9 @@ # number of invoices to create, pay, hold and then cancel -num_iterations = 5 +num_iterations = 100 # seconds to hold the invoices with inflight htlcs -delay_seconds = 12 +delay_seconds = 120 # amount to be used in msat amount_msat = 1_000_100_000 @@ -38,16 +38,14 @@ def test_stress(node_factory, bitcoind): opts={ 'important-plugin': os.path.join( os.getcwd(), - '../../target/release/holdinvoice' + 'target/release/holdinvoice' ) } ) - LOGGER.info("holdinvoice: Nodes created") l1.fundwallet((amount_msat/1000)*num_iterations*20) LOGGER.info("holdinvoice: Funding secured") l1.rpc.connect(l2.info['id'], 'localhost', l2.port) - LOGGER.info("holdinvoice: Nodes connected") - for _ in range(int(num_iterations/10)+1): + for _ in range(int(num_iterations/7)+1): for _ in range(10): res = l1.rpc.fundchannel(l2.info['id'], int( (amount_msat*0.95)/1000), minconf=0) @@ -63,6 +61,10 @@ def test_stress(node_factory, bitcoind): LOGGER.info("holdinvoice: Funded 10 channels") l1.wait_channel_active(scid) + wait_for(lambda: all(channel['state'] == 'CHANNELD_NORMAL' + for channel in + l1.rpc.listpeerchannels(l2.info['id'])['channels'])) + payment_hashes = [] LOGGER.info( @@ -83,14 +85,15 @@ def test_stress(node_factory, bitcoind): # Pay the invoice using a separate thread threading.Thread(target=pay_with_thread, args=( - l1.rpc, invoice["bolt11"])).start() + l1, invoice["bolt11"])).start() time.sleep(1) except Exception as e: LOGGER.error("holdinvoice: Error executing command:", e) LOGGER.info(f"holdinvoice: Done paying {num_iterations} invoices!") # wait a little more for payments to arrive - time.sleep(10) + wait_for(lambda: lookup_stats(l2.rpc, payment_hashes) + ["accepted"] == num_iterations) stats = lookup_stats(l2.rpc, payment_hashes) LOGGER.info(stats) @@ -114,6 +117,9 @@ def test_stress(node_factory, bitcoind): f"holdinvoice: holdinvoice:Error cancelling " f"payment hash {payment_hash}:", e) + wait_for(lambda: lookup_stats(l2.rpc, payment_hashes) + ["canceled"] == num_iterations) + stats = lookup_stats(l2.rpc, payment_hashes) LOGGER.info(stats) assert stats["canceled"] == num_iterations diff --git a/plugins/holdinvoice/tests/util.py b/plugins/holdinvoice/tests/util.py index 5fa414db1f8d..34c22cc3e0c0 100644 --- a/plugins/holdinvoice/tests/util.py +++ b/plugins/holdinvoice/tests/util.py @@ -1,5 +1,6 @@ import string import random +import logging def generate_random_label(): @@ -14,8 +15,9 @@ def generate_random_number(): def pay_with_thread(rpc, bolt11): + LOGGER = logging.getLogger(__name__) try: - rpc.pay(bolt11) + rpc.dev_pay(bolt11, dev_use_shadow=False) except Exception as e: - print(f"holdinvoice: Error paying payment hash:{e}") + LOGGER.debug(f"holdinvoice: Error paying payment hash:{e}") pass