diff --git a/tests/setup.sh b/tests/setup.sh new file mode 100755 index 0000000..05d977e --- /dev/null +++ b/tests/setup.sh @@ -0,0 +1,75 @@ +#!/bin/bash +set -x +# Get the directory of the script +script_dir=$(dirname -- "$(readlink -f -- "$0")") + +# get latest version +version=$(curl -s https://api.github.com/repos/giovannizotta/circular/releases/latest | grep -o '"tag_name": *"[^"]*"' | grep -o '"[^"]*"$' | tr -d '"') + +get_platform_file_end() { + machine=$(uname -m) + kernel=$(uname -s) + + case $kernel in + Darwin) + case $machine in + x86_64) + echo 'darwin-amd64.tar.gz' + ;; + arm64) + echo 'darwin-arm64.tar.gz' + ;; + *) + echo "Unsupported release-architecture: $machine" >&2 + exit 1 + ;; + esac + ;; + Linux) + case $machine in + x86_64) + echo 'linux-amd64.tar.gz' + ;; + aarch64) + echo 'linux-arm64.tar.gz' + ;; + *) + echo "Unsupported release-architecture: $machine" >&2 + exit 1 + ;; + esac + ;; + *) + echo "Unsupported OS: $kernel" >&2 + exit 1 + ;; + esac +} +platform_file_end=$(get_platform_file_end) +archive_file=circular-$version-$platform_file_end + +github_url="https://github.com/giovannizotta/circular/releases/download/$version/$archive_file" + + +# Download the archive using curl +if ! curl -L "$github_url" -o "$script_dir/$archive_file"; then + echo "Error downloading the file from $github_url" >&2 + exit 1 +fi + +# Extract the contents +if [[ $archive_file == *.tar.gz ]]; then + if ! tar -xzvf "$script_dir/$archive_file" -C "$script_dir"; then + echo "Error extracting the contents of $archive_file" >&2 + exit 1 + fi +elif [[ $archive_file == *.zip ]]; then + if ! unzip "$script_dir/$archive_file" -d "$script_dir"; then + echo "Error extracting the contents of $archive_file" >&2 + exit 1 + fi +else + echo "Unknown archive format or unsupported file extension: $archive_file" >&2 + exit 1 +fi + diff --git a/tests/test_circular.py b/tests/test_circular.py new file mode 100644 index 0000000..f73a24c --- /dev/null +++ b/tests/test_circular.py @@ -0,0 +1,131 @@ +#!/usr/bin/python + +import logging +import time + +from pyln.testing.fixtures import * # noqa: F403 +from pyln.testing.utils import only_one, sync_blockheight, wait_for +from util import get_plugin # noqa: F401 + +LOGGER = logging.getLogger(__name__) + + +def test_circular(node_factory, bitcoind, get_plugin): # noqa: F811 + l1, l2, l3 = node_factory.get_nodes(3, opts={"allow-deprecated-apis": True}) + l1.fundwallet(10_000_000) + l2.fundwallet(10_000_000) + l3.fundwallet(10_000_000) + l1.rpc.connect(l2.info["id"], "localhost", l2.port) + l2.rpc.connect(l3.info["id"], "localhost", l3.port) + l3.rpc.connect(l1.info["id"], "localhost", l1.port) + l1.rpc.fundchannel(l2.info["id"], 1_000_000, mindepth=1, announce=True) + l2.rpc.fundchannel(l3.info["id"], 1_000_000, mindepth=1, announce=True) + l3.rpc.fundchannel(l1.info["id"], 1_000_000, mindepth=1, announce=True) + + bitcoind.generate_block(6) + sync_blockheight(bitcoind, [l1, l2, l3]) + + cl1 = l1.rpc.listpeerchannels(l2.info["id"])["channels"][0][ + "short_channel_id" + ] + cl2 = l2.rpc.listpeerchannels(l3.info["id"])["channels"][0][ + "short_channel_id" + ] + cl3 = l3.rpc.listpeerchannels(l1.info["id"])["channels"][0][ + "short_channel_id" + ] + + for n in [l1, l2, l3]: + for scid in [cl1, cl2, cl3]: + n.wait_channel_active(scid) + + # expected graph: + # 1M 0/1M 0/1M 0 + # l1 ------ l2 ------ l3 ------ l1 + # cl1 cl2 cl3 + + # wait for plugin gossip refresh + time.sleep(5) + + l1.rpc.call( + "plugin", + { + "subcommand": "start", + "plugin": str(get_plugin), + "circular-graph-refresh": 1, + "circular-peer-refresh": 1, + }, + ) + + time.sleep(5) + + l1.rpc.call( + "circular", + { + "inscid": cl3, + "outscid": cl1, + "amount": 100_000, + "splitamount": 25000, + "maxppm": 1000, + }, + ) + # expected graph: + # .9M .1M/.9M .1M/.9M .1M + # l1 ------ l2 ------ l3 ------ l1 + # cl1 cl2 cl3 + wait_for( + lambda: only_one(l1.rpc.listpeerchannels(l3.info["id"])["channels"])[ + "to_us_msat" + ] + == 100_000_000 + ) + + l1.rpc.call( + "circular-pull", + { + "inscid": cl3, + "maxoutppm": 1000, + "amount": 100_000, + "splitamount": 25000, + "maxppm": 1000, + }, + ) + # expected graph: + # .8M .2M/.8M .2M/.8M .2M + # l1 ------ l2 ------ l3 ------ l1 + # cl1 cl2 cl3 + wait_for( + lambda: only_one(l1.rpc.listpeerchannels(l3.info["id"])["channels"])[ + "to_us_msat" + ] + == 200_000_000 + ) + stats = l1.rpc.call("circular-stats") + LOGGER.info(f"circular-stats: {stats}") + assert stats["graph_stats"]["nodes"] == 3 + assert stats["graph_stats"]["channels"] == 6 + assert stats["graph_stats"]["active_channels"] == 6 + assert stats["graph_stats"]["liquid_channels"] == 6 + assert stats["graph_stats"]["max_htlc_channels"] == 6 + assert len(stats["successes"]) > 0 + + l1.rpc.call( + "circular-push", + { + "outscid": cl1, + "minoutppm": 0, + "amount": 100_000, + "splitamount": 25000, + "maxppm": 1000, + }, + ) + # expected graph: + # .7M .3M/.7M .3M/.7M .3M + # l1 ------ l2 ------ l3 ------ l1 + # cl1 cl2 cl3 + wait_for( + lambda: only_one(l1.rpc.listpeerchannels(l3.info["id"])["channels"])[ + "to_us_msat" + ] + >= 290_000_000 + ) diff --git a/tests/util.py b/tests/util.py new file mode 100644 index 0000000..7aea0bb --- /dev/null +++ b/tests/util.py @@ -0,0 +1,37 @@ +import logging +import random +import string +from pathlib import Path + +import pytest + +DOWNLOAD_PATH = Path.cwd() / "tests" / "circular" + + +@pytest.fixture +def get_plugin(directory): + if DOWNLOAD_PATH.is_file(): + return DOWNLOAD_PATH + else: + raise ValueError("No files were found.") + + +def generate_random_label(): + label_length = 8 + random_label = "".join( + random.choice(string.ascii_letters) for _ in range(label_length) + ) + return random_label + + +def generate_random_number(): + return random.randint(1, 20_000_000_000_000_00_000) + + +def pay_with_thread(rpc, bolt11): + LOGGER = logging.getLogger(__name__) + try: + rpc.dev_pay(bolt11, dev_use_shadow=False) + except Exception as e: + LOGGER.debug(f"holdinvoice: Error paying payment hash:{e}") + pass