From 597c1fe0a0da742a3d267e8ca2349f4315dc89ca Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Fri, 10 Jan 2025 21:10:12 +0800 Subject: [PATCH 01/57] add fiber --- .github/workflows/fiber-testnet.yml | 45 +++ Makefile | 41 ++- download_fiber.py | 127 ++++++++ ...en_channels.py => fiber_testnet_prepare.sh | 0 framework/test_fiber.py | 2 +- .../devnet/send_payment/test_stop_mid_node.py | 64 ++++ test_cases/fiber/testnet/test_fiber.py | 307 +++++++++++------- 7 files changed, 475 insertions(+), 111 deletions(-) create mode 100644 .github/workflows/fiber-testnet.yml create mode 100644 download_fiber.py rename test_cases/fiber/testnet/test_fiber_open_channels.py => fiber_testnet_prepare.sh (100%) create mode 100644 test_cases/fiber/devnet/send_payment/test_stop_mid_node.py diff --git a/.github/workflows/fiber-testnet.yml b/.github/workflows/fiber-testnet.yml new file mode 100644 index 00000000..7a86f333 --- /dev/null +++ b/.github/workflows/fiber-testnet.yml @@ -0,0 +1,45 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: fiber testnet test + +on: + workflow_dispatch: + + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + + - name: Install dependencies + run: make prepare_fiber_testnet + + - name: Run tests + run: make fiber_testnet_test + +# - name: Setup upterm session +# if: always() +# uses: lhotari/action-upterm@v1 + + - name: Publish reports + if: failure() + uses: actions/upload-artifact@v4 + with: + name: jfoa-build-reports-${{ runner.os }} + path: ./report diff --git a/Makefile b/Makefile index e5ec1137..d36eeb75 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,26 @@ prepare: python3 -m download_ckb_light_client echo "install ckb cli" sh prepare.sh + +prepare_fiber_testnet: + python3 -m venv venv + . venv/bin/activate + python3 -m pip install --upgrade pip + pip install -r requirements.txt + echo "install ckb" + python3 -m download + + python3 -m download_ckb_light_client + echo "install ckb cli" + python3 -m download_fiber_testnet + git clone https://github.com/nervosnetwork/ckb-cli.git + cd ckb-cli + git checkout develop + make prod + cp target/release/ckb-cli ../source/ckb-cli + cd ../ + cp download/0.110.2/ckb-cli ./source/ckb-cli-old + develop_prepare: python3 -m venv venv . venv/bin/activate @@ -56,7 +76,11 @@ fiber_test_cases := \ test_cases/fiber/devnet/new_invoice \ test_cases/fiber/devnet/send_payment \ test_cases/fiber/devnet/shutdown_channel \ - test_cases/fiber/devnet/update_channel + test_cases/fiber/devnet/update_channel \ + test_cases/fiber/devnet/watch_tower + +fiber_testnet_cases := \ + test_cases/fiber/testnet test: @@ -73,6 +97,21 @@ test: exit 1; \ fi + +fiber_testnet_test: + @failed_cases=; \ + for test_case in $(fiber_testnet_cases); do \ + echo "Running tests for $$test_case"; \ + if ! bash test.sh "$$test_case"; then \ + echo "$$test_case" >> failed_test_cases.txt; \ + fi \ + done; \ + if [ -s failed_test_cases.txt ]; then \ + echo "Some test cases failed: $$(cat failed_test_cases.txt)"; \ + rm -f failed_test_cases.txt; \ + exit 1; \ + fi + fiber_test: @failed_cases=; \ for test_case in $(fiber_test_cases); do \ diff --git a/download_fiber.py b/download_fiber.py new file mode 100644 index 00000000..299cae7c --- /dev/null +++ b/download_fiber.py @@ -0,0 +1,127 @@ +""" +download.py + +This module downloads files from specified URLs and saves them locally. +""" + +import os +import platform +import tarfile +import zipfile +import requests +from tqdm import tqdm + + +versions = [ + "0.2.1" +] # Replace with your versions + +DOWNLOAD_DIR = "download/fiber" +SYSTEMS = { + "Windows": { + "url": "https://github.com/nervosnetwork/ckb-light-client/releases/download/v{version}/ckb-light-client_v{" + "version}-x86_64-windows.tar.gz", + "ext": ".tar.gz", + }, + "Linux": { + "x86_64": { + "url": "https://github.com/nervosnetwork/fiber/releases/download/v{version}/fnn_v{" + "version}-x86_64-linux.tar.gz", + "ext": ".tar.gz", + }, + }, + "Darwin": { + "x86_64": { + "url": "https://github.com/nervosnetwork/fiber/releases/download/v{version}/fnn_v{" + "version}-x86_64-darwin-portable.tar.gz", + "ext": ".tar.gz", + }, + "arm64": { + "url": "https://github.com/nervosnetwork/fiber/releases/download/v{version}/fnn_v{" + "version}-x86_64-darwin.tar.gz", + "ext": ".tar.gz", + }, + }, +} + + +def download_file(url, filename): + """ + Download a file from the specified URL and save it locally. + + Args: + url (str): The URL of the file to download. + filename (str): The name to save the downloaded file as. + + Raises: + requests.HTTPError: If an HTTP error occurs during the download. + + """ + print(f"Downloading URL: {url}") + response = requests.get(url, stream=True, timeout=30) + response.raise_for_status() + + total_size = int(response.headers.get("content-length", 0)) + block_size = 1024 # 1 Kibibyte + tq_file = tqdm(total=total_size, unit="iB", unit_scale=True) + + with open(filename, "wb") as file: + for data in response.iter_content(block_size): + tq_file.update(len(data)) + file.write(data) + tq_file.close() + + if total_size not in (0, total_size): + raise requests.HTTPError("ERROR: Something went wrong during the download.") + + +def extract_file(filename, path): + """ + Extract a compressed file to the specified path. + + Args: + filename (str): The name of the compressed file. + path (str): The path to extract the files to. + """ + temp_path = path + os.makedirs(temp_path, exist_ok=True) + + if filename.endswith(".zip"): + with zipfile.ZipFile(filename, "r") as zip_ref: + zip_ref.extractall(temp_path) + elif filename.endswith(".tar.gz"): + with tarfile.open(filename, "r:gz") as tar_ref: + tar_ref.extractall(temp_path) + # Change permission of ckb-light-client + for file in ["ckb-light-client"]: + filepath = os.path.join(path, file) + if os.path.isfile(filepath): + os.chmod(filepath, 0o755) + + +def download_ckb(ckb_version): + """ + download ckb from gitHub by ckb version + :param ckb_version: gitHub release ckb version + :return: None + """ + system = platform.system() + architecture = platform.machine() if system in ["Linux", "Darwin"] else "" + print( + f"system:{system},architecture:{architecture}".format( + system=system, architecture=architecture + ) + ) + url = SYSTEMS[system][architecture]["url"].format(version=ckb_version) + ext = SYSTEMS[system][architecture]["ext"] + + filename = f"ckb_light_client_v{ckb_version}_binary{ext}" + download_path = os.path.join(DOWNLOAD_DIR, ckb_version).split("-")[0] + os.makedirs(download_path, exist_ok=True) + + download_file(url, filename) + extract_file(filename, download_path) + + +for version in versions: + download_ckb(version) diff --git a/test_cases/fiber/testnet/test_fiber_open_channels.py b/fiber_testnet_prepare.sh similarity index 100% rename from test_cases/fiber/testnet/test_fiber_open_channels.py rename to fiber_testnet_prepare.sh diff --git a/framework/test_fiber.py b/framework/test_fiber.py index aa5bbbad..fd860a78 100644 --- a/framework/test_fiber.py +++ b/framework/test_fiber.py @@ -16,7 +16,7 @@ class FiberConfigPath(Enum): - V100_TESTNET = ("/source/template/fiber/config.yml.j2", "download/fiber/0.1.0/fnn") + V100_TESTNET = ("/source/template/fiber/config.yml.j2", "download/fiber/0.2.0/fnn") V100_DEV = ("/source/template/fiber/dev_config.yml.j2", "download/fiber/0.1.0/fnn") def __init__(self, fiber_config_path, fiber_bin_path): diff --git a/test_cases/fiber/devnet/send_payment/test_stop_mid_node.py b/test_cases/fiber/devnet/send_payment/test_stop_mid_node.py new file mode 100644 index 00000000..b0a09239 --- /dev/null +++ b/test_cases/fiber/devnet/send_payment/test_stop_mid_node.py @@ -0,0 +1,64 @@ +import time + +import pytest + +from framework.basic_fiber import FiberTest + + +class TestStopMidNode(FiberTest): + + @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/464") + def test_stop_mid_node(self): + """ + + Returns: + """ + fiber = self.start_new_fiber(self.generate_account(10000)) + self.fiber1.connect_peer(fiber) + fiber = self.start_new_fiber(self.generate_account(10000)) + self.fiber1.connect_peer(fiber) + self.open_channel(self.fibers[0], self.fibers[1], 1000 * 100000000, 1) + self.open_channel(self.fibers[1], self.fibers[2], 1000 * 100000000, 1) + self.open_channel(self.fibers[2], self.fibers[3], 1000 * 100000000, 1) + payment = self.fibers[0].get_client().send_payment( + { + "target_pubkey": self.fibers[3].get_client().node_info()["node_id"], + "amount": hex(10 * 100000000), + "keysend": True, + } + ) + self.wait_payment_state(self.fibers[0], payment["payment_hash"], "Success", 120) + + self.fibers[2].stop() + # fiber0 send payment to fiber3 + payment = self.fibers[0].get_client().send_payment( + { + "target_pubkey": self.fibers[3].get_client().node_info()["node_id"], + "amount": hex(10 * 100000000), + "keysend": True, + } + ) + self.wait_payment_state(self.fibers[0], payment["payment_hash"], "Failed", 120) + self.fibers[2].start() + time.sleep(10) + payment = self.fibers[0].get_client().send_payment( + { + "target_pubkey": self.fibers[3].get_client().node_info()["node_id"], + "amount": hex(10 * 100000000), + "keysend": True, + } + ) + self.wait_payment_state(self.fibers[0], payment["payment_hash"], "Success", 120) + + def test_0000(self): + self.start_new_mock_fiber("") + self.start_new_mock_fiber("") + payment = self.fibers[0].get_client().send_payment( + { + "target_pubkey": self.fibers[2].get_client().node_info()["node_id"], + "amount": hex(10 * 100000000), + "keysend": True, + "dry_run": True + } + ) + # self.wait_payment_state(self.fibers[0], payment["payment_hash"], "Success", 120) diff --git a/test_cases/fiber/testnet/test_fiber.py b/test_cases/fiber/testnet/test_fiber.py index 9a92054c..c81ab22f 100644 --- a/test_cases/fiber/testnet/test_fiber.py +++ b/test_cases/fiber/testnet/test_fiber.py @@ -5,6 +5,9 @@ from framework.test_fiber import Fiber, FiberConfigPath from framework.util import generate_random_preimage +# ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqt2yg5ctyv59wsrqk2d634rj6k7c8kdjycft39my +# ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq0n4lwpc3k24hnt75pmgpmg2hgack50wdgnlsp6m + class TestFiber(CkbTest): cryptapeFiber1 = FiberRPCClient("http://18.163.221.211:8227") @@ -38,20 +41,69 @@ def setup_class(cls): "8230", ) - # cls.fiber1.prepare() - # cls.fiber1.start() + cls.fiber1.prepare() + cls.fiber1.start() + + cls.fiber2.prepare() + cls.fiber2.start() + cls.fiber1.get_client().connect_peer( {"address": cls.cryptapeFiber1.node_info()["addresses"][0]} ) - # cls.fiber2.prepare() - # cls.fiber2.start() cls.fiber2.get_client().connect_peer( {"address": cls.cryptapeFiber2.node_info()["addresses"][0]} ) time.sleep(1) - def test_ckb(self): + @classmethod + def teardown_class(cls): + channels = cls.fiber1.get_client().list_channels({}) + for i in range(len(channels["channels"])): + channel = channels["channels"][i] + if channel["state"]["state_name"] != "CHANNEL_READY": + continue + cls.fiber1.get_client().shutdown_channel( + { + "channel_id": channel["channel_id"], + "close_script": { + "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + "hash_type": "type", + "args": cls.fiber1.get_account()["lock_arg"], + }, + "fee_rate": "0x3FC", + } + ) + wait_for_channel_state( + cls.fiber1.get_client(), cls.cryptapeFiber1.get_peer_id(), "CLOSED", 120 + ) + + channels = cls.fiber2.get_client().list_channels({}) + for i in range(len(channels["channels"])): + channel = channels["channels"][i] + if channel["state"]["state_name"] != "CHANNEL_READY": + continue + cls.fiber2.get_client().shutdown_channel( + { + "channel_id": channel["channel_id"], + "close_script": { + "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + "hash_type": "type", + "args": cls.fiber2.get_account()["lock_arg"], + }, + "fee_rate": "0x3FC", + } + ) + wait_for_channel_state( + cls.fiber2.get_client(), cls.cryptapeFiber2.get_peer_id(), "CLOSED", 120 + ) + + cls.fiber1.stop() + cls.fiber1.clean() + cls.fiber2.stop() + cls.fiber2.clean() + + def test_ckb_01(self): # open_channel temporary_channel_id = self.fiber1.get_client().open_channel( { @@ -79,130 +131,166 @@ def test_ckb(self): wait_for_channel_state( self.fiber2.get_client(), - self.cryptapeFiber1.get_peer_id(), + self.cryptapeFiber2.get_peer_id(), "CHANNEL_READY", 120, ) - + begin = time.time() # wait dry_run success - send_payment(self.fiber1.get_client(), self.fiber2.get_client(), 1000, 9999999) - send_payment(self.fiber2.get_client(), self.fiber1.get_client(), 1000, 9999999) - - def test_udt(self): - pass - - def test_03(self): - account = self.Ckb_cli.util_key_info_by_private_key( - self.Config.ACCOUNT_PRIVATE_1 + send_payment( + self.fiber1.get_client(), self.fiber2.get_client(), 1000, None, 10 * 60 ) - - # start 2 fiber with xudt - self.fiber1 = Fiber.init_by_port( - FiberConfigPath.V100_TESTNET, - self.Config.ACCOUNT_PRIVATE_1, - "fiber/node1", - "8228", - "8227", + fiber1_to_fiber2_time = time.time() + send_payment( + self.fiber2.get_client(), self.fiber1.get_client(), 1000, None, 10 * 60 ) - self.fiber2 = Fiber.init_by_port( - FiberConfigPath.V100_TESTNET, - self.Config.ACCOUNT_PRIVATE_2, - "fiber/node2", - "8229", - "8230", + fiber2_to_fiber1_time = time.time() + print("fiber1_to_fiber2 cost time:", fiber1_to_fiber2_time - begin) + print( + "fiber2_to_fiber1 cost time:", fiber2_to_fiber1_time - fiber1_to_fiber2_time ) - self.fiber1.get_client().graph_nodes() - self.fiber2.get_client().graph_nodes() - self.fiber1.get_client().graph_channels() - self.fiber2.get_client().graph_channels() - - def test_02(self): - account = self.Ckb_cli.util_key_info_by_private_key( - self.Config.ACCOUNT_PRIVATE_1 + def test_ckb_02(self): + self.fiber1.stop() + self.fiber2.stop() + self.fiber1.start() + self.fiber2.start() + begin = time.time() + send_payment( + self.fiber1.get_client(), self.fiber2.get_client(), 1000, None, 10 * 60 ) - - # start 2 fiber with xudt - self.fiber1 = Fiber.init_by_port( - FiberConfigPath.V100, - self.Config.ACCOUNT_PRIVATE_1, - "fiber/node1", - "8228", - "8227", + fiber1_to_fiber2_time = time.time() + send_payment( + self.fiber2.get_client(), self.fiber1.get_client(), 1000, None, 10 * 60 ) - self.fiber2 = Fiber.init_by_port( - FiberConfigPath.V100, - self.Config.ACCOUNT_PRIVATE_2, - "fiber/node2", - "8229", - "8230", + fiber2_to_fiber1_time = time.time() + print("fiber1_to_fiber2 cost time:", fiber1_to_fiber2_time - begin) + print( + "fiber2_to_fiber1 cost time:", fiber2_to_fiber1_time - fiber1_to_fiber2_time ) - self.fiber1.prepare() - self.fiber1.start() - self.fiber2.prepare() - self.fiber2.start() - # connect 2 fiber - self.fiber1.connect_peer(self.fiber2) - # open channel + def test_udt(self): + funding_udt_type_script = { + "code_hash": "0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a", + "hash_type": "type", + "args": "0x878fcc6f1f08d48e87bb1c3b3d5083f23f8a39c5d5c764f253b55b998526439b", + } temporary_channel_id = self.fiber1.get_client().open_channel( { - "peer_id": self.fiber2.get_peer_id(), - "funding_amount": hex(1000 * 100000000), + "peer_id": self.cryptapeFiber1.get_peer_id(), + "funding_amount": hex(20 * 100000000), "public": True, - # "tlc_fee_proportional_millionths": "0x4B0", + "funding_udt_type_script": funding_udt_type_script, } ) - wait_for_channel_state( - self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY", 120 - ) - # transfer - self.fiber1.get_client().graph_channels() - self.fiber1.get_client().graph_nodes() - payment_preimage = generate_random_preimage() - invoice_balance = 100 * 100000000 - invoice = self.fiber2.get_client().new_invoice( + + temporary_channel_id = self.fiber2.get_client().open_channel( { - "amount": hex(invoice_balance), - "currency": "Fibb", - "description": "test invoice generated by node2", - "expiry": "0xe10", - "final_cltv": "0x28", - "payment_preimage": payment_preimage, - "hash_algorithm": "sha256", + "peer_id": self.cryptapeFiber2.get_peer_id(), + "funding_amount": hex(20 * 100000000), + "public": True, + "funding_udt_type_script": funding_udt_type_script, } ) - before_channel = self.fiber1.get_client().list_channels({}) + wait_for_channel_state( + self.fiber1.get_client(), + self.cryptapeFiber1.get_peer_id(), + "CHANNEL_READY", + 120, + ) - self.fiber1.get_client().send_payment( - { - "invoice": invoice["invoice_address"], - } + wait_for_channel_state( + self.fiber2.get_client(), + self.cryptapeFiber2.get_peer_id(), + "CHANNEL_READY", + 120, + ) + begin = time.time() + # wait dry_run success + send_payment( + self.fiber1.get_client(), + self.cryptapeFiber2, + 100000, + funding_udt_type_script, + 10 * 60, ) - time.sleep(10) - after_channel = self.fiber1.get_client().list_channels({}) - assert ( - int(before_channel["channels"][0]["local_balance"], 16) - - int(after_channel["channels"][0]["local_balance"], 16) - == invoice_balance + send_payment( + self.fiber2.get_client(), + self.cryptapeFiber1, + 100000, + funding_udt_type_script, + 10 * 60, ) - channels = self.fiber1.get_client().list_channels( - {"peer_id": self.fiber2.get_peer_id()} + send_payment( + self.fiber1.get_client(), + self.fiber2.get_client(), + 1000, + funding_udt_type_script, + 10 * 60, ) - N1N2_CHANNEL_ID = channels["channels"][0]["channel_id"] - # shut down - self.fiber1.get_client().shutdown_channel( - { - "channel_id": N1N2_CHANNEL_ID, - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": account["lock_arg"], - }, - "fee_rate": "0x3FC", - } + fiber1_to_fiber2_time = time.time() + send_payment( + self.fiber2.get_client(), + self.fiber1.get_client(), + 1000, + funding_udt_type_script, + 10 * 60, + ) + fiber2_to_fiber1_time = time.time() + print("fiber1_to_fiber2 cost time:", fiber1_to_fiber2_time - begin) + print( + "fiber2_to_fiber1 cost time:", fiber2_to_fiber1_time - fiber1_to_fiber2_time + ) + + def test_udt_02(self): + funding_udt_type_script = { + "code_hash": "0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a", + "hash_type": "type", + "args": "0x878fcc6f1f08d48e87bb1c3b3d5083f23f8a39c5d5c764f253b55b998526439b", + } + self.fiber1.stop() + self.fiber1.start() + self.fiber2.stop() + self.fiber2.start() + begin = time.time() + # wait dry_run success + + send_payment( + self.fiber1.get_client(), + self.cryptapeFiber2, + 100000, + funding_udt_type_script, + 10 * 60, + ) + send_payment( + self.fiber2.get_client(), + self.cryptapeFiber1, + 100000, + funding_udt_type_script, + 10 * 60, + ) + + send_payment( + self.fiber1.get_client(), + self.fiber2.get_client(), + 1000, + funding_udt_type_script, + 10 * 60, + ) + fiber1_to_fiber2_time = time.time() + send_payment( + self.fiber2.get_client(), + self.fiber1.get_client(), + 1000, + funding_udt_type_script, + 10 * 60, + ) + fiber2_to_fiber1_time = time.time() + print("fiber1_to_fiber2 cost time:", fiber1_to_fiber2_time - begin) + print( + "fiber2_to_fiber1 cost time:", fiber2_to_fiber1_time - fiber1_to_fiber2_time ) @@ -211,12 +299,12 @@ def send_payment( ): try_times = 0 payment = None - for i in range(try_times): + for i in range(wait_times): try: - payment = fiber1.get_client().send_payment( + payment = fiber1.send_payment( { "amount": hex(amount), - "target_pubkey": fiber2.get_client().node_info()["node_id"], + "target_pubkey": fiber2.node_info()["node_id"], "keysend": True, "udt_type_script": udt, } @@ -227,11 +315,12 @@ def send_payment( print(f"try count: {i}") time.sleep(1) continue - for i in range(wait_times): time.sleep(1) try: - payment = fiber1.get_payment(payment["payment_hash"]) + payment = fiber1.get_payment({"payment_hash": payment["payment_hash"]}) + if payment["status"] == "Failed": + return send_payment(fiber1, fiber2, amount, udt, wait_times - i) if payment["status"] == "Success": print("payment success") return payment @@ -245,7 +334,7 @@ def send_payment( def wait_for_channel_state(client, peer_id, expected_state, timeout=120): """Wait for a channel to reach a specific state.""" for _ in range(timeout): - channels = client.list_channels({"peer_id": peer_id}) + channels = client.list_channels({"peer_id": peer_id, "include_closed": True}) if channels["channels"][0]["state"]["state_name"] == expected_state: print(f"Channel reached expected state: {expected_state}") return channels["channels"][0]["channel_id"] From 4205f07e2eeefaaf08e94dd9dc3840b44892deca Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Fri, 10 Jan 2025 21:12:31 +0800 Subject: [PATCH 02/57] format code --- download_fiber.py | 4 +- .../devnet/send_payment/test_stop_mid_node.py | 66 ++++++++++++------- 2 files changed, 42 insertions(+), 28 deletions(-) diff --git a/download_fiber.py b/download_fiber.py index 299cae7c..539a0b32 100644 --- a/download_fiber.py +++ b/download_fiber.py @@ -12,9 +12,7 @@ from tqdm import tqdm -versions = [ - "0.2.1" -] # Replace with your versions +versions = ["0.2.0", "0.2.1"] # Replace with your versions DOWNLOAD_DIR = "download/fiber" SYSTEMS = { diff --git a/test_cases/fiber/devnet/send_payment/test_stop_mid_node.py b/test_cases/fiber/devnet/send_payment/test_stop_mid_node.py index b0a09239..e4e076a6 100644 --- a/test_cases/fiber/devnet/send_payment/test_stop_mid_node.py +++ b/test_cases/fiber/devnet/send_payment/test_stop_mid_node.py @@ -20,45 +20,61 @@ def test_stop_mid_node(self): self.open_channel(self.fibers[0], self.fibers[1], 1000 * 100000000, 1) self.open_channel(self.fibers[1], self.fibers[2], 1000 * 100000000, 1) self.open_channel(self.fibers[2], self.fibers[3], 1000 * 100000000, 1) - payment = self.fibers[0].get_client().send_payment( - { - "target_pubkey": self.fibers[3].get_client().node_info()["node_id"], - "amount": hex(10 * 100000000), - "keysend": True, - } + payment = ( + self.fibers[0] + .get_client() + .send_payment( + { + "target_pubkey": self.fibers[3].get_client().node_info()["node_id"], + "amount": hex(10 * 100000000), + "keysend": True, + } + ) ) self.wait_payment_state(self.fibers[0], payment["payment_hash"], "Success", 120) self.fibers[2].stop() # fiber0 send payment to fiber3 - payment = self.fibers[0].get_client().send_payment( - { - "target_pubkey": self.fibers[3].get_client().node_info()["node_id"], - "amount": hex(10 * 100000000), - "keysend": True, - } + payment = ( + self.fibers[0] + .get_client() + .send_payment( + { + "target_pubkey": self.fibers[3].get_client().node_info()["node_id"], + "amount": hex(10 * 100000000), + "keysend": True, + } + ) ) self.wait_payment_state(self.fibers[0], payment["payment_hash"], "Failed", 120) self.fibers[2].start() time.sleep(10) - payment = self.fibers[0].get_client().send_payment( - { - "target_pubkey": self.fibers[3].get_client().node_info()["node_id"], - "amount": hex(10 * 100000000), - "keysend": True, - } + payment = ( + self.fibers[0] + .get_client() + .send_payment( + { + "target_pubkey": self.fibers[3].get_client().node_info()["node_id"], + "amount": hex(10 * 100000000), + "keysend": True, + } + ) ) self.wait_payment_state(self.fibers[0], payment["payment_hash"], "Success", 120) def test_0000(self): self.start_new_mock_fiber("") self.start_new_mock_fiber("") - payment = self.fibers[0].get_client().send_payment( - { - "target_pubkey": self.fibers[2].get_client().node_info()["node_id"], - "amount": hex(10 * 100000000), - "keysend": True, - "dry_run": True - } + payment = ( + self.fibers[0] + .get_client() + .send_payment( + { + "target_pubkey": self.fibers[2].get_client().node_info()["node_id"], + "amount": hex(10 * 100000000), + "keysend": True, + "dry_run": True, + } + ) ) # self.wait_payment_state(self.fibers[0], payment["payment_hash"], "Success", 120) From d1098f2e9171eec714d4282ad425d2868453ffeb Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Fri, 10 Jan 2025 21:15:57 +0800 Subject: [PATCH 03/57] update prepare --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d36eeb75..c992fa5b 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ prepare_fiber_testnet: python3 -m download_ckb_light_client echo "install ckb cli" - python3 -m download_fiber_testnet + python3 -m download_fiber git clone https://github.com/nervosnetwork/ckb-cli.git cd ckb-cli git checkout develop From 051339af97f8c39fe19350d3d91fbcf7ee5c7977 Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Fri, 10 Jan 2025 21:23:31 +0800 Subject: [PATCH 04/57] update make file --- Makefile | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Makefile b/Makefile index c992fa5b..b5d6d8b7 100644 --- a/Makefile +++ b/Makefile @@ -23,12 +23,7 @@ prepare_fiber_testnet: python3 -m download_ckb_light_client echo "install ckb cli" python3 -m download_fiber - git clone https://github.com/nervosnetwork/ckb-cli.git - cd ckb-cli - git checkout develop - make prod - cp target/release/ckb-cli ../source/ckb-cli - cd ../ + cp download/0.119.0/ckb-cli ./source/ckb-cli cp download/0.110.2/ckb-cli ./source/ckb-cli-old develop_prepare: From a49866b415ccadf9743fbc120a2b45205574a541 Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Fri, 10 Jan 2025 21:38:06 +0800 Subject: [PATCH 05/57] update test --- test_cases/fiber/testnet/test_fiber.py | 28 +++++++++++++++----------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/test_cases/fiber/testnet/test_fiber.py b/test_cases/fiber/testnet/test_fiber.py index c81ab22f..a6b65ca3 100644 --- a/test_cases/fiber/testnet/test_fiber.py +++ b/test_cases/fiber/testnet/test_fiber.py @@ -4,6 +4,10 @@ from framework.fiber_rpc import FiberRPCClient from framework.test_fiber import Fiber, FiberConfigPath from framework.util import generate_random_preimage +import logging + +LOGGER = logging.getLogger(__name__) + # ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqt2yg5ctyv59wsrqk2d634rj6k7c8kdjycft39my # ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq0n4lwpc3k24hnt75pmgpmg2hgack50wdgnlsp6m @@ -145,9 +149,9 @@ def test_ckb_01(self): self.fiber2.get_client(), self.fiber1.get_client(), 1000, None, 10 * 60 ) fiber2_to_fiber1_time = time.time() - print("fiber1_to_fiber2 cost time:", fiber1_to_fiber2_time - begin) - print( - "fiber2_to_fiber1 cost time:", fiber2_to_fiber1_time - fiber1_to_fiber2_time + LOGGER.info(f"fiber1_to_fiber2 cost time: {fiber1_to_fiber2_time - begin}") + LOGGER.info( + f"fiber2_to_fiber1 cost time: {fiber2_to_fiber1_time - fiber1_to_fiber2_time}" ) def test_ckb_02(self): @@ -164,9 +168,9 @@ def test_ckb_02(self): self.fiber2.get_client(), self.fiber1.get_client(), 1000, None, 10 * 60 ) fiber2_to_fiber1_time = time.time() - print("fiber1_to_fiber2 cost time:", fiber1_to_fiber2_time - begin) - print( - "fiber2_to_fiber1 cost time:", fiber2_to_fiber1_time - fiber1_to_fiber2_time + LOGGER.info(f"fiber1_to_fiber2 cost time: {fiber1_to_fiber2_time - begin}") + LOGGER.info( + f"fiber2_to_fiber1 cost time: {fiber2_to_fiber1_time - fiber1_to_fiber2_time}" ) def test_udt(self): @@ -239,9 +243,9 @@ def test_udt(self): 10 * 60, ) fiber2_to_fiber1_time = time.time() - print("fiber1_to_fiber2 cost time:", fiber1_to_fiber2_time - begin) - print( - "fiber2_to_fiber1 cost time:", fiber2_to_fiber1_time - fiber1_to_fiber2_time + LOGGER.info(f"fiber1_to_fiber2 cost time: {fiber1_to_fiber2_time - begin}") + LOGGER.info( + f"fiber2_to_fiber1 cost time: {fiber2_to_fiber1_time - fiber1_to_fiber2_time}" ) def test_udt_02(self): @@ -288,9 +292,9 @@ def test_udt_02(self): 10 * 60, ) fiber2_to_fiber1_time = time.time() - print("fiber1_to_fiber2 cost time:", fiber1_to_fiber2_time - begin) - print( - "fiber2_to_fiber1 cost time:", fiber2_to_fiber1_time - fiber1_to_fiber2_time + LOGGER.info(f"fiber1_to_fiber2 cost time: {fiber1_to_fiber2_time - begin}") + LOGGER.info( + f"fiber2_to_fiber1 cost time: {fiber2_to_fiber1_time - fiber1_to_fiber2_time}" ) From 20ad06565cec8f6da183f804671371752ee140db Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Mon, 13 Jan 2025 10:18:18 +0800 Subject: [PATCH 06/57] support develop --- .github/workflows/fiber-testnet.yml | 29 ++++++++++++++++++++++++++--- Makefile | 15 +++++++++++++++ develop_fiber.sh | 22 ++++++++++++++++++++++ 3 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 develop_fiber.sh diff --git a/.github/workflows/fiber-testnet.yml b/.github/workflows/fiber-testnet.yml index 7a86f333..b18e3306 100644 --- a/.github/workflows/fiber-testnet.yml +++ b/.github/workflows/fiber-testnet.yml @@ -4,8 +4,19 @@ name: fiber testnet test on: - workflow_dispatch: - + schedule: + - cron: '0 0 */2 * *' # This triggers the workflow daily at midnight UTC + workflow_dispatch: # Allows the workflow to be manually triggered + inputs: + GitUrl: + description: 'fiber -git url' + default: 'https://github.com/nervosnetwork/fiber.git' + GitBranch: + description: 'fiber -git branch' + default: 'develop' + BuildFIBER: + description: 'build fiber' + default: 'true' permissions: contents: read @@ -22,13 +33,25 @@ jobs: with: python-version: "3.10" + - name: Print Input Values + run: | + echo "**Input Values:**" + echo " GitUrl: ${{ github.event.inputs.GitUrl }}" + echo " GitBranch: ${{ github.event.inputs.GitBranch }}" + echo " BuildCKbCLI: ${{ github.event.inputs.BuildFIBER }}" + + - name: Install dependencies run: | python -m pip install --upgrade pip if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Install dependencies - run: make prepare_fiber_testnet + run: make prepare_develop_testnet + env: + GitUrl: '${{ github.event.inputs.GitUrl }}' + GitBranch: '${{ github.event.inputs.GitBranch }}' + BuildFIBER: '${{ github.event.inputs.BuildFIBER }}' - name: Run tests run: make fiber_testnet_test diff --git a/Makefile b/Makefile index b5d6d8b7..27beb567 100644 --- a/Makefile +++ b/Makefile @@ -26,6 +26,21 @@ prepare_fiber_testnet: cp download/0.119.0/ckb-cli ./source/ckb-cli cp download/0.110.2/ckb-cli ./source/ckb-cli-old +prepare_develop_testnet: + python3 -m venv venv + . venv/bin/activate + python3 -m pip install --upgrade pip + pip install -r requirements.txt + echo "install ckb" + python3 -m download + + echo "install fiber" + python3 -m download_fiber + cp download/0.119.0/ckb-cli ./source/ckb-cli + cp download/0.110.2/ckb-cli ./source/ckb-cli-old + bash develop_fiber.sh + + develop_prepare: python3 -m venv venv . venv/bin/activate diff --git a/develop_fiber.sh b/develop_fiber.sh new file mode 100644 index 00000000..28752ce4 --- /dev/null +++ b/develop_fiber.sh @@ -0,0 +1,22 @@ +set -e +# git clone https://github.com/nervosnetwork/ckb-cli.git +# cd ckb-cli +# git checkout pkg/v1.7.0 +# make prod +# cp target/release/ckb-cli ../source/ckb-cli +# cd ../ +DEFAULT_FIBER_BRANCH="develop" +DEFAULT_FIBER_URL="https://github.com/nervosnetwork/fiber.git" +DEFAULT_BUILD_FIBER=false + + +GitFIBERBranch="${GitBranch:-$DEFAULT_FIBER_BRANCH}" +GitFIBERUrl="${GitUrl:-$DEFAULT_FIBER_URL}" +BUILD_FIBER="${BuildFIBER:-$DEFAULT_BUILD_FIBER}" +if [ "$BUILD_FIBER" == "true" ]; then + git clone -b $GitFIBERBranch $GitFIBERUrl + cd fiber + crago build + cp target/debug/fnn ../download/fiber/0.2.0/fnn + cd ../ +fi \ No newline at end of file From b5094456843819b1c27bf26862f9d361f3388f4c Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Mon, 13 Jan 2025 10:25:52 +0800 Subject: [PATCH 07/57] fix build failed --- develop_fiber.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/develop_fiber.sh b/develop_fiber.sh index 28752ce4..d5a68f69 100644 --- a/develop_fiber.sh +++ b/develop_fiber.sh @@ -16,7 +16,7 @@ BUILD_FIBER="${BuildFIBER:-$DEFAULT_BUILD_FIBER}" if [ "$BUILD_FIBER" == "true" ]; then git clone -b $GitFIBERBranch $GitFIBERUrl cd fiber - crago build + cargo build cp target/debug/fnn ../download/fiber/0.2.0/fnn cd ../ fi \ No newline at end of file From 4454eb8cc410844b3112f0175299fb7176b88cb1 Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Mon, 13 Jan 2025 16:11:35 +0800 Subject: [PATCH 08/57] add compatibility test --- framework/basic_fiber.py | 32 ++++++++++--- framework/test_fiber.py | 23 ++++++++- .../fiber/devnet/compatibility/test_data.py | 48 +++++++++++++++++++ .../fiber/devnet/compatibility/test_p2p.py | 24 ++++++++++ .../devnet/send_payment/test_stop_mid_node.py | 19 +------- test_cases/fiber/testnet/test_fiber.py | 4 +- 6 files changed, 122 insertions(+), 28 deletions(-) create mode 100644 test_cases/fiber/devnet/compatibility/test_data.py create mode 100644 test_cases/fiber/devnet/compatibility/test_p2p.py diff --git a/framework/basic_fiber.py b/framework/basic_fiber.py index 8a642a6c..10132b67 100644 --- a/framework/basic_fiber.py +++ b/framework/basic_fiber.py @@ -72,14 +72,14 @@ def setup_method(cls, method): cls.fibers = [] cls.new_fibers = [] cls.fiber1 = Fiber.init_by_port( - FiberConfigPath.V100_DEV, + FiberConfigPath.CURRENT_DEV, cls.account1_private_key, "fiber/node1", "8228", "8227", ) cls.fiber2 = Fiber.init_by_port( - FiberConfigPath.V100_DEV, + FiberConfigPath.CURRENT_DEV, cls.account2_private_key, "fiber/node2", "8229", @@ -206,10 +206,15 @@ def generate_account( ) return account_private_key - def start_new_mock_fiber(self, account_private_key, config=None): + def start_new_mock_fiber( + self, + account_private_key, + config=None, + fiber_version=FiberConfigPath.CURRENT_DEV, + ): i = len(self.new_fibers) fiber = Fiber.init_by_port( - FiberConfigPath.V100_DEV, + fiber_version, account_private_key, f"fiber/node{3 + i}", str(8251 + i), @@ -220,7 +225,12 @@ def start_new_mock_fiber(self, account_private_key, config=None): self.fibers.append(fiber) return fiber - def start_new_fiber(self, account_private_key, config=None): + def start_new_fiber( + self, + account_private_key, + config=None, + fiber_version=FiberConfigPath.CURRENT_DEV, + ): if self.debug: self.logger.debug("=================start mock fiber ==================") return self.start_new_mock_fiber(account_private_key, config) @@ -240,7 +250,7 @@ def start_new_fiber(self, account_private_key, config=None): i = len(self.new_fibers) # start fiber3 fiber = Fiber.init_by_port( - FiberConfigPath.V100_DEV, + fiber_version, account_private_key, f"fiber/node{3 + i}", str(8251 + i), @@ -356,6 +366,16 @@ def open_channel( # assert channels["channels"][0]["local_balance"] == hex(fiber1_balance) # assert channels["channels"][0]["remote_balance"] == hex(fiber2_balance) + def send_payment(self, fiber1, fiber2, amount): + payment = fiber1.get_client().send_payment( + { + "target_pubkey": fiber2.get_client().node_info()["node_id"], + "amount": hex(amount), + "keysend": True, + } + ) + self.wait_payment_state(fiber1, payment["payment_hash"], "Success") + def get_account_script(self, account_private_key): account1 = self.Ckb_cli.util_key_info_by_private_key(account_private_key) return { diff --git a/framework/test_fiber.py b/framework/test_fiber.py index fd860a78..1bbb5a90 100644 --- a/framework/test_fiber.py +++ b/framework/test_fiber.py @@ -16,8 +16,22 @@ class FiberConfigPath(Enum): - V100_TESTNET = ("/source/template/fiber/config.yml.j2", "download/fiber/0.2.0/fnn") - V100_DEV = ("/source/template/fiber/dev_config.yml.j2", "download/fiber/0.1.0/fnn") + CURRENT_DEV = ( + "/source/template/fiber/dev_config.yml.j2", + "download/fiber/0.2.0/fnn", + ) + CURRENT_TESTNET = ( + "/source/template/fiber/config.yml.j2", + "download/fiber/0.2.0/fnn", + ) + + V030_TESTNET = ("/source/template/fiber/config.yml.j2", "download/fiber/0.3.0/fnn") + V020_TESTNET = ("/source/template/fiber/config.yml.j2", "download/fiber/0.3.0/fnn") + V010_TESTNET = ("/source/template/fiber/config.yml.j2", "download/fiber/0.3.0/fnn") + + V030_DEV = ("/source/template/fiber/dev_config.yml.j2", "download/fiber/0.3.0/fnn") + V020_DEV = ("/source/template/fiber/dev_config.yml.j2", "download/fiber/0.2.0/fnn") + V010_DEV = ("/source/template/fiber/dev_config.yml.j2", "download/fiber/0.1.0/fnn") def __init__(self, fiber_config_path, fiber_bin_path): self.fiber_config_path = fiber_config_path @@ -137,6 +151,11 @@ def read_ckb_key(self): self.account_private = f"0x{key}" return self.account_private + def migration(self): + run_command( + f"echo YES | RUST_LOG=info,fnn=debug {get_project_root()}/{self.fiber_config_enum.fiber_bin_path} --migrate -c {self.tmp_path}/config.yml -d {self.tmp_path}" + ) + def start(self, node=None): # env_map = dict(os.environ) # Make a copy of the current environment # if node: diff --git a/test_cases/fiber/devnet/compatibility/test_data.py b/test_cases/fiber/devnet/compatibility/test_data.py new file mode 100644 index 00000000..1a467c63 --- /dev/null +++ b/test_cases/fiber/devnet/compatibility/test_data.py @@ -0,0 +1,48 @@ +import time + +from framework.basic_fiber import FiberTest +from framework.test_fiber import FiberConfigPath + + +class TestData(FiberTest): + + def test_old_fiber(self): + """ + 1. start fiber 0.2.0 + 2. open_channel with fiber + 3. stop fiber + 4. migration and restart fiber 0.3.0 + 5. send_payment + 6. shutdown_channel + Returns: + + """ + # 1. start fiber 0.2.0 + old_fiber_1 = self.start_new_fiber( + self.generate_account(10000), fiber_version=FiberConfigPath.V020_DEV + ) + old_fiber_2 = self.start_new_fiber( + self.generate_account(10000), fiber_version=FiberConfigPath.V020_DEV + ) + old_fiber_1.connect_peer(old_fiber_2) + time.sleep(1) + # 2. open_channel with fiber + self.open_channel( + old_fiber_1, old_fiber_2, 1000 * 100000000, 1000 * 100000000, 1000, 1000 + ) + self.send_payment(old_fiber_1, old_fiber_2, 100) + + # 3. stop fiber + old_fiber_1.stop() + old_fiber_2.stop() + + # 4. migration and restart fiber 0.3.0 + old_fiber_1.fiber_config_enum = FiberConfigPath.CURRENT_DEV + old_fiber_2.fiber_config_enum = FiberConfigPath.CURRENT_DEV + old_fiber_1.migration() + old_fiber_1.start() + old_fiber_2.migration() + old_fiber_2.start() + + # 5. send_payment + self.send_payment(old_fiber_1, old_fiber_2, 100) diff --git a/test_cases/fiber/devnet/compatibility/test_p2p.py b/test_cases/fiber/devnet/compatibility/test_p2p.py new file mode 100644 index 00000000..bbad00d0 --- /dev/null +++ b/test_cases/fiber/devnet/compatibility/test_p2p.py @@ -0,0 +1,24 @@ +import time + +from framework.basic_fiber import FiberTest +from framework.test_fiber import FiberConfigPath + + +class TestP2p(FiberTest): + + def test_old_fiber(self): + old_fiber = self.start_new_fiber( + self.generate_account(10000), fiber_version=FiberConfigPath.V020_DEV + ) + old_fiber.connect_peer(self.fiber1) + time.sleep(1) + self.open_channel( + self.fiber1, + old_fiber, + 1000 * 100000000, + 1000 * 100000000, + 1000, + 1000, + ) + self.send_payment(self.fiber1, old_fiber, 100) + self.send_payment(old_fiber, self.fiber1, 100) diff --git a/test_cases/fiber/devnet/send_payment/test_stop_mid_node.py b/test_cases/fiber/devnet/send_payment/test_stop_mid_node.py index e4e076a6..351e6e8e 100644 --- a/test_cases/fiber/devnet/send_payment/test_stop_mid_node.py +++ b/test_cases/fiber/devnet/send_payment/test_stop_mid_node.py @@ -60,21 +60,4 @@ def test_stop_mid_node(self): } ) ) - self.wait_payment_state(self.fibers[0], payment["payment_hash"], "Success", 120) - - def test_0000(self): - self.start_new_mock_fiber("") - self.start_new_mock_fiber("") - payment = ( - self.fibers[0] - .get_client() - .send_payment( - { - "target_pubkey": self.fibers[2].get_client().node_info()["node_id"], - "amount": hex(10 * 100000000), - "keysend": True, - "dry_run": True, - } - ) - ) - # self.wait_payment_state(self.fibers[0], payment["payment_hash"], "Success", 120) + self.wait_payment_state(self.fibers[0], payment["payment_hash"], "Success", 120) \ No newline at end of file diff --git a/test_cases/fiber/testnet/test_fiber.py b/test_cases/fiber/testnet/test_fiber.py index a6b65ca3..3e2881f1 100644 --- a/test_cases/fiber/testnet/test_fiber.py +++ b/test_cases/fiber/testnet/test_fiber.py @@ -30,7 +30,7 @@ class TestFiber(CkbTest): def setup_class(cls): print("\nSetup TestClass2") cls.fiber1 = Fiber.init_by_port( - FiberConfigPath.V100_TESTNET, + FiberConfigPath.CURRENT_TESTNET, cls.ACCOUNT_PRIVATE_1, "fiber/node1", "8228", @@ -38,7 +38,7 @@ def setup_class(cls): ) cls.fiber2 = Fiber.init_by_port( - FiberConfigPath.V100_TESTNET, + FiberConfigPath.CURRENT_TESTNET, cls.ACCOUNT_PRIVATE_2, "fiber/node2", "8229", From 5f00e143f9331c61eeb2a05d1c9d66ce2395f4d4 Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Mon, 13 Jan 2025 16:34:58 +0800 Subject: [PATCH 09/57] format p2p test --- test_cases/fiber/devnet/compatibility/test_p2p.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test_cases/fiber/devnet/compatibility/test_p2p.py b/test_cases/fiber/devnet/compatibility/test_p2p.py index bbad00d0..f12065c6 100644 --- a/test_cases/fiber/devnet/compatibility/test_p2p.py +++ b/test_cases/fiber/devnet/compatibility/test_p2p.py @@ -7,6 +7,13 @@ class TestP2p(FiberTest): def test_old_fiber(self): + """ + 1. start 0.2.0 node + 2. open_channel with node 0.2.0 + 3. send_payment with node 0.2.0 + Returns: + + """ old_fiber = self.start_new_fiber( self.generate_account(10000), fiber_version=FiberConfigPath.V020_DEV ) From 062e53d510567942f733fdc9d5af1045ab01225f Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Mon, 13 Jan 2025 16:38:15 +0800 Subject: [PATCH 10/57] update fiber --- prepare.sh | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/prepare.sh b/prepare.sh index 4f52362a..fe3d8ce7 100644 --- a/prepare.sh +++ b/prepare.sh @@ -1,17 +1,11 @@ set -e - git clone https://github.com/nervosnetwork/ckb-cli.git - cd ckb-cli - git checkout develop - make prod - cp target/release/ckb-cli ../source/ckb-cli - cd ../ + cp download/0.117.0/ckb-cli ./source/ckb-cli cp download/0.110.2/ckb-cli ./source/ckb-cli-old git clone https://github.com/nervosnetwork/fiber cd fiber cargo build cd ../ - mkdir -p download/fiber/0.1.0 - cp fiber/target/debug/fnn download/fiber/0.1.0 + cp fiber/target/debug/fnn download/fiber/0.2.0 #cp download/0.117.0/ckb-cli ./source/ckb-cli #git clone https://github.com/quake/ckb-light-client.git #cd ckb-light-client From 206abc1aceaf033e1f1325fe3d5d5b181126dcb5 Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Mon, 13 Jan 2025 16:48:05 +0800 Subject: [PATCH 11/57] fix get_tx is unknow --- framework/basic_fiber.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/basic_fiber.py b/framework/basic_fiber.py index 10132b67..22a817a2 100644 --- a/framework/basic_fiber.py +++ b/framework/basic_fiber.py @@ -181,7 +181,7 @@ def faucet( ckb_balance, self.node.rpcUrl, ) - self.Miner.miner_until_tx_committed(self.node, tx_hash) + self.Miner.miner_until_tx_committed(self.node, tx_hash, True) if udt_owner_private_key is None: return account_private_key @@ -192,7 +192,7 @@ def faucet( account_private_key, udt_balance, ) - self.Miner.miner_until_tx_committed(self.node, tx_hash) + self.Miner.miner_until_tx_committed(self.node, tx_hash, True) def generate_account( self, ckb_balance, udt_owner_private_key=None, udt_balance=1000 * 1000000000 From 3edd5b560d5397cfbda50c62b654886ede1a0bc6 Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Mon, 13 Jan 2025 16:50:34 +0800 Subject: [PATCH 12/57] format code --- test_cases/fiber/devnet/send_payment/test_stop_mid_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_cases/fiber/devnet/send_payment/test_stop_mid_node.py b/test_cases/fiber/devnet/send_payment/test_stop_mid_node.py index 351e6e8e..f18965d8 100644 --- a/test_cases/fiber/devnet/send_payment/test_stop_mid_node.py +++ b/test_cases/fiber/devnet/send_payment/test_stop_mid_node.py @@ -60,4 +60,4 @@ def test_stop_mid_node(self): } ) ) - self.wait_payment_state(self.fibers[0], payment["payment_hash"], "Success", 120) \ No newline at end of file + self.wait_payment_state(self.fibers[0], payment["payment_hash"], "Success", 120) From c29a5d95d13f8a225f4f2f253733359ae84b6d57 Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Thu, 16 Jan 2025 19:40:34 +0800 Subject: [PATCH 13/57] update 0.3.0 --- develop_fiber.sh | 2 +- download_fiber.py | 2 +- framework/basic_fiber.py | 7 +++++-- framework/test_fiber.py | 21 +++++++++++++-------- prepare.sh | 2 +- test_cases/fiber/testnet/test_fiber.py | 2 +- 6 files changed, 22 insertions(+), 14 deletions(-) diff --git a/develop_fiber.sh b/develop_fiber.sh index d5a68f69..e34e879a 100644 --- a/develop_fiber.sh +++ b/develop_fiber.sh @@ -17,6 +17,6 @@ if [ "$BUILD_FIBER" == "true" ]; then git clone -b $GitFIBERBranch $GitFIBERUrl cd fiber cargo build - cp target/debug/fnn ../download/fiber/0.2.0/fnn + cp target/debug/fnn ../download/fiber/0.3.0/fnn cd ../ fi \ No newline at end of file diff --git a/download_fiber.py b/download_fiber.py index 539a0b32..09283b8a 100644 --- a/download_fiber.py +++ b/download_fiber.py @@ -12,7 +12,7 @@ from tqdm import tqdm -versions = ["0.2.0", "0.2.1"] # Replace with your versions +versions = ["0.2.0", "0.2.1", "0.3.0"] # Replace with your versions DOWNLOAD_DIR = "download/fiber" SYSTEMS = { diff --git a/framework/basic_fiber.py b/framework/basic_fiber.py index 22a817a2..f410d83a 100644 --- a/framework/basic_fiber.py +++ b/framework/basic_fiber.py @@ -366,15 +366,18 @@ def open_channel( # assert channels["channels"][0]["local_balance"] == hex(fiber1_balance) # assert channels["channels"][0]["remote_balance"] == hex(fiber2_balance) - def send_payment(self, fiber1, fiber2, amount): + def send_payment(self, fiber1, fiber2, amount, wait=True): payment = fiber1.get_client().send_payment( { "target_pubkey": fiber2.get_client().node_info()["node_id"], "amount": hex(amount), "keysend": True, + "allow_self_payment": True, } ) - self.wait_payment_state(fiber1, payment["payment_hash"], "Success") + if wait: + self.wait_payment_state(fiber1, payment["payment_hash"], "Success") + return payment["payment_hash"] def get_account_script(self, account_private_key): account1 = self.Ckb_cli.util_key_info_by_private_key(account_private_key) diff --git a/framework/test_fiber.py b/framework/test_fiber.py index 1bbb5a90..a15d4cfd 100644 --- a/framework/test_fiber.py +++ b/framework/test_fiber.py @@ -18,18 +18,19 @@ class FiberConfigPath(Enum): CURRENT_DEV = ( "/source/template/fiber/dev_config.yml.j2", - "download/fiber/0.2.0/fnn", + "download/fiber/0.3.0/fnn", ) CURRENT_TESTNET = ( "/source/template/fiber/config.yml.j2", - "download/fiber/0.2.0/fnn", + "download/fiber/0.3.0/fnn", ) V030_TESTNET = ("/source/template/fiber/config.yml.j2", "download/fiber/0.3.0/fnn") - V020_TESTNET = ("/source/template/fiber/config.yml.j2", "download/fiber/0.3.0/fnn") - V010_TESTNET = ("/source/template/fiber/config.yml.j2", "download/fiber/0.3.0/fnn") + V020_TESTNET = ("/source/template/fiber/config.yml.j2", "download/fiber/0.2.0/fnn") + V010_TESTNET = ("/source/template/fiber/config.yml.j2", "download/fiber/0.1.0/fnn") V030_DEV = ("/source/template/fiber/dev_config.yml.j2", "download/fiber/0.3.0/fnn") + V021_DEV = ("/source/template/fiber/dev_config.yml.j2", "download/fiber/0.2.1/fnn") V020_DEV = ("/source/template/fiber/dev_config.yml.j2", "download/fiber/0.2.0/fnn") V010_DEV = ("/source/template/fiber/dev_config.yml.j2", "download/fiber/0.1.0/fnn") @@ -153,7 +154,7 @@ def read_ckb_key(self): def migration(self): run_command( - f"echo YES | RUST_LOG=info,fnn=debug {get_project_root()}/{self.fiber_config_enum.fiber_bin_path} --migrate -c {self.tmp_path}/config.yml -d {self.tmp_path}" + f"echo YES | RUST_LOG=info,fnn=debug {get_project_root()}/{self.fiber_config_enum.fiber_bin_path}-migrate -p {self.tmp_path}/fiber/store" ) def start(self, node=None): @@ -168,15 +169,19 @@ def start(self, node=None): # env=env_map, ) # wait rpc start - time.sleep(2) + time.sleep(1) print("start fiber client ") def stop(self): run_command(f"kill $(lsof -t -i:{self.rpc_port})", False) - time.sleep(3) + time.sleep(1) def force_stop(self): - run_command(f"kill -9 $(lsof -t -i:{self.rpc_port})", False) + # run_command(f"kill -9 $(lsof -t -i:{self.rpc_port} | head -1)", False) + run_command( + "kill -9 $(lsof -i:" + self.rpc_port + " | grep LISTEN | awk '{print $2}')", + False, + ) time.sleep(3) def clean(self): diff --git a/prepare.sh b/prepare.sh index fe3d8ce7..d84af5e4 100644 --- a/prepare.sh +++ b/prepare.sh @@ -5,7 +5,7 @@ set -e cd fiber cargo build cd ../ - cp fiber/target/debug/fnn download/fiber/0.2.0 + cp fiber/target/debug/fnn download/fiber/0.3.0 #cp download/0.117.0/ckb-cli ./source/ckb-cli #git clone https://github.com/quake/ckb-light-client.git #cd ckb-light-client diff --git a/test_cases/fiber/testnet/test_fiber.py b/test_cases/fiber/testnet/test_fiber.py index 3e2881f1..a3c84caf 100644 --- a/test_cases/fiber/testnet/test_fiber.py +++ b/test_cases/fiber/testnet/test_fiber.py @@ -58,7 +58,7 @@ def setup_class(cls): cls.fiber2.get_client().connect_peer( {"address": cls.cryptapeFiber2.node_info()["addresses"][0]} ) - time.sleep(1) + time.sleep(10) @classmethod def teardown_class(cls): From 1ecfdc1fd8d7960aae6cce3533c9ac68455a06c9 Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Tue, 21 Jan 2025 11:31:14 +0800 Subject: [PATCH 14/57] update fiber test case --- .../fiber/devnet/compatibility/test_data.py | 77 ++++++++++++++++++- .../fiber/devnet/compatibility/test_p2p.py | 44 +++++++++-- test_cases/fiber/testnet/test_fiber.py | 28 +++---- 3 files changed, 126 insertions(+), 23 deletions(-) diff --git a/test_cases/fiber/devnet/compatibility/test_data.py b/test_cases/fiber/devnet/compatibility/test_data.py index 1a467c63..31900003 100644 --- a/test_cases/fiber/devnet/compatibility/test_data.py +++ b/test_cases/fiber/devnet/compatibility/test_data.py @@ -6,7 +6,7 @@ class TestData(FiberTest): - def test_old_fiber(self): + def test_old_fiber_020(self): """ 1. start fiber 0.2.0 2. open_channel with fiber @@ -46,3 +46,78 @@ def test_old_fiber(self): # 5. send_payment self.send_payment(old_fiber_1, old_fiber_2, 100) + self.send_payment(old_fiber_2, old_fiber_1, 200) + channels = old_fiber_1.get_client().list_channels({}) + old_fiber_1.get_client().shutdown_channel( + { + "channel_id": channels["channels"][0]["channel_id"], + "close_script": self.get_account_script(self.Config.ACCOUNT_PRIVATE_1), + "fee_rate": "0x3FC", + } + ) + tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 120) + tx_message = self.get_tx_message(tx_hash) + print("tx message:", tx_message) + assert { + "args": self.get_account_script(old_fiber_2.account_private)["args"], + "capacity": 106200000000, + } in tx_message["output_cells"] + + def test_old_fiber_021(self): + """ + 1. start fiber 0.2.1 + 2. open_channel with fiber + 3. stop fiber + 4. migration and restart fiber 0.3.0 + 5. send_payment + 6. shutdown_channel + Returns: + + """ + # 1. start fiber 0.2.0 + old_fiber_1 = self.start_new_fiber( + self.generate_account(10000), fiber_version=FiberConfigPath.V021_DEV + ) + old_fiber_2 = self.start_new_fiber( + self.generate_account(10000), fiber_version=FiberConfigPath.V021_DEV + ) + old_fiber_1.connect_peer(old_fiber_2) + time.sleep(1) + # 2. open_channel with fiber + self.open_channel( + old_fiber_1, old_fiber_2, 1000 * 100000000, 1000 * 100000000, 1000, 1000 + ) + self.send_payment(old_fiber_1, old_fiber_2, 100) + + # 3. stop fiber + old_fiber_1.stop() + old_fiber_2.stop() + + # 4. migration and restart fiber 0.3.0 + old_fiber_1.fiber_config_enum = FiberConfigPath.CURRENT_DEV + old_fiber_2.fiber_config_enum = FiberConfigPath.CURRENT_DEV + old_fiber_1.migration() + time.sleep(5) + old_fiber_1.start() + time.sleep(5) + old_fiber_2.migration() + old_fiber_2.start() + + # 5. send_payment + self.send_payment(old_fiber_1, old_fiber_2, 100) + self.send_payment(old_fiber_2, old_fiber_1, 200) + channels = old_fiber_1.get_client().list_channels({}) + old_fiber_1.get_client().shutdown_channel( + { + "channel_id": channels["channels"][0]["channel_id"], + "close_script": self.get_account_script(self.Config.ACCOUNT_PRIVATE_1), + "fee_rate": "0x3FC", + } + ) + tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 120) + tx_message = self.get_tx_message(tx_hash) + print("tx message:", tx_message) + assert { + "args": self.get_account_script(old_fiber_2.account_private)["args"], + "capacity": 106200000000, + } in tx_message["output_cells"] diff --git a/test_cases/fiber/devnet/compatibility/test_p2p.py b/test_cases/fiber/devnet/compatibility/test_p2p.py index f12065c6..05b0beb1 100644 --- a/test_cases/fiber/devnet/compatibility/test_p2p.py +++ b/test_cases/fiber/devnet/compatibility/test_p2p.py @@ -1,11 +1,14 @@ import time +import pytest + from framework.basic_fiber import FiberTest from framework.test_fiber import FiberConfigPath class TestP2p(FiberTest): + @pytest.mark.skip("not support old fiber") def test_old_fiber(self): """ 1. start 0.2.0 node @@ -19,13 +22,38 @@ def test_old_fiber(self): ) old_fiber.connect_peer(self.fiber1) time.sleep(1) + old_fiber.get_client().open_channel( + { + "peer_id": self.fiber1.get_peer_id(), + "funding_amount": hex(1000 + 62 * 100000000), + "tlc_fee_proportional_millionths": hex(1000), + "public": True, + } + ) + + old_fiber.get_client().open_channel( + { + "peer_id": self.fiber1.get_peer_id(), + "funding_amount": hex(2000 * 100000000), + "tlc_fee_proportional_millionths": hex(1000), + "public": True, + } + ) + self.fiber1.get_client().open_channel( + { + "peer_id": old_fiber.get_peer_id(), + "funding_amount": hex(2000 * 100000000), + "tlc_fee_proportional_millionths": hex(1000), + "public": True, + } + ) self.open_channel( - self.fiber1, - old_fiber, - 1000 * 100000000, - 1000 * 100000000, - 1000, - 1000, + self.fiber1, self.fiber2, 1000 * 100000000, 1000 * 100000000, 1000, 1000 + ) + with pytest.raises(Exception) as exc_info: + self.send_payment(old_fiber, self.fiber1, 100) + expected_error_message = "no path found" + assert expected_error_message in exc_info.value.args[0], ( + f"Expected substring '{expected_error_message}' " + f"not found in actual string '{exc_info.value.args[0]}'" ) - self.send_payment(self.fiber1, old_fiber, 100) - self.send_payment(old_fiber, self.fiber1, 100) diff --git a/test_cases/fiber/testnet/test_fiber.py b/test_cases/fiber/testnet/test_fiber.py index a3c84caf..88d202d5 100644 --- a/test_cases/fiber/testnet/test_fiber.py +++ b/test_cases/fiber/testnet/test_fiber.py @@ -142,11 +142,11 @@ def test_ckb_01(self): begin = time.time() # wait dry_run success send_payment( - self.fiber1.get_client(), self.fiber2.get_client(), 1000, None, 10 * 60 + self.fiber1.get_client(), self.fiber2.get_client(), 1000, None, 20 * 60 ) fiber1_to_fiber2_time = time.time() send_payment( - self.fiber2.get_client(), self.fiber1.get_client(), 1000, None, 10 * 60 + self.fiber2.get_client(), self.fiber1.get_client(), 1000, None, 20 * 60 ) fiber2_to_fiber1_time = time.time() LOGGER.info(f"fiber1_to_fiber2 cost time: {fiber1_to_fiber2_time - begin}") @@ -161,11 +161,11 @@ def test_ckb_02(self): self.fiber2.start() begin = time.time() send_payment( - self.fiber1.get_client(), self.fiber2.get_client(), 1000, None, 10 * 60 + self.fiber1.get_client(), self.fiber2.get_client(), 1000, None, 20 * 60 ) fiber1_to_fiber2_time = time.time() send_payment( - self.fiber2.get_client(), self.fiber1.get_client(), 1000, None, 10 * 60 + self.fiber2.get_client(), self.fiber1.get_client(), 1000, None, 20 * 60 ) fiber2_to_fiber1_time = time.time() LOGGER.info(f"fiber1_to_fiber2 cost time: {fiber1_to_fiber2_time - begin}") @@ -216,14 +216,14 @@ def test_udt(self): self.cryptapeFiber2, 100000, funding_udt_type_script, - 10 * 60, + 20 * 60, ) send_payment( self.fiber2.get_client(), self.cryptapeFiber1, 100000, funding_udt_type_script, - 10 * 60, + 20 * 60, ) send_payment( @@ -231,7 +231,7 @@ def test_udt(self): self.fiber2.get_client(), 1000, funding_udt_type_script, - 10 * 60, + 20 * 60, ) fiber1_to_fiber2_time = time.time() @@ -240,7 +240,7 @@ def test_udt(self): self.fiber1.get_client(), 1000, funding_udt_type_script, - 10 * 60, + 20 * 60, ) fiber2_to_fiber1_time = time.time() LOGGER.info(f"fiber1_to_fiber2 cost time: {fiber1_to_fiber2_time - begin}") @@ -266,14 +266,14 @@ def test_udt_02(self): self.cryptapeFiber2, 100000, funding_udt_type_script, - 10 * 60, + 20 * 60, ) send_payment( self.fiber2.get_client(), self.cryptapeFiber1, 100000, funding_udt_type_script, - 10 * 60, + 20 * 60, ) send_payment( @@ -281,7 +281,7 @@ def test_udt_02(self): self.fiber2.get_client(), 1000, funding_udt_type_script, - 10 * 60, + 20 * 60, ) fiber1_to_fiber2_time = time.time() send_payment( @@ -289,7 +289,7 @@ def test_udt_02(self): self.fiber1.get_client(), 1000, funding_udt_type_script, - 10 * 60, + 20 * 60, ) fiber2_to_fiber1_time = time.time() LOGGER.info(f"fiber1_to_fiber2 cost time: {fiber1_to_fiber2_time - begin}") @@ -316,7 +316,7 @@ def send_payment( break except Exception as e: print(e) - print(f"try count: {i}") + print(f"send try count: {i}") time.sleep(1) continue for i in range(wait_times): @@ -330,7 +330,7 @@ def send_payment( return payment except Exception as e: print(e) - print(f"try count: {i}") + print(f"wait try count: {i}") continue raise TimeoutError("payment timeout") From c068e821df6815956284eddf327316f2a663f772 Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Tue, 21 Jan 2025 20:49:40 +0800 Subject: [PATCH 15/57] update fiber --- develop_fiber.sh | 2 +- download_fiber.py | 2 +- framework/basic_fiber.py | 22 ++++++++++++++++++- framework/test_fiber.py | 6 +++-- prepare.sh | 2 +- .../fiber/devnet/send_payment/test_tlc_fee.py | 0 6 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 test_cases/fiber/devnet/send_payment/test_tlc_fee.py diff --git a/develop_fiber.sh b/develop_fiber.sh index e34e879a..d75f51f4 100644 --- a/develop_fiber.sh +++ b/develop_fiber.sh @@ -17,6 +17,6 @@ if [ "$BUILD_FIBER" == "true" ]; then git clone -b $GitFIBERBranch $GitFIBERUrl cd fiber cargo build - cp target/debug/fnn ../download/fiber/0.3.0/fnn + cp target/debug/fnn ../download/fiber/0.3.1/fnn cd ../ fi \ No newline at end of file diff --git a/download_fiber.py b/download_fiber.py index 09283b8a..26d67bf5 100644 --- a/download_fiber.py +++ b/download_fiber.py @@ -12,7 +12,7 @@ from tqdm import tqdm -versions = ["0.2.0", "0.2.1", "0.3.0"] # Replace with your versions +versions = ["0.2.0", "0.2.1", "0.3.0", "0.3.1"] # Replace with your versions DOWNLOAD_DIR = "download/fiber" SYSTEMS = { diff --git a/framework/basic_fiber.py b/framework/basic_fiber.py index f410d83a..9b734976 100644 --- a/framework/basic_fiber.py +++ b/framework/basic_fiber.py @@ -366,13 +366,14 @@ def open_channel( # assert channels["channels"][0]["local_balance"] == hex(fiber1_balance) # assert channels["channels"][0]["remote_balance"] == hex(fiber2_balance) - def send_payment(self, fiber1, fiber2, amount, wait=True): + def send_payment(self, fiber1, fiber2, amount, wait=True, udt=None): payment = fiber1.get_client().send_payment( { "target_pubkey": fiber2.get_client().node_info()["node_id"], "amount": hex(amount), "keysend": True, "allow_self_payment": True, + "udt_type_script": udt, } ) if wait: @@ -397,6 +398,25 @@ def wait_payment_state(self, client, payment_hash, status="Success", timeout=120 f"status did not reach state {expected_state} within timeout period." ) + def calculate_tx_fee(self, balance, fee_list): + """ + A-B-C + A -> B 1000 + B -> C 2000 + calculate_tx_fee(1* 100000000, [2000]) + Args: + balance: + fee_list: + + Returns: + + """ + before_balance = balance + fee_list.reverse() + for fee in fee_list: + balance += balance * (fee / 1000000) + return int(balance - before_balance) + def wait_tx_pool(self, pending_size, try_size=100): for i in range(try_size): tx_pool_info = self.node.getClient().tx_pool_info() diff --git a/framework/test_fiber.py b/framework/test_fiber.py index a15d4cfd..f763300f 100644 --- a/framework/test_fiber.py +++ b/framework/test_fiber.py @@ -18,17 +18,19 @@ class FiberConfigPath(Enum): CURRENT_DEV = ( "/source/template/fiber/dev_config.yml.j2", - "download/fiber/0.3.0/fnn", + "download/fiber/0.3.1/fnn", ) CURRENT_TESTNET = ( "/source/template/fiber/config.yml.j2", - "download/fiber/0.3.0/fnn", + "download/fiber/0.3.1/fnn", ) + V031_TESTNET = ("/source/template/fiber/config.yml.j2", "download/fiber/0.3.1/fnn") V030_TESTNET = ("/source/template/fiber/config.yml.j2", "download/fiber/0.3.0/fnn") V020_TESTNET = ("/source/template/fiber/config.yml.j2", "download/fiber/0.2.0/fnn") V010_TESTNET = ("/source/template/fiber/config.yml.j2", "download/fiber/0.1.0/fnn") + V031_DEV = ("/source/template/fiber/dev_config.yml.j2", "download/fiber/0.3.1/fnn") V030_DEV = ("/source/template/fiber/dev_config.yml.j2", "download/fiber/0.3.0/fnn") V021_DEV = ("/source/template/fiber/dev_config.yml.j2", "download/fiber/0.2.1/fnn") V020_DEV = ("/source/template/fiber/dev_config.yml.j2", "download/fiber/0.2.0/fnn") diff --git a/prepare.sh b/prepare.sh index d84af5e4..0fb07081 100644 --- a/prepare.sh +++ b/prepare.sh @@ -5,7 +5,7 @@ set -e cd fiber cargo build cd ../ - cp fiber/target/debug/fnn download/fiber/0.3.0 + cp fiber/target/debug/fnn download/fiber/0.3.1 #cp download/0.117.0/ckb-cli ./source/ckb-cli #git clone https://github.com/quake/ckb-light-client.git #cd ckb-light-client diff --git a/test_cases/fiber/devnet/send_payment/test_tlc_fee.py b/test_cases/fiber/devnet/send_payment/test_tlc_fee.py new file mode 100644 index 00000000..e69de29b From 89241898dd7193e0a69af848e4aaa5cd06cb500a Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Wed, 5 Feb 2025 15:11:19 +0800 Subject: [PATCH 16/57] add watch tower test --- .../devnet/watch_tower/test_watch_tower.py | 28 ++--- .../watch_tower/test_watch_tower_udt.py | 17 ++- .../fiber/devnet/watch_tower/test_with_tx.py | 102 ++++++++++++++++-- 3 files changed, 115 insertions(+), 32 deletions(-) diff --git a/test_cases/fiber/devnet/watch_tower/test_watch_tower.py b/test_cases/fiber/devnet/watch_tower/test_watch_tower.py index 62a1c960..1826e3ae 100644 --- a/test_cases/fiber/devnet/watch_tower/test_watch_tower.py +++ b/test_cases/fiber/devnet/watch_tower/test_watch_tower.py @@ -610,7 +610,6 @@ def test_node1_shutdown_after_send_tx1_and_node2_split_tx(self): ) assert tx_message["output_cells"][1]["capacity"] == 19899999545 - @pytest.mark.skip("failed") def test_node2_shutdown_after_send_tx1_and_node1_split_tx(self): """ Test scenario where node2 shuts down after sending a transaction and node1 splits the transaction. @@ -748,8 +747,8 @@ def test_node2_shutdown_after_send_tx1_and_node2_split_tx(self): ) # Step 3: Send a payment from node1 to node2 - self.send_payment(self.fiber1, self.fiber2, 1 * 100000000, True) - + self.send_payment(self.fiber1, self.fiber2, 11 * 100000000, True) + time.sleep(1) # Step 4: Shutdown the channel from node1 self.fiber1.get_client().shutdown_channel( { @@ -795,7 +794,7 @@ def test_node2_shutdown_after_send_tx1_and_node2_split_tx(self): # Step 12: Wait for the transaction to be committed and check the transaction message tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) tx_message = self.get_tx_message(tx_hash) - + print(tx_message) # Step 13: Assert the capacity and arguments of input and output cells in the transaction message assert tx_message["input_cells"][0]["capacity"] == 26199999545 assert ( @@ -807,15 +806,20 @@ def test_node2_shutdown_after_send_tx1_and_node2_split_tx(self): tx_message["output_cells"][0]["args"] == self.get_account_script(self.fiber2.account_private)["args"] ) - assert tx_message["output_cells"][0]["capacity"] == 6199999545 + assert ( + tx_message["output_cells"][0]["capacity"] + == 62 * 100000000 + 11 * 100000000 - 455 + ) assert ( tx_message["output_cells"][1]["args"] == self.get_account_script(self.fiber1.account_private)["args"] ) - assert tx_message["output_cells"][1]["capacity"] == 19899999545 + assert ( + tx_message["output_cells"][1]["capacity"] + == 200 * 100000000 - 11 * 100000000 - 455 + ) - @pytest.mark.skip("commit tx err") def test_node1_shutdown_after_send_tx2_and_node1_split_tx(self): """ Test scenario where node1 shuts down after sending a transaction and node1 splits the transaction. @@ -878,7 +882,7 @@ def test_node1_shutdown_after_send_tx2_and_node1_split_tx(self): # Step 6: Mine additional blocks for i in range(5): self.Miner.miner_with_version(self.node, "0x0") - self.get_fiber_env() + # Step 7: Check the list of channels for both nodes node1_channel = self.fiber1.get_client().list_channels({}) node2_channel = self.fiber2.get_client().list_channels({}) @@ -890,10 +894,9 @@ def test_node1_shutdown_after_send_tx2_and_node1_split_tx(self): # Step 9: Check graph channels for both nodes node1_graph_channels = self.fiber1.get_client().graph_channels() node2_graph_channels = self.fiber2.get_client().graph_channels() - # self.fiber2.stop() - # Step 10: Stop node1 - self.fiber1.stop() + # Step 10: Stop node2 + self.fiber2.stop() # Step 11: Generate epochs self.node.getClient().generate_epochs("0xa") @@ -922,9 +925,7 @@ def test_node1_shutdown_after_send_tx2_and_node1_split_tx(self): == self.get_account_script(self.fiber1.account_private)["args"] ) assert tx_message["output_cells"][1]["capacity"] == 19999999545 - self.get_fiber_env() - @pytest.mark.skip("commit tx err") def test_node1_shutdown_after_send_tx2_and_node2_split_tx(self): """ Test scenario where node1 shuts down after sending a transaction and node2 splits the transaction. @@ -1130,7 +1131,6 @@ def test_node2_shutdown_after_send_tx2_and_node1_split_tx(self): ) assert tx_message["output_cells"][1]["capacity"] == 6199999545 - @pytest.mark.skip("commit tx err") def test_node2_shutdown_after_send_tx2_and_node2_split_tx(self): """ Test scenario where node2 shuts down after sending a transaction and node2 splits the transaction. diff --git a/test_cases/fiber/devnet/watch_tower/test_watch_tower_udt.py b/test_cases/fiber/devnet/watch_tower/test_watch_tower_udt.py index 96b575ef..9ca26404 100644 --- a/test_cases/fiber/devnet/watch_tower/test_watch_tower_udt.py +++ b/test_cases/fiber/devnet/watch_tower/test_watch_tower_udt.py @@ -629,7 +629,6 @@ def test_node1_shutdown_after_send_tx1_and_node2_split_tx(self): ], } in tx_message["output_cells"] - @pytest.mark.skip("failed") def test_node2_shutdown_after_send_tx1_and_node1_split_tx(self): """ Test scenario where node2 shuts down after sending a transaction and node1 splits the transaction. @@ -853,7 +852,6 @@ def test_node2_shutdown_after_send_tx1_and_node2_split_tx(self): ], } in tx_message["output_cells"] - @pytest.mark.skip("commit tx err") def test_node1_shutdown_after_send_tx2_and_node1_split_tx(self): """ Test scenario where node1 shuts down after sending multiple transactions and node1 splits the transaction. @@ -868,7 +866,7 @@ def test_node1_shutdown_after_send_tx2_and_node1_split_tx(self): 7. Check the list of channels for both nodes. 8. Check node information for both nodes. 9. Check graph channels for both nodes. - 10. Stop node1. + 10. Stop node2. 11. Generate epochs. 12. Wait for the transaction to be committed and check the transaction message. 13. Assert the capacity and arguments of input and output cells in the transaction message. @@ -930,10 +928,9 @@ def test_node1_shutdown_after_send_tx2_and_node1_split_tx(self): # Step 9: Check graph channels for both nodes node1_graph_channels = self.fiber1.get_client().graph_channels() node2_graph_channels = self.fiber2.get_client().graph_channels() - # self.fiber2.stop() - # Step 10: Stop node1 - self.fiber1.stop() + # Step 10: Stop node2 + self.fiber2.stop() # Step 11: Generate epochs self.node.getClient().generate_epochs("0xa") @@ -948,14 +945,14 @@ def test_node1_shutdown_after_send_tx2_and_node1_split_tx(self): assert tx_message["input_cells"][0]["capacity"] == 28599999407 assert ( tx_message["input_cells"][1]["args"] - == self.get_account_script(self.fiber2.account_private)["args"] + == self.get_account_script(self.fiber1.account_private)["args"] ) assert tx_message["input_cells"][0]["udt_capacity"] == 20000000000 assert { "capacity": 14299999407, "args": self.get_account_script(self.fiber1.account_private)["args"], - "udt_capacity": 19900000000, + "udt_capacity": 20000000000, "udt_args": self.get_account_udt_script(self.fiber1.account_private)[ "args" ], @@ -964,13 +961,12 @@ def test_node1_shutdown_after_send_tx2_and_node1_split_tx(self): assert { "capacity": 14299999407, "args": self.get_account_script(self.fiber2.account_private)["args"], - "udt_capacity": 100000000, + "udt_capacity": 0, "udt_args": self.get_account_udt_script(self.fiber1.account_private)[ "args" ], } in tx_message["output_cells"] - @pytest.mark.skip("commit tx err") def test_node1_shutdown_after_send_tx2_and_node2_split_tx(self): """ Test scenario where node1 shuts down after sending multiple transactions and node2 splits the transaction. @@ -1198,7 +1194,6 @@ def test_node2_shutdown_after_send_tx2_and_node1_split_tx(self): ], } in tx_message["output_cells"] - @pytest.mark.skip("commit tx err") def test_node2_shutdown_after_send_tx2_and_node2_split_tx(self): """ Test scenario where node2 shuts down after sending multiple transactions and node2 splits the transaction. diff --git a/test_cases/fiber/devnet/watch_tower/test_with_tx.py b/test_cases/fiber/devnet/watch_tower/test_with_tx.py index 287a058e..df452148 100644 --- a/test_cases/fiber/devnet/watch_tower/test_with_tx.py +++ b/test_cases/fiber/devnet/watch_tower/test_with_tx.py @@ -1,13 +1,14 @@ import time +import pytest + from framework.basic_fiber import FiberTest class WithTx(FiberTest): - # FiberTest.debug = True - - def test_with_tx(self): + @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/503") + def test_send_force_shutdown_with_tx(self): temporary_channel_id = self.fiber1.get_client().open_channel( { "peer_id": self.fiber2.get_peer_id(), @@ -22,14 +23,14 @@ def test_with_tx(self): ) payment = self.fiber1.get_client().send_payment( { - "amount": hex(100), + "amount": hex(100 * 100000000), "target_pubkey": self.fiber2.get_client().node_info()["node_id"], "keysend": True, } ) self.wait_payment_state(self.fiber1, payment["payment_hash"]) - amount = 1 + amount = 10 * 100000000 invoice = self.fiber2.get_client().new_invoice( { "amount": hex(amount), @@ -77,6 +78,93 @@ def test_with_tx(self): } ) time.sleep(3) - self.fiber1.stop() - self.fiber1.start() self.node.getClient().generate_epochs("0xf") + tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 600) + message = self.get_tx_message(tx_hash) + self.get_fibers_balance_message() + print("message:", message) + assert { + "args": "0x470dcdc5e44064909650113a274b3b36aecb6dc7", + "capacity": 16199999545, + } in message["output_cells"] + + # todo + payment_hash = self.send_payment(self.fiber2, self.fiber1, 1, False) + self.wait_payment_finished(self.fiber2, payment_hash, 120) + self.wait_payment_state(self.fiber2, payment_hash, "Failed") + + def test_receive_force_shutdown_with_tx(self): + temporary_channel_id = self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(200 * 100000000), + "public": True, + # "tlc_fee_proportional_millionths": "0x4B0", + } + ) + time.sleep(1) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY", 120 + ) + payment = self.fiber1.get_client().send_payment( + { + "amount": hex(100 * 100000000), + "target_pubkey": self.fiber2.get_client().node_info()["node_id"], + "keysend": True, + } + ) + self.wait_payment_state(self.fiber1, payment["payment_hash"]) + + amount = 10 * 100000000 + invoice = self.fiber2.get_client().new_invoice( + { + "amount": hex(amount), + "currency": "Fibd", + "description": "test invoice generated by node2", + "expiry": "0xe10", + "final_cltv": "0x28", + "payment_preimage": self.generate_random_preimage(), + "hash_algorithm": "sha256", + } + ) + time.sleep(1) + + channels = self.fiber1.get_client().list_channels( + {"peer_id": self.fiber2.get_peer_id()} + ) + N1N2_CHANNEL_ID = channels["channels"][0]["channel_id"] + + # 5. Send payment using the created invoice + payment = self.fiber1.get_client().send_payment( + { + "invoice": invoice["invoice_address"], + } + ) + self.fiber2.get_client().shutdown_channel( + { + "channel_id": N1N2_CHANNEL_ID, + "close_script": { + "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + "hash_type": "type", + "args": self.account2["lock_arg"], + }, + "fee_rate": "0x3FC", + "force": True, + } + ) + + self.fiber1.get_client().get_payment({"payment_hash": payment["payment_hash"]}) + time.sleep(5) + self.node.getClient().generate_epochs("0xf") + tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 600) + message = self.get_tx_message(tx_hash) + assert { + "args": "0x470dcdc5e44064909650113a274b3b36aecb6dc7", + "capacity": 16199999545, + } in message["output_cells"] + self.get_fibers_balance_message() + print("message:", message) + assert { + "args": "0x470dcdc5e44064909650113a274b3b36aecb6dc7", + "capacity": 16199999545, + } in message["output_cells"] From e9fc1dea3767c852cee33a84a611a22e4c9704cb Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Wed, 5 Feb 2025 17:41:02 +0800 Subject: [PATCH 17/57] update accept channel --- .../accept_channel/test_funding_amount.py | 106 ++++++++++++++++-- .../accept_channel/test_shutdown_script.py | 43 ++++++- .../test_temporary_channel_id.py | 25 ++++- 3 files changed, 154 insertions(+), 20 deletions(-) diff --git a/test_cases/fiber/devnet/accept_channel/test_funding_amount.py b/test_cases/fiber/devnet/accept_channel/test_funding_amount.py index f57d7851..0ba5592c 100644 --- a/test_cases/fiber/devnet/accept_channel/test_funding_amount.py +++ b/test_cases/fiber/devnet/accept_channel/test_funding_amount.py @@ -8,11 +8,31 @@ class TestFundingAmount(FiberTest): def test_ckb_funding_amount_zero(self): + """ + accept_channel: + ckb + funding_amount :0x0 + error: The funding amount (0) should be greater than or equal to 6200000000 + + Steps: + 1. Get node information. + 2. Retrieve the minimum CKB funding amount for auto-accepting channels. + 3. Open a temporary channel with funding amount just below the minimum. + 4. Wait for the channel to open. + 5. Attempt to accept a channel with zero funding amount and expect an exception. + 6. Verify the exception message contains the expected error message. + + Returns: + """ + # Step 1: Get node information node_info = self.fiber1.get_client().node_info() + + # Step 2: Retrieve the minimum CKB funding amount for auto-accepting channels open_channel_auto_accept_min_ckb_funding_amount = node_info[ "open_channel_auto_accept_min_ckb_funding_amount" ] + # Step 3: Open a temporary channel with funding amount just below the minimum temporary_channel = self.fiber1.get_client().open_channel( { "peer_id": self.fiber2.get_peer_id(), @@ -22,7 +42,11 @@ def test_ckb_funding_amount_zero(self): "public": True, } ) + + # Step 4: Wait for the channel to open time.sleep(1) + + # Step 5: Attempt to accept a channel with zero funding amount and expect an exception with pytest.raises(Exception) as exc_info: self.fiber2.get_client().accept_channel( { @@ -30,6 +54,8 @@ def test_ckb_funding_amount_zero(self): "funding_amount": "0x0", } ) + + # Step 6: Verify the exception message contains the expected error message expected_error_message = "should be greater than or equal to 6200000000" assert expected_error_message in exc_info.value.args[0], ( f"Expected substring '{expected_error_message}' " @@ -38,7 +64,16 @@ def test_ckb_funding_amount_zero(self): def test_udt_funding_amount_zero(self): """ - funding_amount :0x0 + accept_channel: + udt + funding_amount :0x0 + accept channel success + + 1. fiber1 open udt channel + 2. fiber2 accept udt channel 0 + 3. fiber1 send 1 udt to fiber2 + 4. shutdown channel + 5. check balance Returns: """ temporary_channel = self.fiber1.get_client().open_channel( @@ -117,8 +152,9 @@ def test_udt_funding_amount_zero(self): "fee_rate": "0x3FC", } ) - # todo wait close txx commit - time.sleep(20) + # todo wait close tx commit + tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) + self.Miner.miner_until_tx_committed(self.node, tx_hash) after_account1 = self.udtContract.list_cell( self.node.getClient(), @@ -136,6 +172,19 @@ def test_udt_funding_amount_zero(self): assert after_account2[-1]["balance"] == 1 * 100000000 def test_ckb_funding_amount_eq_auto_accept_channel_ckb_funding_amount(self): + """ + accept_channel: + ckb + funding_amount == open_channel_auto_accept_min_ckb_funding_amount + accept channel success + 1. fiber1 call node info get open_channel_auto_accept_min_ckb_funding_amount + 2. fiber1 open channel with fiber, ckb == open_channel_auto_accept_min_ckb_funding_amount + 3. fiber1 send fiber2 1 ckb + 4. shutdown channel + 5. check balance + Returns: + + """ node_info = self.fiber1.get_client().node_info() open_channel_auto_accept_min_ckb_funding_amount = node_info[ "open_channel_auto_accept_min_ckb_funding_amount" @@ -215,8 +264,10 @@ def test_ckb_funding_amount_eq_auto_accept_channel_ckb_funding_amount(self): "fee_rate": "0x3FC", } ) - # todo wait close txx commit - time.sleep(20) + + tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) + self.Miner.miner_until_tx_committed(self.node, tx_hash) + after_balance1 = self.Ckb_cli.wallet_get_capacity( self.fiber1.get_account()["address"]["testnet"] ) @@ -230,6 +281,19 @@ def test_ckb_funding_amount_eq_auto_accept_channel_ckb_funding_amount(self): assert after_balance2 - before_balance2 == 63 def test_ckb_funding_amount_gt_auto_accept_channel_ckb_funding_amount(self): + """ + accept_channel: + ckb + funding_amount > open_channel_auto_accept_min_ckb_funding_amount + accept channel success + 1. fiber1 call node info get open_channel_auto_accept_min_ckb_funding_amount + 2. fiber1 open channel with fiber, ckb == 1 + open_channel_auto_accept_min_ckb_funding_amount + 3. fiber1 send fiber2 1 ckb + 4. shutdown channel + 5. check balance + Returns: + + """ node_info = self.fiber1.get_client().node_info() open_channel_auto_accept_min_ckb_funding_amount = node_info[ "open_channel_auto_accept_min_ckb_funding_amount" @@ -323,6 +387,7 @@ def test_ckb_funding_amount_gt_auto_accept_channel_ckb_funding_amount(self): print("after_balance2:", after_balance2) assert after_balance2 - before_balance2 == 64 + @pytest.mark.skip("repeat") def test_ckb_funding_amount_lt_account(self): """ funding_amount < account @@ -477,10 +542,12 @@ def test_udt_funding_amount_lt_account(self): print(after_account2) assert after_account2[-1]["balance"] == (10000 + 1 - 9999) * 100000000 - @pytest.mark.skip("无法再次 accept") def test_ckb_funding_amount_gt_account(self): """ - funding_amount > account + 1. accept funding_amount > account + failed message in log file + 2. accept again + err: No channel with temp id Returns: """ temporary_channel = self.fiber1.get_client().open_channel( @@ -509,16 +576,23 @@ def test_ckb_funding_amount_gt_account(self): 120, ) # 失败了, 好像不能再次accept_channel - self.fiber2.get_client().accept_channel( - { - "temporary_channel_id": temporary_channel["temporary_channel_id"], - "funding_amount": hex(1000 * 100000000), - } + with pytest.raises(Exception) as exc_info: + self.fiber2.get_client().accept_channel( + { + "temporary_channel_id": temporary_channel["temporary_channel_id"], + "funding_amount": hex(1000 * 100000000), + } + ) + expected_error_message = "No channel with temp id" + assert expected_error_message in exc_info.value.args[0], ( + f"Expected substring '{expected_error_message}' " + f"not found in actual string '{exc_info.value.args[0]}'" ) def test_funding_amount_over_flow(self): """ funding_amount > int.max + err: Invalid params Returns: """ temporary_channel = self.fiber1.get_client().open_channel( @@ -547,6 +621,14 @@ def test_funding_amount_over_flow(self): ) def test_udt_funding_amount_gt_account(self): + """ + 1. accept funding_amount > account + failed message in log file + 2025-02-05T08:59:50.811397Z ERROR fnn::fiber::network: Failed to fund channel: Failed to build CKB tx: other error: `can not find enough UDT owner cells for funding transaction` + + Returns: + + """ self.faucet( self.fiber2.account_private, 1000, diff --git a/test_cases/fiber/devnet/accept_channel/test_shutdown_script.py b/test_cases/fiber/devnet/accept_channel/test_shutdown_script.py index b53fdea0..adda0473 100644 --- a/test_cases/fiber/devnet/accept_channel/test_shutdown_script.py +++ b/test_cases/fiber/devnet/accept_channel/test_shutdown_script.py @@ -16,6 +16,14 @@ def test_shutdown_script_none(self): def test_ckb_shutdown_script(self): """ + shutdown_script + is not None + shut_down the channel triggers a balance refund to the shutdown script. + 1. fiber1 open channel with fiber2 + 2. fiber2 accept channel with shutdown script + 3. fiber1 send payment to fiber2 + 4. fiber1 shutdown channel + 5. check balance Returns: """ @@ -94,8 +102,9 @@ def test_ckb_shutdown_script(self): "fee_rate": "0x3FC", } ) - # todo wait close txx commit - time.sleep(20) + tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 120) + self.Miner.miner_until_tx_committed(self.node, tx_hash) + after_balance1 = self.Ckb_cli.wallet_get_capacity( self.fiber1.get_account()["address"]["testnet"] ) @@ -114,6 +123,18 @@ def test_ckb_shutdown_script(self): assert after_new_balance == 63 def test_udt_shutdown_script(self): + """ + shutdown_script + is not none + shut_down the channel triggers a balance refund to the shutdown script. + 1. fiber1 open channel with fiber2 + 2. fiber2 accept channel with shutdown script + 3. fiber1 send payment to fiber2 + 4. fiber1 shutdown channel + 5. check balance + Returns: + + """ new_account_private_key = self.generate_account(0) new_account = self.Ckb_cli.util_key_info_by_private_key(new_account_private_key) temporary_channel = self.fiber1.get_client().open_channel( @@ -193,8 +214,9 @@ def test_udt_shutdown_script(self): "fee_rate": "0x3FC", } ) - # todo wait close txx commit - time.sleep(20) + + tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 120) + self.Miner.miner_until_tx_committed(self.node, tx_hash) after_account1 = self.udtContract.list_cell( self.node.getClient(), @@ -218,6 +240,12 @@ def test_udt_shutdown_script(self): assert after_new_account[-1]["balance"] == 1 * 100000000 def test_shutdown_script_too_long_gt_funding_amount(self): + """ + shutdown_script : data too big ,will cause ckb not enough + err: "The funding amount (6200000000) should be greater than or equal to 147200000000" + Returns: + + """ node_info = self.fiber1.get_client().node_info() open_channel_auto_accept_min_ckb_funding_amount = node_info[ "open_channel_auto_accept_min_ckb_funding_amount" @@ -253,7 +281,12 @@ def test_shutdown_script_too_long_gt_funding_amount(self): def test_shutdown_script_too_long(self): """ - shutdown_script : data too big ,will cause ckb not enough + shutdown_script : data too big + pass + 1. fiber1 open channel with fiber2 + 2. fiber2 accept channel with big data arg + 3. fiber1 send payment to fiber2 + 4. fiber1 shutdown channel Returns: """ node_info = self.fiber1.get_client().node_info() diff --git a/test_cases/fiber/devnet/accept_channel/test_temporary_channel_id.py b/test_cases/fiber/devnet/accept_channel/test_temporary_channel_id.py index aa3e7e72..c39e65c3 100644 --- a/test_cases/fiber/devnet/accept_channel/test_temporary_channel_id.py +++ b/test_cases/fiber/devnet/accept_channel/test_temporary_channel_id.py @@ -10,9 +10,14 @@ class TestTemporaryChannelId(FiberTest): def test_temporary_channel_id_not_exist(self): """ - Returns: + Test scenario where a temporary channel ID does not exist. + + Steps: + 1. Attempt to accept a channel with a non-existent temporary channel ID. + 2. Verify that the expected error message is raised. """ + # Step 1: Attempt to accept a channel with a non-existent temporary channel ID with pytest.raises(Exception) as exc_info: self.fiber2.get_client().accept_channel( { @@ -21,6 +26,7 @@ def test_temporary_channel_id_not_exist(self): } ) + # Step 2: Verify that the expected error message is raised expected_error_message = "No channel with temp id" assert expected_error_message in exc_info.value.args[0], ( f"Expected substring '{expected_error_message}' " @@ -29,13 +35,22 @@ def test_temporary_channel_id_not_exist(self): def test_temporary_channel_id_again(self): """ - Returns: + Test scenario where a temporary channel ID is used again. + + Steps: + 1. Get node information. + 2. Open a channel with a funding amount slightly less than the minimum auto-accept amount. + 3. Accept the channel with a specified funding amount. + 4. Verify the channel ID. + 5. Attempt to accept the channel again and verify the expected error message. """ + # Step 1: Get node information node_info = self.fiber1.get_client().node_info() open_channel_auto_accept_min_ckb_funding_amount = node_info[ "open_channel_auto_accept_min_ckb_funding_amount" ] + # Step 2: Open a channel with a funding amount slightly less than the minimum auto-accept amount temporary_channel = self.fiber1.get_client().open_channel( { "peer_id": self.fiber2.get_peer_id(), @@ -46,6 +61,8 @@ def test_temporary_channel_id_again(self): } ) time.sleep(1) + + # Step 3: Accept the channel with a specified funding amount accept_channel = self.fiber2.get_client().accept_channel( { "temporary_channel_id": temporary_channel["temporary_channel_id"], @@ -53,10 +70,12 @@ def test_temporary_channel_id_again(self): } ) time.sleep(1) - # channel_id + + # Step 4: Verify the channel ID channel = self.fiber1.get_client().list_channels({}) assert channel["channels"][0]["channel_id"] == accept_channel["channel_id"] + # Step 5: Attempt to accept the channel again and verify the expected error message with pytest.raises(Exception) as exc_info: self.fiber2.get_client().accept_channel( { From 261b70e99e34b0097829bf6ac81e57a0894d6efd Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Thu, 6 Feb 2025 17:18:05 +0800 Subject: [PATCH 18/57] update test case --- .gitignore | 4 + Makefile | 2 + develop_fiber.sh | 9 +- download_ckb_light_client.py | 17 +- download_fiber.py | 20 +- fiber_testnet_prepare.sh | 17 ++ framework/basic_fiber.py | 67 ++++- framework/test_fiber.py | 3 +- prepare.sh | 24 +- .../cancel_invoice/test_cancel_invoice.py | 250 ++++++++++++++---- .../fiber/devnet/compatibility/test_data.py | 100 +++++++ .../devnet/connect_peer/test_connect_peer.py | 21 +- .../disconnect_peer/test_disconnect_peer.py | 18 +- .../devnet/graph_nodes/test_graph_nodes.py | 14 +- .../fiber/devnet/issue/test_issue_478.py | 31 +++ .../fiber/devnet/issue/test_issue_484.py | 22 ++ .../devnet/open_channel/test_ckb_cell.py | 6 +- .../test_max_tlc_number_in_flight.py | 8 +- .../test_max_tlc_value_in_flight.py | 33 +-- .../test_tlc_fee_proportional_millionths.py | 92 +++++-- .../devnet/open_channel/test_tlc_min_value.py | 237 +++++------------ .../send_payment/test_allow_self_payment.py | 42 ++- .../fiber/devnet/send_payment/test_dry_run.py | 3 +- .../devnet/send_payment/test_find_path.py | 91 ++++--- .../send_payment/test_private_channel.py | 59 +++++ .../fiber/devnet/send_payment/test_restart.py | 4 +- .../devnet/send_payment/test_send_payment.py | 2 +- .../test_send_payment_with_shutdown.py | 104 ++++++++ .../test_send_payment_with_update_channel.py | 86 ++++++ .../fiber/devnet/send_payment/test_tlc_fee.py | 73 +++++ .../shutdown_channel/test_node_state.py | 11 +- .../devnet/update_channel/test_channel_id.py | 4 +- .../devnet/update_channel/test_enabled.py | 129 ++++++++- .../test_tlc_fee_proportional_millionths.py | 8 +- .../update_channel/test_update_channel.py | 4 +- 35 files changed, 1218 insertions(+), 397 deletions(-) create mode 100644 test_cases/fiber/devnet/issue/test_issue_478.py create mode 100644 test_cases/fiber/devnet/issue/test_issue_484.py create mode 100644 test_cases/fiber/devnet/send_payment/test_private_channel.py create mode 100644 test_cases/fiber/devnet/send_payment/test_send_payment_with_shutdown.py create mode 100644 test_cases/fiber/devnet/send_payment/test_send_payment_with_update_channel.py diff --git a/.gitignore b/.gitignore index 3cabeca1..5be01bca 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,7 @@ tmp download pytest.ini reprot +__pycache__ +pid.txt +.DS_Store +.idea \ No newline at end of file diff --git a/Makefile b/Makefile index 27beb567..060cb63c 100644 --- a/Makefile +++ b/Makefile @@ -87,6 +87,8 @@ fiber_test_cases := \ test_cases/fiber/devnet/send_payment \ test_cases/fiber/devnet/shutdown_channel \ test_cases/fiber/devnet/update_channel \ + test_cases/fiber/devnet/issue \ + test_cases/fiber/devnet/compatibility \ test_cases/fiber/devnet/watch_tower fiber_testnet_cases := \ diff --git a/develop_fiber.sh b/develop_fiber.sh index d75f51f4..39e36676 100644 --- a/develop_fiber.sh +++ b/develop_fiber.sh @@ -7,7 +7,7 @@ set -e # cd ../ DEFAULT_FIBER_BRANCH="develop" DEFAULT_FIBER_URL="https://github.com/nervosnetwork/fiber.git" -DEFAULT_BUILD_FIBER=false +DEFAULT_BUILD_FIBER=true GitFIBERBranch="${GitBranch:-$DEFAULT_FIBER_BRANCH}" @@ -17,6 +17,9 @@ if [ "$BUILD_FIBER" == "true" ]; then git clone -b $GitFIBERBranch $GitFIBERUrl cd fiber cargo build - cp target/debug/fnn ../download/fiber/0.3.1/fnn - cd ../ + cp target/debug/fnn ../download/fiber/current/fnn + cd migrate + cargo build + cp target/debug/fnn-migrate ../../download/fiber/current/fnn-migrate + cd ../../ fi \ No newline at end of file diff --git a/download_ckb_light_client.py b/download_ckb_light_client.py index 27500342..90fc152f 100644 --- a/download_ckb_light_client.py +++ b/download_ckb_light_client.py @@ -12,16 +12,7 @@ from tqdm import tqdm -versions = [ - "0.2.4", - "0.3.0", - "0.3.1", - "0.3.2", - "0.3.3", - "0.3.4", - "0.3.5", - "0.3.6", -] # Replace with your versions +versions = ["0.2.1"] # Replace with your versions DOWNLOAD_DIR = "download" SYSTEMS = { @@ -32,19 +23,19 @@ }, "Linux": { "x86_64": { - "url": "https://github.com/nervosnetwork/ckb-light-client/releases/download/v{version}/ckb-light-client_v{" + "url": "https://github.com/nervosnetwork/fiber/releases/download/v{version}/fnn_v{" "version}-x86_64-linux.tar.gz", "ext": ".tar.gz", }, }, "Darwin": { "x86_64": { - "url": "https://github.com/nervosnetwork/ckb-light-client/releases/download/v{version}/ckb-light-client_v{" + "url": "https://github.com/nervosnetwork/fiber/releases/download/v{version}/fnn_v{" "version}-x86_64-darwin-portable.tar.gz", "ext": ".tar.gz", }, "arm64": { - "url": "https://github.com/nervosnetwork/ckb-light-client/releases/download/v{version}/ckb-light-client_v{" + "url": "https://github.com/nervosnetwork/fiber/releases/download/v{version}/fnn_v{" "version}-x86_64-darwin.tar.gz", "ext": ".tar.gz", }, diff --git a/download_fiber.py b/download_fiber.py index 26d67bf5..25cae650 100644 --- a/download_fiber.py +++ b/download_fiber.py @@ -11,7 +11,6 @@ import requests from tqdm import tqdm - versions = ["0.2.0", "0.2.1", "0.3.0", "0.3.1"] # Replace with your versions DOWNLOAD_DIR = "download/fiber" @@ -90,14 +89,9 @@ def extract_file(filename, path): elif filename.endswith(".tar.gz"): with tarfile.open(filename, "r:gz") as tar_ref: tar_ref.extractall(temp_path) - # Change permission of ckb-light-client - for file in ["ckb-light-client"]: - filepath = os.path.join(path, file) - if os.path.isfile(filepath): - os.chmod(filepath, 0o755) -def download_ckb(ckb_version): +def download_ckb(ckb_version, last_one=False): """ download ckb from gitHub by ckb version :param ckb_version: gitHub release ckb version @@ -119,7 +113,15 @@ def download_ckb(ckb_version): download_file(url, filename) extract_file(filename, download_path) + if last_one: + current_download_path = os.path.join(DOWNLOAD_DIR, "current") + extract_file(filename, current_download_path) + +for i in range(len(versions)): + current = i == len(versions) - 1 + download_ckb(versions[i], current) -for version in versions: - download_ckb(version) +# for version in versions: +# +# download_ckb(version) diff --git a/fiber_testnet_prepare.sh b/fiber_testnet_prepare.sh index e69de29b..6febde6a 100644 --- a/fiber_testnet_prepare.sh +++ b/fiber_testnet_prepare.sh @@ -0,0 +1,17 @@ +set -e +git clone https://github.com/nervosnetwork/ckb-cli.git +cd ckb-cli +git checkout develop +make prod +cp target/release/ckb-cli ../source/ckb-cli +cd ../ +cp download/0.110.2/ckb-cli ./source/ckb-cli-old + +#cp download/0.117.0/ckb-cli ./source/ckb-cli +#git clone https://github.com/quake/ckb-light-client.git +#cd ckb-light-client +#git checkout quake/fix-set-scripts-partial-bug +#cargo build --release +#cd ../ +#mkdir -p download/0.3.5 +#cp ckb-light-client/target/release/ckb-light-client download/0.3.5 diff --git a/framework/basic_fiber.py b/framework/basic_fiber.py index 9b734976..92aee887 100644 --- a/framework/basic_fiber.py +++ b/framework/basic_fiber.py @@ -366,19 +366,24 @@ def open_channel( # assert channels["channels"][0]["local_balance"] == hex(fiber1_balance) # assert channels["channels"][0]["remote_balance"] == hex(fiber2_balance) - def send_payment(self, fiber1, fiber2, amount, wait=True, udt=None): - payment = fiber1.get_client().send_payment( - { - "target_pubkey": fiber2.get_client().node_info()["node_id"], - "amount": hex(amount), - "keysend": True, - "allow_self_payment": True, - "udt_type_script": udt, - } - ) - if wait: - self.wait_payment_state(fiber1, payment["payment_hash"], "Success") - return payment["payment_hash"] + def send_payment(self, fiber1, fiber2, amount, wait=True, udt=None, try_count=5): + for i in range(try_count): + try: + payment = fiber1.get_client().send_payment( + { + "target_pubkey": fiber2.get_client().node_info()["node_id"], + "amount": hex(amount), + "keysend": True, + "allow_self_payment": True, + "udt_type_script": udt, + } + ) + if wait: + self.wait_payment_state(fiber1, payment["payment_hash"], "Success") + return payment["payment_hash"] + except Exception as e: + time.sleep(1) + continue def get_account_script(self, account_private_key): account1 = self.Ckb_cli.util_key_info_by_private_key(account_private_key) @@ -398,6 +403,42 @@ def wait_payment_state(self, client, payment_hash, status="Success", timeout=120 f"status did not reach state {expected_state} within timeout period." ) + def wait_payment_finished(self, client, payment_hash, timeout=120): + for i in range(timeout): + result = client.get_client().get_payment({"payment_hash": payment_hash}) + if result["status"] == "Success" or result["status"] == "Failed": + return result + time.sleep(1) + raise TimeoutError( + f"status did not reach state {expected_state} within timeout period." + ) + + def get_fibers_balance_message(self): + messages = [] + for fiber in self.fibers: + messages.append(self.get_fiber_balance(fiber)) + for i in range(len(messages)): + self.logger.debug(f"fiber{i} balance:{messages[i]}") + + def get_fiber_balance(self, fiber): + channels = fiber.get_client().list_channels({}) + channels_balance = 0 + channels_offered_tlc_balance = 0 + channels_received_tlc_balance = 0 + for i in range(len(channels["channels"])): + channel = channels["channels"][i] + if channel["state"]["state_name"] == "CHANNEL_READY": + channels_balance += int(channel["local_balance"], 16) + channels_offered_tlc_balance += int(channel["offered_tlc_balance"], 16) + channels_received_tlc_balance += int( + channel["received_tlc_balance"], 16 + ) + return { + "local_balance": channels_balance, + "offered_tlc_balance": channels_offered_tlc_balance, + "received_tlc_balance": channels_received_tlc_balance, + } + def calculate_tx_fee(self, balance, fee_list): """ A-B-C diff --git a/framework/test_fiber.py b/framework/test_fiber.py index f763300f..21db4fcb 100644 --- a/framework/test_fiber.py +++ b/framework/test_fiber.py @@ -16,9 +16,10 @@ class FiberConfigPath(Enum): + CURRENT_DEV = ( "/source/template/fiber/dev_config.yml.j2", - "download/fiber/0.3.1/fnn", + "download/fiber/current/fnn", ) CURRENT_TESTNET = ( "/source/template/fiber/config.yml.j2", diff --git a/prepare.sh b/prepare.sh index 0fb07081..013f1009 100644 --- a/prepare.sh +++ b/prepare.sh @@ -1,16 +1,10 @@ set -e - cp download/0.117.0/ckb-cli ./source/ckb-cli - cp download/0.110.2/ckb-cli ./source/ckb-cli-old - git clone https://github.com/nervosnetwork/fiber - cd fiber - cargo build - cd ../ - cp fiber/target/debug/fnn download/fiber/0.3.1 -#cp download/0.117.0/ckb-cli ./source/ckb-cli -#git clone https://github.com/quake/ckb-light-client.git -#cd ckb-light-client -#git checkout quake/fix-set-scripts-partial-bug -#cargo build --release -#cd ../ -#mkdir -p download/0.3.5 -#cp ckb-light-client/target/release/ckb-light-client download/0.3.5 +cp download/0.117.0/ckb-cli ./source/ckb-cli +cp download/0.110.2/ckb-cli ./source/ckb-cli-old +git clone https://github.com/nervosnetwork/fiber +cd fiber +cargo build +cp target/debug/fnn ../download/fiber/current/fnn +cd migrate +cargo build +cp target/debug/fnn-migrate ../../download/fiber/current/fnn-migrate \ No newline at end of file diff --git a/test_cases/fiber/devnet/cancel_invoice/test_cancel_invoice.py b/test_cases/fiber/devnet/cancel_invoice/test_cancel_invoice.py index e3ffb8e7..870cf77c 100644 --- a/test_cases/fiber/devnet/cancel_invoice/test_cancel_invoice.py +++ b/test_cases/fiber/devnet/cancel_invoice/test_cancel_invoice.py @@ -25,9 +25,15 @@ class TestCancelInvoice(FiberTest): def test_not_exist_payment_hash(self): """ - 1. 不存在的payment hash + Test case for canceling an invoice with a non-existent payment hash. + + Steps: + 1. Generate a random preimage to simulate a non-existent payment hash. + 2. Attempt to cancel the invoice with the generated payment hash. + 3. Verify that the expected error message "invoice not found" is raised. + Returns: - err: "invoice not found" + None """ with pytest.raises(Exception) as exc_info: self.fiber1.get_client().cancel_invoice( @@ -41,11 +47,17 @@ def test_not_exist_payment_hash(self): def test_cancel_invoice_that_statue_is_open(self): """ - 1. new invoice - 2. cancel invoice - 3. query invoice + Test case for canceling an invoice that is in the 'Open' state. + + Steps: + 1. Create a new invoice. + 2. Cancel the created invoice. + 3. Query the invoice to verify its status is 'Cancelled'. + Returns: + None """ + # Step 1: Generate a random preimage and create a new invoice preimage = self.generate_random_preimage() invoice = self.fiber1.get_client().new_invoice( { @@ -58,9 +70,13 @@ def test_cancel_invoice_that_statue_is_open(self): "hash_algorithm": "sha256", } ) + + # Step 2: Cancel the created invoice self.fiber1.get_client().cancel_invoice( {"payment_hash": invoice["invoice"]["data"]["payment_hash"]} ) + + # Step 3: Query the invoice to verify its status is 'Cancelled' result = self.fiber1.get_client().get_invoice( {"payment_hash": invoice["invoice"]["data"]["payment_hash"]} ) @@ -68,12 +84,18 @@ def test_cancel_invoice_that_statue_is_open(self): def test_cancel_invoice_that_statue_is_cancelled(self): """ - 1. new invoice - 2. cancel invoice - 3. cancel invoice again - invoice can not be canceled, current status: Cancelled + Test case for canceling an invoice that is already in the 'Cancelled' state. + + Steps: + 1. Create a new invoice. + 2. Cancel the created invoice. + 3. Attempt to cancel the invoice again. + 4. Verify that the expected error message "invoice can not be canceled, current status: Cancelled" is raised. + Returns: + None """ + # Step 1: Generate a random preimage and create a new invoice preimage = self.generate_random_preimage() invoice = self.fiber1.get_client().new_invoice( { @@ -86,14 +108,19 @@ def test_cancel_invoice_that_statue_is_cancelled(self): "hash_algorithm": "sha256", } ) + + # Step 2: Cancel the created invoice self.fiber1.get_client().cancel_invoice( {"payment_hash": invoice["invoice"]["data"]["payment_hash"]} ) + + # Step 3: Query the invoice to verify its status is 'Cancelled' result = self.fiber1.get_client().get_invoice( {"payment_hash": invoice["invoice"]["data"]["payment_hash"]} ) assert result["status"] == "Cancelled" + # Step 4: Attempt to cancel the invoice again and verify the expected error message with pytest.raises(Exception) as exc_info: self.fiber1.get_client().cancel_invoice( {"payment_hash": invoice["invoice"]["data"]["payment_hash"]} @@ -108,10 +135,18 @@ def test_cancel_invoice_that_statue_is_cancelled(self): def test_cancel_invoice_that_statue_is_expired(self): """ - Expired - Returns: + Test case for canceling an invoice that is in the 'Expired' state. + + Steps: + 1. Generate a random preimage and create a new invoice with an expiry of 0. + 2. Query the invoice to verify its status is 'Expired'. + 3. Cancel the expired invoice. + 4. Query the invoice again to verify its status is 'Cancelled'. + Returns: + None """ + # Step 1: Generate a random preimage and create a new invoice with an expiry of 0 preimage = self.generate_random_preimage() invoice = self.fiber1.get_client().new_invoice( { @@ -124,16 +159,20 @@ def test_cancel_invoice_that_statue_is_expired(self): "hash_algorithm": "sha256", } ) - # time.sleep(1) + + # Step 2: Query the invoice to verify its status is 'Expired' result = self.fiber1.get_client().get_invoice( {"payment_hash": invoice["invoice"]["data"]["payment_hash"]} ) - assert result["status"] == "Expired" + + # Step 3: Cancel the expired invoice result = self.fiber1.get_client().cancel_invoice( {"payment_hash": invoice["invoice"]["data"]["payment_hash"]} ) assert result["status"] == "Cancelled" + + # Step 4: Query the invoice again to verify its status is 'Cancelled' result = self.fiber1.get_client().get_invoice( {"payment_hash": invoice["invoice"]["data"]["payment_hash"]} ) @@ -141,11 +180,21 @@ def test_cancel_invoice_that_statue_is_expired(self): def test_send_failed_that_invoice_cancel(self): """ - 取消后,发送失败,下次交易不受影响 + Test case for sending a payment after an invoice is canceled and ensuring subsequent transactions are not affected. - Returns: + Steps: + 1. Open a new channel. + 2. Create a new invoice. + 3. Cancel the created invoice. + 4. Attempt to send a payment using the canceled invoice and verify it fails. + 5. Create a new invoice. + 6. Send a payment using the new invoice and verify it succeeds. + 7. Verify the channel balance is updated correctly. + Returns: + None """ + # Step 1: Open a new channel temporary_channel_id = self.fiber2.get_client().open_channel( { "peer_id": self.fiber1.get_peer_id(), @@ -160,6 +209,8 @@ def test_send_failed_that_invoice_cancel(self): "CHANNEL_READY", 120, ) + + # Step 2: Create a new invoice payment_preimage = self.generate_random_preimage() invoice_balance = 1 * 100000000 invoice = self.fiber1.get_client().new_invoice( @@ -174,16 +225,20 @@ def test_send_failed_that_invoice_cancel(self): } ) + # Step 3: Cancel the created invoice self.fiber1.get_client().cancel_invoice( {"payment_hash": invoice["invoice"]["data"]["payment_hash"]} ) + + # Step 4: Attempt to send a payment using the canceled invoice and verify it fails payment = self.fiber2.get_client().send_payment( { "invoice": invoice["invoice_address"], } ) - self.wait_payment_state(self.fiber2, payment["payment_hash"], "Failed") + + # Step 5: Create a new invoice payment_preimage = self.generate_random_preimage() invoice_balance = 1 * 100000000 invoice = self.fiber1.get_client().new_invoice( @@ -197,6 +252,8 @@ def test_send_failed_that_invoice_cancel(self): "hash_algorithm": "sha256", } ) + + # Step 6: Send a payment using the new invoice and verify it succeeds before_channel = self.fiber2.get_client().list_channels({}) payment = self.fiber2.get_client().send_payment( { @@ -205,6 +262,8 @@ def test_send_failed_that_invoice_cancel(self): ) self.wait_payment_state(self.fiber2, payment["payment_hash"], "Success") after_channel = self.fiber2.get_client().list_channels({}) + + # Step 7: Verify the channel balance is updated correctly assert ( int(before_channel["channels"][0]["local_balance"], 16) - int(after_channel["channels"][0]["local_balance"], 16) @@ -212,6 +271,19 @@ def test_send_failed_that_invoice_cancel(self): ) def test_cancel_invoice_that_statue_is_paid(self): + """ + Test case for canceling an invoice that is in the 'Paid' state. + + Steps: + 1. Open a new channel. + 2. Create a new invoice. + 3. Send a payment using the created invoice. + 4. Attempt to cancel the invoice and verify the expected error message "invoice can not be canceled, current status: Paid" is raised. + + Returns: + None + """ + # Step 1: Open a new channel temporary_channel_id = self.fiber2.get_client().open_channel( { "peer_id": self.fiber1.get_peer_id(), @@ -226,20 +298,8 @@ def test_cancel_invoice_that_statue_is_paid(self): "CHANNEL_READY", 120, ) - payment_preimage = self.generate_random_preimage() - invoice_balance = 1 * 100000000 - invoice = self.fiber1.get_client().new_invoice( - { - "amount": hex(invoice_balance), - "currency": "Fibd", - "description": "test invoice generated by node2", - "expiry": "0xe10", - "final_cltv": "0x28", - "payment_preimage": payment_preimage, - "hash_algorithm": "sha256", - } - ) + # Step 2: Create a new invoice payment_preimage = self.generate_random_preimage() invoice_balance = 1 * 100000000 invoice = self.fiber1.get_client().new_invoice( @@ -253,6 +313,8 @@ def test_cancel_invoice_that_statue_is_paid(self): "hash_algorithm": "sha256", } ) + + # Step 3: Send a payment using the created invoice before_channel = self.fiber2.get_client().list_channels({}) payment = self.fiber2.get_client().send_payment( { @@ -267,6 +329,7 @@ def test_cancel_invoice_that_statue_is_paid(self): == invoice_balance ) + # Step 4: Attempt to cancel the invoice and verify the expected error message with pytest.raises(Exception) as exc_info: self.fiber1.get_client().cancel_invoice( {"payment_hash": invoice["invoice"]["data"]["payment_hash"]} @@ -278,6 +341,23 @@ def test_cancel_invoice_that_statue_is_paid(self): ) def test_cancel_invoice_that_statue_is_receive(self): + """ + Test case for canceling an invoice that is in the 'Received' state. + + Steps: + 1. Open a new channel. + 2. Create a new invoice. + 3. Send a payment using the created invoice. + 4. Cancel the invoice and verify the payment fails. + 5. Verify the channel balance is unchanged. + 6. Create a new invoice. + 7. Send a payment using the new invoice and verify it succeeds. + 8. Verify the channel balance is updated correctly. + + Returns: + None + """ + # Step 1: Open a new channel temporary_channel_id = self.fiber2.get_client().open_channel( { "peer_id": self.fiber1.get_peer_id(), @@ -292,20 +372,8 @@ def test_cancel_invoice_that_statue_is_receive(self): "CHANNEL_READY", 120, ) - payment_preimage = self.generate_random_preimage() - invoice_balance = 1 * 100000000 - invoice = self.fiber1.get_client().new_invoice( - { - "amount": hex(invoice_balance), - "currency": "Fibd", - "description": "test invoice generated by node2", - "expiry": "0xe10", - "final_cltv": "0x28", - "payment_preimage": payment_preimage, - "hash_algorithm": "sha256", - } - ) + # Step 2: Create a new invoice payment_preimage = self.generate_random_preimage() invoice_balance = 1 * 100000000 invoice = self.fiber1.get_client().new_invoice( @@ -319,8 +387,9 @@ def test_cancel_invoice_that_statue_is_receive(self): "hash_algorithm": "sha256", } ) - before_channel = self.fiber2.get_client().list_channels({}) + # Step 3: Send a payment using the created invoice + before_channel = self.fiber2.get_client().list_channels({}) payment = self.fiber2.get_client().send_payment( { "invoice": invoice["invoice_address"], @@ -333,13 +402,14 @@ def test_cancel_invoice_that_statue_is_receive(self): # 20, # 0, # ) + + # Step 4: Cancel the invoice and verify the payment fails self.fiber1.get_client().cancel_invoice( {"payment_hash": invoice["invoice"]["data"]["payment_hash"]} ) self.wait_payment_state(self.fiber2, payment["payment_hash"], "Failed") - self.fiber1.get_client().get_invoice( - {"payment_hash": invoice["invoice"]["data"]["payment_hash"]} - ) + + # Step 5: Verify the channel balance is unchanged channels = self.fiber2.get_client().list_channels({}) assert ( channels["channels"][0]["local_balance"] @@ -350,6 +420,7 @@ def test_cancel_invoice_that_statue_is_receive(self): != before_channel["channels"][0]["latest_commitment_transaction_hash"] ) + # Step 6: Create a new invoice invoice_balance = 1 * 100000000 invoice = self.fiber1.get_client().new_invoice( { @@ -362,8 +433,9 @@ def test_cancel_invoice_that_statue_is_receive(self): "hash_algorithm": "sha256", } ) - before_channel = self.fiber2.get_client().list_channels({}) + # Step 7: Send a payment using the new invoice and verify it succeeds + before_channel = self.fiber2.get_client().list_channels({}) payment = self.fiber2.get_client().send_payment( { "invoice": invoice["invoice_address"], @@ -371,6 +443,90 @@ def test_cancel_invoice_that_statue_is_receive(self): ) self.wait_payment_state(self.fiber2, payment["payment_hash"], "Success") after_channel = self.fiber2.get_client().list_channels({}) + + # Step 8: Verify the channel balance is updated correctly assert after_channel["channels"][0]["local_balance"] == hex( int(before_channel["channels"][0]["local_balance"], 16) - invoice_balance ) + + def test_batch_cancel(self): + """ + Test case for batch canceling invoices. + + Steps: + 1. Start new fibers. + 2. Open channels between fibers. + 3. Create new invoices. + 4. Send payments and cancel invoices. + 5. Verify payment statuses. + 6. Verify fiber balance. + + Returns: + None + """ + cancel_size = 50 + channel_length = 4 + + # Step 1: Start new fibers + for i in range(channel_length - 2): + self.start_new_fiber(self.generate_account(10000)) + + # Step 2: Open channels between fibers + for i in range(len(self.fibers)): + self.open_channel( + self.fibers[i], + self.fibers[(i + 1) % len(self.fibers)], + 1000 * 100000000, + 1000 * 100000000, + ) + + # Step 3: Create new invoices + new_invoices = [] + for i in range(cancel_size): + new_invoice = ( + self.fibers[0] + .get_client() + .new_invoice( + { + "amount": hex(1 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node2", + "expiry": "0xe10", + "final_cltv": "0x28", + "payment_preimage": self.generate_random_preimage(), + "hash_algorithm": "sha256", + } + ) + ) + new_invoices.append(new_invoice) + + # Step 4: Send payments and cancel invoices + for i in range(cancel_size): + self.fiber1.get_client().send_payment( + { + "invoice": new_invoices[i]["invoice_address"], + "allow_self_payment": True, + } + ) + self.fiber1.get_client().cancel_invoice( + {"payment_hash": new_invoices[i]["invoice"]["data"]["payment_hash"]} + ) + + # Step 5: Verify payment statuses + payment_status = [] + for i in range(cancel_size): + payment = self.fiber1.get_client().get_payment( + {"payment_hash": new_invoices[i]["invoice"]["data"]["payment_hash"]} + ) + payment_status.append(payment) + + for i in range(cancel_size): + self.wait_payment_state( + self.fibers[0], + new_invoices[i]["invoice"]["data"]["payment_hash"], + "Failed", + ) + + # Step 6: Verify fiber balance + balance = self.get_fiber_balance(self.fibers[0]) + assert balance["local_balance"] == 200000000000 diff --git a/test_cases/fiber/devnet/compatibility/test_data.py b/test_cases/fiber/devnet/compatibility/test_data.py index 31900003..8bd96d4a 100644 --- a/test_cases/fiber/devnet/compatibility/test_data.py +++ b/test_cases/fiber/devnet/compatibility/test_data.py @@ -26,6 +26,7 @@ def test_old_fiber_020(self): ) old_fiber_1.connect_peer(old_fiber_2) time.sleep(1) + # 2. open_channel with fiber self.open_channel( old_fiber_1, old_fiber_2, 1000 * 100000000, 1000 * 100000000, 1000, 1000 @@ -83,6 +84,105 @@ def test_old_fiber_021(self): ) old_fiber_1.connect_peer(old_fiber_2) time.sleep(1) + + # 2. open_channel with fiber + self.open_channel( + old_fiber_1, old_fiber_2, 1000 * 100000000, 1000 * 100000000, 1000, 1000 + ) + self.send_payment(old_fiber_1, old_fiber_2, 100) + + # 3. stop fiber + old_fiber_1.stop() + old_fiber_2.stop() + + # 4. migration and restart fiber 0.3.0 + old_fiber_1.fiber_config_enum = FiberConfigPath.CURRENT_DEV + old_fiber_2.fiber_config_enum = FiberConfigPath.CURRENT_DEV + old_fiber_1.migration() + time.sleep(5) + old_fiber_1.start() + time.sleep(5) + old_fiber_2.migration() + old_fiber_2.start() + + # 5. send_payment + self.send_payment(old_fiber_1, old_fiber_2, 100) + self.send_payment(old_fiber_2, old_fiber_1, 200) + channels = old_fiber_1.get_client().list_channels({}) + old_fiber_1.get_client().shutdown_channel( + { + "channel_id": channels["channels"][0]["channel_id"], + "close_script": self.get_account_script(self.Config.ACCOUNT_PRIVATE_1), + "fee_rate": "0x3FC", + } + ) + tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 120) + tx_message = self.get_tx_message(tx_hash) + print("tx message:", tx_message) + assert { + "args": self.get_account_script(old_fiber_2.account_private)["args"], + "capacity": 106200000000, + } in tx_message["output_cells"] + + def test_old_fiber_030(self): + # 1. start fiber 0.2.0 + old_fiber_1 = self.start_new_fiber( + self.generate_account(10000), fiber_version=FiberConfigPath.V030_DEV + ) + old_fiber_2 = self.start_new_fiber( + self.generate_account(10000), fiber_version=FiberConfigPath.V030_DEV + ) + old_fiber_1.connect_peer(old_fiber_2) + time.sleep(1) + # 2. open_channel with fiber + self.open_channel( + old_fiber_1, old_fiber_2, 1000 * 100000000, 1000 * 100000000, 1000, 1000 + ) + self.send_payment(old_fiber_1, old_fiber_2, 100) + + # 3. stop fiber + old_fiber_1.stop() + old_fiber_2.stop() + + # 4. migration and restart fiber 0.3.0 + old_fiber_1.fiber_config_enum = FiberConfigPath.CURRENT_DEV + old_fiber_2.fiber_config_enum = FiberConfigPath.CURRENT_DEV + old_fiber_1.migration() + time.sleep(5) + old_fiber_1.start() + time.sleep(5) + old_fiber_2.migration() + old_fiber_2.start() + + # 5. send_payment + self.send_payment(old_fiber_1, old_fiber_2, 100) + self.send_payment(old_fiber_2, old_fiber_1, 200) + channels = old_fiber_1.get_client().list_channels({}) + old_fiber_1.get_client().shutdown_channel( + { + "channel_id": channels["channels"][0]["channel_id"], + "close_script": self.get_account_script(self.Config.ACCOUNT_PRIVATE_1), + "fee_rate": "0x3FC", + } + ) + tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 120) + tx_message = self.get_tx_message(tx_hash) + print("tx message:", tx_message) + assert { + "args": self.get_account_script(old_fiber_2.account_private)["args"], + "capacity": 106200000000, + } in tx_message["output_cells"] + + def test_old_fiber_031(self): + # 1. start fiber 0.2.0 + old_fiber_1 = self.start_new_fiber( + self.generate_account(10000), fiber_version=FiberConfigPath.V031_DEV + ) + old_fiber_2 = self.start_new_fiber( + self.generate_account(10000), fiber_version=FiberConfigPath.V031_DEV + ) + old_fiber_1.connect_peer(old_fiber_2) + time.sleep(1) # 2. open_channel with fiber self.open_channel( old_fiber_1, old_fiber_2, 1000 * 100000000, 1000 * 100000000, 1000, 1000 diff --git a/test_cases/fiber/devnet/connect_peer/test_connect_peer.py b/test_cases/fiber/devnet/connect_peer/test_connect_peer.py index ef6cb2e7..99fbc1aa 100644 --- a/test_cases/fiber/devnet/connect_peer/test_connect_peer.py +++ b/test_cases/fiber/devnet/connect_peer/test_connect_peer.py @@ -6,14 +6,31 @@ class TestConnectPeer(FiberTest): def test_connect_peer(self): - # exist node + """ + Test connecting to a peer. + + Steps: + 1. Connect to an existing node. + 2. Attempt to connect to a non-existing node. + 3. Wait for 2 seconds to ensure the connection is established. + 4. Retrieve node information. + 5. Assert that the peer count is 1. + """ + # Step 1: Connect to an existing node self.fiber1.connect_peer(self.fiber2) - # not exist node + + # Step 2: Attempt to connect to a non-existing node self.fiber1.get_client().connect_peer( { "address": "/ip4/127.0.0.1/tcp/8231/p2p/QmNoDjLNbJujKpBorKHWPHPKoLrzND1fYtmmEVxkq35Hgp" } ) + + # Step 3: Wait for 2 seconds to ensure the connection is established time.sleep(2) + + # Step 4: Retrieve node information node_info = self.fiber1.get_client().node_info() + + # Step 5: Assert that the peer count is 1 assert node_info["peers_count"] == "0x1" diff --git a/test_cases/fiber/devnet/disconnect_peer/test_disconnect_peer.py b/test_cases/fiber/devnet/disconnect_peer/test_disconnect_peer.py index 35d8f25d..3d06b33a 100644 --- a/test_cases/fiber/devnet/disconnect_peer/test_disconnect_peer.py +++ b/test_cases/fiber/devnet/disconnect_peer/test_disconnect_peer.py @@ -5,15 +5,31 @@ class TestDisconnectPeer(FiberTest): def test_disconnect_peer(self): + """ + Test disconnecting from a peer. + + Steps: + 1. Retrieve initial node information and assert the peer count is 1. + 2. Disconnect from the peer. + 3. Wait for 1 second to ensure the disconnection is processed. + 4. Retrieve node information and assert the peer count is 0. + 5. Attempt to disconnect from a non-existing peer. + """ + # Step 1: Retrieve initial node information and assert the peer count is 1 before_node_info = self.fiber1.get_client().node_info() assert before_node_info["peers_count"] == "0x1" + # Step 2: Disconnect from the peer self.fiber1.get_client().disconnect_peer({"peer_id": self.fiber2.get_peer_id()}) + + # Step 3: Wait for 1 second to ensure the disconnection is processed time.sleep(1) + + # Step 4: Retrieve node information and assert the peer count is 0 after_node_info = self.fiber1.get_client().node_info() assert after_node_info["peers_count"] == "0x0" - # not exist peer_id + # Step 5: Attempt to disconnect from a non-existing peer self.fiber1.get_client().disconnect_peer( {"peer_id": "QmNoDjLNbJujKpBorKHWPHPKoLrzND1fYtmmEVxkq35Hgp"} ) diff --git a/test_cases/fiber/devnet/graph_nodes/test_graph_nodes.py b/test_cases/fiber/devnet/graph_nodes/test_graph_nodes.py index d3f1764c..707aca71 100644 --- a/test_cases/fiber/devnet/graph_nodes/test_graph_nodes.py +++ b/test_cases/fiber/devnet/graph_nodes/test_graph_nodes.py @@ -17,7 +17,7 @@ class TestGraphNodes(FiberTest): 3. 测试迭代 """ - @pytest.mark.skip("todo 待确定") + # @pytest.mark.skip("todo 待确定") def test_add_nodes(self): """ add nodes @@ -39,10 +39,10 @@ def test_add_nodes(self): current_fiber2.connect_peer(current_fiber) time.sleep(5) assert len(current_fiber.get_client().graph_nodes()["nodes"]) == 35 - assert len(current_fiber1.get_client().graph_nodes()["nodes"]) == 5 + assert len(current_fiber1.get_client().graph_nodes()["nodes"]) == 35 assert len(current_fiber2.get_client().graph_nodes()["nodes"]) == 35 - assert len(self.fiber1.get_client().graph_nodes()["nodes"]) == 5 - assert len(self.fiber2.get_client().graph_nodes()["nodes"]) == 5 + assert len(self.fiber1.get_client().graph_nodes()["nodes"]) == 35 + assert len(self.fiber2.get_client().graph_nodes()["nodes"]) == 35 # 测试迭代 for fiber in self.fibers: graph_nodes = fiber.get_client().graph_nodes() @@ -92,7 +92,7 @@ def test_node_info(self): # udt_cfg_infos assert node["udt_cfg_infos"] == node_info["udt_cfg_infos"] - @pytest.mark.skip("其他节点的graph_nodes 不一定会更新") + # @pytest.mark.skip("其他节点的graph_nodes 不一定会更新") def test_change_node_info(self): """ 1. 修改配置 ,重启节点 @@ -114,7 +114,6 @@ def test_change_node_info(self): node_info = self.fibers[i].get_client().node_info() # alias # assert node['alias'] == node_info['node_name'] - assert node["alias"] == "" # addresses assert node["addresses"] == node_info["addresses"] # node_id @@ -144,9 +143,6 @@ def test_change_node_info(self): for i in range(len(graph_nodes["nodes"])): node = graph_nodes["nodes"][i] node_info = self.fibers[i].get_client().node_info() - # alias - # assert node['alias'] == node_info['node_name'] - assert node["alias"] == "" # addresses assert node["addresses"] == node_info["addresses"] # node_id diff --git a/test_cases/fiber/devnet/issue/test_issue_478.py b/test_cases/fiber/devnet/issue/test_issue_478.py new file mode 100644 index 00000000..5d4dfaa1 --- /dev/null +++ b/test_cases/fiber/devnet/issue/test_issue_478.py @@ -0,0 +1,31 @@ +import time + +from framework.basic_fiber import FiberTest +from framework.test_fiber import FiberConfigPath + + +class TestIssue478(FiberTest): + + def test_issue_478(self): + """ + https://github.com/nervosnetwork/fiber/pull/478 + Returns: + + """ + + # start new fiber + self.fiber3 = self.start_new_fiber( + self.generate_account(10000), + config=None, + fiber_version=FiberConfigPath.CURRENT_TESTNET, + ) + self.fiber4 = self.start_new_fiber( + self.generate_account(10000), + config=None, + fiber_version=FiberConfigPath.CURRENT_DEV, + ) + + self.fiber3.connect_peer(self.fiber4) + time.sleep(2) + self.fiber3.get_client().graph_nodes({}) + self.fiber4.get_client().graph_nodes({}) diff --git a/test_cases/fiber/devnet/issue/test_issue_484.py b/test_cases/fiber/devnet/issue/test_issue_484.py new file mode 100644 index 00000000..9f6be451 --- /dev/null +++ b/test_cases/fiber/devnet/issue/test_issue_484.py @@ -0,0 +1,22 @@ +import time + +import pytest + +from framework.basic_fiber import FiberTest + + +class Test484(FiberTest): + # FiberTest.debug = True + + # @pytest.mark.skip("https://github.com/nervosnetwork/fiber/pull/484") + def test_484(self): + self.open_channel( + self.fiber1, self.fiber2, 1000 * 100000000, 1000 * 100000000, 1000, 1000 + ) + payment1 = self.send_payment(self.fiber1, self.fiber2, 600 * 100000000, False) + payment2 = self.send_payment(self.fiber1, self.fiber2, 600 * 100000000, False) + payment3 = self.send_payment(self.fiber1, self.fiber2, 300 * 100000000, False) + self.wait_payment_state(self.fiber1, payment1, "Success") + self.wait_payment_state(self.fiber1, payment2, "Failed") + self.wait_payment_state(self.fiber1, payment3, "Success") + self.send_payment(self.fiber2, self.fiber1, 300 * 100000000) diff --git a/test_cases/fiber/devnet/open_channel/test_ckb_cell.py b/test_cases/fiber/devnet/open_channel/test_ckb_cell.py index d6556699..f3337448 100644 --- a/test_cases/fiber/devnet/open_channel/test_ckb_cell.py +++ b/test_cases/fiber/devnet/open_channel/test_ckb_cell.py @@ -130,7 +130,7 @@ def test_account_mutil_cell_gt_funding_amount_2(self): # FiberTest.debug = True - @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/284") + # @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/284") def test_config_not_eq(self): """ @@ -175,6 +175,10 @@ def test_config_not_eq(self): } ) self.wait_payment_state(self.fiber2, payment["payment_hash"], "Success", 120) + self.send_payment(new_fiber, self.fiber2, 1 * 100000000) + nodes = self.fiber2.get_client().graph_nodes({}) + new_nodes = new_fiber.get_client().graph_nodes({}) + assert len(nodes["nodes"]) == len(new_nodes["nodes"]) def test_config_eq(self): account3_private_key = self.generate_account(1000) diff --git a/test_cases/fiber/devnet/open_channel/test_max_tlc_number_in_flight.py b/test_cases/fiber/devnet/open_channel/test_max_tlc_number_in_flight.py index 7988eb05..0b0481d0 100644 --- a/test_cases/fiber/devnet/open_channel/test_max_tlc_number_in_flight.py +++ b/test_cases/fiber/devnet/open_channel/test_max_tlc_number_in_flight.py @@ -107,7 +107,7 @@ def test_max_tlc_number_in_flight_very_big(self): f"not found in actual string '{exc_info.value.args[0]}'" ) - @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/450") + # @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/450") def test_max_tlc_number_in_flight_zero(self): """ max_tlc_number_in_flight = 0 @@ -149,9 +149,9 @@ def test_max_tlc_number_in_flight_zero(self): "invoice": invoice["invoice_address"], } ) - expected_error_message = "TemporaryChannelFailure" - assert expected_error_message in payment["failed_error"] - + # expected_error_message = "TemporaryChannelFailure" + # assert expected_error_message in payment["failed_error"] + self.wait_payment_state(self.fiber1, payment["payment_hash"], "Failed") channels = self.fiber1.get_client().list_channels( {"peer_id": self.fiber2.get_peer_id()} ) diff --git a/test_cases/fiber/devnet/open_channel/test_max_tlc_value_in_flight.py b/test_cases/fiber/devnet/open_channel/test_max_tlc_value_in_flight.py index 958eda11..e5f9f747 100644 --- a/test_cases/fiber/devnet/open_channel/test_max_tlc_value_in_flight.py +++ b/test_cases/fiber/devnet/open_channel/test_max_tlc_value_in_flight.py @@ -15,7 +15,7 @@ def test_max_tlc_value_in_flight_none(self): """ # self.test_linked_peer() - @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/450") + # @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/450") def test_max_tlc_value_in_flight_is_zero(self): """ max_tlc_value_in_flight = 0 @@ -74,8 +74,10 @@ def test_max_tlc_value_in_flight_is_zero(self): "invoice": invoice["invoice_address"], } ) - expected_error_message = "TemporaryChannelFailure" - assert expected_error_message in payment["failed_error"] + + self.wait_payment_state(self.fiber1, payment["payment_hash"], "Failed") + # expected_error_message = "TemporaryChannelFailure" + # assert expected_error_message in payment["failed_error"] # invoice_balance = hex(160 * 100000000) # payment_preimage = self.generate_random_preimage() @@ -466,7 +468,7 @@ def test_udt_max_tlc_value_in_flight_too_min(self): print("after_balance2:", after_balance2) assert after_balance2 - before_balance2 == 143 - @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/450") + # @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/450") def test_debug_ckb_max_tlc_value_in_flight_not_eq_default(self): self.fiber1.get_client().open_channel( { @@ -508,14 +510,10 @@ def test_debug_ckb_max_tlc_value_in_flight_not_eq_default(self): "invoice": invoice["invoice_address"], } ) - time.sleep(1) - payment = self.fiber1.get_client().get_payment( - {"payment_hash": payment["payment_hash"]} - ) - print("payment:", payment) + self.wait_payment_state(self.fiber1, payment["payment_hash"], "Failed") - @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/450") + # @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/450") def test_ckb_max_tlc_value_in_flight_not_eq_default(self): """ max_tlc_value_in_flight != default @@ -563,8 +561,10 @@ def test_ckb_max_tlc_value_in_flight_not_eq_default(self): "invoice": invoice["invoice_address"], } ) - expected_error_message = "TemporaryChannelFailure" - assert expected_error_message in payment["failed_error"] + self.wait_payment_state(self.fiber1, payment["payment_hash"], "Failed") + time.sleep(1) + # expected_error_message = "TemporaryChannelFailure" + # assert expected_error_message in payment["failed_error"] # send 1 ckb invoice_balance = hex(1 * 100000000) payment_preimage = self.generate_random_preimage() @@ -683,7 +683,7 @@ def test_ckb_max_tlc_value_in_flight_not_eq_default(self): print("after_balance2:", after_balance2) assert after_balance2 - before_balance2 == 62.90000057220459 - @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/450") + # @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/450") def test_udt_max_tlc_value_in_flight_not_eq_default(self): """ max_tlc_value_in_flight != default @@ -737,8 +737,9 @@ def test_udt_max_tlc_value_in_flight_not_eq_default(self): "invoice": invoice["invoice_address"], } ) - expected_error_message = "TemporaryChannelFailure" - assert expected_error_message in payment["failed_error"] + self.wait_payment_state(self.fiber1, payment["payment_hash"], "Failed") + # expected_error_message = "TemporaryChannelFailure" + # assert expected_error_message in payment["failed_error"] # send 1 ckb invoice_balance = hex(1 * 100000000) payment_preimage = self.generate_random_preimage() @@ -758,7 +759,7 @@ def test_udt_max_tlc_value_in_flight_not_eq_default(self): } ) before_channel = self.fiber1.get_client().list_channels({}) - + time.sleep(1) payment = self.fiber1.get_client().send_payment( { "invoice": invoice["invoice_address"], diff --git a/test_cases/fiber/devnet/open_channel/test_tlc_fee_proportional_millionths.py b/test_cases/fiber/devnet/open_channel/test_tlc_fee_proportional_millionths.py index 357b70f2..dcb121be 100644 --- a/test_cases/fiber/devnet/open_channel/test_tlc_fee_proportional_millionths.py +++ b/test_cases/fiber/devnet/open_channel/test_tlc_fee_proportional_millionths.py @@ -127,7 +127,7 @@ def test_ckb_tlc_fee_proportional_millionths_not_eq_default(self): temporary_channel_id = self.fiber1.get_client().open_channel( { "peer_id": self.fiber2.get_peer_id(), - "funding_amount": hex(200 * 100000000), + "funding_amount": hex(500 * 100000000), "public": True, # "funding_fee_rate": "0xffff", # "tlc_fee_proportional_millionths": hex(1000000), @@ -168,7 +168,7 @@ def test_ckb_tlc_fee_proportional_millionths_not_eq_default(self): ) before_channel_12 = self.fiber1.get_client().list_channels({}) before_channel_32 = new_fiber.get_client().list_channels({}) - time.sleep(10) + time.sleep(1) self.fiber2.get_client().graph_channels() self.fiber2.get_client().graph_nodes() payment = self.fiber1.get_client().send_payment( @@ -187,7 +187,10 @@ def test_ckb_tlc_fee_proportional_millionths_not_eq_default(self): assert int(before_channel_12["channels"][0]["local_balance"], 16) - int( after_channel_12["channels"][0]["local_balance"], 16 - ) == (int(invoice_balance, 16) + int(int(invoice_balance, 16) * 0.001)) + ) == ( + int(invoice_balance, 16) + + int(int(invoice_balance, 16) * fiber2_tlc_fee / 1000000) + ) assert int(after_channel_32["channels"][0]["local_balance"], 16) - int( before_channel_32["channels"][0]["local_balance"], 16 @@ -221,28 +224,75 @@ def test_ckb_tlc_fee_proportional_millionths_not_eq_default(self): assert ( int(after_channel_32["channels"][0]["local_balance"], 16) - int(after_channel_2_32["channels"][0]["local_balance"], 16) - == int(invoice_balance, 16) - + int(invoice_balance, 16) * fiber2_tlc_fee / 1000000 + == int(invoice_balance, 16) + int(invoice_balance, 16) * 1000 / 1000000 ) assert int(after_channel_2_12["channels"][0]["local_balance"], 16) - int( after_channel_12["channels"][0]["local_balance"], 16 ) == int(invoice_balance, 16) + def test_normal_fee(self): + """ + 1-2-3 + 1 send 3 fee= 1000000 + 3 send 1 fee=1000 + Returns: + + """ + self.start_new_fiber(self.generate_account(1000)) + self.open_channel( + self.fibers[0], + self.fibers[1], + 1000 * 100000000, + 1, + fiber1_fee=1000, + fiber2_fee=1000, + ) + self.open_channel( + self.fibers[1], + self.fibers[2], + 1000 * 100000000, + 1, + fiber1_fee=1000000, + fiber2_fee=1000, + ) + time.sleep(5) + payment_hash = self.send_payment(self.fibers[0], self.fibers[2], 10 * 100000000) + payment = ( + self.fibers[0].get_client().get_payment({"payment_hash": payment_hash}) + ) + assert payment["fee"] == hex(int(10 * 100000000 * 1000000 / 1000000)) + payment_hash = self.send_payment(self.fibers[2], self.fibers[0], 1 * 100000000) + payment = ( + self.fibers[2].get_client().get_payment({"payment_hash": payment_hash}) + ) + assert payment["fee"] == hex(int(1 * 100000000 * 1000 / 1000000)) + channels1 = self.fiber1.get_client().list_channels({}) + channels2 = self.fibers[2].get_client().list_channels({}) + assert channels1["channels"][0]["local_balance"] == hex(98100000000) + assert channels2["channels"][0]["local_balance"] == hex(899900001) + + # def test_fee_mutil(self): + # self.start_new_fiber(self.generate_account(10000)) + # self.start_new_fiber(self.generate_account(10000)) + # + # self.open_channel(self.fibers[0], self.fibers[1], 1000 * 100000000, 1, fiber1_fee=1000, fiber2_fee=1000) + # self.open_channel(self.fibers[1], self.fibers[2], 1000 * 100000000, 1, fiber1_fee=1000000, fiber2_fee=1000) + # self.open_channel(self.fibers[2], self.fibers[3], 1000 * 100000000, 1, fiber1_fee=2000000, fiber2_fee=1000) + # self.open_channel(self.fibers[3], self.fibers[0], 1000 * 100000000, 1, fiber1_fee=3000000, fiber2_fee=1000) + # for i in range(100): + # self.send_payment(self.fibers[0], self.fibers[0], 1 * 100000000) + # # (100000000 * (1+ 3000000/ 1000000) ) * (1+ 2000000/ 1000000) + def test_tlc_fee_proportional_millionths_max(self): account3_private_key = self.generate_account(1000) new_fiber = self.start_new_fiber(account3_private_key) - time.sleep(3) - self.fiber2.connect_peer(new_fiber) - - # self.fiber3.connect_peer(self.fiber2) - time.sleep(3) self.fiber2.connect_peer(new_fiber) time.sleep(3) temporary_channel_id = self.fiber1.get_client().open_channel( { "peer_id": self.fiber2.get_peer_id(), - "funding_amount": hex(200 * 100000000), + "funding_amount": hex(20000 * 100000000), "public": True, # "funding_fee_rate": "0xffff", # "tlc_fee_proportional_millionths": hex(1000000), @@ -256,7 +306,7 @@ def test_tlc_fee_proportional_millionths_max(self): temporary_channel_id = self.fiber2.get_client().open_channel( { "peer_id": new_fiber.get_peer_id(), - "funding_amount": hex(1000 * 100000000), + "funding_amount": hex(10000 * 100000000), "public": True, "tlc_fee_proportional_millionths": hex(fiber2_tlc_fee), # "tlc_min_value": hex(2 * 100000000) @@ -268,7 +318,7 @@ def test_tlc_fee_proportional_millionths_max(self): self.wait_for_channel_state( self.fiber2.get_client(), new_fiber.get_peer_id(), "CHANNEL_READY", 120 ) - invoice_balance = hex(100 * 100000000) + invoice_balance = hex(1 * 100000000) payment_preimage = self.generate_random_preimage() invoice = new_fiber.get_client().new_invoice( { @@ -302,7 +352,10 @@ def test_tlc_fee_proportional_millionths_max(self): assert int(before_channel_12["channels"][0]["local_balance"], 16) - int( after_channel_12["channels"][0]["local_balance"], 16 - ) == (int(invoice_balance, 16) + int(int(invoice_balance, 16) * 0.001)) + ) == ( + int(invoice_balance, 16) + + int(int(invoice_balance, 16) * fiber2_tlc_fee / 1000000) + ) assert int(after_channel_32["channels"][0]["local_balance"], 16) - int( before_channel_32["channels"][0]["local_balance"], 16 @@ -336,8 +389,7 @@ def test_tlc_fee_proportional_millionths_max(self): assert ( int(after_channel_32["channels"][0]["local_balance"], 16) - int(after_channel_2_32["channels"][0]["local_balance"], 16) - == int(invoice_balance, 16) - + int(invoice_balance, 16) * fiber2_tlc_fee / 1000000 + == int(invoice_balance, 16) + int(invoice_balance, 16) * 1000 / 1000000 ) assert int(after_channel_2_12["channels"][0]["local_balance"], 16) - int( @@ -473,7 +525,10 @@ def test_udt_tlc_fee_proportional_millionths_not_eq_default(self): assert int(before_channel_12["channels"][0]["local_balance"], 16) - int( after_channel_12["channels"][0]["local_balance"], 16 - ) == (int(invoice_balance, 16) + int(int(invoice_balance, 16) * 0.001)) + ) == ( + int(invoice_balance, 16) + + int(int(invoice_balance, 16) * fiber2_tlc_fee / 1000000) + ) assert int(after_channel_32["channels"][0]["local_balance"], 16) - int( before_channel_32["channels"][0]["local_balance"], 16 @@ -515,8 +570,7 @@ def test_udt_tlc_fee_proportional_millionths_not_eq_default(self): assert ( int(after_channel_32["channels"][0]["local_balance"], 16) - int(after_channel_2_32["channels"][0]["local_balance"], 16) - == int(invoice_balance, 16) - + int(invoice_balance, 16) * fiber2_tlc_fee / 1000000 + == int(invoice_balance, 16) + int(invoice_balance, 16) * 1000 / 1000000 ) assert int(after_channel_2_12["channels"][0]["local_balance"], 16) - int( diff --git a/test_cases/fiber/devnet/open_channel/test_tlc_min_value.py b/test_cases/fiber/devnet/open_channel/test_tlc_min_value.py index 815a84f9..3bb97ccf 100644 --- a/test_cases/fiber/devnet/open_channel/test_tlc_min_value.py +++ b/test_cases/fiber/devnet/open_channel/test_tlc_min_value.py @@ -117,37 +117,22 @@ def test_tlc_min_value_is_not_zero(self): self.wait_for_channel_state( self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY", 120 ) - time.sleep(5) # transfer - self.fiber1.get_client().graph_channels() - self.fiber1.get_client().graph_nodes() - payment_preimage = self.generate_random_preimage() - channels = self.fiber1.get_client().list_channels({}) - invoice_balance = hex(100 * 100000000) - - invoice = self.fiber2.get_client().new_invoice( - { - "amount": invoice_balance, - "currency": "Fibd", - "description": "test invoice generated by node2", - "expiry": "0xe10", - "final_cltv": "0x28", - "payment_preimage": payment_preimage, - "hash_algorithm": "sha256", - } - ) before_channel = self.fiber1.get_client().list_channels({}) - - self.fiber1.get_client().send_payment( - { - "invoice": invoice["invoice_address"], - } + with pytest.raises(Exception) as exc_info: + self.send_payment(self.fiber1, self.fiber2, 2 * 100000000 - 1) + expected_error_message = "Failed to build route" + assert expected_error_message in exc_info.value.args[0], ( + f"Expected substring '{expected_error_message}' " + f"not found in actual string '{exc_info.value.args[0]}'" ) - time.sleep(10) + self.send_payment(self.fiber1, self.fiber2, 100 * 100000000) after_channel = self.fiber1.get_client().list_channels({}) - assert int(before_channel["channels"][0]["local_balance"], 16) - int( - after_channel["channels"][0]["local_balance"], 16 - ) == int(invoice_balance, 16) + assert ( + int(before_channel["channels"][0]["local_balance"], 16) + - int(after_channel["channels"][0]["local_balance"], 16) + == 100 * 100000000 + ) # node2 send 2 ckb invoice_balance = hex(2 * 100000000) @@ -178,46 +163,14 @@ def test_tlc_min_value_is_not_zero(self): ) == int(invoice_balance, 16) # node2 send 1 ckb + channels = self.fiber1.get_client().list_channels({}) - invoice_balance = hex(2 * 100000000 - 1) - payment_preimage = self.generate_random_preimage() - invoice = self.fiber1.get_client().new_invoice( - { - "amount": invoice_balance, - "currency": "Fibd", - "description": "test invoice generated by node2", - "expiry": "0xe10", - "final_cltv": "0x28", - "payment_preimage": payment_preimage, - "hash_algorithm": "sha256", - } - ) - before_channel = self.fiber2.get_client().list_channels({}) - print("node2 send 1 ckb failed ") - with pytest.raises(Exception) as exc_info: - self.fiber2.get_client().send_payment( - { - "invoice": invoice["invoice_address"], - } - ) - expected_error_message = "Failed to build route" - assert expected_error_message in exc_info.value.args[0], ( - f"Expected substring '{expected_error_message}' " - f"not found in actual string '{exc_info.value.args[0]}'" - ) - + self.send_payment(self.fiber2, self.fiber1, 2 * 100000000 - 1) channels = self.fiber1.get_client().list_channels( {"peer_id": self.fiber2.get_peer_id()} ) N1N2_CHANNEL_ID = channels["channels"][0]["channel_id"] - self.fiber1.get_client().graph_channels() - - before_balance1 = self.Ckb_cli.wallet_get_capacity( - self.account1["address"]["testnet"] - ) - before_balance2 = self.Ckb_cli.wallet_get_capacity( - self.account2["address"]["testnet"] - ) + channels = self.fiber1.get_client().list_channels({}) # shut down self.fiber1.get_client().shutdown_channel( { @@ -231,18 +184,15 @@ def test_tlc_min_value_is_not_zero(self): } ) # todo wait close tx commit - time.sleep(20) - after_balance1 = self.Ckb_cli.wallet_get_capacity( - self.account1["address"]["testnet"] - ) - after_balance2 = self.Ckb_cli.wallet_get_capacity( - self.account2["address"]["testnet"] - ) - print("before_balance1:", before_balance1) - print("before_balance2:", before_balance2) - print("after_balance1:", after_balance1) - print("after_balance2:", after_balance2) - assert after_balance2 - before_balance2 == 160 + tx_hash = self.wait_and_check_tx_pool_fee(1000, False) + tx_response = self.get_tx_message(tx_hash) + print("tx response:", tx_response) + print("channels:", channels) + assert 26200000000 == tx_response["input_cells"][0]["capacity"] + assert { + "args": self.fiber2.get_account()["lock_arg"], + "capacity": 15800000001, + } in tx_response["output_cells"] def test_tlc_min_value_gt_funding_amount(self): """ @@ -284,36 +234,8 @@ def test_tlc_min_value_gt_funding_amount(self): ) before_channel = self.fiber1.get_client().list_channels({}) - payment = self.fiber1.get_client().send_payment( - { - "invoice": invoice["invoice_address"], - } - ) - self.wait_payment_state(self.fiber1, payment["payment_hash"], "Success") - after_channel = self.fiber1.get_client().list_channels({}) - assert int(before_channel["channels"][0]["local_balance"], 16) - int( - after_channel["channels"][0]["local_balance"], 16 - ) == int(invoice_balance, 16) - - # node2 send all ckb to node 1 - - invoice_balance = channels["channels"][0]["local_balance"] - payment_preimage = self.generate_random_preimage() - invoice = self.fiber1.get_client().new_invoice( - { - "amount": invoice_balance, - "currency": "Fibd", - "description": "test invoice generated by node2", - "expiry": "0xe10", - "final_cltv": "0x28", - "payment_preimage": payment_preimage, - "hash_algorithm": "sha256", - } - ) - before_channel = self.fiber2.get_client().list_channels({}) - print("node2 send 1 ckb failed ") with pytest.raises(Exception) as exc_info: - self.fiber2.get_client().send_payment( + payment = self.fiber1.get_client().send_payment( { "invoice": invoice["invoice_address"], } @@ -324,40 +246,6 @@ def test_tlc_min_value_gt_funding_amount(self): f"not found in actual string '{exc_info.value.args[0]}'" ) - channels = self.fiber1.get_client().list_channels( - {"peer_id": self.fiber2.get_peer_id()} - ) - N1N2_CHANNEL_ID = channels["channels"][0]["channel_id"] - self.fiber1.get_client().graph_channels() - - before_balance1 = self.Ckb_cli.wallet_get_capacity( - self.account1["address"]["testnet"] - ) - before_balance2 = self.Ckb_cli.wallet_get_capacity( - self.account2["address"]["testnet"] - ) - # shut down - self.fiber1.get_client().shutdown_channel( - { - "channel_id": N1N2_CHANNEL_ID, - "close_script": self.get_account_script(self.fiber1.account_private), - "fee_rate": "0x3FC", - } - ) - # todo wait close tx commit - time.sleep(20) - after_balance1 = self.Ckb_cli.wallet_get_capacity( - self.account1["address"]["testnet"] - ) - after_balance2 = self.Ckb_cli.wallet_get_capacity( - self.account2["address"]["testnet"] - ) - print("before_balance1:", before_balance1) - print("before_balance2:", before_balance2) - print("after_balance1:", after_balance1) - print("after_balance2:", after_balance2) - assert after_balance2 - before_balance2 == 200 - def test_ckb_tlc_min_value_not_eq_default(self): """ tlc_min_value != default @@ -398,6 +286,27 @@ def test_udt_tlc_min_value_not_eq_default(self): self.fiber1.get_client().graph_nodes() payment_preimage = self.generate_random_preimage() channels = self.fiber1.get_client().list_channels({}) + + with pytest.raises(Exception) as exc_info: + self.send_payment( + self.fiber1, + self.fiber2, + 160 * 100000000 - 1, + True, + { + "code_hash": self.udtContract.get_code_hash(True, self.node.rpcUrl), + "hash_type": "type", + "args": self.udtContract.get_owner_arg_by_lock_arg( + self.account1["lock_arg"] + ), + }, + ) + expected_error_message = "Failed to build route" + assert expected_error_message in exc_info.value.args[0], ( + f"Expected substring '{expected_error_message}' " + f"not found in actual string '{exc_info.value.args[0]}'" + ) + invoice_balance = hex(500 * 100000000) invoice = self.fiber2.get_client().new_invoice( @@ -420,12 +329,12 @@ def test_udt_tlc_min_value_not_eq_default(self): ) before_channel = self.fiber1.get_client().list_channels({}) - self.fiber1.get_client().send_payment( + payment = self.fiber1.get_client().send_payment( { "invoice": invoice["invoice_address"], } ) - time.sleep(10) + self.wait_payment_state(self.fiber1, payment["payment_hash"], "Success") after_channel = self.fiber1.get_client().list_channels({}) assert int(before_channel["channels"][0]["local_balance"], 16) - int( after_channel["channels"][0]["local_balance"], 16 @@ -454,12 +363,13 @@ def test_udt_tlc_min_value_not_eq_default(self): ) before_channel = self.fiber2.get_client().list_channels({}) - self.fiber2.get_client().send_payment( + payment = self.fiber2.get_client().send_payment( { "invoice": invoice["invoice_address"], } ) - time.sleep(10) + self.wait_payment_state(self.fiber2, payment["payment_hash"], "Success") + after_channel = self.fiber2.get_client().list_channels({}) assert int(before_channel["channels"][0]["local_balance"], 16) - int( after_channel["channels"][0]["local_balance"], 16 @@ -488,18 +398,12 @@ def test_udt_tlc_min_value_not_eq_default(self): } ) before_channel = self.fiber2.get_client().list_channels({}) - print("node2 send 1 ckb failed ") - with pytest.raises(Exception) as exc_info: - self.fiber2.get_client().send_payment( - { - "invoice": invoice["invoice_address"], - } - ) - expected_error_message = "Failed to build route" - assert expected_error_message in exc_info.value.args[0], ( - f"Expected substring '{expected_error_message}' " - f"not found in actual string '{exc_info.value.args[0]}'" + payment = self.fiber2.get_client().send_payment( + { + "invoice": invoice["invoice_address"], + } ) + self.wait_payment_state(self.fiber2, payment["payment_hash"], "Success") channels = self.fiber1.get_client().list_channels( {"peer_id": self.fiber2.get_peer_id()} @@ -507,12 +411,6 @@ def test_udt_tlc_min_value_not_eq_default(self): N1N2_CHANNEL_ID = channels["channels"][0]["channel_id"] self.fiber1.get_client().graph_channels() - before_balance1 = self.Ckb_cli.wallet_get_capacity( - self.account1["address"]["testnet"] - ) - before_balance2 = self.Ckb_cli.wallet_get_capacity( - self.account2["address"]["testnet"] - ) # shut down self.fiber1.get_client().shutdown_channel( { @@ -525,18 +423,17 @@ def test_udt_tlc_min_value_not_eq_default(self): "fee_rate": "0x3FC", } ) - # todo wait close tx commit - time.sleep(20) - after_balance1 = self.Ckb_cli.wallet_get_capacity( - self.account1["address"]["testnet"] - ) - after_balance2 = self.Ckb_cli.wallet_get_capacity( - self.account2["address"]["testnet"] - ) - print("before_balance1:", before_balance1) - print("before_balance2:", before_balance2) - print("after_balance1:", after_balance1) - print("after_balance2:", after_balance2) + tx_hash = self.wait_and_check_tx_pool_fee(1000, False) + tx_response = self.get_tx_message(tx_hash) + print("tx_response:", tx_response) + assert { + "args": self.account2["lock_arg"], + "capacity": 14300000000, + "udt_args": self.udtContract.get_owner_arg_by_lock_arg( + self.account1["lock_arg"] + ), + "udt_capacity": 18016000000, + } in tx_response["output_cells"] # fiber remove tlc_max_value option # diff --git a/test_cases/fiber/devnet/send_payment/test_allow_self_payment.py b/test_cases/fiber/devnet/send_payment/test_allow_self_payment.py index 23b37475..2172b980 100644 --- a/test_cases/fiber/devnet/send_payment/test_allow_self_payment.py +++ b/test_cases/fiber/devnet/send_payment/test_allow_self_payment.py @@ -5,8 +5,8 @@ from framework.basic_fiber import FiberTest -class TestAllowSelfPaymnent(FiberTest): - # FiberTest.debug = True +class TestAllowSelfPayment(FiberTest): + def test_a1_to_b1_to_a1(self): """ a1-b1-a1 @@ -103,6 +103,7 @@ def test_a1_to_b1_to_a1(self): f"Expected substring '{expected_error_message}' " f"not found in actual string '{exc_info.value.args[0]}'" ) + time.sleep(1) # a1(1000 )->b1(100) a1 send payment with self pay payment1 = self.fiber1.get_client().send_payment( { @@ -218,6 +219,7 @@ def test_a1_to_b1_to_a2(self): "payment_preimage": self.generate_random_preimage(), } ) + time.sleep(1) payment1 = self.fiber1.get_client().send_payment( { "invoice": invoice["invoice_address"], @@ -228,6 +230,7 @@ def test_a1_to_b1_to_a2(self): f"Expected substring '{expected_error_message}' " f"not found in actual string '{exc_info.value.args[0]}'" ) + time.sleep(1) # a1(100000000 )->b1(0) a1 send payment with self pay payment1 = self.fiber1.get_client().send_payment( @@ -258,7 +261,6 @@ def test_a1_to_b1_to_a2(self): # todo a1(1000 )->b1(100) a1 send payment with self pay # @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/362") - @pytest.mark.skip("wait ") def test_a1_to_b1_to_c1_a2(self): """ @@ -303,17 +305,29 @@ def test_a1_to_b1_to_c1_a2(self): ) # node1 send to self time.sleep(1) - for i in range(3): - payment1 = self.fiber1.get_client().send_payment( - { - "target_pubkey": self.fiber1.get_client().node_info()["node_id"], - "amount": hex(6 * 10000000), - "keysend": True, - "allow_self_payment": True, - } - ) - self.wait_payment_state(self.fiber1, payment1["payment_hash"], "Success") - # after fix todo add check + success_size = 0 + try_size = 0 + while success_size < 3: + try_size += 1 + assert try_size < 30 + try: + payment1 = self.fiber1.get_client().send_payment( + { + "target_pubkey": self.fiber1.get_client().node_info()[ + "node_id" + ], + "amount": hex(6 * 10000000), + "keysend": True, + "allow_self_payment": True, + } + ) + time.sleep(3) + self.wait_payment_state( + self.fiber1, payment1["payment_hash"], "Success" + ) + success_size += 1 + except Exception as e: + continue def test_a1_to_b1_to_c1_a2_2(self): """ diff --git a/test_cases/fiber/devnet/send_payment/test_dry_run.py b/test_cases/fiber/devnet/send_payment/test_dry_run.py index 57749e27..b8103583 100644 --- a/test_cases/fiber/devnet/send_payment/test_dry_run.py +++ b/test_cases/fiber/devnet/send_payment/test_dry_run.py @@ -159,5 +159,6 @@ def test_mutil_channel(self): "dry_run": True, } ) - assert payment["fee"] == hex(2000000) + # assert payment["fee"] == hex(2000000) + assert payment["fee"] == hex(1000000) print("payment:", payment) diff --git a/test_cases/fiber/devnet/send_payment/test_find_path.py b/test_cases/fiber/devnet/send_payment/test_find_path.py index 82550153..f283eb3f 100644 --- a/test_cases/fiber/devnet/send_payment/test_find_path.py +++ b/test_cases/fiber/devnet/send_payment/test_find_path.py @@ -1,6 +1,8 @@ import time import heapq +import pytest + from framework.basic_fiber import FiberTest @@ -45,8 +47,6 @@ def test_linked_net(self): self.send_payment(self.fibers[0], self.fibers[2], 3000 * 10000000) - # FiberTest.debug = True - def test_mul_path(self): """ 1. A -> B -> D B(fee= 1000) @@ -147,39 +147,58 @@ def test_mul_path(self): print("payment2", payment2) print("payment3", payment3) - def send_payment(self, src_fiber, to_fiber, amount, key_send=False): - if not key_send: - invoice_address = to_fiber.get_client().new_invoice( - { - "amount": hex(amount), - "currency": "Fibd", - "description": "test invoice generated by node2", - "expiry": "0xe10", - "final_cltv": "0x28", - "payment_preimage": self.generate_random_preimage(), - "hash_algorithm": "sha256", - } - )["invoice_address"] - payment = src_fiber.get_client().send_payment( - { - "invoice": invoice_address, - "dry_run": True, - } + # @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/475") + def test_cycle_net(self): + """ + 0-1-2 + | | | + 3-4-5 + channel(1000,1000) + for i in range(100): + send_payment(500 ,self) + Returns: + """ + for i in range(4): + fiber = self.start_new_fiber(self.generate_account(10000)) + fiber.connect_peer(self.fiber1) + for i in range(2): + self.open_channel( + self.fibers[i], self.fibers[i + 1], 1000 * 100000000, 1000 * 100000000 ) - payment = src_fiber.get_client().send_payment( - { - "invoice": invoice_address, - } + for i in range(2): + self.open_channel( + self.fibers[i + 3], + self.fibers[i + 4], + 1000 * 100000000, + 1000 * 100000000, ) - self.wait_payment_state(src_fiber, payment["payment_hash"], "Success") - self.wait_invoice_state(to_fiber, payment["payment_hash"], "Paid") - return payment["payment_hash"] - payment = src_fiber.get_client().send_payment( - { - "amount": hex(amount), - "target_pubkey": to_fiber.get_client().node_info()["node_id"], - "keysend": True, - } - ) - self.wait_payment_state(src_fiber, payment["payment_hash"], "Success") - return payment["payment_hash"] + for i in range(3): + self.open_channel( + self.fibers[i], self.fibers[i + 3], 1000 * 100000000, 1000 * 100000000 + ) + hashes = [[], [], [], [], [], []] + for j in range(100): + for i in range(len(self.fibers)): + try: + payment_hash = self.send_payment( + self.fibers[i], self.fibers[i], 500 * 10000000, False + ) + hashes[i].append(payment_hash) + except: + pass + + for i in range(len(hashes)): + for j in range(len(hashes[i])): + self.wait_payment_finished(self.fibers[i], hashes[i][j], 1200) + + for i in range(len(self.fibers)): + payment_hash = self.send_payment( + self.fibers[i], self.fibers[i], 500 * 10000000, False + ) + self.wait_payment_state(self.fibers[i], payment_hash, "Success", 1200) + for i in range(len(self.fibers)): + channels_balance = self.get_fiber_balance(self.fibers[i]) + assert channels_balance["offered_tlc_balance"] == 0 + assert channels_balance["received_tlc_balance"] == 0 + + # FiberTest.debug = True diff --git a/test_cases/fiber/devnet/send_payment/test_private_channel.py b/test_cases/fiber/devnet/send_payment/test_private_channel.py new file mode 100644 index 00000000..70088fe9 --- /dev/null +++ b/test_cases/fiber/devnet/send_payment/test_private_channel.py @@ -0,0 +1,59 @@ +import time + +import pytest + +from framework.basic_fiber import FiberTest + + +class TestPrivateChannel(FiberTest): + + # @pytest.mark.skip("https://github.com/nervosnetwork/fiber/pull/502") + def test_private_channel(self): + """ + a-私-b-c-d-私-a + 1. a->b + 2. a->c + 3. a->d + 4. a->a + Returns: + + """ + self.start_new_fiber(self.generate_account(10000)) + self.start_new_fiber(self.generate_account(10000)) + + fiber1_balance = 1000 * 100000000 + fiber1_fee = 1000 + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(fiber1_balance + 62 * 100000000), + "tlc_fee_proportional_millionths": hex(fiber1_fee), + "public": False, + } + ) + time.sleep(1) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" + ) + self.open_channel(self.fibers[1], self.fibers[2], 1000 * 100000000, 1) + self.open_channel(self.fibers[2], self.fibers[3], 1000 * 100000000, 1) + + self.fibers[3].connect_peer(self.fibers[0]) + time.sleep(1) + self.fibers[3].get_client().open_channel( + { + "peer_id": self.fibers[0].get_peer_id(), + "funding_amount": hex(fiber1_balance + 62 * 100000000), + "tlc_fee_proportional_millionths": hex(fiber1_fee), + "public": False, + } + ) + time.sleep(1) + self.wait_for_channel_state( + self.fibers[3].get_client(), self.fibers[0].get_peer_id(), "CHANNEL_READY" + ) + + for i in range(1, len(self.fibers)): + self.send_payment(self.fibers[0], self.fibers[i], 1 * 100000000) + + self.send_payment(self.fibers[0], self.fibers[0], 1 * 100000000) diff --git a/test_cases/fiber/devnet/send_payment/test_restart.py b/test_cases/fiber/devnet/send_payment/test_restart.py index 37e8add1..e9c0d923 100644 --- a/test_cases/fiber/devnet/send_payment/test_restart.py +++ b/test_cases/fiber/devnet/send_payment/test_restart.py @@ -230,7 +230,7 @@ def test_restart_node_send_payment_invoice(self): # FiberTest.debug = True - # @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/363") + @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/363") def test_restart_when_node_send_payment(self): account3_private = self.generate_account(1000) self.fiber3 = self.start_new_fiber(account3_private) @@ -291,7 +291,7 @@ def test_restart_when_node_send_payment(self): print("channels:", channels) channels = self.fiber3.get_client().list_channels({}) print("channels:", channels) - # todo 如果fiber1 不启动了 + # todo 如果 fiber1 不启动了 # todo check fiber2 钱是否会丢 self.fiber1.start() diff --git a/test_cases/fiber/devnet/send_payment/test_send_payment.py b/test_cases/fiber/devnet/send_payment/test_send_payment.py index 3c8c51a1..26040a4a 100644 --- a/test_cases/fiber/devnet/send_payment/test_send_payment.py +++ b/test_cases/fiber/devnet/send_payment/test_send_payment.py @@ -483,7 +483,7 @@ def test_send_payment_each_other_2(self): # self.wait_payment_state(self.fiber1, payment1["payment_hash"], "Success") - @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/436") + # @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/436") def test_send_payment_0x1(self): account_private = self.generate_account(1000) self.fiber3 = self.start_new_fiber(account_private) diff --git a/test_cases/fiber/devnet/send_payment/test_send_payment_with_shutdown.py b/test_cases/fiber/devnet/send_payment/test_send_payment_with_shutdown.py new file mode 100644 index 00000000..00c29be5 --- /dev/null +++ b/test_cases/fiber/devnet/send_payment/test_send_payment_with_shutdown.py @@ -0,0 +1,104 @@ +import pytest + +from framework.basic_fiber import FiberTest + + +class TestSendPaymentWithShutdown(FiberTest): + + @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/503") + def test_shutdown_in_send_payment(self): + self.start_new_fiber(self.generate_account(10000)) + self.start_new_fiber(self.generate_account(10000)) + self.open_channel( + self.fibers[0], self.fibers[1], 1000 * 100000000, 1000 * 100000000 + ) + self.open_channel( + self.fibers[1], self.fibers[2], 1000 * 100000000, 1000 * 100000000 + ) + self.open_channel( + self.fibers[2], self.fibers[3], 1000 * 100000000, 1000 * 100000000 + ) + payment_node1_hashes = [] + payment_node3_hashes = [] + for i in range(10): + payment_hash = self.send_payment(self.fibers[0], self.fibers[3], 1, False) + payment_node1_hashes.append(payment_hash) + payment_hash = self.send_payment(self.fibers[3], self.fibers[0], 1, False) + payment_node3_hashes.append(payment_hash) + N3N4_CHANNEL_ID = ( + self.fibers[3].get_client().list_channels({})["channels"][0]["channel_id"] + ) + + self.fibers[3].get_client().shutdown_channel( + { + "channel_id": N3N4_CHANNEL_ID, + "close_script": { + "code_hash": "0x1bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + "hash_type": "type", + "args": self.fibers[3].get_account()["lock_arg"], + }, + "fee_rate": "0x3FC", + } + ) + tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 120) + tx_message = self.get_tx_message(tx_hash) + for payment_hash in payment_node1_hashes: + payment = ( + self.fibers[0].get_client().get_payment({"payment_hash": payment_hash}) + ) + # todo add check + # print("payment status:", payment["status"]) + self.wait_payment_finished(self.fibers[0], payment_hash, 1200) + for payment_hash in payment_node3_hashes: + payment = ( + self.fibers[3].get_client().get_payment({"payment_hash": payment_hash}) + ) + # todo add check + # print("payment status:", payment["status"]) + self.wait_payment_finished(self.fibers[3], payment_hash, 1200) + + # print("tx message:", tx_message) + + @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/503") + def test_force_shutdown_in_send_payment(self): + """""" + self.start_new_fiber(self.generate_account(10000)) + self.start_new_fiber(self.generate_account(10000)) + self.open_channel( + self.fibers[0], self.fibers[1], 1000 * 100000000, 1000 * 100000000 + ) + self.open_channel( + self.fibers[1], self.fibers[2], 1000 * 100000000, 1000 * 100000000 + ) + self.open_channel( + self.fibers[2], self.fibers[3], 1000 * 100000000, 1000 * 100000000 + ) + payment_hashes = [] + for i in range(10): + payment_hash = self.send_payment(self.fibers[0], self.fibers[3], 1, False) + payment_hashes.append(payment_hash) + N3N4_CHANNEL_ID = ( + self.fibers[3].get_client().list_channels({})["channels"][0]["channel_id"] + ) + + self.fibers[3].get_client().shutdown_channel( + { + "channel_id": N3N4_CHANNEL_ID, + "close_script": { + "code_hash": "0x1bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + "hash_type": "type", + "args": self.fibers[3].get_account()["lock_arg"], + }, + "force": True, + "fee_rate": "0x3FC", + } + ) + tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 120) + tx_message = self.get_tx_message(tx_hash) + print("tx_message:") + for payment_hash in payment_hashes: + payment = ( + self.fibers[0].get_client().get_payment({"payment_hash": payment_hash}) + ) + # print("payment status:", payment["status"]) + self.wait_payment_finished(self.fibers[0], payment_hash, 1200) diff --git a/test_cases/fiber/devnet/send_payment/test_send_payment_with_update_channel.py b/test_cases/fiber/devnet/send_payment/test_send_payment_with_update_channel.py new file mode 100644 index 00000000..7593a9d2 --- /dev/null +++ b/test_cases/fiber/devnet/send_payment/test_send_payment_with_update_channel.py @@ -0,0 +1,86 @@ +from framework.basic_fiber import FiberTest + + +class TestSendPaymentWithUpdateChannel(FiberTest): + + def test_01(self): + """ + Test sending payments with updating channel. + Returns: + None + """ + # Start new fibers with initial accounts + self.start_new_fiber(self.generate_account(10000)) + self.start_new_fiber(self.generate_account(10000)) + + # Open channels between fibers + self.open_channel( + self.fibers[0], self.fibers[1], 1000 * 100000000, 1000 * 100000000 + ) + self.open_channel( + self.fibers[1], self.fibers[2], 1000 * 100000000, 1000 * 100000000 + ) + self.open_channel( + self.fibers[2], self.fibers[3], 1000 * 100000000, 1000 * 100000000 + ) + + payment_hashes = [] + + for j in range(3): + # Send initial payments + for i in range(30): + payment_hash = self.send_payment( + self.fibers[0], self.fibers[3], 100000000, False + ) + payment_hashes.append(payment_hash) + + # Get channel ID and update channel + N3N4_CHANNEL_ID = ( + self.fibers[3] + .get_client() + .list_channels({})["channels"][0]["channel_id"] + ) + channels = ( + self.fibers[1] + .get_client() + .list_channels({"peer_id": self.fibers[2].get_peer_id()}) + ) + self.fibers[1].get_client().update_channel( + { + "channel_id": channels["channels"][0]["channel_id"], + "tlc_fee_proportional_millionths": hex(10000 + (1 + j) * 10000), + } + ) + + # Send additional payments + for i in range(30): + payment_hash = self.send_payment( + self.fibers[0], self.fibers[3], 100000000, False + ) + payment_hashes.append(payment_hash) + + payment_results = [] + + # Get payment results + for payment_hash in payment_hashes: + payment = ( + self.fibers[0] + .get_client() + .get_payment({"payment_hash": payment_hash}) + ) + print("payment status:", payment["status"]) + payment_results.append(payment) + + # Print payment results + idx = 0 + for payment in payment_results: + print( + f'idx:{idx} status:{payment["status"]}, fee:{int(payment["fee"], 16)},hash:{payment["payment_hash"]}' + ) + idx += 1 + self.wait_payment_finished( + self.fibers[0], payment["payment_hash"], 1200 + ) + + # Check transaction success + self.send_payment(self.fibers[0], self.fibers[3], 1) diff --git a/test_cases/fiber/devnet/send_payment/test_tlc_fee.py b/test_cases/fiber/devnet/send_payment/test_tlc_fee.py index e69de29b..93797fe7 100644 --- a/test_cases/fiber/devnet/send_payment/test_tlc_fee.py +++ b/test_cases/fiber/devnet/send_payment/test_tlc_fee.py @@ -0,0 +1,73 @@ +from framework.basic_fiber import FiberTest + + +class TestTlcFee(FiberTest): + + def test_01(self): + """ + Returns: + """ + for i in range(2): + self.start_new_fiber(self.generate_account(10000)) + self.open_channel( + self.fibers[0], + self.fibers[1], + 1000 * 100000000, + 1000 * 100000000, + 1000, + 1000, + ) + self.open_channel( + self.fibers[1], + self.fibers[2], + 1000 * 100000000, + 1000 * 100000000, + 2000, + 1000, + ) + self.open_channel( + self.fibers[2], + self.fibers[3], + 1000 * 100000000, + 1000 * 100000000, + 3000, + 1000, + ) + + payment_hash = self.send_payment(self.fibers[0], self.fibers[3], 1 * 100000000) + payment = ( + self.fibers[0].get_client().get_payment({"payment_hash": payment_hash}) + ) + amount = 1 * 100000000 + payment = ( + self.fibers[0] + .get_client() + .send_payment( + { + "target_pubkey": self.fibers[3].get_client().node_info()["node_id"], + "amount": hex(amount), + "keysend": True, + "allow_self_payment": True, + "udt_type_script": None, + "dry_run": True, + } + ) + ) + payment = ( + self.fibers[0] + .get_client() + .send_payment( + { + "target_pubkey": self.fibers[3].get_client().node_info()["node_id"], + "amount": hex(amount), + "keysend": True, + "allow_self_payment": True, + "udt_type_script": None, + "max_fee_amount": payment["fee"], + } + ) + ) + self.wait_payment_state( + self.fibers[0], payment["payment_hash"], "Success", 1200 + ) + assert int(payment["fee"], 16) == self.calculate_tx_fee(amount, [2000, 3000]) diff --git a/test_cases/fiber/devnet/shutdown_channel/test_node_state.py b/test_cases/fiber/devnet/shutdown_channel/test_node_state.py index f84c5af0..afef775f 100644 --- a/test_cases/fiber/devnet/shutdown_channel/test_node_state.py +++ b/test_cases/fiber/devnet/shutdown_channel/test_node_state.py @@ -16,10 +16,8 @@ class TestNodeState(FiberTest): """ - # FiberTest.debug = True - # @pytest.mark.skip("node1 send payment node4 failed") - @pytest.mark.skip("交易发送一半,如果交易卡在Inflight,下一笔交易好像也发不出去") + # @pytest.mark.skip("交易发送一半,如果交易卡在Inflight,下一笔交易好像也发不出去") def test_shutdown_in_send_payment(self): """ payment state 卡在 Inflight @@ -67,13 +65,6 @@ def test_shutdown_in_send_payment(self): self.wait_for_channel_state( self.fiber3.get_client(), self.fiber4.get_peer_id(), "CHANNEL_READY", 120 ) - # node4 open channel node1 - # self.fiber4.get_client().open_channel({ - # "peer_id": self.fiber1.get_peer_id(), - # "funding_amount": hex(200 * 100000000), - # "public": True, - # }) - # self.wait_for_channel_state(self.fiber4.get_client(), self.fiber1.get_peer_id(), "CHANNEL_READY", 120) time.sleep(3) # node1 send payment to node4 node4_info = self.fiber4.get_client().node_info() diff --git a/test_cases/fiber/devnet/update_channel/test_channel_id.py b/test_cases/fiber/devnet/update_channel/test_channel_id.py index 6d96a976..ebc7d777 100644 --- a/test_cases/fiber/devnet/update_channel/test_channel_id.py +++ b/test_cases/fiber/devnet/update_channel/test_channel_id.py @@ -28,7 +28,6 @@ def test_channel_id_exist(self): 5. fiber3 send_payment fiber2 1 ckb 手续费1000变为为2000 6. check update_channel success - Returns: """ @@ -85,11 +84,12 @@ def test_channel_id_exist(self): after_channel["channels"][0]["local_balance"], 16 ) == int(invoice_balance, 16) before_channel = self.fiber3.get_client().list_channels({}) + before2_channel = self.fiber2.get_client().list_channels({}) # 4. fiber2 call update_channel (id,tlc_fee_proportional_millionths) tlc_fee_proportional_millionths = 2000 channels = self.fiber1.get_client().update_channel( { - "channel_id": before_channel["channels"][0]["channel_id"], + "channel_id": before2_channel["channels"][0]["channel_id"], # "enabled": False, "tlc_fee_proportional_millionths": hex(tlc_fee_proportional_millionths), } diff --git a/test_cases/fiber/devnet/update_channel/test_enabled.py b/test_cases/fiber/devnet/update_channel/test_enabled.py index 1e1674f1..3ca4c8fb 100644 --- a/test_cases/fiber/devnet/update_channel/test_enabled.py +++ b/test_cases/fiber/devnet/update_channel/test_enabled.py @@ -1,13 +1,132 @@ +import time + +import pytest + from framework.basic_fiber import FiberTest class TestEnable(FiberTest): + """ + 1. 可用路径中存在禁用路径, 能够避开禁用路径 + 2. 可用路径中 全都被禁用, send_payment (dry_run = true) 返回错误 ,send_payment 返回错误 + 3. + """ - def test_none(self): - pass + # FiberTest.debug = True + @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/499") def test_true(self): - pass + """ + A-B-C + 0. A->C 不会报错 + 1. B.update_channel(id:B-C,enable:False) + 2. A->C 报错 + 3. B->C 报错 + 4. B->A 不会报错 , C->B 不会报错 + 5. C->A 不会报错 + 6. B.update_channel(id:B-C,enable:True) + 7. A->C 不会报错 + 8. B->C 不会报错 + Returns: + """ + self.start_new_fiber(self.generate_account(10000)) + self.open_channel( + self.fibers[0], self.fibers[1], 1000 * 100000000, 1000 * 100000000 + ) + self.open_channel( + self.fibers[1], self.fibers[2], 1000 * 100000000, 1000 * 100000000 + ) + self.send_payment(self.fibers[0], self.fibers[2], 1 * 100000000) + + # 1. B.update_channel(id:B-C,enable:False) + channel = ( + self.fibers[1] + .get_client() + .list_channels({"peer_id": self.fibers[2].get_peer_id()}) + ) + self.fibers[1].get_client().update_channel( + {"channel_id": channel["channels"][0]["channel_id"], "enabled": False} + ) + time.sleep(1) + # 2. A->C 报错 + with pytest.raises(Exception) as exc_info: + self.send_payment(self.fibers[0], self.fibers[2], 1) + expected_error_message = "no path found" + assert expected_error_message in exc_info.value.args[0], ( + f"Expected substring '{expected_error_message}' " + f"not found in actual string '{exc_info.value.args[0]}'" + ) + # 3. B->C 报错 + with pytest.raises(Exception) as exc_info: + self.send_payment(self.fibers[1], self.fibers[2], 1) + expected_error_message = "no path found" + assert expected_error_message in exc_info.value.args[0], ( + f"Expected substring '{expected_error_message}' " + f"not found in actual string '{exc_info.value.args[0]}'" + ) + # 4. B->A 不会报错 , C->B 不会报错 + self.send_payment(self.fibers[1], self.fibers[0], 1) + self.send_payment(self.fibers[2], self.fibers[1], 1) + + # 5. C->A 不会报错 + self.send_payment(self.fibers[2], self.fibers[0], 1) + + # 6. B.update_channel(id:B-C,enable:True) + channel = ( + self.fibers[1] + .get_client() + .list_channels({"peer_id": self.fibers[2].get_peer_id()}) + ) + self.fibers[1].get_client().update_channel( + {"channel_id": channel["channels"][0]["channel_id"], "enabled": True} + ) + time.sleep(1) + # 7. A->C 不会报错 + # 8. B->C 不会报错 + self.send_payment(self.fibers[0], self.fibers[2], 1) + self.send_payment(self.fibers[1], self.fibers[2], 1) + self.send_payment(self.fibers[1], self.fibers[0], 1) + self.send_payment(self.fibers[2], self.fibers[1], 1) + self.send_payment(self.fibers[2], self.fibers[0], 1) + + # 可用路径中 全都被禁用, send_payment (dry_run = true) 返回错误 ,send_payment 返回错误 + def test_channels_enabled_fee_more(self): + """ + 1. A-B-C + 2. B-C 建立多个channel + 3. 将fee 比较低的channel 都禁用掉 + """ + self.start_new_fiber(self.generate_account(10000)) + self.open_channel( + self.fibers[0], self.fibers[1], 1000 * 100000000, 1000 * 100000000 + ) + self.open_channel( + self.fibers[1], self.fibers[2], 1000 * 100000000, 1000 * 100000000 + ) + + channel = ( + self.fibers[1] + .get_client() + .list_channels({"peer_id": self.fibers[2].get_peer_id()}) + ) + self.fibers[1].get_client().update_channel( + {"channel_id": channel["channels"][0]["channel_id"], "enabled": False} + ) + self.open_channel( + self.fibers[1], + self.fibers[2], + 1000 * 100000000, + 1000 * 100000000, + 20000, + 20000, + ) + self.open_channel( + self.fibers[1], + self.fibers[2], + 1000 * 100000000, + 1000 * 100000000, + 30000, + 30000, + ) - def test_false(self): - pass + self.send_payment(self.fibers[0], self.fibers[2], 1 * 100000000) diff --git a/test_cases/fiber/devnet/update_channel/test_tlc_fee_proportional_millionths.py b/test_cases/fiber/devnet/update_channel/test_tlc_fee_proportional_millionths.py index f21f64e7..676d930c 100644 --- a/test_cases/fiber/devnet/update_channel/test_tlc_fee_proportional_millionths.py +++ b/test_cases/fiber/devnet/update_channel/test_tlc_fee_proportional_millionths.py @@ -73,11 +73,13 @@ def test_0x0(self): after_channel["channels"][0]["local_balance"], 16 ) == int(invoice_balance, 16) before_channel = self.fiber3.get_client().list_channels({}) + before2_channel = self.fiber2.get_client().list_channels({}) + # 4. fiber2 call update_channel (id,tlc_fee_proportional_millionths) tlc_fee_proportional_millionths = 0 channels = self.fiber1.get_client().update_channel( { - "channel_id": before_channel["channels"][0]["channel_id"], + "channel_id": before2_channel["channels"][0]["channel_id"], # "enabled": False, "tlc_fee_proportional_millionths": hex(tlc_fee_proportional_millionths), } @@ -252,11 +254,13 @@ def test_normal(self): after_channel["channels"][0]["local_balance"], 16 ) == int(invoice_balance, 16) before_channel = self.fiber3.get_client().list_channels({}) + before2_channel = self.fiber2.get_client().list_channels({}) + # 4. fiber2 call update_channel (id,tlc_fee_proportional_millionths) tlc_fee_proportional_millionths = 2000 channels = self.fiber1.get_client().update_channel( { - "channel_id": before_channel["channels"][0]["channel_id"], + "channel_id": before2_channel["channels"][0]["channel_id"], # "enabled": False, "tlc_fee_proportional_millionths": hex(tlc_fee_proportional_millionths), } diff --git a/test_cases/fiber/devnet/update_channel/test_update_channel.py b/test_cases/fiber/devnet/update_channel/test_update_channel.py index f722cc79..515d52df 100644 --- a/test_cases/fiber/devnet/update_channel/test_update_channel.py +++ b/test_cases/fiber/devnet/update_channel/test_update_channel.py @@ -123,10 +123,12 @@ def test_01(self): after_channel["channels"][0]["local_balance"], 16 ) == int(invoice_balance, 16) channel = self.fiber3.get_client().list_channels({}) + channel2 = self.fiber2.get_client().list_channels({}) + # 4. fiber2 call update_channel (id,tlc_fee_proportional_millionths) tlc_fee_proportional_millionths = 2000 - update_channel_param["channel_id"] = channel["channels"][0]["channel_id"] + update_channel_param["channel_id"] = channel2["channels"][0]["channel_id"] channels = self.fiber1.get_client().update_channel(update_channel_param) time.sleep(1) # 1. todo check enable From 6c1617b89f8cb75cee3d93fccd939ea8eec950a0 Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Thu, 6 Feb 2025 17:32:49 +0800 Subject: [PATCH 19/57] update make file --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 060cb63c..0fe8ada3 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,7 @@ prepare: python3 -m download_ckb_light_client echo "install ckb cli" + python3 -m download_fiber sh prepare.sh prepare_fiber_testnet: From 09dae9346c3e3ebb36668af2f69eb2fa4ba03112 Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Fri, 7 Feb 2025 15:50:12 +0800 Subject: [PATCH 20/57] update test --- framework/basic_fiber.py | 18 +++++-- .../devnet/get_invoice/test_get_invoice.py | 19 ++++--- .../graph_channels/test_graph_channels.py | 54 ++++++++++++++----- .../devnet/open_channel/test_tlc_min_value.py | 2 +- .../devnet/send_payment/test_find_path.py | 16 ++++-- .../watch_tower/test_watch_tower_udt.py | 4 +- 6 files changed, 82 insertions(+), 31 deletions(-) diff --git a/framework/basic_fiber.py b/framework/basic_fiber.py index 92aee887..4a25ce97 100644 --- a/framework/basic_fiber.py +++ b/framework/basic_fiber.py @@ -99,6 +99,9 @@ def setup_method(cls, method): if cls.debug: return # # issue + cls.node.getClient().clear_tx_pool() + for i in range(5): + cls.Miner.miner_with_version(cls.node, "0x0") tx_hash = issue_udt_tx( cls.udtContract, cls.node.rpcUrl, @@ -143,9 +146,6 @@ def setup_method(cls, method): cls.fiber1.connect_peer(cls.fiber2) time.sleep(1) cls.logger.debug(f"\nSetting up method:{method.__name__}") - cls.node.getClient().clear_tx_pool() - for i in range(5): - cls.Miner.miner_with_version(cls.node, "0x0") def teardown_method(self, method): if self.debug: @@ -384,6 +384,18 @@ def send_payment(self, fiber1, fiber2, amount, wait=True, udt=None, try_count=5) except Exception as e: time.sleep(1) continue + payment = fiber1.get_client().send_payment( + { + "target_pubkey": fiber2.get_client().node_info()["node_id"], + "amount": hex(amount), + "keysend": True, + "allow_self_payment": True, + "udt_type_script": udt, + } + ) + if wait: + self.wait_payment_state(fiber1, payment["payment_hash"], "Success") + return payment["payment_hash"] def get_account_script(self, account_private_key): account1 = self.Ckb_cli.util_key_info_by_private_key(account_private_key) diff --git a/test_cases/fiber/devnet/get_invoice/test_get_invoice.py b/test_cases/fiber/devnet/get_invoice/test_get_invoice.py index 1cd211c9..4c6cb551 100644 --- a/test_cases/fiber/devnet/get_invoice/test_get_invoice.py +++ b/test_cases/fiber/devnet/get_invoice/test_get_invoice.py @@ -18,10 +18,9 @@ def test_get_exist_new_invoice(self): """ 1. new invoice - parse invoice 能够解析 invoice_address ,解析结果和invoice 一致 - - Returns: - """ + # Step 1: Create a new invoice invoice = self.fiber1.get_client().new_invoice( { "amount": hex(1), @@ -34,15 +33,19 @@ def test_get_exist_new_invoice(self): } ) + # Step 2: Retrieve the created invoice using its payment hash result = self.fiber1.get_client().get_invoice( {"payment_hash": invoice["invoice"]["data"]["payment_hash"]} ) node_info = self.fiber1.get_client().node_info() + # Step 3: Verify the node ID matches the PayeePublicKey in the invoice assert ( node_info["node_id"] == result["invoice"]["data"]["attrs"][3]["PayeePublicKey"] ) + + # Step 4: Parse the invoice and verify the parsed result matches the original invoice parse_invoice = self.fiber1.get_client().parse_invoice( {"invoice": invoice["invoice_address"]} ) @@ -56,23 +59,27 @@ def test_get_exist_new_invoice(self): assert invoice["invoice"]["data"]["attrs"][1]["ExpiryTime"]["secs"] == 3600 assert invoice["invoice"]["data"]["attrs"][2]["HashAlgorithm"] == "sha256" - # assert invoice['invoice']['data']['timestamp'] + # Step 5: Verify the timestamp of the invoice assert int(int(invoice["invoice"]["data"]["timestamp"], 16) / 1000) == int( datetime.datetime.now().timestamp() ) def test_get_not_exist_invoice(self): """ - not exist invoice - return err "invoice not found" + Test case for querying a non-existent invoice. + Steps: + 1. Attempt to retrieve an invoice with a random payment hash. + 2. Verify that the error message "invoice not found" is returned. Returns: - """ + # Step 1: Attempt to retrieve an invoice with a random payment hash with pytest.raises(Exception) as exc_info: result = self.fiber1.get_client().get_invoice( {"payment_hash": self.generate_random_preimage()} ) + + # Step 2: Verify that the error message "invoice not found" is returned expected_error_message = "invoice not found" assert expected_error_message in exc_info.value.args[0], ( f"Expected substring '{expected_error_message}' " diff --git a/test_cases/fiber/devnet/graph_channels/test_graph_channels.py b/test_cases/fiber/devnet/graph_channels/test_graph_channels.py index dcdde7b7..04bc751e 100644 --- a/test_cases/fiber/devnet/graph_channels/test_graph_channels.py +++ b/test_cases/fiber/devnet/graph_channels/test_graph_channels.py @@ -14,19 +14,36 @@ class TestGraphChannels(FiberTest): def test_add_channels(self): """ - 1. node1 add channel - 2. node2 add channel - 3. node3 add channel + Test adding channels. - 4. add new node link node3 - Returns: + Steps: + 1. Generate a new account with 1000 units of balance + 2. Start a new fiber with the generated account + 3. Connect fiber3 to fiber2 + 4. Open a new public channel with fiber1 as the client and fiber2 as the peer + 5. Check the graph channels for node1, node2, and node3 + 6. Open a new private channel with fiber1 as the client and fiber2 as the peer + 7. Check the graph channels for node1, node2, and node3 + 8. Open a new public channel with fiber2 as the client and fiber3 as the peer + 9. Check the graph channels for node1, node2, and node3 + 10. Open a new private channel with fiber2 as the client and fiber3 as the peer + 11. Check the graph channels for node1, node2, and node3 + 12. Start a new fiber and connect it to fiber3 + 13. Check the graph channels for node4 + Returns: """ + # Step 1: Generate a new account with 1000 units of balance account3_private_key = self.generate_account(1000) + + # Step 2: Start a new fiber with the generated account self.fiber3 = self.start_new_fiber(account3_private_key) + + # Step 3: Connect fiber3 to fiber2 self.fiber3.connect_peer(self.fiber2) time.sleep(1) - # n1-n2 创建 public channel + + # Step 4: Open a new public channel with fiber1 as the client and fiber2 as the peer self.fiber1.get_client().open_channel( { "peer_id": self.fiber2.get_peer_id(), @@ -37,7 +54,8 @@ def test_add_channels(self): self.wait_for_channel_state( self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" ) - # check node1 graph channels + + # Step 5: Check the graph channels for node1, node2, and node3 time.sleep(1) node1_channels = self.fiber1.get_client().graph_channels() node2_channels = self.fiber2.get_client().graph_channels() @@ -49,10 +67,8 @@ def test_add_channels(self): assert len(node2_channels["channels"]) == 1 print("node3_channels", node3_channels) assert len(node3_channels["channels"]) == 1 - # check node2 graph channels - # check node3 graph channels - # n1-n2 创建 私有 channel + # Step 6: Open a new private channel with fiber1 as the client and fiber2 as the peer self.fiber1.get_client().open_channel( { "peer_id": self.fiber2.get_peer_id(), @@ -63,6 +79,8 @@ def test_add_channels(self): self.wait_for_channel_state( self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" ) + + # Step 7: Check the graph channels for node1, node2, and node3 time.sleep(1) node1_channels = self.fiber1.get_client().graph_channels() node2_channels = self.fiber2.get_client().graph_channels() @@ -74,7 +92,8 @@ def test_add_channels(self): assert len(node2_channels["channels"]) == 1 print("node3_channels", node3_channels) assert len(node3_channels["channels"]) == 1 - # n2-n3 创建 public channel + + # Step 8: Open a new public channel with fiber2 as the client and fiber3 as the peer self.fiber2.get_client().open_channel( { "peer_id": self.fiber3.get_peer_id(), @@ -85,6 +104,8 @@ def test_add_channels(self): self.wait_for_channel_state( self.fiber2.get_client(), self.fiber3.get_peer_id(), "CHANNEL_READY" ) + + # Step 9: Check the graph channels for node1, node2, and node3 time.sleep(1) node1_channels = self.fiber1.get_client().graph_channels() node2_channels = self.fiber2.get_client().graph_channels() @@ -96,7 +117,8 @@ def test_add_channels(self): assert len(node2_channels["channels"]) == 2 print("node3_channels", node3_channels) assert len(node3_channels["channels"]) == 2 - # n2-n3 创建 私有 channel + + # Step 10: Open a new private channel with fiber2 as the client and fiber3 as the peer self.fiber2.get_client().open_channel( { "peer_id": self.fiber3.get_peer_id(), @@ -107,6 +129,8 @@ def test_add_channels(self): self.wait_for_channel_state( self.fiber2.get_client(), self.fiber3.get_peer_id(), "CHANNEL_READY" ) + + # Step 11: Check the graph channels for node1, node2, and node3 time.sleep(1) node1_channels = self.fiber1.get_client().graph_channels() node2_channels = self.fiber2.get_client().graph_channels() @@ -119,15 +143,17 @@ def test_add_channels(self): print("node3_channels", node3_channels) assert len(node3_channels["channels"]) == 2 + # Step 12: Start a new fiber and connect it to fiber3 fiber4 = self.start_new_fiber(self.generate_random_preimage()) fiber4.connect_peer(self.fiber3) time.sleep(5) + + # Step 13: Check the graph channels for node4 node4_channels = fiber4.get_client().graph_channels() print("node4_channels", node4_channels) assert len(node4_channels["channels"]) == 2 - @pytest.mark.skip("remove failed ") - def test_remove_channels(self): + def test_remove_channels_with_force(self): """ force close channel close channel diff --git a/test_cases/fiber/devnet/open_channel/test_tlc_min_value.py b/test_cases/fiber/devnet/open_channel/test_tlc_min_value.py index 3bb97ccf..4d270bca 100644 --- a/test_cases/fiber/devnet/open_channel/test_tlc_min_value.py +++ b/test_cases/fiber/devnet/open_channel/test_tlc_min_value.py @@ -120,7 +120,7 @@ def test_tlc_min_value_is_not_zero(self): # transfer before_channel = self.fiber1.get_client().list_channels({}) with pytest.raises(Exception) as exc_info: - self.send_payment(self.fiber1, self.fiber2, 2 * 100000000 - 1) + self.send_payment(self.fiber1, self.fiber2, 2 * 100000000 - 1, 1) expected_error_message = "Failed to build route" assert expected_error_message in exc_info.value.args[0], ( f"Expected substring '{expected_error_message}' " diff --git a/test_cases/fiber/devnet/send_payment/test_find_path.py b/test_cases/fiber/devnet/send_payment/test_find_path.py index f283eb3f..64a089e2 100644 --- a/test_cases/fiber/devnet/send_payment/test_find_path.py +++ b/test_cases/fiber/devnet/send_payment/test_find_path.py @@ -181,7 +181,7 @@ def test_cycle_net(self): for i in range(len(self.fibers)): try: payment_hash = self.send_payment( - self.fibers[i], self.fibers[i], 500 * 10000000, False + self.fibers[i], self.fibers[i], 500 * 10000000, False, None, 0 ) hashes[i].append(payment_hash) except: @@ -192,10 +192,16 @@ def test_cycle_net(self): self.wait_payment_finished(self.fibers[i], hashes[i][j], 1200) for i in range(len(self.fibers)): - payment_hash = self.send_payment( - self.fibers[i], self.fibers[i], 500 * 10000000, False - ) - self.wait_payment_state(self.fibers[i], payment_hash, "Success", 1200) + for i in range(20): + payment_hash = self.send_payment( + self.fibers[i], self.fibers[i], 500 * 10000000, False + ) + result = self.wait_payment_finished(self.fibers[i], payment_hash, 1200) + if result["status"] == "Success": + break + time.sleep(1) + if i == 19: + raise Exception("payment failed") for i in range(len(self.fibers)): channels_balance = self.get_fiber_balance(self.fibers[i]) assert channels_balance["offered_tlc_balance"] == 0 diff --git a/test_cases/fiber/devnet/watch_tower/test_watch_tower_udt.py b/test_cases/fiber/devnet/watch_tower/test_watch_tower_udt.py index 9ca26404..6296297b 100644 --- a/test_cases/fiber/devnet/watch_tower/test_watch_tower_udt.py +++ b/test_cases/fiber/devnet/watch_tower/test_watch_tower_udt.py @@ -172,7 +172,7 @@ def test_node2_shutdown_when_open_and_node2_split_tx(self): # Step 4: Wait for the transaction to be committed tx = self.wait_and_check_tx_pool_fee(1000, False) self.Miner.miner_until_tx_committed(self.node, tx) - + time.sleep(1) # Step 5: Mine additional blocks for i in range(5): self.Miner.miner_with_version(self.node, "0x0") @@ -275,7 +275,7 @@ def test_node2_shutdown_when_open_and_node1_split_tx(self): # Step 4: Wait for the transaction to be committed tx = self.wait_and_check_tx_pool_fee(1000, False) self.Miner.miner_until_tx_committed(self.node, tx) - + time.sleep(1) # Step 5: Mine additional blocks for i in range(5): self.Miner.miner_with_version(self.node, "0x0") From 47bc255c10a5b0196dda567f244423c988935e4d Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Fri, 7 Feb 2025 15:53:36 +0800 Subject: [PATCH 21/57] fix format --- test_cases/fiber/devnet/graph_channels/test_graph_channels.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test_cases/fiber/devnet/graph_channels/test_graph_channels.py b/test_cases/fiber/devnet/graph_channels/test_graph_channels.py index 04bc751e..142ac22b 100644 --- a/test_cases/fiber/devnet/graph_channels/test_graph_channels.py +++ b/test_cases/fiber/devnet/graph_channels/test_graph_channels.py @@ -152,6 +152,7 @@ def test_add_channels(self): node4_channels = fiber4.get_client().graph_channels() print("node4_channels", node4_channels) assert len(node4_channels["channels"]) == 2 + @pytest.mark.skip("remove failed ") def test_remove_channels_with_force(self): """ From e0807c0670327c92d3bd3c2aa6e1dec84b95c69b Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Sat, 8 Feb 2025 12:01:26 +0800 Subject: [PATCH 22/57] fix ci --- framework/basic_fiber.py | 1 + .../devnet/graph_nodes/test_graph_nodes.py | 42 ++++++++++++------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/framework/basic_fiber.py b/framework/basic_fiber.py index 4a25ce97..e50710ea 100644 --- a/framework/basic_fiber.py +++ b/framework/basic_fiber.py @@ -324,6 +324,7 @@ def open_channel( "public": True, } ) + time.sleep(1) fiber2.get_client().accept_channel( { "temporary_channel_id": temporary_channel["temporary_channel_id"], diff --git a/test_cases/fiber/devnet/graph_nodes/test_graph_nodes.py b/test_cases/fiber/devnet/graph_nodes/test_graph_nodes.py index 707aca71..d658d9f1 100644 --- a/test_cases/fiber/devnet/graph_nodes/test_graph_nodes.py +++ b/test_cases/fiber/devnet/graph_nodes/test_graph_nodes.py @@ -25,7 +25,7 @@ def test_add_nodes(self): # add nodes new_fibers = [] current_fiber = self.start_new_fiber(self.generate_random_preimage()) - for i in range(30): + for i in range(10): new_fiber = self.start_new_fiber(self.generate_random_preimage()) current_fiber.connect_peer(new_fiber) new_fibers.append(new_fiber) @@ -37,16 +37,20 @@ def test_add_nodes(self): current_fiber1.connect_peer(self.fiber2) current_fiber2 = self.start_new_fiber(self.generate_random_preimage()) current_fiber2.connect_peer(current_fiber) - time.sleep(5) - assert len(current_fiber.get_client().graph_nodes()["nodes"]) == 35 - assert len(current_fiber1.get_client().graph_nodes()["nodes"]) == 35 - assert len(current_fiber2.get_client().graph_nodes()["nodes"]) == 35 - assert len(self.fiber1.get_client().graph_nodes()["nodes"]) == 35 - assert len(self.fiber2.get_client().graph_nodes()["nodes"]) == 35 + # wait fiber3 nodes == 15 + self.wait_graph_nodes(self.fibers[3], 15) + assert len(current_fiber.get_client().graph_nodes()["nodes"]) == 15 + assert len(current_fiber1.get_client().graph_nodes()["nodes"]) == 15 + assert len(current_fiber2.get_client().graph_nodes()["nodes"]) == 15 + assert len(self.fiber1.get_client().graph_nodes()["nodes"]) == 15 + assert len(self.fiber2.get_client().graph_nodes()["nodes"]) == 15 # 测试迭代 + idx = 0 + for fiber in self.fibers: graph_nodes = fiber.get_client().graph_nodes() - print("current:", len(graph_nodes["nodes"])) + print(f"idx:{idx}, current:{len(graph_nodes['nodes'])}", len(graph_nodes["nodes"])) + idx += 1 graph_nodes = get_graph_nodes(fiber, 3) total_graph_nodes = fiber.get_client().graph_nodes() assert len(graph_nodes) == len(total_graph_nodes["nodes"]) @@ -86,8 +90,8 @@ def test_node_info(self): assert node["chain_hash"] == node_info["chain_hash"] # auto_accept_min_ckb_funding_amount assert ( - node["auto_accept_min_ckb_funding_amount"] - == node_info["open_channel_auto_accept_min_ckb_funding_amount"] + node["auto_accept_min_ckb_funding_amount"] + == node_info["open_channel_auto_accept_min_ckb_funding_amount"] ) # udt_cfg_infos assert node["udt_cfg_infos"] == node_info["udt_cfg_infos"] @@ -124,8 +128,8 @@ def test_change_node_info(self): assert node["chain_hash"] == node_info["chain_hash"] # auto_accept_min_ckb_funding_amount assert ( - node["auto_accept_min_ckb_funding_amount"] - == node_info["open_channel_auto_accept_min_ckb_funding_amount"] + node["auto_accept_min_ckb_funding_amount"] + == node_info["open_channel_auto_accept_min_ckb_funding_amount"] ) # udt_cfg_infos assert node["udt_cfg_infos"] == node_info["udt_cfg_infos"] @@ -153,12 +157,22 @@ def test_change_node_info(self): assert node["chain_hash"] == node_info["chain_hash"] # auto_accept_min_ckb_funding_amount assert ( - node["auto_accept_min_ckb_funding_amount"] - == node_info["open_channel_auto_accept_min_ckb_funding_amount"] + node["auto_accept_min_ckb_funding_amount"] + == node_info["open_channel_auto_accept_min_ckb_funding_amount"] ) # udt_cfg_infos assert node["udt_cfg_infos"] == node_info["udt_cfg_infos"] + def wait_graph_nodes(self, fiber, number, time_out=30): + start_time = time.time() + while True: + graph_nodes = fiber.get_client().graph_nodes() + if len(graph_nodes["nodes"]) == number: + return + time.sleep(1) + if time.time() - start_time > time_out: + raise Exception("wait graph nodes timeout") + def get_graph_nodes(fiber, page_size): after = None From c2aa79c88315d2fc03411801d61dd9585f41df9c Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Sat, 8 Feb 2025 13:58:26 +0800 Subject: [PATCH 23/57] add rpc --- framework/rpc.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/framework/rpc.py b/framework/rpc.py index b92e4f4f..fd05172a 100644 --- a/framework/rpc.py +++ b/framework/rpc.py @@ -100,7 +100,9 @@ def get_fee_rate_statics(self, target=None): return self.call("get_fee_rate_statics", [target]) def generate_epochs(self, epoch): - return self.call("generate_epochs", [epoch]) + response = self.call("generate_epochs", [epoch]) + time.sleep(1) + return response def generate_block(self): return self.call("generate_block", []) From 643ad814cc3dcd6c1b1c5c53cf57079ffd9159e1 Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Sat, 8 Feb 2025 14:00:21 +0800 Subject: [PATCH 24/57] update test_graph_nodes.py --- .../devnet/graph_nodes/test_graph_nodes.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/test_cases/fiber/devnet/graph_nodes/test_graph_nodes.py b/test_cases/fiber/devnet/graph_nodes/test_graph_nodes.py index d658d9f1..5b48b42e 100644 --- a/test_cases/fiber/devnet/graph_nodes/test_graph_nodes.py +++ b/test_cases/fiber/devnet/graph_nodes/test_graph_nodes.py @@ -49,7 +49,10 @@ def test_add_nodes(self): for fiber in self.fibers: graph_nodes = fiber.get_client().graph_nodes() - print(f"idx:{idx}, current:{len(graph_nodes['nodes'])}", len(graph_nodes["nodes"])) + print( + f"idx:{idx}, current:{len(graph_nodes['nodes'])}", + len(graph_nodes["nodes"]), + ) idx += 1 graph_nodes = get_graph_nodes(fiber, 3) total_graph_nodes = fiber.get_client().graph_nodes() @@ -90,8 +93,8 @@ def test_node_info(self): assert node["chain_hash"] == node_info["chain_hash"] # auto_accept_min_ckb_funding_amount assert ( - node["auto_accept_min_ckb_funding_amount"] - == node_info["open_channel_auto_accept_min_ckb_funding_amount"] + node["auto_accept_min_ckb_funding_amount"] + == node_info["open_channel_auto_accept_min_ckb_funding_amount"] ) # udt_cfg_infos assert node["udt_cfg_infos"] == node_info["udt_cfg_infos"] @@ -128,8 +131,8 @@ def test_change_node_info(self): assert node["chain_hash"] == node_info["chain_hash"] # auto_accept_min_ckb_funding_amount assert ( - node["auto_accept_min_ckb_funding_amount"] - == node_info["open_channel_auto_accept_min_ckb_funding_amount"] + node["auto_accept_min_ckb_funding_amount"] + == node_info["open_channel_auto_accept_min_ckb_funding_amount"] ) # udt_cfg_infos assert node["udt_cfg_infos"] == node_info["udt_cfg_infos"] @@ -157,8 +160,8 @@ def test_change_node_info(self): assert node["chain_hash"] == node_info["chain_hash"] # auto_accept_min_ckb_funding_amount assert ( - node["auto_accept_min_ckb_funding_amount"] - == node_info["open_channel_auto_accept_min_ckb_funding_amount"] + node["auto_accept_min_ckb_funding_amount"] + == node_info["open_channel_auto_accept_min_ckb_funding_amount"] ) # udt_cfg_infos assert node["udt_cfg_infos"] == node_info["udt_cfg_infos"] From 07566fadd118b36dd341f69167177ed45946eb72 Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Sun, 9 Feb 2025 20:15:15 +0800 Subject: [PATCH 25/57] update basic fiber --- framework/basic_fiber.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/framework/basic_fiber.py b/framework/basic_fiber.py index e50710ea..0a7707d6 100644 --- a/framework/basic_fiber.py +++ b/framework/basic_fiber.py @@ -349,20 +349,21 @@ def open_channel( fiber1.get_client(), fiber2.get_peer_id(), "CHANNEL_READY" ) channels = fiber1.get_client().list_channels({"peer_id": fiber2.get_peer_id()}) - payment = fiber1.get_client().send_payment( - { - "target_pubkey": fiber2.get_client().node_info()["node_id"], - "amount": hex(fiber2_balance), - "keysend": True, - } - ) + # payment = fiber1.get_client().send_payment( + # { + # "target_pubkey": fiber2.get_client().node_info()["node_id"], + # "amount": hex(fiber2_balance), + # "keysend": True, + # } + # ) + self.send_payment(fiber1, fiber2, fiber2_balance, True, None, 10) fiber2.get_client().update_channel( { "channel_id": channels["channels"][0]["channel_id"], "tlc_fee_proportional_millionths": hex(fiber2_fee), } ) - self.wait_payment_state(fiber1, payment["payment_hash"], "Success") + # self.wait_payment_state(fiber1, payment["payment_hash"], "Success") # channels = fiber1.get_client().list_channels({"peer_id": fiber2.get_peer_id()}) # assert channels["channels"][0]["local_balance"] == hex(fiber1_balance) # assert channels["channels"][0]["remote_balance"] == hex(fiber2_balance) From 7c76291fedad1e989592dcf4b0b3056a664e3310 Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Sat, 1 Mar 2025 00:03:21 +0800 Subject: [PATCH 26/57] update v0.4.0 --- framework/test_node.py | 2 +- prepare.sh | 1 + source/contract/fiber/auth | Bin 150904 -> 150904 bytes source/contract/fiber/commitment-lock | Bin 70920 -> 63624 bytes source/contract/fiber/funding-lock | Bin 32760 -> 30352 bytes source/template/fiber/config.yml.j2 | 13 ++- source/template/fiber/dev_config.yml.j2 | 1 + source/template/fiber/main_config.yml.j2 | 71 ++++++++++++++++ .../fiber/devnet/ckb/test_ckb_remove_tx.py | 79 ++++++++++++++++++ .../test_commitment_delay_epoch.py | 46 ++++++++++ 10 files changed, 205 insertions(+), 8 deletions(-) create mode 100644 source/template/fiber/main_config.yml.j2 create mode 100644 test_cases/fiber/devnet/ckb/test_ckb_remove_tx.py create mode 100644 test_cases/fiber/devnet/open_channel/test_commitment_delay_epoch.py diff --git a/framework/test_node.py b/framework/test_node.py index 38bb63ae..b3d4a3d7 100644 --- a/framework/test_node.py +++ b/framework/test_node.py @@ -318,7 +318,7 @@ def prepare( root_path=get_project_root(), spec_path=self.ckb_config_path.ckb_spec_path, ), - self.ckb_dir, + "{ckb_dir}/dev.toml".format(ckb_dir=self.ckb_dir), ) shutil.copy( diff --git a/prepare.sh b/prepare.sh index 013f1009..66db96ad 100644 --- a/prepare.sh +++ b/prepare.sh @@ -3,6 +3,7 @@ cp download/0.117.0/ckb-cli ./source/ckb-cli cp download/0.110.2/ckb-cli ./source/ckb-cli-old git clone https://github.com/nervosnetwork/fiber cd fiber +git checkout v0.4.0 cargo build cp target/debug/fnn ../download/fiber/current/fnn cd migrate diff --git a/source/contract/fiber/auth b/source/contract/fiber/auth index 726e4d8bc3b75f9ce79cc6124c83b837c883e751..e59d00fba3cceb39bdcc75366a29302123c8d2c1 100755 GIT binary patch delta 16211 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 Lr7HJT-1UC|mTb}w delta 18671 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 literal 70920 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 diff --git a/source/contract/fiber/funding-lock b/source/contract/fiber/funding-lock index ec974f32974736a511018f5ec299a856679599b2..c139a68202a0e868fa84dc8f1ae9b499f38f5b2c 100755 GIT binary patch literal 30352 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 Cqpo@Y diff --git a/source/template/fiber/config.yml.j2 b/source/template/fiber/config.yml.j2 index 6816a59f..8981b8f9 100644 --- a/source/template/fiber/config.yml.j2 +++ b/source/template/fiber/config.yml.j2 @@ -13,11 +13,11 @@ fiber: args: 0x cell_deps: - out_point: - tx_hash: 0x89af398edc7ed0054506b33349b031097d94378e11e77bf0690ee69d82623a43 - index: 0x0 + tx_hash: 0x5a5288769cecde6451cb5d301416c297a6da43dc3ac2f3253542b4082478b19b + index: 0x1 dep_type: code - out_point: - tx_hash: 0xbfd6d68b328a02606f1f65ee0f79f8ed5f76dfe86998c7aaa9ee4720d53f4c49 # ckb_auth + tx_hash: 0x5a5288769cecde6451cb5d301416c297a6da43dc3ac2f3253542b4082478b19b # ckb_auth index: 0x0 dep_type: code - name: CommitmentLock @@ -27,14 +27,13 @@ fiber: args: 0x cell_deps: - out_point: - tx_hash: 0x89af398edc7ed0054506b33349b031097d94378e11e77bf0690ee69d82623a43 - index: 0x1 + tx_hash: 0x5a5288769cecde6451cb5d301416c297a6da43dc3ac2f3253542b4082478b19b + index: 0x2 dep_type: code - out_point: - tx_hash: 0xbfd6d68b328a02606f1f65ee0f79f8ed5f76dfe86998c7aaa9ee4720d53f4c49 #ckb_auth + tx_hash: 0x5a5288769cecde6451cb5d301416c297a6da43dc3ac2f3253542b4082478b19b #ckb_auth index: 0x0 dep_type: code - rpc: listening_addr: {{ rpc_listening_addr | default("127.0.0.1:8227") }} enabled_modules: diff --git a/source/template/fiber/dev_config.yml.j2 b/source/template/fiber/dev_config.yml.j2 index de40cf1a..8c1a82c5 100644 --- a/source/template/fiber/dev_config.yml.j2 +++ b/source/template/fiber/dev_config.yml.j2 @@ -6,6 +6,7 @@ fiber: gossip_store_maintenance_interval_ms: 1000 gossip_network_maintenance_interval_ms: 1000 open_channel_auto_accept_min_ckb_funding_amount: {{ fiber_open_channel_auto_accept_min_ckb_funding_amount | default(10000000000)}} + watchtower_check_interval_seconds: {{ watchtower_check_interval_seconds | default(60) }} rpc: listening_addr: {{ rpc_listening_addr | default("127.0.0.1:8227") }} diff --git a/source/template/fiber/main_config.yml.j2 b/source/template/fiber/main_config.yml.j2 new file mode 100644 index 00000000..e5315b5c --- /dev/null +++ b/source/template/fiber/main_config.yml.j2 @@ -0,0 +1,71 @@ +# This configuration file only contains the necessary configurations for the mainnet deployment. +# All options' descriptions can be found via `fnn --help` and be overridden by command line arguments or environment variables. +fiber: + listening_addr: {{ fiber_listening_addr | default("/ip4/127.0.0.1/tcp/8228") }} + bootnode_addrs: + # - "/ip4/43.199.24.44/tcp/8228/p2p/" + # - "/ip4/54.255.71.126/tcp/8228/p2p/" + announce_listening_addr: true + announce_private_addr: true + announced_addrs: + # If you want to announce your fiber node public address to the network, you need to add the address here, please change the ip to your public ip accordingly. + # - "/ip4/YOUR-FIBER-NODE-PUBLIC-IP/tcp/8228" + chain: mainnet + # lock script configurations related to fiber network + # https://github.com/nervosnetwork/fiber-scripts/blob/main/deployment/mainnet/migrations/2025-02-28-114908.json + scripts: + - name: FundingLock + script: + code_hash: 0xe45b1f8f21bff23137035a3ab751d75b36a981deec3e7820194b9c042967f4f1 + hash_type: type + args: 0x + cell_deps: + - out_point: + tx_hash: 0x22ccb3018ca1aa7acd7b0ef7f5b01048be2525bb7364eafc8af04fd4d7279384 + index: 0x1 + dep_type: code + - out_point: + tx_hash: 0x22ccb3018ca1aa7acd7b0ef7f5b01048be2525bb7364eafc8af04fd4d7279384 # ckb_auth + index: 0x0 + dep_type: code + - name: CommitmentLock + script: + code_hash: 0x2d45c4d3ed3e942f1945386ee82a5d1b7e4bb16d7fe1ab015421174ab747406c + hash_type: type + args: 0x + cell_deps: + - out_point: + tx_hash: 0x22ccb3018ca1aa7acd7b0ef7f5b01048be2525bb7364eafc8af04fd4d7279384 + index: 0x2 + dep_type: code + - out_point: + tx_hash: 0x22ccb3018ca1aa7acd7b0ef7f5b01048be2525bb7364eafc8af04fd4d7279384 #ckb_auth + index: 0x0 + dep_type: code + +rpc: + # By default RPC only binds to localhost, thus it only allows accessing from the same machine. + # Allowing arbitrary machines to access the JSON-RPC port is dangerous and strongly discouraged. + # Please strictly limit the access to only trusted machines. + listening_addr: {{ rpc_listening_addr | default("127.0.0.1:8227") }} + +ckb: + # Please use a trusted CKB RPC node, the node should be able to provide the correct data and should be stable. + rpc_url: "https://mainnet.ckbapp.dev/" + udt_whitelist: + ## https://github.com/CKBFansDAO/xudtlogos/blob/f2557839ecde0409ba674516a62ae6752bc0daa9/public/tokens/token_list.json#L548 + # - name: USDI + # script: + # code_hash: 0xbfa35a9c38a676682b65ade8f02be164d48632281477e36f8dc2f41f79e56bfc + # hash_type: type + # args: 0xd591ebdc69626647e056e13345fd830c8b876bb06aa07ba610479eb77153ea9f + # cell_deps: + # - tx_hash: 0xf6a5eef65101899db9709c8de1cc28f23c1bee90d857ebe176f6647ef109e20d + # index: 0 + # dep_type: code + # auto_accept_amount: 10000000 + +services: + - fiber + - rpc + - ckb \ No newline at end of file diff --git a/test_cases/fiber/devnet/ckb/test_ckb_remove_tx.py b/test_cases/fiber/devnet/ckb/test_ckb_remove_tx.py new file mode 100644 index 00000000..530b1cb9 --- /dev/null +++ b/test_cases/fiber/devnet/ckb/test_ckb_remove_tx.py @@ -0,0 +1,79 @@ +import time + +import pytest + +from framework.basic_fiber import FiberTest + + +class TestCkbRemoveTx(FiberTest): + + @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/515") + def test_remove_open_tx_stuck_node1(self): + """ + 导致节点1 node_info 卡住 + + Returns: + + """ + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(1000 * 100000000), + "public": True, + } + ) + self.wait_tx_pool(pending_size=1, try_size=100) + self.node.client.clear_tx_pool() + # self.node.restart() + # self.node.start_miner() + time.sleep(3) + self.open_channel(self.fiber1, self.fiber2, 1000 * 100000000, 1) + # self.fiber1.get_client().open_channel({ + # "peer_id": self.fiber2.get_peer_id(), + # "funding_amount": hex(1000 * 100000000), + # "public": True, + # }) + # time.sleep(5) + # self.fiber1.get_client().node_info() + # self.fiber2.get_client().node_info() + + @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/515") + def test_remove_open_tx_stuck_node2(self): + """ + 导致节点2 node_info 卡住 + + Returns: + + """ + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(1000 * 100000000), + "public": True, + } + ) + self.wait_tx_pool(pending_size=1, try_size=100) + self.node.client.clear_tx_pool() + # self.node.restart() + # self.node.start_miner() + time.sleep(3) + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(1000 * 100000000), + "public": True, + } + ) + time.sleep(5) + self.fiber2.get_client().node_info() + fiber3 = self.start_new_fiber(self.generate_account(10000)) + self.open_channel(fiber3, self.fiber2, 1000 * 100000000, 1) + # fiber3.connect_peer(self.fiber2) + # time.sleep(1) + # fiber3.get_client().open_channel({ + # "peer_id": self.fiber2.get_peer_id(), + # "funding_amount": hex(1000 * 100000000), + # "public": True, + # }) + # time.sleep(5) + # self.fiber2.get_client().node_info() diff --git a/test_cases/fiber/devnet/open_channel/test_commitment_delay_epoch.py b/test_cases/fiber/devnet/open_channel/test_commitment_delay_epoch.py new file mode 100644 index 00000000..7e07d46d --- /dev/null +++ b/test_cases/fiber/devnet/open_channel/test_commitment_delay_epoch.py @@ -0,0 +1,46 @@ +from framework.basic_fiber import FiberTest + + +class TestCommitmentDelayEpoch(FiberTest): + + def test_epoch(self): + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(1000 * 100000000), + "public": True, + "commitment_delay_epoch": hex(2), + } + ) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY", 120 + ) + self.fiber1.get_client().shutdown_channel( + { + "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ], + "close_script": { + "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + "hash_type": "type", + "args": self.account1["lock_arg"], + }, + "fee_rate": "0x3FC", + "force": True, + } + ) + tx_hash = self.wait_and_check_tx_pool_fee(1000, False) + self.Miner.miner_until_tx_committed(self.node, tx_hash) + self.node.getClient().generate_epochs(hex(2)) + tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1200) + self.Miner.miner_until_tx_committed(self.node, tx_hash) + message = self.get_tx_message(tx_hash) + print(message) + assert { + "args": "0x470dcdc5e44064909650113a274b3b36aecb6dc7", + "capacity": 6199999545, + } in message["output_cells"] + assert { + "args": "0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7", + "capacity": 99999999545, + } in message["output_cells"] From 522235470a4a006409f68a86fbaf6b16c48330e9 Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Mon, 3 Mar 2025 10:35:58 +0800 Subject: [PATCH 27/57] fix ci failed --- test_cases/fiber/devnet/list_channels/test_list_channels.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test_cases/fiber/devnet/list_channels/test_list_channels.py b/test_cases/fiber/devnet/list_channels/test_list_channels.py index eb38cf65..e2ad1382 100644 --- a/test_cases/fiber/devnet/list_channels/test_list_channels.py +++ b/test_cases/fiber/devnet/list_channels/test_list_channels.py @@ -169,8 +169,10 @@ def test_created_at(self): channels = self.fiber1.get_client().list_channels({}) created_at_hex = int(channels["channels"][0]["created_at"], 16) / 1000 - assert int(created_at_hex / 1000) == int( - int(datetime.datetime.now().timestamp()) / 1000 + assert ( + int(int(datetime.datetime.now().timestamp()) / 1000) + - int(created_at_hex / 1000) + < 10 ) def test_is_public(self): From 38589e8fe83d6cfed6cd9e3c4c770f0151889462 Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Mon, 3 Mar 2025 14:29:05 +0800 Subject: [PATCH 28/57] remove skip 363 --- test_cases/fiber/devnet/send_payment/test_force_restart.py | 4 +--- test_cases/fiber/devnet/send_payment/test_restart.py | 7 +++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/test_cases/fiber/devnet/send_payment/test_force_restart.py b/test_cases/fiber/devnet/send_payment/test_force_restart.py index 57b208e3..0178c3bb 100644 --- a/test_cases/fiber/devnet/send_payment/test_force_restart.py +++ b/test_cases/fiber/devnet/send_payment/test_force_restart.py @@ -363,6 +363,4 @@ def test_restart_when_node_send_payment(self): ) print("self.fiber3.force_stop() payment:", payment) self.fiber3.start() - self.wait_payment_state(self.fiber1, payment["payment_hash"], "Success", 120) - channels = self.fiber3.get_client().list_channels({}) - assert channels["channels"][0]["local_balance"] == hex(30 * 100000000) + self.wait_payment_finished(self.fiber1, payment["payment_hash"], 120) diff --git a/test_cases/fiber/devnet/send_payment/test_restart.py b/test_cases/fiber/devnet/send_payment/test_restart.py index e9c0d923..123e3070 100644 --- a/test_cases/fiber/devnet/send_payment/test_restart.py +++ b/test_cases/fiber/devnet/send_payment/test_restart.py @@ -230,7 +230,7 @@ def test_restart_node_send_payment_invoice(self): # FiberTest.debug = True - @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/363") + # @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/363") def test_restart_when_node_send_payment(self): account3_private = self.generate_account(1000) self.fiber3 = self.start_new_fiber(account3_private) @@ -365,6 +365,5 @@ def test_restart_when_node_send_payment(self): ) print("self.fiber3.stop() payment:", payment) self.fiber3.start() - self.wait_payment_state(self.fiber1, payment["payment_hash"], "Success", 120) - channels = self.fiber3.get_client().list_channels({}) - assert channels["channels"][0]["local_balance"] == hex(30 * 100000000) + self.wait_payment_finished(self.fiber1, payment["payment_hash"], 120) + self.send_payment(self.fiber1, self.fiber2, 1) From 6c34487139a0a34bfc02eb0859396f534968a93c Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Mon, 3 Mar 2025 21:36:15 +0800 Subject: [PATCH 29/57] update testnet ci --- download_fiber.py | 2 +- framework/basic_fiber.py | 11 ++++++++++- framework/fiber_rpc.py | 3 +++ framework/test_fiber.py | 3 ++- test_cases/fiber/testnet/test_fiber.py | 7 ++++++- 5 files changed, 22 insertions(+), 4 deletions(-) diff --git a/download_fiber.py b/download_fiber.py index 25cae650..11e0e37d 100644 --- a/download_fiber.py +++ b/download_fiber.py @@ -11,7 +11,7 @@ import requests from tqdm import tqdm -versions = ["0.2.0", "0.2.1", "0.3.0", "0.3.1"] # Replace with your versions +versions = ["0.2.0", "0.2.1", "0.3.0", "0.3.1", "0.4.0"] # Replace with your versions DOWNLOAD_DIR = "download/fiber" SYSTEMS = { diff --git a/framework/basic_fiber.py b/framework/basic_fiber.py index 0a7707d6..1c867bc5 100644 --- a/framework/basic_fiber.py +++ b/framework/basic_fiber.py @@ -576,7 +576,16 @@ def get_tx_message(self, tx_hash): } ) print({"input_cells": input_cells, "output_cells": output_cells}) - return {"input_cells": input_cells, "output_cells": output_cells} + input_cap = 0 + for i in range(len(input_cells)): + input_cap = input_cap + input_cells[i]["capacity"] + for i in range(len(output_cells)): + input_cap = input_cap - output_cells[i]["capacity"] + return { + "input_cells": input_cells, + "output_cells": output_cells, + "fee": input_cap, + } def get_fiber_env(self, new_fiber_count=0): # self.logger.debug ckb tip number diff --git a/framework/fiber_rpc.py b/framework/fiber_rpc.py index da833438..64dffaa7 100644 --- a/framework/fiber_rpc.py +++ b/framework/fiber_rpc.py @@ -193,6 +193,9 @@ def graph_channels(self, param={}): """ return self.call("graph_channels", [param]) + def remove_watch_channel(self, param): + return self.call("remove_watch_channel", [param]) + def get_peer_id(self): return self.node_info()["addresses"][0].split("/")[-1] diff --git a/framework/test_fiber.py b/framework/test_fiber.py index 21db4fcb..18f8e130 100644 --- a/framework/test_fiber.py +++ b/framework/test_fiber.py @@ -23,9 +23,10 @@ class FiberConfigPath(Enum): ) CURRENT_TESTNET = ( "/source/template/fiber/config.yml.j2", - "download/fiber/0.3.1/fnn", + "download/fiber/0.4.0/fnn", ) + V040_TESTNET = ("/source/template/fiber/config.yml.j2", "download/fiber/0.4.0/fnn") V031_TESTNET = ("/source/template/fiber/config.yml.j2", "download/fiber/0.3.1/fnn") V030_TESTNET = ("/source/template/fiber/config.yml.j2", "download/fiber/0.3.0/fnn") V020_TESTNET = ("/source/template/fiber/config.yml.j2", "download/fiber/0.2.0/fnn") diff --git a/test_cases/fiber/testnet/test_fiber.py b/test_cases/fiber/testnet/test_fiber.py index 88d202d5..b6ca31ac 100644 --- a/test_cases/fiber/testnet/test_fiber.py +++ b/test_cases/fiber/testnet/test_fiber.py @@ -14,7 +14,9 @@ class TestFiber(CkbTest): - cryptapeFiber1 = FiberRPCClient("http://18.163.221.211:8227") + # cryptapeFiber1 = FiberRPCClient("http://18.163.221.211:8227") + # todo wait 18.163.221.211 recover + cryptapeFiber1 = FiberRPCClient("http://18.162.235.225:8227") cryptapeFiber2 = FiberRPCClient("http://18.162.235.225:8227") ACCOUNT_PRIVATE_1 = ( @@ -107,6 +109,9 @@ def teardown_class(cls): cls.fiber2.stop() cls.fiber2.clean() + def test_dd(self): + pass + def test_ckb_01(self): # open_channel temporary_channel_id = self.fiber1.get_client().open_channel( From 0df23c6eb8f035044b6d9adc8331e5e3b1f54dae Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Wed, 5 Mar 2025 09:23:37 +0800 Subject: [PATCH 30/57] update testnet rpc --- test_cases/fiber/testnet/test_fiber.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test_cases/fiber/testnet/test_fiber.py b/test_cases/fiber/testnet/test_fiber.py index b6ca31ac..afc03a7a 100644 --- a/test_cases/fiber/testnet/test_fiber.py +++ b/test_cases/fiber/testnet/test_fiber.py @@ -14,8 +14,7 @@ class TestFiber(CkbTest): - # cryptapeFiber1 = FiberRPCClient("http://18.163.221.211:8227") - # todo wait 18.163.221.211 recover + cryptapeFiber1 = FiberRPCClient("http://18.162.235.225:8227") cryptapeFiber2 = FiberRPCClient("http://18.162.235.225:8227") From 3658eff80f8786d87c3647f6d511e165165bfcdc Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Thu, 20 Mar 2025 10:07:52 +0800 Subject: [PATCH 31/57] 0.4.1 --- download_fiber.py | 9 +- framework/basic_fiber.py | 36 ++-- framework/fiber_rpc.py | 3 + framework/rpc.py | 2 +- framework/test_fiber.py | 4 +- framework/test_node.py | 6 +- prepare.sh | 2 +- .../devnet/get_invoice/test_get_invoice.py | 8 +- .../devnet/new_invoice/test_description.py | 2 +- .../open_channel/test_commitment_fee_rate.py | 4 +- .../fiber/devnet/open_channel/test_n_user.py | 54 ++++++ .../send_payment/test_custom_records.py | 163 ++++++++++++++++++ .../devnet/send_payment/test_force_restart.py | 4 +- .../devnet/send_payment/test_mutil_channel.py | 37 ++++ .../devnet/send_payment/test_payment_hash.py | 6 +- .../devnet/send_payment/test_send_payment.py | 2 +- .../devnet/send_payment/test_used_preimage.py | 46 +++++ .../devnet/update_channel/test_enabled.py | 38 +++- 18 files changed, 383 insertions(+), 43 deletions(-) create mode 100644 test_cases/fiber/devnet/open_channel/test_n_user.py create mode 100644 test_cases/fiber/devnet/send_payment/test_custom_records.py create mode 100644 test_cases/fiber/devnet/send_payment/test_mutil_channel.py create mode 100644 test_cases/fiber/devnet/send_payment/test_used_preimage.py diff --git a/download_fiber.py b/download_fiber.py index 11e0e37d..f29abf0f 100644 --- a/download_fiber.py +++ b/download_fiber.py @@ -11,7 +11,14 @@ import requests from tqdm import tqdm -versions = ["0.2.0", "0.2.1", "0.3.0", "0.3.1", "0.4.0"] # Replace with your versions +versions = [ + "0.2.0", + "0.2.1", + "0.3.0", + "0.3.1", + "0.4.0", + "0.4.2", +] # Replace with your versions DOWNLOAD_DIR = "download/fiber" SYSTEMS = { diff --git a/framework/basic_fiber.py b/framework/basic_fiber.py index 1c867bc5..9dd2f65a 100644 --- a/framework/basic_fiber.py +++ b/framework/basic_fiber.py @@ -22,6 +22,7 @@ class FiberTest(CkbTest): debug = False first_debug = False logger = logging.getLogger(__name__) + start_fiber_config = {} @classmethod def setup_class(cls): @@ -113,31 +114,21 @@ def setup_method(cls, method): cls.node.start_miner() # deploy fiber # start 2 fiber with xudt + update_config = { + "ckb_rpc_url": cls.node.rpcUrl, + "ckb_udt_whitelist": True, + "xudt_script_code_hash": cls.Contract.get_ckb_contract_codehash( + deploy_hash, deploy_index, True, cls.node.rpcUrl + ), + "xudt_cell_deps_tx_hash": deploy_hash, + "xudt_cell_deps_index": deploy_index, + } + update_config.update(cls.start_fiber_config) - cls.fiber1.prepare( - update_config={ - "ckb_rpc_url": cls.node.rpcUrl, - "ckb_udt_whitelist": True, - "xudt_script_code_hash": cls.Contract.get_ckb_contract_codehash( - deploy_hash, deploy_index, True, cls.node.rpcUrl - ), - "xudt_cell_deps_tx_hash": deploy_hash, - "xudt_cell_deps_index": deploy_index, - } - ) + cls.fiber1.prepare(update_config=update_config) cls.fiber1.start(cls.node) - cls.fiber2.prepare( - update_config={ - "ckb_rpc_url": cls.node.rpcUrl, - "ckb_udt_whitelist": True, - "xudt_script_code_hash": cls.Contract.get_ckb_contract_codehash( - deploy_hash, deploy_index, True, cls.node.rpcUrl - ), - "xudt_cell_deps_tx_hash": deploy_hash, - "xudt_cell_deps_index": deploy_index, - } - ) + cls.fiber2.prepare(update_config=update_config) cls.fiber2.start(cls.node) before_balance1 = cls.Ckb_cli.wallet_get_capacity( cls.account1["address"]["testnet"], api_url=cls.node.getClient().url @@ -246,6 +237,7 @@ def start_new_fiber( "xudt_cell_deps_tx_hash": deploy_hash, "xudt_cell_deps_index": deploy_index, } + update_config.update(self.start_fiber_config) i = len(self.new_fibers) # start fiber3 diff --git a/framework/fiber_rpc.py b/framework/fiber_rpc.py index 64dffaa7..b3986b1a 100644 --- a/framework/fiber_rpc.py +++ b/framework/fiber_rpc.py @@ -16,6 +16,9 @@ def __init__(self, url): def send_btc(self, btc_pay_req): return self.call("send_btc", [btc_pay_req]) + def abandon_channel(self, param): + return self.call("abandon_channel", [param]) + def open_channel(self, param): """ curl --location 'http://127.0.0.1:8227' --header 'Content-Type: application/json' --data '{ diff --git a/framework/rpc.py b/framework/rpc.py index fd05172a..17e3d168 100644 --- a/framework/rpc.py +++ b/framework/rpc.py @@ -101,7 +101,7 @@ def get_fee_rate_statics(self, target=None): def generate_epochs(self, epoch): response = self.call("generate_epochs", [epoch]) - time.sleep(1) + time.sleep(2) return response def generate_block(self): diff --git a/framework/test_fiber.py b/framework/test_fiber.py index 18f8e130..297d64c5 100644 --- a/framework/test_fiber.py +++ b/framework/test_fiber.py @@ -23,15 +23,17 @@ class FiberConfigPath(Enum): ) CURRENT_TESTNET = ( "/source/template/fiber/config.yml.j2", - "download/fiber/0.4.0/fnn", + "download/fiber/0.4.2/fnn", ) + V042_TESTNET = ("/source/template/fiber/config.yml.j2", "download/fiber/0.4.2/fnn") V040_TESTNET = ("/source/template/fiber/config.yml.j2", "download/fiber/0.4.0/fnn") V031_TESTNET = ("/source/template/fiber/config.yml.j2", "download/fiber/0.3.1/fnn") V030_TESTNET = ("/source/template/fiber/config.yml.j2", "download/fiber/0.3.0/fnn") V020_TESTNET = ("/source/template/fiber/config.yml.j2", "download/fiber/0.2.0/fnn") V010_TESTNET = ("/source/template/fiber/config.yml.j2", "download/fiber/0.1.0/fnn") + V040_DEV = ("/source/template/fiber/dev_config.yml.j2", "download/fiber/0.4.0/fnn") V031_DEV = ("/source/template/fiber/dev_config.yml.j2", "download/fiber/0.3.1/fnn") V030_DEV = ("/source/template/fiber/dev_config.yml.j2", "download/fiber/0.3.0/fnn") V021_DEV = ("/source/template/fiber/dev_config.yml.j2", "download/fiber/0.2.1/fnn") diff --git a/framework/test_node.py b/framework/test_node.py index b3d4a3d7..889284a8 100644 --- a/framework/test_node.py +++ b/framework/test_node.py @@ -281,8 +281,10 @@ def stop(self): # run_command("kill {pid}".format(pid=self.ckb_pid)) # self.ckb_pid = -1 port = self.rpcUrl.split(":")[-1] - - run_command(f"kill $(lsof -t -i:{port})", check_exit_code=False) + run_command( + f"kill $(lsof -i:{port} | grep LISTEN | awk '{{print $2}}')", + check_exit_code=False, + ) self.ckb_pid = -1 time.sleep(3) diff --git a/prepare.sh b/prepare.sh index 66db96ad..b2e1f166 100644 --- a/prepare.sh +++ b/prepare.sh @@ -3,7 +3,7 @@ cp download/0.117.0/ckb-cli ./source/ckb-cli cp download/0.110.2/ckb-cli ./source/ckb-cli-old git clone https://github.com/nervosnetwork/fiber cd fiber -git checkout v0.4.0 +git checkout v0.4.2 cargo build cp target/debug/fnn ../download/fiber/current/fnn cd migrate diff --git a/test_cases/fiber/devnet/get_invoice/test_get_invoice.py b/test_cases/fiber/devnet/get_invoice/test_get_invoice.py index 4c6cb551..6295e124 100644 --- a/test_cases/fiber/devnet/get_invoice/test_get_invoice.py +++ b/test_cases/fiber/devnet/get_invoice/test_get_invoice.py @@ -42,7 +42,7 @@ def test_get_exist_new_invoice(self): # Step 3: Verify the node ID matches the PayeePublicKey in the invoice assert ( node_info["node_id"] - == result["invoice"]["data"]["attrs"][3]["PayeePublicKey"] + == result["invoice"]["data"]["attrs"][3]["payee_public_key"] ) # Step 4: Parse the invoice and verify the parsed result matches the original invoice @@ -53,11 +53,11 @@ def test_get_exist_new_invoice(self): assert invoice["invoice"]["currency"] == "Fibd" assert invoice["invoice"]["amount"] == "0x1" assert ( - invoice["invoice"]["data"]["attrs"][0]["Description"] + invoice["invoice"]["data"]["attrs"][0]["description"] == "test invoice generated by node2" ) - assert invoice["invoice"]["data"]["attrs"][1]["ExpiryTime"]["secs"] == 3600 - assert invoice["invoice"]["data"]["attrs"][2]["HashAlgorithm"] == "sha256" + assert invoice["invoice"]["data"]["attrs"][1]["expiry_time"] == hex(3600) + assert invoice["invoice"]["data"]["attrs"][2]["hash_algorithm"] == "sha256" # Step 5: Verify the timestamp of the invoice assert int(int(invoice["invoice"]["data"]["timestamp"], 16) / 1000) == int( diff --git a/test_cases/fiber/devnet/new_invoice/test_description.py b/test_cases/fiber/devnet/new_invoice/test_description.py index b20c7e31..2376f6e9 100644 --- a/test_cases/fiber/devnet/new_invoice/test_description.py +++ b/test_cases/fiber/devnet/new_invoice/test_description.py @@ -140,7 +140,7 @@ def test_description_rd_str(self): {"payment_hash": payment["payment_hash"]} ) assert ( - invoice["invoice"]["data"]["attrs"][0]["Description"] + invoice["invoice"]["data"]["attrs"][0]["description"] == test_description_str[i] ) # 7. List channels after sending payment diff --git a/test_cases/fiber/devnet/open_channel/test_commitment_fee_rate.py b/test_cases/fiber/devnet/open_channel/test_commitment_fee_rate.py index db89d31f..af781d1c 100644 --- a/test_cases/fiber/devnet/open_channel/test_commitment_fee_rate.py +++ b/test_cases/fiber/devnet/open_channel/test_commitment_fee_rate.py @@ -25,9 +25,7 @@ def test_commitment_fee_rate_very_big(self): # "tlc_fee_proportional_millionths": "0x4B0", } ) - expected_error_message = ( - "Commitment fee 18446744073709551 which caculated by commitment fee rate" - ) + expected_error_message = "is larger than half of reserved fee 100000000" assert expected_error_message in exc_info.value.args[0], ( f"Expected substring '{expected_error_message}' " f"not found in actual string '{exc_info.value.args[0]}'" diff --git a/test_cases/fiber/devnet/open_channel/test_n_user.py b/test_cases/fiber/devnet/open_channel/test_n_user.py new file mode 100644 index 00000000..4d513293 --- /dev/null +++ b/test_cases/fiber/devnet/open_channel/test_n_user.py @@ -0,0 +1,54 @@ +import time + +import pytest + +from framework.basic_fiber import FiberTest + + +class TestNUser(FiberTest): + + @pytest.mark.skip("skip") + def test_n_user(self): + n_user = 5 + for i in range(n_user): + self.start_new_fiber(self.generate_account(10000)) + for i in range(0, len(self.fibers) - 1): + self.fibers[i].connect_peer(self.fibers[-1]) + time.sleep(1) + for i in range(0, len(self.fibers) - 1): + self.fibers[i].get_client().open_channel( + { + "peer_id": self.fibers[-1].get_peer_id(), + "funding_amount": hex(1000 * 100000000), + "public": True, + } + ) + for i in range(0, len(self.fibers) - 1): + self.wait_for_channel_state( + self.fibers[i].get_client(), + self.fibers[-1].get_peer_id(), + "CHANNEL_READY", + 120, + ) + + for i in range(0, len(self.fibers) - 1): + self.send_payment(self.fibers[i], self.fibers[-1]) + + for i in range(0, len(self.fibers) - 1): + self.fibers[i].get_client().shutdown_channel( + { + "channel_id": self.fibers[i] + .get_client() + .list_channels({"peer_id": self.fibers[-1].get_peer_id()})[ + "channels" + ][0]["channel_id"], + "close_script": self.get_account_script( + self.fibers[i].account_private + ), + "fee_rate": "0x3FC", + } + ) + for i in range(0, len(self.fibers) - 1): + self.wait_for_channel_state( + self.fibers[i], self.fibers[-1].get_peer_id(), "CLOSED" + ) diff --git a/test_cases/fiber/devnet/send_payment/test_custom_records.py b/test_cases/fiber/devnet/send_payment/test_custom_records.py new file mode 100644 index 00000000..983fa794 --- /dev/null +++ b/test_cases/fiber/devnet/send_payment/test_custom_records.py @@ -0,0 +1,163 @@ +import time + +import pytest + +from framework.basic_fiber import FiberTest + + +class TestCustomRecords(FiberTest): + + def test_custom(self): + """ + {} + none + 单独的key + 重复的key + 会过滤掉重复的key + key 最大值 + 0xffffffff + key 最小值 + 0x0 + 特别大 + OnionPacket(Sphinx(HopDataLenTooLarge)) + 查询 + 我方可以通过get_payment 查询 + 接受方可以通过get_payment 查询: 目前不可以 + Returns: + + """ + self.open_channel(self.fiber1, self.fiber2, 1000 * 100000000, 1000 * 100000000) + payment = self.fiber1.get_client().send_payment( + { + "target_pubkey": self.fiber2.get_client().node_info()["node_id"], + "amount": hex(100), + "keysend": True, + "allow_self_payment": True, + "custom_records": { + "0x1": "0x1234", + "0x2": "0x5678", + }, + } + ) + print("payment:", payment) + time.sleep(1) + payment = self.fiber1.get_client().get_payment( + { + "payment_hash": payment["payment_hash"], + } + ) + assert {"0x1": "0x1234", "0x2": "0x5678"} == payment["custom_records"] + # todo + # self.fiber2.get_client().get_payment({ + # "payment_hash": payment["payment_hash"], + # }) + + # custom_records empty string + payment = self.fiber1.get_client().send_payment( + { + "target_pubkey": self.fiber2.get_client().node_info()["node_id"], + "amount": hex(100), + "keysend": True, + "allow_self_payment": True, + "custom_records": {}, + } + ) + print("payment:", payment) + time.sleep(1) + payment = self.fiber1.get_client().get_payment( + { + "payment_hash": payment["payment_hash"], + } + ) + assert {} == payment["custom_records"] + + payment = self.fiber1.get_client().get_payment( + { + "payment_hash": payment["payment_hash"], + } + ) + + # 单独的key + custom_records = {} + for i in range(0, 100): + custom_records.update({hex(i): self.generate_random_preimage()}) + payment = self.fiber1.get_client().send_payment( + { + "target_pubkey": self.fiber2.get_client().node_info()["node_id"], + "amount": hex(100), + "keysend": True, + "allow_self_payment": True, + "custom_records": custom_records, + } + ) + print("payment:", payment) + time.sleep(1) + payment = self.fiber1.get_client().get_payment( + { + "payment_hash": payment["payment_hash"], + } + ) + assert custom_records == payment["custom_records"] + + # 最大值 和最小值 + payment = self.fiber1.get_client().send_payment( + { + "target_pubkey": self.fiber2.get_client().node_info()["node_id"], + "amount": hex(100), + "keysend": True, + "allow_self_payment": True, + "custom_records": { + "0xffffffff": "0x1234", + "0x0": "0x5678", + }, + } + ) + print("payment:", payment) + time.sleep(1) + payment = self.fiber1.get_client().get_payment( + { + "payment_hash": payment["payment_hash"], + } + ) + assert { + "0xffffffff": "0x1234", + "0x0": "0x5678", + } == payment["custom_records"] + + # 特别大 报错: OnionPacket(Sphinx(HopDataLenTooLarge)) + with pytest.raises(Exception) as exc_info: + payment = self.fiber1.get_client().send_payment( + { + "target_pubkey": self.fiber2.get_client().node_info()["node_id"], + "amount": hex(100), + "keysend": True, + "allow_self_payment": True, + "custom_records": { + "0xffffffff": self.generate_random_str(13000), + # "0x0": "0x5678", + }, + } + ) + expected_error_message = "HopDataLenTooLarge" + assert expected_error_message in exc_info.value.args[0], ( + f"Expected substring '{expected_error_message}' " + f"not found in actual string '{exc_info.value.args[0]}'" + ) + + # none + payment = self.fiber1.get_client().send_payment( + { + "target_pubkey": self.fiber2.get_client().node_info()["node_id"], + "amount": hex(100), + "keysend": True, + "allow_self_payment": True, + } + ) + print("payment:", payment) + time.sleep(1) + payment = self.fiber1.get_client().get_payment( + { + "payment_hash": payment["payment_hash"], + } + ) + assert None == payment["custom_records"] diff --git a/test_cases/fiber/devnet/send_payment/test_force_restart.py b/test_cases/fiber/devnet/send_payment/test_force_restart.py index 0178c3bb..5ac7973a 100644 --- a/test_cases/fiber/devnet/send_payment/test_force_restart.py +++ b/test_cases/fiber/devnet/send_payment/test_force_restart.py @@ -228,7 +228,7 @@ def test_restart_node_send_payment_invoice(self): channels = self.fiber3.get_client().list_channels({}) assert channels["channels"][0]["local_balance"] == hex(30 * 100000000) - @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/363") + # @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/363") def test_restart_when_node_send_payment(self): account3_private = self.generate_account(1000) self.fiber3 = self.start_new_fiber(account3_private) @@ -332,7 +332,7 @@ def test_restart_when_node_send_payment(self): print("fiber2 list channels:", channelsN23) self.fiber2.start() - self.wait_payment_state(self.fiber1, payment["payment_hash"], "Inflight", 120) + self.wait_payment_state(self.fiber1, payment["payment_hash"], "Success", 120) channels = self.fiber3.get_client().list_channels({}) assert channels["channels"][0]["local_balance"] == hex(20 * 100000000) diff --git a/test_cases/fiber/devnet/send_payment/test_mutil_channel.py b/test_cases/fiber/devnet/send_payment/test_mutil_channel.py new file mode 100644 index 00000000..6cee1511 --- /dev/null +++ b/test_cases/fiber/devnet/send_payment/test_mutil_channel.py @@ -0,0 +1,37 @@ +from framework.basic_fiber import FiberTest + + +class TestMutilChannel(FiberTest): + + def test_mutil_channel(self): + """ + 1. 节点1 和节点2之间建立多个channel + 2. 节点1 疯狂给节点2发交易 + 可以用到多个channel + Returns: + """ + open_channel_size = 5 + send_payment_size = 20 + for i in range(open_channel_size): + self.open_channel(self.fiber1, self.fiber2, 1000 * 100000000, 1) + + payment_list = [] + before_channels = self.fiber1.get_client().list_channels({}) + + for i in range(send_payment_size): + payment_list.append( + self.send_payment(self.fiber1, self.fiber2, 1 * 100000000, False) + ) + + for payment_hash in payment_list: + self.wait_payment_finished(self.fiber1, payment_hash, 120) + + channels = self.fiber1.get_client().list_channels({}) + for i in range(len(before_channels["channels"])): + print(before_channels["channels"][i]) + for i in range(len(channels["channels"])): + print(channels["channels"][i]) + assert ( + channels["channels"][i]["remote_balance"] + > before_channels["channels"][i]["remote_balance"] + ) diff --git a/test_cases/fiber/devnet/send_payment/test_payment_hash.py b/test_cases/fiber/devnet/send_payment/test_payment_hash.py index 000fb3da..20d993f2 100644 --- a/test_cases/fiber/devnet/send_payment/test_payment_hash.py +++ b/test_cases/fiber/devnet/send_payment/test_payment_hash.py @@ -53,7 +53,7 @@ def test_payment_hash_not_exist(self): payment1 = self.fiber1.get_client().send_payment( { "target_pubkey": parse_invoice["invoice"]["data"]["attrs"][3][ - "PayeePublicKey" + "payee_public_key" ], "currency": parse_invoice["invoice"]["currency"], "payment_hash": self.generate_random_preimage(), @@ -105,7 +105,7 @@ def test_rand_hash_Musig2VerifyError(self): payment1 = self.fiber1.get_client().send_payment( { "target_pubkey": parse_invoice["invoice"]["data"]["attrs"][3][ - "PayeePublicKey" + "payee_public_key" ], "currency": parse_invoice["invoice"]["currency"], "payment_hash": self.generate_random_preimage(), @@ -116,7 +116,7 @@ def test_rand_hash_Musig2VerifyError(self): payment1 = self.fiber1.get_client().send_payment( { "target_pubkey": parse_invoice["invoice"]["data"]["attrs"][3][ - "PayeePublicKey" + "payee_public_key" ], "currency": parse_invoice["invoice"]["currency"], "payment_hash": self.generate_random_preimage(), diff --git a/test_cases/fiber/devnet/send_payment/test_send_payment.py b/test_cases/fiber/devnet/send_payment/test_send_payment.py index 26040a4a..e65df5ff 100644 --- a/test_cases/fiber/devnet/send_payment/test_send_payment.py +++ b/test_cases/fiber/devnet/send_payment/test_send_payment.py @@ -138,7 +138,7 @@ def test_Musig2VerifyError(self): payment1 = self.fiber1.get_client().send_payment( { "target_pubkey": parse_invoice["invoice"]["data"]["attrs"][3][ - "PayeePublicKey" + "payee_public_key" ], "currency": parse_invoice["invoice"]["currency"], "amount": parse_invoice["invoice"]["amount"], diff --git a/test_cases/fiber/devnet/send_payment/test_used_preimage.py b/test_cases/fiber/devnet/send_payment/test_used_preimage.py new file mode 100644 index 00000000..976df537 --- /dev/null +++ b/test_cases/fiber/devnet/send_payment/test_used_preimage.py @@ -0,0 +1,46 @@ +import pytest + +from framework.basic_fiber import FiberTest + + +class TestUsedPreimage(FiberTest): + + def test_used_preimage(self): + self.open_channel(self.fiber1, self.fiber2, 1000 * 100000000, 1000 * 100000000) + payment_preimage = self.generate_random_preimage() + invoice = self.fiber2.get_client().new_invoice( + { + "amount": hex(1), + "currency": "Fibd", + "description": "test invoice generated by node2", + "expiry": "0xe10", + "final_cltv": "0x28", + "payment_preimage": payment_preimage, + "hash_algorithm": "sha256", + } + ) + payment = self.fiber1.get_client().send_payment( + { + "invoice": invoice["invoice_address"], + } + ) + self.wait_payment_state(self.fiber1, payment["payment_hash"], "Success") + # send payment again + + with pytest.raises(Exception) as exc_info: + invoice = self.fiber2.get_client().new_invoice( + { + "amount": hex(1), + "currency": "Fibd", + "description": "test invoice generated by node2", + "expiry": "0xe10", + "final_cltv": "0x28", + "payment_preimage": payment_preimage, + "hash_algorithm": "sha256", + } + ) + expected_error_message = "Duplicated invoice" + assert expected_error_message in exc_info.value.args[0], ( + f"Expected substring '{expected_error_message}' " + f"not found in actual string '{exc_info.value.args[0]}'" + ) diff --git a/test_cases/fiber/devnet/update_channel/test_enabled.py b/test_cases/fiber/devnet/update_channel/test_enabled.py index 3ca4c8fb..e1534258 100644 --- a/test_cases/fiber/devnet/update_channel/test_enabled.py +++ b/test_cases/fiber/devnet/update_channel/test_enabled.py @@ -14,7 +14,7 @@ class TestEnable(FiberTest): # FiberTest.debug = True - @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/499") + # @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/499") def test_true(self): """ A-B-C @@ -44,10 +44,29 @@ def test_true(self): .get_client() .list_channels({"peer_id": self.fibers[2].get_peer_id()}) ) + channels = self.fibers[1].get_client().graph_channels({}) + assert len(channels["channels"]) == 2 self.fibers[1].get_client().update_channel( {"channel_id": channel["channels"][0]["channel_id"], "enabled": False} ) time.sleep(1) + channels = self.fibers[1].get_client().graph_channels({}) + assert len(channels["channels"]) == 1 + channel = ( + self.fibers[1] + .get_client() + .list_channels({"peer_id": self.fibers[2].get_peer_id()}) + ) + print("fiber1 channel:", channel) + assert channel["channels"][0]["enabled"] == False + channel = ( + self.fibers[2] + .get_client() + .list_channels({"peer_id": self.fibers[1].get_peer_id()}) + ) + print("fiber2 channel:", channel) + assert channel["channels"][0]["enabled"] == True + # 2. A->C 报错 with pytest.raises(Exception) as exc_info: self.send_payment(self.fibers[0], self.fibers[2], 1) @@ -81,6 +100,23 @@ def test_true(self): {"channel_id": channel["channels"][0]["channel_id"], "enabled": True} ) time.sleep(1) + channels = self.fibers[1].get_client().graph_channels({}) + print("after true graph_channels:", channels) + assert len(channels["channels"]) == 2 + channel = ( + self.fibers[1] + .get_client() + .list_channels({"peer_id": self.fibers[2].get_peer_id()}) + ) + print("fiber1 channel:", channel) + assert channel["channels"][0]["enabled"] == True + channel = ( + self.fibers[2] + .get_client() + .list_channels({"peer_id": self.fibers[1].get_peer_id()}) + ) + print("fiber2 channel:", channel) + assert channel["channels"][0]["enabled"] == True # 7. A->C 不会报错 # 8. B->C 不会报错 self.send_payment(self.fibers[0], self.fibers[2], 1) From abe112941f60e47e559ecc622f90e5d3dc6cdc4d Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Thu, 20 Mar 2025 10:43:09 +0800 Subject: [PATCH 32/57] update testnet fiber --- test_cases/fiber/testnet/test_fiber.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test_cases/fiber/testnet/test_fiber.py b/test_cases/fiber/testnet/test_fiber.py index afc03a7a..88d202d5 100644 --- a/test_cases/fiber/testnet/test_fiber.py +++ b/test_cases/fiber/testnet/test_fiber.py @@ -14,8 +14,7 @@ class TestFiber(CkbTest): - - cryptapeFiber1 = FiberRPCClient("http://18.162.235.225:8227") + cryptapeFiber1 = FiberRPCClient("http://18.163.221.211:8227") cryptapeFiber2 = FiberRPCClient("http://18.162.235.225:8227") ACCOUNT_PRIVATE_1 = ( @@ -108,9 +107,6 @@ def teardown_class(cls): cls.fiber2.stop() cls.fiber2.clean() - def test_dd(self): - pass - def test_ckb_01(self): # open_channel temporary_channel_id = self.fiber1.get_client().open_channel( From 28d6c7bc7da0a5b0b306fd47e13c3225b939e4e8 Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Thu, 20 Mar 2025 12:28:23 +0800 Subject: [PATCH 33/57] fix testnet --- test_cases/fiber/testnet/test_fiber.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test_cases/fiber/testnet/test_fiber.py b/test_cases/fiber/testnet/test_fiber.py index 88d202d5..a8aa880e 100644 --- a/test_cases/fiber/testnet/test_fiber.py +++ b/test_cases/fiber/testnet/test_fiber.py @@ -255,8 +255,8 @@ def test_udt_02(self): "args": "0x878fcc6f1f08d48e87bb1c3b3d5083f23f8a39c5d5c764f253b55b998526439b", } self.fiber1.stop() - self.fiber1.start() self.fiber2.stop() + self.fiber1.start() self.fiber2.start() begin = time.time() # wait dry_run success @@ -299,7 +299,7 @@ def test_udt_02(self): def send_payment( - fiber1: FiberRPCClient, fiber2: FiberRPCClient, amount, udt=None, wait_times=300 + fiber1: FiberRPCClient, fiber2: FiberRPCClient, amount, udt=None, wait_times=300 ): try_times = 0 payment = None From ca57e506387215bb2f549c8cdc8abfbaa40aff2b Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Thu, 20 Mar 2025 13:38:11 +0800 Subject: [PATCH 34/57] format code --- test_cases/fiber/testnet/test_fiber.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_cases/fiber/testnet/test_fiber.py b/test_cases/fiber/testnet/test_fiber.py index a8aa880e..1809af4f 100644 --- a/test_cases/fiber/testnet/test_fiber.py +++ b/test_cases/fiber/testnet/test_fiber.py @@ -299,7 +299,7 @@ def test_udt_02(self): def send_payment( - fiber1: FiberRPCClient, fiber2: FiberRPCClient, amount, udt=None, wait_times=300 + fiber1: FiberRPCClient, fiber2: FiberRPCClient, amount, udt=None, wait_times=300 ): try_times = 0 payment = None From fe2bb2efcee0ad61ca7722a2cf6c5af26092c178 Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Wed, 26 Mar 2025 11:10:07 +0800 Subject: [PATCH 35/57] add abandon channel --- .../abandon_channel/TestAbandonChannel.py | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 test_cases/fiber/devnet/abandon_channel/TestAbandonChannel.py diff --git a/test_cases/fiber/devnet/abandon_channel/TestAbandonChannel.py b/test_cases/fiber/devnet/abandon_channel/TestAbandonChannel.py new file mode 100644 index 00000000..d26a061c --- /dev/null +++ b/test_cases/fiber/devnet/abandon_channel/TestAbandonChannel.py @@ -0,0 +1,154 @@ +import time + +import pytest + +from framework.basic_fiber import FiberTest + + +class TestAbandonChannel(FiberTest): + """ + abandon channel + 存在的chain id + tmp_id + channel 状态 + sign + await tx ready + ready + shutdown + close + 批量open_channel 随机abandon channel + 不存在的channel id + """ + + def test_tmp_id(self): + channel = self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(1 + 62 * 100000000), + "public": True, + } + ) + time.sleep(1) + response = self.fiber1.get_client().abandon_channel( + {"channel_id": channel["temporary_channel_id"]} + ) + self.fiber2.get_client().accept_channel( + { + "temporary_channel_id": channel["temporary_channel_id"], + "funding_amount": hex(62 * 100000000), + } + ) + channel = self.fiber1.get_client().list_channels({}) + assert len(channel["channels"]) == 0 + channel = self.fiber2.get_client().list_channels({}) + assert len(channel["channels"]) == 0 + + def test_abandon_channel_accept(self): + # self.open_channel(self.fiber1, self.fiber2, 1000 * 100000000, 10000 * 100000000) + channel = self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(1 + 62 * 100000000), + "public": True, + } + ) + time.sleep(1) + self.fiber2.get_client().accept_channel( + { + "temporary_channel_id": channel["temporary_channel_id"], + "funding_amount": hex(62 * 100000000), + } + ) + # self.wait_and_check_tx_pool_fee(1000, False, 120) + + # Test AbandonChannel + channel_id = self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ] + response = self.fiber1.get_client().abandon_channel({"channel_id": channel_id}) + time.sleep(1) + channel = self.fiber1.get_client().list_channels({}) + assert len(channel["channels"]) == 0 + + channel = self.fiber2.get_client().list_channels({}) + tx = self.node.getClient().get_transaction( + channel["channels"][0]["channel_outpoint"][:-8] + ) + assert tx["tx_status"]["status"] == "unknown" + + @pytest.mark.skip("资金会丢失") + def test_abandon_channel_when_tx_send(self): + channel = self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(1000 * 100000000), + "public": True, + } + ) + self.wait_and_check_tx_pool_fee(1000, False, 120) + channel = self.fiber1.get_client().list_channels({}) + response = self.fiber1.get_client().abandon_channel( + {"channel_id": channel["channels"][0]["channel_id"]} + ) + time.sleep(5) + channel = self.fiber1.get_client().list_channels({}) + assert len(channel["channels"]) == 0 + channel = self.fiber2.get_client().list_channels({}) + print(channel) + assert channel["channels"][0]["state"]["state_name"] == "AWAITING_CHANNEL_READY" + tx = self.node.getClient().get_transaction( + channel["channels"][0]["channel_outpoint"][:-8] + ) + assert tx["tx_status"]["status"] == "committed" + + def test_chain_status_ready_or_shutdown_close(self): + self.open_channel(self.fiber1, self.fiber2, 1000 * 100000000, 1) + channels = self.fiber1.get_client().list_channels({}) + + # ChannelReady + with pytest.raises(Exception) as exc_info: + response = self.fiber1.get_client().abandon_channel( + {"channel_id": channels["channels"][0]["channel_id"]} + ) + expected_error_message = ( + "cannot be abandoned, please shutdown the channel instead" + ) + assert expected_error_message in exc_info.value.args[0], ( + f"Expected substring '{expected_error_message}' " + f"not found in actual string '{exc_info.value.args[0]}'" + ) + + # shutdown + self.fiber1.get_client().shutdown_channel( + { + "channel_id": channels["channels"][0]["channel_id"], + "close_script": self.get_account_script(self.Config.ACCOUNT_PRIVATE_1), + "fee_rate": "0x3FC", + } + ) + with pytest.raises(Exception) as exc_info: + response = self.fiber1.get_client().abandon_channel( + {"channel_id": channels["channels"][0]["channel_id"]} + ) + expected_error_message = ( + "cannot be abandoned, please shutdown the channel instead" + ) + assert expected_error_message in exc_info.value.args[0], ( + f"Expected substring '{expected_error_message}' " + f"not found in actual string '{exc_info.value.args[0]}'" + ) + # closed + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "Closed", True + ) + with pytest.raises(Exception) as exc_info: + response = self.fiber1.get_client().abandon_channel( + {"channel_id": channels["channels"][0]["channel_id"]} + ) + expected_error_message = ( + "cannot be abandoned, please shutdown the channel instead" + ) + assert expected_error_message in exc_info.value.args[0], ( + f"Expected substring '{expected_error_message}' " + f"not found in actual string '{exc_info.value.args[0]}'" + ) From 8a00acbe7003e9e651ee468de0e0d61087e73411 Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Thu, 27 Mar 2025 14:10:19 +0800 Subject: [PATCH 36/57] fix ci failed --- .../devnet/send_payment/test_mutil_channel.py | 7 ++-- .../devnet/watch_tower/test_watch_tower.py | 34 ++++++++++--------- .../watch_tower/test_watch_tower_udt.py | 28 +++++++-------- 3 files changed, 37 insertions(+), 32 deletions(-) diff --git a/test_cases/fiber/devnet/send_payment/test_mutil_channel.py b/test_cases/fiber/devnet/send_payment/test_mutil_channel.py index 6cee1511..f3bf784a 100644 --- a/test_cases/fiber/devnet/send_payment/test_mutil_channel.py +++ b/test_cases/fiber/devnet/send_payment/test_mutil_channel.py @@ -29,9 +29,12 @@ def test_mutil_channel(self): channels = self.fiber1.get_client().list_channels({}) for i in range(len(before_channels["channels"])): print(before_channels["channels"][i]) + used_channel = 0 for i in range(len(channels["channels"])): print(channels["channels"][i]) - assert ( + if ( channels["channels"][i]["remote_balance"] > before_channels["channels"][i]["remote_balance"] - ) + ): + used_channel += 1 + assert used_channel > 1 diff --git a/test_cases/fiber/devnet/watch_tower/test_watch_tower.py b/test_cases/fiber/devnet/watch_tower/test_watch_tower.py index 1826e3ae..6f74ff68 100644 --- a/test_cases/fiber/devnet/watch_tower/test_watch_tower.py +++ b/test_cases/fiber/devnet/watch_tower/test_watch_tower.py @@ -80,7 +80,7 @@ def test_node1_shutdown_when_open_and_node2_split_tx(self): self.fiber1.stop() # Step 10: Generate epochs - self.node.getClient().generate_epochs("0xa") + self.node.getClient().generate_epochs("0x6", 0) # 11. Wait for node2 splits the transaction to be committed and check the transaction message. tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) @@ -186,7 +186,7 @@ def test_node2_shutdown_when_open_and_node2_split_tx(self): self.fiber1.stop() # Step 10: Generate epochs - self.node.getClient().generate_epochs("0xa") + self.node.getClient().generate_epochs("0x6", 0) # Step 11: Wait for node2 splits the transaction to be committed and check the transaction message tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) @@ -283,7 +283,7 @@ def test_node2_shutdown_when_open_and_node1_split_tx(self): self.fiber2.stop() # Step 10: Generate epochs - self.node.getClient().generate_epochs("0xa") + self.node.getClient().generate_epochs("0x6", 0) # Step 11: Wait for node1 splits the transaction to be committed and check the transaction message tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) @@ -380,7 +380,7 @@ def test_node1_shutdown_when_open_and_node1_split_tx(self): self.fiber2.stop() # Step 10: Generate epochs - self.node.getClient().generate_epochs("0xa") + self.node.getClient().generate_epochs("0x6", 0) # Step 11: Wait for node1 splits the transaction to be committed and check the transaction message tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) @@ -484,7 +484,7 @@ def test_node1_shutdown_after_send_tx1_and_node1_split_tx(self): self.fiber2.stop() # Step 11: Generate epochs - self.node.getClient().generate_epochs("0xa") + self.node.getClient().generate_epochs("0x6", 0) # Step 12: Wait for the transaction to be committed and check the transaction message tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) @@ -585,7 +585,7 @@ def test_node1_shutdown_after_send_tx1_and_node2_split_tx(self): self.fiber1.stop() # Step 11: Generate epochs - self.node.getClient().generate_epochs("0xa") + self.node.getClient().generate_epochs("0x6", 0) # Step 12: Wait for the transaction to be committed and check the transaction message tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) @@ -688,7 +688,7 @@ def test_node2_shutdown_after_send_tx1_and_node1_split_tx(self): self.fiber2.stop() # Step 11: Generate epochs - self.node.getClient().generate_epochs("0xa") + self.node.getClient().generate_epochs("0x6", 0) # Step 12: Wait for the transaction to be committed and check the transaction message tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) @@ -789,7 +789,7 @@ def test_node2_shutdown_after_send_tx1_and_node2_split_tx(self): self.fiber1.stop() # Step 11: Generate epochs - self.node.getClient().generate_epochs("0xa") + self.node.getClient().generate_epochs("0x6", 0) # Step 12: Wait for the transaction to be committed and check the transaction message tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) @@ -899,7 +899,7 @@ def test_node1_shutdown_after_send_tx2_and_node1_split_tx(self): self.fiber2.stop() # Step 11: Generate epochs - self.node.getClient().generate_epochs("0xa") + self.node.getClient().generate_epochs("0x6", 0) # Step 12: Wait for the transaction to be committed and check the transaction message tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) @@ -1001,9 +1001,11 @@ def test_node1_shutdown_after_send_tx2_and_node2_split_tx(self): # Step 10: Stop node1 self.fiber1.stop() + self.fiber2.stop() # Step 11: Generate epochs - self.node.getClient().generate_epochs("0xa") + self.node.getClient().generate_epochs("0x6", 0) + self.fiber2.start() # Step 12: Wait for the transaction to be committed and check the transaction message tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) @@ -1106,7 +1108,7 @@ def test_node2_shutdown_after_send_tx2_and_node1_split_tx(self): self.fiber1.stop() # Step 11: Generate epochs - self.node.getClient().generate_epochs("0xa") + self.node.getClient().generate_epochs("0x6", 0) # Step 12: Wait for the transaction to be committed and check the transaction message tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) @@ -1209,7 +1211,7 @@ def test_node2_shutdown_after_send_tx2_and_node2_split_tx(self): self.fiber1.stop() # Step 11: Generate epochs - self.node.getClient().generate_epochs("0xa") + self.node.getClient().generate_epochs("0x6", 0) # Step 12: Wait for the transaction to be committed and check the transaction message tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) @@ -1311,7 +1313,7 @@ def test_node1_shutdown_after_send_txN_and_node1_split_tx(self): self.fiber2.stop() # Step 11: Generate epochs - self.node.getClient().generate_epochs("0xa") + self.node.getClient().generate_epochs("0x6", 0) # Step 12: Wait for the transaction to be committed and check the transaction message tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) @@ -1415,10 +1417,10 @@ def test_node2_shutdown_after_send_txN_and_node1_split_tx(self): # Step 10: Stop node1 self.fiber1.stop() - + self.fiber2.stop() # Step 11: Generate epochs - self.node.getClient().generate_epochs("0xa") - + self.node.getClient().generate_epochs("0x6", 0) + self.fiber2.start() # Step 12: Wait for the transaction to be committed and check the transaction message tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) tx_message = self.get_tx_message(tx_hash) diff --git a/test_cases/fiber/devnet/watch_tower/test_watch_tower_udt.py b/test_cases/fiber/devnet/watch_tower/test_watch_tower_udt.py index 6296297b..7c8657d4 100644 --- a/test_cases/fiber/devnet/watch_tower/test_watch_tower_udt.py +++ b/test_cases/fiber/devnet/watch_tower/test_watch_tower_udt.py @@ -83,7 +83,7 @@ def test_node1_shutdown_when_open_and_node2_split_tx(self): self.fiber1.stop() # Step 10: Generate epochs - self.node.getClient().generate_epochs("0xa") + self.node.getClient().generate_epochs("0x6", 0) # Step 11: Wait for the transaction to be committed and check the transaction message tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) @@ -193,7 +193,7 @@ def test_node2_shutdown_when_open_and_node2_split_tx(self): self.fiber1.stop() # Step 10: Generate epochs - self.node.getClient().generate_epochs("0xa") + self.node.getClient().generate_epochs("0x6", 0) # Step 11: Wait for the transaction to be committed and check the transaction message tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) @@ -296,7 +296,7 @@ def test_node2_shutdown_when_open_and_node1_split_tx(self): self.fiber2.stop() # Step 10: Generate epochs - self.node.getClient().generate_epochs("0xa") + self.node.getClient().generate_epochs("0x6", 0) # Step 11: Wait for the transaction to be committed and check the transaction message tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) @@ -375,7 +375,7 @@ def test_node1_shutdown_when_open_and_node1_split_tx(self): node1_graph_channels = self.fiber1.get_client().graph_channels() node2_graph_channels = self.fiber2.get_client().graph_channels() self.fiber2.stop() - self.node.getClient().generate_epochs("0xa") + self.node.getClient().generate_epochs("0x6", 0) tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) tx_message = self.get_tx_message(tx_hash) # assert tx_message['input_cells'][0]['capacity'] == @@ -485,7 +485,7 @@ def test_node1_shutdown_after_send_tx1_and_node1_split_tx(self): self.fiber2.stop() # Step 11: Generate epochs - self.node.getClient().generate_epochs("0xa") + self.node.getClient().generate_epochs("0x6", 0) # Step 12: Wait for the transaction to be committed and check the transaction message tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) @@ -595,7 +595,7 @@ def test_node1_shutdown_after_send_tx1_and_node2_split_tx(self): self.fiber1.stop() # Step 11: Generate epochs - self.node.getClient().generate_epochs("0xa") + self.node.getClient().generate_epochs("0x6", 0) # Step 12: Wait for the transaction to be committed and check the transaction message tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) @@ -709,7 +709,7 @@ def test_node2_shutdown_after_send_tx1_and_node1_split_tx(self): self.fiber2.stop() # Step 11: Generate epochs - self.node.getClient().generate_epochs("0xa") + self.node.getClient().generate_epochs("0x6", 0) # Step 12: Wait for the transaction to be committed and check the transaction message tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) @@ -820,7 +820,7 @@ def test_node2_shutdown_after_send_tx1_and_node2_split_tx(self): self.fiber1.stop() # Step 11: Generate epochs - self.node.getClient().generate_epochs("0xa") + self.node.getClient().generate_epochs("0x6", 0) # Step 12: Wait for the transaction to be committed and check the transaction message tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) @@ -933,7 +933,7 @@ def test_node1_shutdown_after_send_tx2_and_node1_split_tx(self): self.fiber2.stop() # Step 11: Generate epochs - self.node.getClient().generate_epochs("0xa") + self.node.getClient().generate_epochs("0x6", 0) # Step 12: Wait for the transaction to be committed and check the transaction message tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) @@ -1047,7 +1047,7 @@ def test_node1_shutdown_after_send_tx2_and_node2_split_tx(self): self.fiber1.stop() # Step 11: Generate epochs - self.node.getClient().generate_epochs("0xa") + self.node.getClient().generate_epochs("0x6", 0) # Step 12: Wait for the transaction to be committed and check the transaction message tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) @@ -1162,7 +1162,7 @@ def test_node2_shutdown_after_send_tx2_and_node1_split_tx(self): self.fiber1.stop() # Step 11: Generate epochs - self.node.getClient().generate_epochs("0xa") + self.node.getClient().generate_epochs("0x6", 0) # Step 12: Wait for the transaction to be committed and check the transaction message tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) @@ -1275,7 +1275,7 @@ def test_node2_shutdown_after_send_tx2_and_node2_split_tx(self): self.fiber1.stop() # Step 11: Generate epochs - self.node.getClient().generate_epochs("0xa") + self.node.getClient().generate_epochs("0x6", 0) # Step 12: Wait for the transaction to be committed and check the transaction message tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) @@ -1388,7 +1388,7 @@ def test_node1_shutdown_after_send_txN_and_node1_split_tx(self): self.fiber2.stop() # Step 11: Generate epochs - self.node.getClient().generate_epochs("0xa") + self.node.getClient().generate_epochs("0x6", 0) # Step 12: Wait for the transaction to be committed and check the transaction message tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) @@ -1504,7 +1504,7 @@ def test_node2_shutdown_after_send_txN_and_node1_split_tx(self): self.fiber1.stop() # Step 11: Generate epochs - self.node.getClient().generate_epochs("0xa") + self.node.getClient().generate_epochs("0x6", 0) # Step 12: Wait for the transaction to be committed and check the transaction message tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 1000) From ae76b01bb757ecbd21a8323b7cfa01467c1f0e0d Mon Sep 17 00:00:00 2001 From: 15168316096 <15168316096@163.com> Date: Thu, 27 Mar 2025 14:11:59 +0800 Subject: [PATCH 37/57] add hophint case --- .../fiber/devnet/send_payment/test_hophint.py | 255 ++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 test_cases/fiber/devnet/send_payment/test_hophint.py diff --git a/test_cases/fiber/devnet/send_payment/test_hophint.py b/test_cases/fiber/devnet/send_payment/test_hophint.py new file mode 100644 index 00000000..0d451053 --- /dev/null +++ b/test_cases/fiber/devnet/send_payment/test_hophint.py @@ -0,0 +1,255 @@ +import time + +import pytest + +from framework.basic_fiber import FiberTest + + + +class TestHopHint(FiberTest): #a-b + # FiberTest.debug = True + + def test_not_hophit(self): + """ + a-私-b-c-d-私-a + 1. a->b + 2. a->c + 3. a->d + 4. a->a + 5. b-a(不通过hophit应该发送失败) + Returns: + + """ + self.start_new_fiber(self.generate_account(10000)) #c + self.start_new_fiber(self.generate_account(10000)) #d + + fiber1_balance = 1000 * 100000000 + fiber1_fee = 1000 + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(fiber1_balance + 62 * 100000000), + "tlc_fee_proportional_millionths": hex(fiber1_fee), + "public": False, + } + )#a-b private channel + time.sleep(1) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" + ) + self.open_channel(self.fibers[1], self.fibers[2], 1000 * 100000000, 1) #b-c + self.open_channel(self.fibers[2], self.fibers[3], 1000 * 100000000, 1) #c-d == b-c-d + + self.fibers[3].connect_peer(self.fibers[0])#d-a + time.sleep(1) + self.fibers[3].get_client().open_channel( #d -a private channel + { + "peer_id": self.fibers[0].get_peer_id(), + "funding_amount": hex(fiber1_balance + 62 * 100000000), + "tlc_fee_proportional_millionths": hex(fiber1_fee), + "public": False, + } + ) + time.sleep(1) + self.wait_for_channel_state( + self.fibers[3].get_client(), self.fibers[0].get_peer_id(), "CHANNEL_READY" + ) + + # for i in range(1, len(self.fibers)): #b,c,d + # self.send_payment(self.fibers[0], self.fibers[i], 1 * 100000000) #a->b,a->c,a>d + # + # self.send_payment(self.fibers[0], self.fibers[0], 1 * 100000000)#a->a + + #b-a(不通过hophit应该发送失败) + + try: + self.send_payment(self.fibers[1], self.fibers[0], 1 * 100000000) + except Exception as e: + error_message = str(e) + assert error_message == "Error: Send payment error: Failed to build route, PathFind error: no path found", \ + f"Unexpected error message: {error_message}" + + # @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/620") + def test_not_hophit_issue620(self): + """ + a-私-b-c-d-私-a + 1. a->b + 2. a->c + 3. a->d + 4. a->a + 5. b-a(不通过hophit应该发送失败) + Returns: + + """ + self.start_new_fiber(self.generate_account(10000)) #c + self.start_new_fiber(self.generate_account(10000)) #d + + fiber1_balance = 1000 * 100000000 + fiber1_fee = 1000 + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(fiber1_balance + 62 * 100000000), + "tlc_fee_proportional_millionths": hex(fiber1_fee), + "public": False, + } + )#a-b private channel + time.sleep(1) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" + ) + self.open_channel(self.fibers[1], self.fibers[2], 1000 * 100000000, 1) #b-c + self.open_channel(self.fibers[2], self.fibers[3], 1000 * 100000000, 1) #c-d == b-c-d + + self.fibers[3].connect_peer(self.fibers[0])#d-a + time.sleep(1) + self.fibers[3].get_client().open_channel( #d -a private channel + { + "peer_id": self.fibers[0].get_peer_id(), + "funding_amount": hex(fiber1_balance + 62 * 100000000), + "tlc_fee_proportional_millionths": hex(fiber1_fee), + "public": False, + } + ) + time.sleep(1) + self.wait_for_channel_state( + self.fibers[3].get_client(), self.fibers[0].get_peer_id(), "CHANNEL_READY" + ) + + for i in range(1, len(self.fibers)): #b,c,d + self.send_payment(self.fibers[0], self.fibers[i], 1 * 100000000) #a->b,a->c,a>d + + self.send_payment(self.fibers[0], self.fibers[0], 1 * 100000000)#a->a + + #b-a(不通过hophit应该发送失败) + + self.send_payment(self.fibers[1], self.fibers[0], 1 * 100000000)#b->a + + + def test_use_hophit(self): + """ + a-私-b-c-d-私-a + 1. b-a(通过hophit应该发送成功) + Returns: + """ + self.start_new_fiber(self.generate_account(10000)) # c + self.start_new_fiber(self.generate_account(10000)) # d + + fiber1_balance = 1000 * 100000000 + fiber1_fee = 1000 + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(fiber1_balance + 62 * 100000000), + "tlc_fee_proportional_millionths": hex(fiber1_fee), + "public": False, + } + ) # a-b private channel + time.sleep(1) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" + ) + self.open_channel(self.fibers[1], self.fibers[2], 1000 * 100000000, 1) # b-c + self.open_channel(self.fibers[2], self.fibers[3], 1000 * 100000000, 1) # c-d == b-c-d + + self.fibers[3].connect_peer(self.fibers[0]) # d-a + time.sleep(1) + self.fibers[3].get_client().open_channel( # d -a private channel + { + "peer_id": self.fibers[0].get_peer_id(), + "funding_amount": hex(fiber1_balance + 62 * 100000000), + "tlc_fee_proportional_millionths": hex(fiber1_fee), + "public": False, + } + ) + time.sleep(1) + self.wait_for_channel_state( + self.fibers[3].get_client(), self.fibers[0].get_peer_id(), "CHANNEL_READY" + ) + #查看d-a的channeloutpoint + print(f"a peer_id:{self.fibers[0].get_peer_id()}") + print(f"d peer_id:{self.fibers[3].get_peer_id()}") + channels = self.fibers[3].get_client().list_channels( + {"peer_id": self.fibers[0].get_peer_id()} + ) + print(f"d-a,channel:{channels}") #{'channels': [{'channel_id': '0xe59fc475a5e32bfd4130f5d7b73e2c77e94e40a1c4de0f4c4f7cb65a23cfa808', 'is_public': False, 'channel_outpoint': '0x7f2fc106cbc01d25e9682826ec131e67be8e9868fbb37edd6591bb7423feb21000000000', 'peer_id': 'QmT5SaY3CSSY9XvgoqJ521TXSUQ5DBZ58DTdafPKFBEcWf', 'funding_udt_type_script': None, 'state': {'state_name': 'CHANNEL_READY'}, 'local_balance': '0x174876e800', 'offered_tlc_balance': '0x0', 'remote_balance': '0x0', 'received_tlc_balance': '0x0', 'latest_commitment_transaction_hash': '0x93e75ade9b0016dbca0a56698fbf04f4a4ca5d8bc3c40fa781769e37ebb9fba2', 'created_at': '0x195d58690aa', 'enabled': True, 'tlc_expiry_delta': '0x5265c00', 'tlc_fee_proportional_millionths': '0x3e8'}, {'channel_id': '0xab117469b812d64410e1f4a6429475908f8672eda1a492772387880fd9046f07', 'is_public': False, 'channel_outpoint': '0xfe4f6fbfd2fb31ec9ca6dc7a4e43efa4e864002fae281c7c5e38fc51cd89465f00000000', 'peer_id': 'QmT5SaY3CSSY9XvgoqJ521TXSUQ5DBZ58DTdafPKFBEcWf', 'funding_udt_type_script': None, 'state': {'state_name': 'CHANNEL_READY'}, 'local_balance': '0x174876e800', 'offered_tlc_balance': '0x0', 'remote_balance': '0x0', 'received_tlc_balance': '0x0', 'latest_commitment_transaction_hash': '0xc6f38fe84030eba95376c63e76ccfa9605c07f5ca407e395ca53db609b305787', 'created_at': '0x195d2e09a4d', 'enabled': True, 'tlc_expiry_delta': '0x5265c00', 'tlc_fee_proportional_millionths': '0x3e8'}]} + da_channel_outpoint = channels["channels"][0]["channel_outpoint"] + print(f"d-a, channel_outpoint:{da_channel_outpoint}") + #b-a,怎么填d-私-a的信息 + + payment_hash = self.fibers[1].get_client().send_payment( # b + { + "target_pubkey": self.fibers[0].get_client().node_info()["node_id"], + "amount": hex(10 * 100000000), + "keysend": True, + "hop_hints": [{"pubkey": self.fibers[3].get_client().node_info()["node_id"], + # 填的是 d 的 pubkey,表示在 d 节点使用 channel_outpoint 到 a + "channel_outpoint": da_channel_outpoint, + "fee_rate": 1000, + "tlc_expiry_delta": 1000}] + } + ) + + payment = ( + self.fibers[1].get_client().get_payment({"payment_hash": payment_hash["payment_hash"]}) + ) + print("payment", payment) + assert payment["status"] == "Created" + + + def test_use_hophit_simple(self): + """ + b-c-d-私-a + 1. b-a(通过hophit应该发送成功) + Returns: + """ + self.start_new_fiber(self.generate_account(10000)) # c + self.start_new_fiber(self.generate_account(10000)) # d + + fiber1_balance = 1000 * 100000000 + fiber1_fee = 1000 + + self.open_channel(self.fibers[1], self.fibers[2], 1000 * 100000000, 1) # b-c + self.open_channel(self.fibers[2], self.fibers[3], 1000 * 100000000, 1) # c-d == b-c-d + + self.fibers[3].connect_peer(self.fibers[0]) # d-a + time.sleep(1) + self.fibers[3].get_client().open_channel( # d -a private channel + { + "peer_id": self.fibers[0].get_peer_id(), + "funding_amount": hex(fiber1_balance + 62 * 100000000), + "tlc_fee_proportional_millionths": hex(fiber1_fee), + "public": False, + } + ) + time.sleep(1) + self.wait_for_channel_state( + self.fibers[3].get_client(), self.fibers[0].get_peer_id(), "CHANNEL_READY" + ) + #查看d-a的channeloutpoint + print(f"a peer_id:{self.fibers[0].get_peer_id()}") + print(f"d peer_id:{self.fibers[3].get_peer_id()}") + channels = self.fibers[3].get_client().list_channels( + {"peer_id": self.fibers[0].get_peer_id()} + ) + print(f"d-a,channel:{channels}") #{'channels': [{'channel_id': '0xe59fc475a5e32bfd4130f5d7b73e2c77e94e40a1c4de0f4c4f7cb65a23cfa808', 'is_public': False, 'channel_outpoint': '0x7f2fc106cbc01d25e9682826ec131e67be8e9868fbb37edd6591bb7423feb21000000000', 'peer_id': 'QmT5SaY3CSSY9XvgoqJ521TXSUQ5DBZ58DTdafPKFBEcWf', 'funding_udt_type_script': None, 'state': {'state_name': 'CHANNEL_READY'}, 'local_balance': '0x174876e800', 'offered_tlc_balance': '0x0', 'remote_balance': '0x0', 'received_tlc_balance': '0x0', 'latest_commitment_transaction_hash': '0x93e75ade9b0016dbca0a56698fbf04f4a4ca5d8bc3c40fa781769e37ebb9fba2', 'created_at': '0x195d58690aa', 'enabled': True, 'tlc_expiry_delta': '0x5265c00', 'tlc_fee_proportional_millionths': '0x3e8'}, {'channel_id': '0xab117469b812d64410e1f4a6429475908f8672eda1a492772387880fd9046f07', 'is_public': False, 'channel_outpoint': '0xfe4f6fbfd2fb31ec9ca6dc7a4e43efa4e864002fae281c7c5e38fc51cd89465f00000000', 'peer_id': 'QmT5SaY3CSSY9XvgoqJ521TXSUQ5DBZ58DTdafPKFBEcWf', 'funding_udt_type_script': None, 'state': {'state_name': 'CHANNEL_READY'}, 'local_balance': '0x174876e800', 'offered_tlc_balance': '0x0', 'remote_balance': '0x0', 'received_tlc_balance': '0x0', 'latest_commitment_transaction_hash': '0xc6f38fe84030eba95376c63e76ccfa9605c07f5ca407e395ca53db609b305787', 'created_at': '0x195d2e09a4d', 'enabled': True, 'tlc_expiry_delta': '0x5265c00', 'tlc_fee_proportional_millionths': '0x3e8'}]} + da_channel_outpoint = channels["channels"][0]["channel_outpoint"] + print(f"d-a, channel_outpoint:{da_channel_outpoint}") + #b-a,怎么填d-私-a的信息 + + payment_hash = self.fibers[1].get_client().send_payment( #b + { + "target_pubkey": self.fibers[0].get_client().node_info()["node_id"], + "amount": hex(10 * 100000000), + "keysend": True, + "hop_hints": [{"pubkey": self.fibers[3].get_client().node_info()["node_id"], #填的是 d 的 pubkey,表示在 d 节点使用 channel_outpoint 到 a + "channel_outpoint": da_channel_outpoint, + "fee_rate": 1000, + "tlc_expiry_delta": 1000}] + } + ) + payment = ( + self.fibers[1].get_client().get_payment({"payment_hash": payment_hash["payment_hash"]}) + ) + print("payment", payment) + assert payment["status"] == "Created" From ad58c9ce51a0ef9d470a93f71fcac5525bcca8a2 Mon Sep 17 00:00:00 2001 From: 15168316096 <15168316096@163.com> Date: Thu, 27 Mar 2025 14:33:35 +0800 Subject: [PATCH 38/57] format --- .../fiber/devnet/send_payment/test_hophint.py | 169 +++++++++++------- 1 file changed, 106 insertions(+), 63 deletions(-) diff --git a/test_cases/fiber/devnet/send_payment/test_hophint.py b/test_cases/fiber/devnet/send_payment/test_hophint.py index 0d451053..0a7fc8f8 100644 --- a/test_cases/fiber/devnet/send_payment/test_hophint.py +++ b/test_cases/fiber/devnet/send_payment/test_hophint.py @@ -5,8 +5,7 @@ from framework.basic_fiber import FiberTest - -class TestHopHint(FiberTest): #a-b +class TestHopHint(FiberTest): # a-b # FiberTest.debug = True def test_not_hophit(self): @@ -20,8 +19,8 @@ def test_not_hophit(self): Returns: """ - self.start_new_fiber(self.generate_account(10000)) #c - self.start_new_fiber(self.generate_account(10000)) #d + self.start_new_fiber(self.generate_account(10000)) # c + self.start_new_fiber(self.generate_account(10000)) # d fiber1_balance = 1000 * 100000000 fiber1_fee = 1000 @@ -32,17 +31,19 @@ def test_not_hophit(self): "tlc_fee_proportional_millionths": hex(fiber1_fee), "public": False, } - )#a-b private channel + ) # a-b private channel time.sleep(1) self.wait_for_channel_state( self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" ) - self.open_channel(self.fibers[1], self.fibers[2], 1000 * 100000000, 1) #b-c - self.open_channel(self.fibers[2], self.fibers[3], 1000 * 100000000, 1) #c-d == b-c-d + self.open_channel(self.fibers[1], self.fibers[2], 1000 * 100000000, 1) # b-c + self.open_channel( + self.fibers[2], self.fibers[3], 1000 * 100000000, 1 + ) # c-d == b-c-d - self.fibers[3].connect_peer(self.fibers[0])#d-a + self.fibers[3].connect_peer(self.fibers[0]) # d-a time.sleep(1) - self.fibers[3].get_client().open_channel( #d -a private channel + self.fibers[3].get_client().open_channel( # d -a private channel { "peer_id": self.fibers[0].get_peer_id(), "funding_amount": hex(fiber1_balance + 62 * 100000000), @@ -60,14 +61,16 @@ def test_not_hophit(self): # # self.send_payment(self.fibers[0], self.fibers[0], 1 * 100000000)#a->a - #b-a(不通过hophit应该发送失败) + # b-a(不通过hophit应该发送失败) try: self.send_payment(self.fibers[1], self.fibers[0], 1 * 100000000) except Exception as e: error_message = str(e) - assert error_message == "Error: Send payment error: Failed to build route, PathFind error: no path found", \ - f"Unexpected error message: {error_message}" + assert ( + error_message + == "Error: Send payment error: Failed to build route, PathFind error: no path found" + ), f"Unexpected error message: {error_message}" # @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/620") def test_not_hophit_issue620(self): @@ -81,8 +84,8 @@ def test_not_hophit_issue620(self): Returns: """ - self.start_new_fiber(self.generate_account(10000)) #c - self.start_new_fiber(self.generate_account(10000)) #d + self.start_new_fiber(self.generate_account(10000)) # c + self.start_new_fiber(self.generate_account(10000)) # d fiber1_balance = 1000 * 100000000 fiber1_fee = 1000 @@ -93,17 +96,19 @@ def test_not_hophit_issue620(self): "tlc_fee_proportional_millionths": hex(fiber1_fee), "public": False, } - )#a-b private channel + ) # a-b private channel time.sleep(1) self.wait_for_channel_state( self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" ) - self.open_channel(self.fibers[1], self.fibers[2], 1000 * 100000000, 1) #b-c - self.open_channel(self.fibers[2], self.fibers[3], 1000 * 100000000, 1) #c-d == b-c-d + self.open_channel(self.fibers[1], self.fibers[2], 1000 * 100000000, 1) # b-c + self.open_channel( + self.fibers[2], self.fibers[3], 1000 * 100000000, 1 + ) # c-d == b-c-d - self.fibers[3].connect_peer(self.fibers[0])#d-a + self.fibers[3].connect_peer(self.fibers[0]) # d-a time.sleep(1) - self.fibers[3].get_client().open_channel( #d -a private channel + self.fibers[3].get_client().open_channel( # d -a private channel { "peer_id": self.fibers[0].get_peer_id(), "funding_amount": hex(fiber1_balance + 62 * 100000000), @@ -116,15 +121,16 @@ def test_not_hophit_issue620(self): self.fibers[3].get_client(), self.fibers[0].get_peer_id(), "CHANNEL_READY" ) - for i in range(1, len(self.fibers)): #b,c,d - self.send_payment(self.fibers[0], self.fibers[i], 1 * 100000000) #a->b,a->c,a>d - - self.send_payment(self.fibers[0], self.fibers[0], 1 * 100000000)#a->a + for i in range(1, len(self.fibers)): # b,c,d + self.send_payment( + self.fibers[0], self.fibers[i], 1 * 100000000 + ) # a->b,a->c,a>d - #b-a(不通过hophit应该发送失败) + self.send_payment(self.fibers[0], self.fibers[0], 1 * 100000000) # a->a - self.send_payment(self.fibers[1], self.fibers[0], 1 * 100000000)#b->a + # b-a(不通过hophit应该发送失败) + self.send_payment(self.fibers[1], self.fibers[0], 1 * 100000000) # b->a def test_use_hophit(self): """ @@ -150,7 +156,9 @@ def test_use_hophit(self): self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" ) self.open_channel(self.fibers[1], self.fibers[2], 1000 * 100000000, 1) # b-c - self.open_channel(self.fibers[2], self.fibers[3], 1000 * 100000000, 1) # c-d == b-c-d + self.open_channel( + self.fibers[2], self.fibers[3], 1000 * 100000000, 1 + ) # c-d == b-c-d self.fibers[3].connect_peer(self.fibers[0]) # d-a time.sleep(1) @@ -166,37 +174,52 @@ def test_use_hophit(self): self.wait_for_channel_state( self.fibers[3].get_client(), self.fibers[0].get_peer_id(), "CHANNEL_READY" ) - #查看d-a的channeloutpoint + # 查看d-a的channeloutpoint print(f"a peer_id:{self.fibers[0].get_peer_id()}") print(f"d peer_id:{self.fibers[3].get_peer_id()}") - channels = self.fibers[3].get_client().list_channels( - {"peer_id": self.fibers[0].get_peer_id()} + channels = ( + self.fibers[3] + .get_client() + .list_channels({"peer_id": self.fibers[0].get_peer_id()}) ) - print(f"d-a,channel:{channels}") #{'channels': [{'channel_id': '0xe59fc475a5e32bfd4130f5d7b73e2c77e94e40a1c4de0f4c4f7cb65a23cfa808', 'is_public': False, 'channel_outpoint': '0x7f2fc106cbc01d25e9682826ec131e67be8e9868fbb37edd6591bb7423feb21000000000', 'peer_id': 'QmT5SaY3CSSY9XvgoqJ521TXSUQ5DBZ58DTdafPKFBEcWf', 'funding_udt_type_script': None, 'state': {'state_name': 'CHANNEL_READY'}, 'local_balance': '0x174876e800', 'offered_tlc_balance': '0x0', 'remote_balance': '0x0', 'received_tlc_balance': '0x0', 'latest_commitment_transaction_hash': '0x93e75ade9b0016dbca0a56698fbf04f4a4ca5d8bc3c40fa781769e37ebb9fba2', 'created_at': '0x195d58690aa', 'enabled': True, 'tlc_expiry_delta': '0x5265c00', 'tlc_fee_proportional_millionths': '0x3e8'}, {'channel_id': '0xab117469b812d64410e1f4a6429475908f8672eda1a492772387880fd9046f07', 'is_public': False, 'channel_outpoint': '0xfe4f6fbfd2fb31ec9ca6dc7a4e43efa4e864002fae281c7c5e38fc51cd89465f00000000', 'peer_id': 'QmT5SaY3CSSY9XvgoqJ521TXSUQ5DBZ58DTdafPKFBEcWf', 'funding_udt_type_script': None, 'state': {'state_name': 'CHANNEL_READY'}, 'local_balance': '0x174876e800', 'offered_tlc_balance': '0x0', 'remote_balance': '0x0', 'received_tlc_balance': '0x0', 'latest_commitment_transaction_hash': '0xc6f38fe84030eba95376c63e76ccfa9605c07f5ca407e395ca53db609b305787', 'created_at': '0x195d2e09a4d', 'enabled': True, 'tlc_expiry_delta': '0x5265c00', 'tlc_fee_proportional_millionths': '0x3e8'}]} + print( + f"d-a,channel:{channels}" + ) # {'channels': [{'channel_id': '0xe59fc475a5e32bfd4130f5d7b73e2c77e94e40a1c4de0f4c4f7cb65a23cfa808', 'is_public': False, 'channel_outpoint': '0x7f2fc106cbc01d25e9682826ec131e67be8e9868fbb37edd6591bb7423feb21000000000', 'peer_id': 'QmT5SaY3CSSY9XvgoqJ521TXSUQ5DBZ58DTdafPKFBEcWf', 'funding_udt_type_script': None, 'state': {'state_name': 'CHANNEL_READY'}, 'local_balance': '0x174876e800', 'offered_tlc_balance': '0x0', 'remote_balance': '0x0', 'received_tlc_balance': '0x0', 'latest_commitment_transaction_hash': '0x93e75ade9b0016dbca0a56698fbf04f4a4ca5d8bc3c40fa781769e37ebb9fba2', 'created_at': '0x195d58690aa', 'enabled': True, 'tlc_expiry_delta': '0x5265c00', 'tlc_fee_proportional_millionths': '0x3e8'}, {'channel_id': '0xab117469b812d64410e1f4a6429475908f8672eda1a492772387880fd9046f07', 'is_public': False, 'channel_outpoint': '0xfe4f6fbfd2fb31ec9ca6dc7a4e43efa4e864002fae281c7c5e38fc51cd89465f00000000', 'peer_id': 'QmT5SaY3CSSY9XvgoqJ521TXSUQ5DBZ58DTdafPKFBEcWf', 'funding_udt_type_script': None, 'state': {'state_name': 'CHANNEL_READY'}, 'local_balance': '0x174876e800', 'offered_tlc_balance': '0x0', 'remote_balance': '0x0', 'received_tlc_balance': '0x0', 'latest_commitment_transaction_hash': '0xc6f38fe84030eba95376c63e76ccfa9605c07f5ca407e395ca53db609b305787', 'created_at': '0x195d2e09a4d', 'enabled': True, 'tlc_expiry_delta': '0x5265c00', 'tlc_fee_proportional_millionths': '0x3e8'}]} da_channel_outpoint = channels["channels"][0]["channel_outpoint"] print(f"d-a, channel_outpoint:{da_channel_outpoint}") - #b-a,怎么填d-私-a的信息 - - payment_hash = self.fibers[1].get_client().send_payment( # b - { - "target_pubkey": self.fibers[0].get_client().node_info()["node_id"], - "amount": hex(10 * 100000000), - "keysend": True, - "hop_hints": [{"pubkey": self.fibers[3].get_client().node_info()["node_id"], - # 填的是 d 的 pubkey,表示在 d 节点使用 channel_outpoint 到 a - "channel_outpoint": da_channel_outpoint, - "fee_rate": 1000, - "tlc_expiry_delta": 1000}] - } + # b-a,怎么填d-私-a的信息 + + payment_hash = ( + self.fibers[1] + .get_client() + .send_payment( # b + { + "target_pubkey": self.fibers[0].get_client().node_info()["node_id"], + "amount": hex(10 * 100000000), + "keysend": True, + "hop_hints": [ + { + "pubkey": self.fibers[3] + .get_client() + .node_info()["node_id"], + # 填的是 d 的 pubkey,表示在 d 节点使用 channel_outpoint 到 a + "channel_outpoint": da_channel_outpoint, + "fee_rate": 1000, + "tlc_expiry_delta": 1000, + } + ], + } + ) ) payment = ( - self.fibers[1].get_client().get_payment({"payment_hash": payment_hash["payment_hash"]}) + self.fibers[1] + .get_client() + .get_payment({"payment_hash": payment_hash["payment_hash"]}) ) print("payment", payment) assert payment["status"] == "Created" - def test_use_hophit_simple(self): """ b-c-d-私-a @@ -210,7 +233,9 @@ def test_use_hophit_simple(self): fiber1_fee = 1000 self.open_channel(self.fibers[1], self.fibers[2], 1000 * 100000000, 1) # b-c - self.open_channel(self.fibers[2], self.fibers[3], 1000 * 100000000, 1) # c-d == b-c-d + self.open_channel( + self.fibers[2], self.fibers[3], 1000 * 100000000, 1 + ) # c-d == b-c-d self.fibers[3].connect_peer(self.fibers[0]) # d-a time.sleep(1) @@ -226,30 +251,48 @@ def test_use_hophit_simple(self): self.wait_for_channel_state( self.fibers[3].get_client(), self.fibers[0].get_peer_id(), "CHANNEL_READY" ) - #查看d-a的channeloutpoint + # 查看d-a的channeloutpoint print(f"a peer_id:{self.fibers[0].get_peer_id()}") print(f"d peer_id:{self.fibers[3].get_peer_id()}") - channels = self.fibers[3].get_client().list_channels( - {"peer_id": self.fibers[0].get_peer_id()} + channels = ( + self.fibers[3] + .get_client() + .list_channels({"peer_id": self.fibers[0].get_peer_id()}) ) - print(f"d-a,channel:{channels}") #{'channels': [{'channel_id': '0xe59fc475a5e32bfd4130f5d7b73e2c77e94e40a1c4de0f4c4f7cb65a23cfa808', 'is_public': False, 'channel_outpoint': '0x7f2fc106cbc01d25e9682826ec131e67be8e9868fbb37edd6591bb7423feb21000000000', 'peer_id': 'QmT5SaY3CSSY9XvgoqJ521TXSUQ5DBZ58DTdafPKFBEcWf', 'funding_udt_type_script': None, 'state': {'state_name': 'CHANNEL_READY'}, 'local_balance': '0x174876e800', 'offered_tlc_balance': '0x0', 'remote_balance': '0x0', 'received_tlc_balance': '0x0', 'latest_commitment_transaction_hash': '0x93e75ade9b0016dbca0a56698fbf04f4a4ca5d8bc3c40fa781769e37ebb9fba2', 'created_at': '0x195d58690aa', 'enabled': True, 'tlc_expiry_delta': '0x5265c00', 'tlc_fee_proportional_millionths': '0x3e8'}, {'channel_id': '0xab117469b812d64410e1f4a6429475908f8672eda1a492772387880fd9046f07', 'is_public': False, 'channel_outpoint': '0xfe4f6fbfd2fb31ec9ca6dc7a4e43efa4e864002fae281c7c5e38fc51cd89465f00000000', 'peer_id': 'QmT5SaY3CSSY9XvgoqJ521TXSUQ5DBZ58DTdafPKFBEcWf', 'funding_udt_type_script': None, 'state': {'state_name': 'CHANNEL_READY'}, 'local_balance': '0x174876e800', 'offered_tlc_balance': '0x0', 'remote_balance': '0x0', 'received_tlc_balance': '0x0', 'latest_commitment_transaction_hash': '0xc6f38fe84030eba95376c63e76ccfa9605c07f5ca407e395ca53db609b305787', 'created_at': '0x195d2e09a4d', 'enabled': True, 'tlc_expiry_delta': '0x5265c00', 'tlc_fee_proportional_millionths': '0x3e8'}]} + print( + f"d-a,channel:{channels}" + ) # {'channels': [{'channel_id': '0xe59fc475a5e32bfd4130f5d7b73e2c77e94e40a1c4de0f4c4f7cb65a23cfa808', 'is_public': False, 'channel_outpoint': '0x7f2fc106cbc01d25e9682826ec131e67be8e9868fbb37edd6591bb7423feb21000000000', 'peer_id': 'QmT5SaY3CSSY9XvgoqJ521TXSUQ5DBZ58DTdafPKFBEcWf', 'funding_udt_type_script': None, 'state': {'state_name': 'CHANNEL_READY'}, 'local_balance': '0x174876e800', 'offered_tlc_balance': '0x0', 'remote_balance': '0x0', 'received_tlc_balance': '0x0', 'latest_commitment_transaction_hash': '0x93e75ade9b0016dbca0a56698fbf04f4a4ca5d8bc3c40fa781769e37ebb9fba2', 'created_at': '0x195d58690aa', 'enabled': True, 'tlc_expiry_delta': '0x5265c00', 'tlc_fee_proportional_millionths': '0x3e8'}, {'channel_id': '0xab117469b812d64410e1f4a6429475908f8672eda1a492772387880fd9046f07', 'is_public': False, 'channel_outpoint': '0xfe4f6fbfd2fb31ec9ca6dc7a4e43efa4e864002fae281c7c5e38fc51cd89465f00000000', 'peer_id': 'QmT5SaY3CSSY9XvgoqJ521TXSUQ5DBZ58DTdafPKFBEcWf', 'funding_udt_type_script': None, 'state': {'state_name': 'CHANNEL_READY'}, 'local_balance': '0x174876e800', 'offered_tlc_balance': '0x0', 'remote_balance': '0x0', 'received_tlc_balance': '0x0', 'latest_commitment_transaction_hash': '0xc6f38fe84030eba95376c63e76ccfa9605c07f5ca407e395ca53db609b305787', 'created_at': '0x195d2e09a4d', 'enabled': True, 'tlc_expiry_delta': '0x5265c00', 'tlc_fee_proportional_millionths': '0x3e8'}]} da_channel_outpoint = channels["channels"][0]["channel_outpoint"] print(f"d-a, channel_outpoint:{da_channel_outpoint}") - #b-a,怎么填d-私-a的信息 - - payment_hash = self.fibers[1].get_client().send_payment( #b - { - "target_pubkey": self.fibers[0].get_client().node_info()["node_id"], - "amount": hex(10 * 100000000), - "keysend": True, - "hop_hints": [{"pubkey": self.fibers[3].get_client().node_info()["node_id"], #填的是 d 的 pubkey,表示在 d 节点使用 channel_outpoint 到 a - "channel_outpoint": da_channel_outpoint, - "fee_rate": 1000, - "tlc_expiry_delta": 1000}] - } + # b-a,怎么填d-私-a的信息 + + payment_hash = ( + self.fibers[1] + .get_client() + .send_payment( # b + { + "target_pubkey": self.fibers[0].get_client().node_info()["node_id"], + "amount": hex(10 * 100000000), + "keysend": True, + "hop_hints": [ + { + "pubkey": self.fibers[3] + .get_client() + .node_info()[ + "node_id" + ], # 填的是 d 的 pubkey,表示在 d 节点使用 channel_outpoint 到 a + "channel_outpoint": da_channel_outpoint, + "fee_rate": 1000, + "tlc_expiry_delta": 1000, + } + ], + } + ) ) payment = ( - self.fibers[1].get_client().get_payment({"payment_hash": payment_hash["payment_hash"]}) + self.fibers[1] + .get_client() + .get_payment({"payment_hash": payment_hash["payment_hash"]}) ) print("payment", payment) assert payment["status"] == "Created" From b941f58bfa58158f90a00e5e14c825bcc22bec3b Mon Sep 17 00:00:00 2001 From: 15168316096 <15168316096@163.com> Date: Thu, 27 Mar 2025 15:00:56 +0800 Subject: [PATCH 39/57] add b-c-d,d-a private no use hint --- .../fiber/devnet/send_payment/test_hophint.py | 54 +++++++++++++++---- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/test_cases/fiber/devnet/send_payment/test_hophint.py b/test_cases/fiber/devnet/send_payment/test_hophint.py index 0a7fc8f8..a1c2bc1b 100644 --- a/test_cases/fiber/devnet/send_payment/test_hophint.py +++ b/test_cases/fiber/devnet/send_payment/test_hophint.py @@ -8,14 +8,53 @@ class TestHopHint(FiberTest): # a-b # FiberTest.debug = True + def test_not_hophit_simple(self): + """ + b-c-d-私-a + 1.b-a(不通过hophit应该发送失败) + Returns: + + """ + self.start_new_fiber(self.generate_account(10000)) # c + self.start_new_fiber(self.generate_account(10000)) # d + + fiber1_balance = 1000 * 100000000 + fiber1_fee = 1000 + self.open_channel(self.fibers[1], self.fibers[2], 1000 * 100000000, 1) # b-c + self.open_channel( + self.fibers[2], self.fibers[3], 1000 * 100000000, 1 + ) # c-d == b-c-d + + self.fibers[3].connect_peer(self.fibers[0]) # d-a + time.sleep(1) + self.fibers[3].get_client().open_channel( # d -a private channel + { + "peer_id": self.fibers[0].get_peer_id(), + "funding_amount": hex(fiber1_balance + 62 * 100000000), + "tlc_fee_proportional_millionths": hex(fiber1_fee), + "public": False, + } + ) + time.sleep(1) + self.wait_for_channel_state( + self.fibers[3].get_client(), self.fibers[0].get_peer_id(), "CHANNEL_READY" + ) + + # b-a(不通过hophit应该发送失败) + + try: + self.send_payment(self.fibers[1], self.fibers[0], 1 * 100000000) + except Exception as e: + error_message = str(e) + assert ( + error_message + == "Error: Send payment error: Failed to build route, PathFind error: no path found" + ), f"Unexpected error message: {error_message}" + def test_not_hophit(self): """ a-私-b-c-d-私-a - 1. a->b - 2. a->c - 3. a->d - 4. a->a - 5. b-a(不通过hophit应该发送失败) + 1. b-a(不通过hophit应该发送失败) Returns: """ @@ -56,11 +95,6 @@ def test_not_hophit(self): self.fibers[3].get_client(), self.fibers[0].get_peer_id(), "CHANNEL_READY" ) - # for i in range(1, len(self.fibers)): #b,c,d - # self.send_payment(self.fibers[0], self.fibers[i], 1 * 100000000) #a->b,a->c,a>d - # - # self.send_payment(self.fibers[0], self.fibers[0], 1 * 100000000)#a->a - # b-a(不通过hophit应该发送失败) try: From 38e2bb740921277e30f9160e93c01fd4fafe385b Mon Sep 17 00:00:00 2001 From: 15168316096 <15168316096@163.com> Date: Thu, 27 Mar 2025 16:50:13 +0800 Subject: [PATCH 40/57] add assert for route a-b --- .../fiber/devnet/send_payment/test_hophint.py | 58 ++++++++++++++++--- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/test_cases/fiber/devnet/send_payment/test_hophint.py b/test_cases/fiber/devnet/send_payment/test_hophint.py index a1c2bc1b..93402162 100644 --- a/test_cases/fiber/devnet/send_payment/test_hophint.py +++ b/test_cases/fiber/devnet/send_payment/test_hophint.py @@ -54,7 +54,9 @@ def test_not_hophit_simple(self): def test_not_hophit(self): """ a-私-b-c-d-私-a - 1. b-a(不通过hophit应该发送失败) + 路径选择 + 1. b-a,预期是能成功 + 2. 如果走的是b-c-d-a,则需要走hint才可以成功,预期是失败(大概率走这个路径) Returns: """ @@ -95,11 +97,22 @@ def test_not_hophit(self): self.fibers[3].get_client(), self.fibers[0].get_peer_id(), "CHANNEL_READY" ) - # b-a(不通过hophit应该发送失败) - try: - self.send_payment(self.fibers[1], self.fibers[0], 1 * 100000000) + #走b-a会成功 + payment = ( + self.fibers[1] + .get_client() + .send_payment( # b + { + "target_pubkey": self.fibers[0].get_client().node_info()["node_id"], + "amount": hex(10 * 100000000), + "keysend": True, + } + )) + print(f"debug payment content:{payment}") + except Exception as e: + #如果走的是b-c-d-a,不通过hophit应该发送失败 error_message = str(e) assert ( error_message @@ -114,7 +127,9 @@ def test_not_hophit_issue620(self): 2. a->c 3. a->d 4. a->a - 5. b-a(不通过hophit应该发送失败) + 5. 路径选择 + 5.1. b-a,预期是能成功(大概率走这个) + 5.2. 如果走的是b-c-d-a,则需要走hint才可以成功,预期是失败 Returns: """ @@ -163,8 +178,37 @@ def test_not_hophit_issue620(self): self.send_payment(self.fibers[0], self.fibers[0], 1 * 100000000) # a->a # b-a(不通过hophit应该发送失败) - - self.send_payment(self.fibers[1], self.fibers[0], 1 * 100000000) # b->a + try: + payment = ( + self.fibers[1] + .get_client() + .send_payment( # b + { + "target_pubkey": self.fibers[0].get_client().node_info()["node_id"], + "amount": hex(1 * 100000000), + "keysend": True, + } + )) + print(f"debug payment content:{payment}") + + channels = ( + self.fibers[1] + .get_client() + .list_channels({"peer_id": self.fibers[0].get_peer_id()}) + ) + print( + f"b-a,channel:{channels}" + ) + ba_channel_outpoint = channels["channels"][0]["channel_outpoint"] + print(f"b-a, channel_outpoint:{ba_channel_outpoint}") + assert payment["router"]["nodes"][0]["channel_outpoint"] == ba_channel_outpoint + except Exception as e: + # 如果走的是b-c-d-a,不通过hophit应该发送失败 + error_message = str(e) + assert ( + error_message + == "Error: Send payment error: Failed to build route, PathFind error: no path found" + ), f"Unexpected error message: {error_message}" def test_use_hophit(self): """ From ce78c3cc9f071e9651282456cd02c9f4288ae318 Mon Sep 17 00:00:00 2001 From: 15168316096 <15168316096@163.com> Date: Thu, 27 Mar 2025 16:51:00 +0800 Subject: [PATCH 41/57] add assert for route a-b --- .../fiber/devnet/send_payment/test_hophint.py | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/test_cases/fiber/devnet/send_payment/test_hophint.py b/test_cases/fiber/devnet/send_payment/test_hophint.py index 93402162..c16af94a 100644 --- a/test_cases/fiber/devnet/send_payment/test_hophint.py +++ b/test_cases/fiber/devnet/send_payment/test_hophint.py @@ -98,21 +98,24 @@ def test_not_hophit(self): ) try: - #走b-a会成功 + # 走b-a会成功 payment = ( self.fibers[1] - .get_client() - .send_payment( # b + .get_client() + .send_payment( # b { - "target_pubkey": self.fibers[0].get_client().node_info()["node_id"], + "target_pubkey": self.fibers[0] + .get_client() + .node_info()["node_id"], "amount": hex(10 * 100000000), "keysend": True, } - )) + ) + ) print(f"debug payment content:{payment}") except Exception as e: - #如果走的是b-c-d-a,不通过hophit应该发送失败 + # 如果走的是b-c-d-a,不通过hophit应该发送失败 error_message = str(e) assert ( error_message @@ -181,33 +184,36 @@ def test_not_hophit_issue620(self): try: payment = ( self.fibers[1] - .get_client() - .send_payment( # b + .get_client() + .send_payment( # b { - "target_pubkey": self.fibers[0].get_client().node_info()["node_id"], + "target_pubkey": self.fibers[0] + .get_client() + .node_info()["node_id"], "amount": hex(1 * 100000000), "keysend": True, } - )) + ) + ) print(f"debug payment content:{payment}") channels = ( self.fibers[1] - .get_client() - .list_channels({"peer_id": self.fibers[0].get_peer_id()}) - ) - print( - f"b-a,channel:{channels}" + .get_client() + .list_channels({"peer_id": self.fibers[0].get_peer_id()}) ) + print(f"b-a,channel:{channels}") ba_channel_outpoint = channels["channels"][0]["channel_outpoint"] print(f"b-a, channel_outpoint:{ba_channel_outpoint}") - assert payment["router"]["nodes"][0]["channel_outpoint"] == ba_channel_outpoint + assert ( + payment["router"]["nodes"][0]["channel_outpoint"] == ba_channel_outpoint + ) except Exception as e: # 如果走的是b-c-d-a,不通过hophit应该发送失败 error_message = str(e) assert ( - error_message - == "Error: Send payment error: Failed to build route, PathFind error: no path found" + error_message + == "Error: Send payment error: Failed to build route, PathFind error: no path found" ), f"Unexpected error message: {error_message}" def test_use_hophit(self): From 8eb5baa36f8bf197ee31bf9aa607b2fc05b0bb48 Mon Sep 17 00:00:00 2001 From: 15168316096 <15168316096@163.com> Date: Thu, 27 Mar 2025 17:32:27 +0800 Subject: [PATCH 42/57] add desc --- test_cases/fiber/devnet/send_payment/test_hophint.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test_cases/fiber/devnet/send_payment/test_hophint.py b/test_cases/fiber/devnet/send_payment/test_hophint.py index c16af94a..c278d93d 100644 --- a/test_cases/fiber/devnet/send_payment/test_hophint.py +++ b/test_cases/fiber/devnet/send_payment/test_hophint.py @@ -129,7 +129,7 @@ def test_not_hophit_issue620(self): 1. a->b 2. a->c 3. a->d - 4. a->a + 4. a->a(不支持,不能自己转给自己) 5. 路径选择 5.1. b-a,预期是能成功(大概率走这个) 5.2. 如果走的是b-c-d-a,则需要走hint才可以成功,预期是失败 @@ -177,8 +177,9 @@ def test_not_hophit_issue620(self): self.send_payment( self.fibers[0], self.fibers[i], 1 * 100000000 ) # a->b,a->c,a>d - - self.send_payment(self.fibers[0], self.fibers[0], 1 * 100000000) # a->a + print(f"debug:a-a,route") + # a->a是不支持的,除非a-私-b-c-d-私-a这么走一圈,异常捕捉在方法里面 + self.send_payment(self.fibers[0], self.fibers[0], 1 * 100000000) # b-a(不通过hophit应该发送失败) try: From 4343cb2e3b0328042676db23e4a25b817ed17286 Mon Sep 17 00:00:00 2001 From: gpBlockchain <744158715@qq.com> Date: Fri, 28 Mar 2025 16:52:49 +0800 Subject: [PATCH 43/57] update rpc --- framework/rpc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/rpc.py b/framework/rpc.py index 17e3d168..b14daaa1 100644 --- a/framework/rpc.py +++ b/framework/rpc.py @@ -99,9 +99,9 @@ def get_consensus(self): def get_fee_rate_statics(self, target=None): return self.call("get_fee_rate_statics", [target]) - def generate_epochs(self, epoch): + def generate_epochs(self, epoch, wait_time=2): response = self.call("generate_epochs", [epoch]) - time.sleep(2) + time.sleep(wait_time) return response def generate_block(self): From 509cca349c99b193b7bd76f9bcb8948b709a77f3 Mon Sep 17 00:00:00 2001 From: 15168316096 <15168316096@163.com> Date: Wed, 9 Apr 2025 10:24:22 +0800 Subject: [PATCH 44/57] add build_router and send_payment_with_router rpc --- framework/basic_fiber.py | 1 + framework/fiber_rpc.py | 8 +- .../devnet/build_router/test_build_router.py | 98 +++++ .../test_send_payment_with_router.py | 410 ++++++++++++++++++ 4 files changed, 516 insertions(+), 1 deletion(-) create mode 100644 test_cases/fiber/devnet/build_router/test_build_router.py create mode 100644 test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py diff --git a/framework/basic_fiber.py b/framework/basic_fiber.py index 9dd2f65a..3c9c63f0 100644 --- a/framework/basic_fiber.py +++ b/framework/basic_fiber.py @@ -372,6 +372,7 @@ def send_payment(self, fiber1, fiber2, amount, wait=True, udt=None, try_count=5) "udt_type_script": udt, } ) + print(f"===debug route:{payment}") if wait: self.wait_payment_state(fiber1, payment["payment_hash"], "Success") return payment["payment_hash"] diff --git a/framework/fiber_rpc.py b/framework/fiber_rpc.py index b3986b1a..cd0701c2 100644 --- a/framework/fiber_rpc.py +++ b/framework/fiber_rpc.py @@ -16,6 +16,12 @@ def __init__(self, url): def send_btc(self, btc_pay_req): return self.call("send_btc", [btc_pay_req]) + def build_router(self, param): + return self.call("build_router", [param]) + + def send_payment_with_router(self, param): + return self.call("send_payment_with_router", [param]) + def abandon_channel(self, param): return self.call("abandon_channel", [param]) @@ -210,7 +216,7 @@ def call(self, method, params): url=self.url, data=json.dumps(data, indent=4) ) ) - for i in range(100): + for i in range(200): try: response = requests.post( self.url, data=json.dumps(data), headers=headers diff --git a/test_cases/fiber/devnet/build_router/test_build_router.py b/test_cases/fiber/devnet/build_router/test_build_router.py new file mode 100644 index 00000000..ef299140 --- /dev/null +++ b/test_cases/fiber/devnet/build_router/test_build_router.py @@ -0,0 +1,98 @@ +import time +from framework.basic_fiber import FiberTest + + +class TestBuildRouter(FiberTest): + """ + 测试 build_router RPC 的功能: + 1. 基本路由构建 + - 测试指定 amount 和不指定 amount 的情况 + - 测试指定和不指定 final_tlc_expiry_delta 的情况 + + 2. 通道指定测试 + - 测试指定 channel_outpoint 的情况 + - 测试不指定 channel_outpoint 的情况(让算法自动选择通道) + - 测试指定的 channel_outpoint 无效的情况 + + 3. 路径验证 + - 测试所有节点都存在的有效路径 + - 测试节点不存在的无效路径 + - 测试节点存在但无可用通道的情况 + + 4. 特殊情况 + - 测试空的 hops_info + - 测试只有一个 hop 的情况 + - 测试包含重复节点的情况 + + 5. UDT 支付路由(可选) + - 测试指定 udt 支付的情况 + - 测试 ckb 支付的情况 + """ + + def test_base_build_router(self): + """ + b-c-d-私-a网络 + 1. d-a建立了路由关系,查看构建的路由返回信息 + """ + self.start_new_fiber(self.generate_account(10000)) + self.start_new_fiber(self.generate_account(10000)) + + fiber1_balance = 1000 * 100000000 + fiber1_fee = 1000 + + self.open_channel(self.fibers[1], self.fibers[2], 1000 * 100000000, 1) # b-c + self.open_channel( + self.fibers[2], self.fibers[3], 1000 * 100000000, 1 + ) # c-d == b-c-d + + self.fibers[3].connect_peer(self.fibers[0]) # d-a + time.sleep(1) + self.fibers[3].get_client().open_channel( # d -a private channel + { + "peer_id": self.fibers[0].get_peer_id(), + "funding_amount": hex(fiber1_balance + 62 * 100000000), + "tlc_fee_proportional_millionths": hex(fiber1_fee), + "public": False, + } + ) + time.sleep(1) + self.wait_for_channel_state( + self.fibers[3].get_client(), self.fibers[0].get_peer_id(), "CHANNEL_READY" + ) + # 查看d-a的channeloutpoint + print(f"a peer_id:{self.fibers[0].get_peer_id()}") + print(f"d peer_id:{self.fibers[3].get_peer_id()}") + channels = ( + self.fibers[3] + .get_client() + .list_channels({"peer_id": self.fibers[0].get_peer_id()}) + ) + print(f"d-a,channel:{channels}") + da_channel_outpoint = channels["channels"][0]["channel_outpoint"] + print(f"d-a, channel_outpoint:{da_channel_outpoint}") + + router_hops = ( + self.fibers[3] + .get_client() + .build_router( + { + "amount": hex(1 + 62 * 100000000), + "udt_type_script": None, + "hops_info": [ + { + "pubkey": self.fibers[0] + .get_client() + .node_info()["node_id"], + "channel_outpoint": da_channel_outpoint, + }, + ], + "final_tlc_expiry_delta": None, + } + ) + ) + print(f"router_hops:{router_hops}") + hop = router_hops["router_hops"][0] + print(f"hop:{hop}") + assert hop["channel_outpoint"] == da_channel_outpoint + assert hop["target"] == self.fibers[0].get_client().node_info()["node_id"] + assert hop["amount_received"] == hex(1 + 62 * 100000000) diff --git a/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py b/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py new file mode 100644 index 00000000..b80b10da --- /dev/null +++ b/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py @@ -0,0 +1,410 @@ +import time +from framework.basic_fiber import FiberTest +import pytest + + +class TestSendPaymentWithRouter(FiberTest): + """ + 测试 send_payment_with_router RPC 的功能: + 1. 基本支付功能 + - 使用指定路由发送支付 + - 验证支付状态和路由信息 + + 2. 路由指定测试 + - 使用 build_router 构建的路由进行支付 + - 手动指定完整路由进行支付 + - 测试无效路由的情况 + + 3. 支付选项测试 + - 测试指定 payment_hash 的情况 + - 测试使用 invoice 的情况 + - 测试 keysend 支付 + - 测试带自定义记录的支付 + + 4. 特殊情况测试 + - 使用 dry_run 模式测试支付可行性 + - 测试 UDT 支付 + - 测试支付失败的错误处理 + + 5. 路由追踪 + - 验证支付历史中的路由信息 + - 验证每个节点的金额和通道信息 + """ + + def test_base_send_payment_with_router(self): + """ + b-c-d-私-a网络 + 1. d-a建立了路由关系,查看构建的路由返回信息 + 2. d call a ,route info: d-a channel outpoint + 3. b call a ,走route info: b-c-d-私-a网络(检查应该不支持自动拼接完整的路由) + """ + self.start_new_fiber(self.generate_account(10000)) + self.start_new_fiber(self.generate_account(10000)) + + fiber1_balance = 1000 * 100000000 + fiber1_fee = 1000 + + self.open_channel(self.fibers[1], self.fibers[2], 1000 * 100000000, 1) # b-c + self.open_channel( + self.fibers[2], self.fibers[3], 1000 * 100000000, 1 + ) # c-d == b-c-d + + self.fibers[3].connect_peer(self.fibers[0]) # d-a + time.sleep(1) + self.fibers[3].get_client().open_channel( # d -a private channel + { + "peer_id": self.fibers[0].get_peer_id(), + "funding_amount": hex(fiber1_balance + 62 * 100000000), + "tlc_fee_proportional_millionths": hex(fiber1_fee), + "public": False, + } + ) + time.sleep(1) + self.wait_for_channel_state( + self.fibers[3].get_client(), self.fibers[0].get_peer_id(), "CHANNEL_READY" + ) + # 查看d-a的channeloutpoint,预期能调用成功 + print(f"a peer_id:{self.fibers[0].get_peer_id()}") + print(f"d peer_id:{self.fibers[3].get_peer_id()}") + channels = ( + self.fibers[3] + .get_client() + .list_channels({"peer_id": self.fibers[0].get_peer_id()}) + ) + print(f"d-a,channel:{channels}") + da_channel_outpoint = channels["channels"][0]["channel_outpoint"] + print(f"d-a, channel_outpoint:{da_channel_outpoint}") + + router_hops = ( + self.fibers[3] + .get_client() + .build_router( + { + "amount": hex(1 + 62 * 100000000), + "udt_type_script": None, + "hops_info": [ + { + "pubkey": self.fibers[0] + .get_client() + .node_info()["node_id"], + "channel_outpoint": da_channel_outpoint, + }, + ], + "final_tlc_expiry_delta": None, + } + ) + ) + print(f"router_hops:{router_hops}") + hop = router_hops["router_hops"][0] + print(f"hop:{hop}") + assert hop["channel_outpoint"] == da_channel_outpoint + assert hop["target"] == self.fibers[0].get_client().node_info()["node_id"] + assert hop["amount_received"] == hex(1 + 62 * 100000000) + + # d call a ,route info: d-a channel outpoint + payment = ( + self.fibers[3] + .get_client() + .send_payment_with_router( + { + "payment_hash": None, + "invoice": None, + "keysend": True, + "custom_records": None, + "dry_run": False, + "udt_type_script": None, + "router": router_hops["router_hops"], + } + ) + ) + print(f"payment:{payment}") + assert payment["status"] == "Created" + + # b call a ,走route info: b-c-d-私-a网络(检查应该不支持自动拼接完整的路由) + payment = ( + self.fibers[1] + .get_client() + .send_payment_with_router( + { + "payment_hash": None, + "invoice": None, + "keysend": True, + "custom_records": None, + "dry_run": False, + "udt_type_script": None, + "router": router_hops["router_hops"], + } + ) + ) + print(f"payment:{payment}") + payment = ( + self.fibers[1] + .get_client() + .get_payment({"payment_hash": payment["payment_hash"]}) + ) + print("payment", payment) + # assert payment["status"] == "Created" 预期应该不支持,待解决 + + @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/641") + def test_auto_send_payment_with_router(self): + """ + b-c-d-私-a网络 + 1. d-a建立了路由关系,查看构建的路由返回信息 + 2. b call a ,走route info: b-c-d-私-a网络(检查应该不支持自动拼接完整的路由) + """ + self.start_new_fiber(self.generate_account(10000)) + self.start_new_fiber(self.generate_account(10000)) + + fiber1_balance = 1000 * 100000000 + fiber1_fee = 1000 + + self.open_channel(self.fibers[1], self.fibers[2], 1000 * 100000000, 1) # b-c + self.open_channel( + self.fibers[2], self.fibers[3], 1000 * 100000000, 1 + ) # c-d == b-c-d + + self.fibers[3].connect_peer(self.fibers[0]) # d-a + time.sleep(1) + self.fibers[3].get_client().open_channel( # d -a private channel + { + "peer_id": self.fibers[0].get_peer_id(), + "funding_amount": hex(fiber1_balance + 62 * 100000000), + "tlc_fee_proportional_millionths": hex(fiber1_fee), + "public": False, + } + ) + time.sleep(1) + self.wait_for_channel_state( + self.fibers[3].get_client(), self.fibers[0].get_peer_id(), "CHANNEL_READY" + ) + # 查看d-a的channeloutpoint,预期能调用成功 + print(f"a peer_id:{self.fibers[0].get_peer_id()}") + print(f"d peer_id:{self.fibers[3].get_peer_id()}") + channels = ( + self.fibers[3] + .get_client() + .list_channels({"peer_id": self.fibers[0].get_peer_id()}) + ) + print(f"d-a,channel:{channels}") + da_channel_outpoint = channels["channels"][0]["channel_outpoint"] + print(f"d-a, channel_outpoint:{da_channel_outpoint}") + + router_hops = ( + self.fibers[3] + .get_client() + .build_router( + { + "amount": hex(1 + 62 * 100000000), + "udt_type_script": None, + "hops_info": [ + { + "pubkey": self.fibers[0] + .get_client() + .node_info()["node_id"], + "channel_outpoint": da_channel_outpoint, + }, + ], + "final_tlc_expiry_delta": None, + } + ) + ) + print(f"router_hops:{router_hops}") + hop = router_hops["router_hops"][0] + print(f"hop:{hop}") + assert hop["channel_outpoint"] == da_channel_outpoint + assert hop["target"] == self.fibers[0].get_client().node_info()["node_id"] + assert hop["amount_received"] == hex(1 + 62 * 100000000) + + # b call a ,走route info: b-c-d-私-a网络(检查应该不支持自动拼接完整的路由) + payment = ( + self.fibers[1] + .get_client() + .send_payment_with_router( + { + "payment_hash": None, + "invoice": None, + "keysend": True, + "custom_records": None, + "dry_run": False, + "udt_type_script": None, + "router": router_hops["router_hops"], + } + ) + ) + print(f"payment:{payment}") + payment = ( + self.fibers[1] + .get_client() + .get_payment({"payment_hash": payment["payment_hash"]}) + ) + print("payment", payment) + # assert payment["status"] == "Created" 预期应该不支持,待解决 + + def test_loop_send_payment_with_router(self): + """ + b-c-d-私-a网络-b网络(网络成环状) + 1. b call b成环,走route info:b-c-d-a-b + """ + self.start_new_fiber(self.generate_account(10000)) + self.start_new_fiber(self.generate_account(10000)) + + fiber1_balance = 1000 * 100000000 + fiber1_fee = 1000 + self.open_channel(self.fibers[0], self.fibers[1], 1000 * 100000000, 1) # a-b + self.open_channel(self.fibers[1], self.fibers[2], 1000 * 100000000, 1) # b-c + self.open_channel( + self.fibers[2], self.fibers[3], 1000 * 100000000, 1 + ) # c-d == b-c-d + + self.fibers[3].connect_peer(self.fibers[0]) # d-a + time.sleep(1) + self.fibers[3].get_client().open_channel( # d -a private channel + { + "peer_id": self.fibers[0].get_peer_id(), + "funding_amount": hex(fiber1_balance + 62 * 100000000), + "tlc_fee_proportional_millionths": hex(fiber1_fee), + "public": False, + } + ) + time.sleep(1) + self.wait_for_channel_state( + self.fibers[3].get_client(), self.fibers[0].get_peer_id(), "CHANNEL_READY" + ) + + bc_channel_outpoint = self.get_channel_outpoint(self.fibers[1], self.fibers[2]) + print(f"b-c, channel_outpoint:{bc_channel_outpoint}") + cd_channel_outpoint = self.get_channel_outpoint(self.fibers[2], self.fibers[3]) + print(f"c-d, channel_outpoint:{cd_channel_outpoint}") + da_channel_outpoint = self.get_channel_outpoint(self.fibers[3], self.fibers[0]) + print(f"d-a, channel_outpoint:{da_channel_outpoint}") + ab_channel_outpoint = self.get_channel_outpoint(self.fibers[0], self.fibers[1]) + print(f"a-b, channel_outpoint:{ab_channel_outpoint}") + + bc_router_hops = ( + self.fibers[1] + .get_client() + .build_router( + { + "amount": hex(1 + 62 * 100000000), + "udt_type_script": None, + "hops_info": [ + { + "pubkey": self.fibers[2] + .get_client() + .node_info()["node_id"], + "channel_outpoint": bc_channel_outpoint, + }, + ], + "final_tlc_expiry_delta": None, + } + ) + ) + + cd_router_hops = ( + self.fibers[2] + .get_client() + .build_router( + { + "amount": hex(1 + 62 * 100000000), + "udt_type_script": None, + "hops_info": [ + { + "pubkey": self.fibers[3] + .get_client() + .node_info()["node_id"], + "channel_outpoint": cd_channel_outpoint, + }, + ], + "final_tlc_expiry_delta": None, + } + ) + ) + + da_router_hops = ( + self.fibers[3] + .get_client() + .build_router( + { + "amount": hex(1 + 62 * 100000000), + "udt_type_script": None, + "hops_info": [ + { + "pubkey": self.fibers[0] + .get_client() + .node_info()["node_id"], + "channel_outpoint": da_channel_outpoint, + }, + ], + "final_tlc_expiry_delta": None, + } + ) + ) + + ab_router_hops = ( + self.fibers[0] + .get_client() + .build_router( + { + "amount": hex(1 + 62 * 100000000), + "udt_type_script": None, + "hops_info": [ + { + "pubkey": self.fibers[1] + .get_client() + .node_info()["node_id"], + "channel_outpoint": ab_channel_outpoint, + }, + ], + "final_tlc_expiry_delta": None, + } + ) + ) + + # # b call b ,route info:b-c,c-d,d-a,a-b的route + payment = ( + self.fibers[1] + .get_client() + .send_payment_with_router( + { + "payment_hash": None, + "invoice": None, + "keysend": True, + "custom_records": None, + "dry_run": False, + "udt_type_script": None, + "router": [ + bc_router_hops["router_hops"][0], + cd_router_hops["router_hops"][0], + da_router_hops["router_hops"][0], + ab_router_hops["router_hops"][0], + ], + } + ) + ) + print(f"payment:{payment}") + payment = ( + self.fibers[1] + .get_client() + .get_payment({"payment_hash": payment["payment_hash"]}) + ) + print("payment", payment) + + def get_channel_outpoint(self, from_fiber, to_fiber): + """ + 获取两个节点之间的通道outpoint + Args: + from_fiber: 起始节点 + to_fiber: 目标节点 + + Returns: + channel_outpoint: 通道outpoint + """ + channels = from_fiber.get_client().list_channels( + {"peer_id": to_fiber.get_peer_id()} + ) + print(f"{from_fiber.get_peer_id()}-{to_fiber.get_peer_id()},channel:{channels}") + channel_outpoint = channels["channels"][0]["channel_outpoint"] + print( + f"{from_fiber.get_peer_id()}-{to_fiber.get_peer_id()}, channel_outpoint:{channel_outpoint}" + ) + return channel_outpoint From 5ebd1cfccfb218037ea5d69594a36ac99f40bedb Mon Sep 17 00:00:00 2001 From: 15168316096 <15168316096@163.com> Date: Wed, 9 Apr 2025 10:26:04 +0800 Subject: [PATCH 45/57] add build_router and send_payment_with_router rpc --- .../test_send_payment_with_router.py | 25 ------------------- 1 file changed, 25 deletions(-) diff --git a/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py b/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py index b80b10da..c4563455 100644 --- a/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py +++ b/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py @@ -120,31 +120,6 @@ def test_base_send_payment_with_router(self): print(f"payment:{payment}") assert payment["status"] == "Created" - # b call a ,走route info: b-c-d-私-a网络(检查应该不支持自动拼接完整的路由) - payment = ( - self.fibers[1] - .get_client() - .send_payment_with_router( - { - "payment_hash": None, - "invoice": None, - "keysend": True, - "custom_records": None, - "dry_run": False, - "udt_type_script": None, - "router": router_hops["router_hops"], - } - ) - ) - print(f"payment:{payment}") - payment = ( - self.fibers[1] - .get_client() - .get_payment({"payment_hash": payment["payment_hash"]}) - ) - print("payment", payment) - # assert payment["status"] == "Created" 预期应该不支持,待解决 - @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/641") def test_auto_send_payment_with_router(self): """ From c7713daa0e8a27bd4eac114c0507a55f10cae5b8 Mon Sep 17 00:00:00 2001 From: 15168316096 <15168316096@163.com> Date: Wed, 9 Apr 2025 16:19:05 +0800 Subject: [PATCH 46/57] add assert for payment status build_router and send_payment_with_router rpc --- .../test_send_payment_with_router.py | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py b/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py index c4563455..32fe52c9 100644 --- a/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py +++ b/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py @@ -30,13 +30,13 @@ class TestSendPaymentWithRouter(FiberTest): - 验证支付历史中的路由信息 - 验证每个节点的金额和通道信息 """ + FiberTest.debug = True def test_base_send_payment_with_router(self): """ b-c-d-私-a网络 1. d-a建立了路由关系,查看构建的路由返回信息 2. d call a ,route info: d-a channel outpoint - 3. b call a ,走route info: b-c-d-私-a网络(检查应该不支持自动拼接完整的路由) """ self.start_new_fiber(self.generate_account(10000)) self.start_new_fiber(self.generate_account(10000)) @@ -119,8 +119,9 @@ def test_base_send_payment_with_router(self): ) print(f"payment:{payment}") assert payment["status"] == "Created" + self.wait_payment_state(self.fibers[3], payment["payment_hash"], "Success") - @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/641") + # @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/641") def test_auto_send_payment_with_router(self): """ b-c-d-私-a网络 @@ -207,13 +208,8 @@ def test_auto_send_payment_with_router(self): ) ) print(f"payment:{payment}") - payment = ( - self.fibers[1] - .get_client() - .get_payment({"payment_hash": payment["payment_hash"]}) - ) - print("payment", payment) - # assert payment["status"] == "Created" 预期应该不支持,待解决 + assert payment["status"] == "Created" + self.wait_payment_state(self.fibers[3], payment["payment_hash"], "Success") def test_loop_send_payment_with_router(self): """ @@ -357,12 +353,8 @@ def test_loop_send_payment_with_router(self): ) ) print(f"payment:{payment}") - payment = ( - self.fibers[1] - .get_client() - .get_payment({"payment_hash": payment["payment_hash"]}) - ) - print("payment", payment) + assert payment["status"] == "Created" + self.wait_payment_state(self.fibers[3], payment["payment_hash"], "Success") def get_channel_outpoint(self, from_fiber, to_fiber): """ From 597485b0e985ffd69da48a8b4c81d9f5328f7cb6 Mon Sep 17 00:00:00 2001 From: 15168316096 <15168316096@163.com> Date: Wed, 9 Apr 2025 16:20:07 +0800 Subject: [PATCH 47/57] del annotation for debug --- .../send_payment_with_router/test_send_payment_with_router.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py b/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py index 32fe52c9..79193b69 100644 --- a/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py +++ b/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py @@ -30,7 +30,8 @@ class TestSendPaymentWithRouter(FiberTest): - 验证支付历史中的路由信息 - 验证每个节点的金额和通道信息 """ - FiberTest.debug = True + + # FiberTest.debug = True def test_base_send_payment_with_router(self): """ From df8bab57994a3c98fdce14eebcca779b5bd33ce7 Mon Sep 17 00:00:00 2001 From: 15168316096 <15168316096@163.com> Date: Wed, 9 Apr 2025 16:21:32 +0800 Subject: [PATCH 48/57] modify node call --- .../send_payment_with_router/test_send_payment_with_router.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py b/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py index 79193b69..0aab7cb4 100644 --- a/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py +++ b/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py @@ -210,7 +210,7 @@ def test_auto_send_payment_with_router(self): ) print(f"payment:{payment}") assert payment["status"] == "Created" - self.wait_payment_state(self.fibers[3], payment["payment_hash"], "Success") + self.wait_payment_state(self.fibers[1], payment["payment_hash"], "Success") def test_loop_send_payment_with_router(self): """ @@ -355,7 +355,7 @@ def test_loop_send_payment_with_router(self): ) print(f"payment:{payment}") assert payment["status"] == "Created" - self.wait_payment_state(self.fibers[3], payment["payment_hash"], "Success") + self.wait_payment_state(self.fibers[1], payment["payment_hash"], "Success") def get_channel_outpoint(self, from_fiber, to_fiber): """ From 7c9fb4deaa76659e82b885d9e49bf3c16b800033 Mon Sep 17 00:00:00 2001 From: sunchengzhu Date: Wed, 9 Apr 2025 16:53:22 +0800 Subject: [PATCH 49/57] Use the correct tar.gz name for arm64 --- download_ckb_light_client.py | 2 +- download_fiber.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/download_ckb_light_client.py b/download_ckb_light_client.py index 90fc152f..f5876a86 100644 --- a/download_ckb_light_client.py +++ b/download_ckb_light_client.py @@ -36,7 +36,7 @@ }, "arm64": { "url": "https://github.com/nervosnetwork/fiber/releases/download/v{version}/fnn_v{" - "version}-x86_64-darwin.tar.gz", + "version}-x86_64-darwin-portable.tar.gz", "ext": ".tar.gz", }, }, diff --git a/download_fiber.py b/download_fiber.py index f29abf0f..84581865 100644 --- a/download_fiber.py +++ b/download_fiber.py @@ -42,7 +42,7 @@ }, "arm64": { "url": "https://github.com/nervosnetwork/fiber/releases/download/v{version}/fnn_v{" - "version}-x86_64-darwin.tar.gz", + "version}-x86_64-darwin-portable.tar.gz", "ext": ".tar.gz", }, }, From 899eb9ca59da61146ceea7d9fa4ea423526fd8a1 Mon Sep 17 00:00:00 2001 From: 15168316096 <15168316096@163.com> Date: Wed, 9 Apr 2025 17:20:19 +0800 Subject: [PATCH 50/57] add Assert and fix issue --- framework/basic_fiber.py | 4 +- .../test_send_payment_with_router.py | 37 ++++++++++--------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/framework/basic_fiber.py b/framework/basic_fiber.py index 3c9c63f0..c0f25054 100644 --- a/framework/basic_fiber.py +++ b/framework/basic_fiber.py @@ -400,14 +400,14 @@ def get_account_script(self, account_private_key): "args": account1["lock_arg"], } - def wait_payment_state(self, client, payment_hash, status="Success", timeout=120): + def wait_payment_state(self, client, payment_hash, status="Success", timeout=360): for i in range(timeout): result = client.get_client().get_payment({"payment_hash": payment_hash}) if result["status"] == status: return time.sleep(1) raise TimeoutError( - f"status did not reach state {expected_state} within timeout period." + f"status did not reach state: {status} within timeout period." ) def wait_payment_finished(self, client, payment_hash, timeout=120): diff --git a/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py b/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py index 0aab7cb4..6dae8da6 100644 --- a/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py +++ b/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py @@ -31,7 +31,7 @@ class TestSendPaymentWithRouter(FiberTest): - 验证每个节点的金额和通道信息 """ - # FiberTest.debug = True + FiberTest.debug = True def test_base_send_payment_with_router(self): """ @@ -193,24 +193,25 @@ def test_auto_send_payment_with_router(self): assert hop["amount_received"] == hex(1 + 62 * 100000000) # b call a ,走route info: b-c-d-私-a网络(检查应该不支持自动拼接完整的路由) - payment = ( - self.fibers[1] - .get_client() - .send_payment_with_router( - { - "payment_hash": None, - "invoice": None, - "keysend": True, - "custom_records": None, - "dry_run": False, - "udt_type_script": None, - "router": router_hops["router_hops"], - } + try: + payment = ( + self.fibers[1] + .get_client() + .send_payment_with_router( + { + "payment_hash": None, + "invoice": None, + "keysend": True, + "custom_records": None, + "dry_run": False, + "udt_type_script": None, + "router": router_hops["router_hops"], + } + ) ) - ) - print(f"payment:{payment}") - assert payment["status"] == "Created" - self.wait_payment_state(self.fibers[1], payment["payment_hash"], "Success") + except Exception as e: + error_message = str(e) + assert "Error: Send payment first hop error: Failed to send onion packet with error UnknownNextPeer" in error_message, f"预期错误信息不匹配,实际错误: {error_message}" def test_loop_send_payment_with_router(self): """ From cf37701bcc7468d168c7130a0a49a49bb789fd0f Mon Sep 17 00:00:00 2001 From: 15168316096 <15168316096@163.com> Date: Wed, 9 Apr 2025 17:27:44 +0800 Subject: [PATCH 51/57] format code --- .../test_send_payment_with_router.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py b/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py index 6dae8da6..13d179ab 100644 --- a/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py +++ b/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py @@ -211,7 +211,10 @@ def test_auto_send_payment_with_router(self): ) except Exception as e: error_message = str(e) - assert "Error: Send payment first hop error: Failed to send onion packet with error UnknownNextPeer" in error_message, f"预期错误信息不匹配,实际错误: {error_message}" + assert ( + "Error: Send payment first hop error: Failed to send onion packet with error UnknownNextPeer" + in error_message + ), f"预期错误信息不匹配,实际错误: {error_message}" def test_loop_send_payment_with_router(self): """ From 550dbfdbdfc9f6191b12e835e9337e6b83ebd1e9 Mon Sep 17 00:00:00 2001 From: 15168316096 <15168316096@163.com> Date: Wed, 9 Apr 2025 20:04:45 +0800 Subject: [PATCH 52/57] update test_loop_send_payment_with_router case --- .../test_send_payment_with_router.py | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py b/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py index 13d179ab..d148946d 100644 --- a/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py +++ b/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py @@ -261,7 +261,7 @@ def test_loop_send_payment_with_router(self): .get_client() .build_router( { - "amount": hex(1 + 62 * 100000000), + "amount": hex(4 * (1 + 62 * 100000000)), "udt_type_script": None, "hops_info": [ { @@ -281,7 +281,7 @@ def test_loop_send_payment_with_router(self): .get_client() .build_router( { - "amount": hex(1 + 62 * 100000000), + "amount": hex(3 * (1 + 62 * 100000000)), "udt_type_script": None, "hops_info": [ { @@ -301,7 +301,7 @@ def test_loop_send_payment_with_router(self): .get_client() .build_router( { - "amount": hex(1 + 62 * 100000000), + "amount": hex(2 * (1 + 62 * 100000000)), "udt_type_script": None, "hops_info": [ { @@ -321,7 +321,7 @@ def test_loop_send_payment_with_router(self): .get_client() .build_router( { - "amount": hex(1 + 62 * 100000000), + "amount": hex(1 * (1 + 62 * 100000000)), "udt_type_script": None, "hops_info": [ { @@ -336,7 +336,24 @@ def test_loop_send_payment_with_router(self): ) ) - # # b call b ,route info:b-c,c-d,d-a,a-b的route + + # 获取各个路由跳的基本信息 + bc_hop = bc_router_hops["router_hops"][0] + cd_hop = cd_router_hops["router_hops"][0] + da_hop = da_router_hops["router_hops"][0] + ab_hop = ab_router_hops["router_hops"][0] + + # 修改每一跳的 incoming_tlc_expiry 值,依次增加 172800000 + base_expiry = 86400000 # 基础过期时间 + delta = 172800000 # 每一跳增加的差值 + + # 从最后一跳开始,依次增加过期时间 + ab_hop["incoming_tlc_expiry"] = hex(base_expiry) # 0x5265c00 + da_hop["incoming_tlc_expiry"] = hex(base_expiry + delta) # 0xa4cb800 + cd_hop["incoming_tlc_expiry"] = hex(base_expiry + 2 * delta) # 0xf731400 + bc_hop["incoming_tlc_expiry"] = hex(base_expiry + 3 * delta) # 0x14997000 + + # b call b ,route info:b-c,c-d,d-a,a-b的route payment = ( self.fibers[1] .get_client() @@ -349,14 +366,15 @@ def test_loop_send_payment_with_router(self): "dry_run": False, "udt_type_script": None, "router": [ - bc_router_hops["router_hops"][0], - cd_router_hops["router_hops"][0], - da_router_hops["router_hops"][0], - ab_router_hops["router_hops"][0], + bc_hop, + cd_hop, + da_hop, + ab_hop, ], } ) ) + print(f"payment:{payment}") assert payment["status"] == "Created" self.wait_payment_state(self.fibers[1], payment["payment_hash"], "Success") From 8a4c42f71930436b2c649d78dbde90ab2b3c209a Mon Sep 17 00:00:00 2001 From: 15168316096 <15168316096@163.com> Date: Thu, 10 Apr 2025 13:58:13 +0800 Subject: [PATCH 53/57] format code --- .../test_send_payment_with_router.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py b/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py index d148946d..aa374d27 100644 --- a/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py +++ b/test_cases/fiber/devnet/send_payment_with_router/test_send_payment_with_router.py @@ -336,23 +336,22 @@ def test_loop_send_payment_with_router(self): ) ) - # 获取各个路由跳的基本信息 bc_hop = bc_router_hops["router_hops"][0] cd_hop = cd_router_hops["router_hops"][0] da_hop = da_router_hops["router_hops"][0] ab_hop = ab_router_hops["router_hops"][0] - + # 修改每一跳的 incoming_tlc_expiry 值,依次增加 172800000 base_expiry = 86400000 # 基础过期时间 delta = 172800000 # 每一跳增加的差值 - + # 从最后一跳开始,依次增加过期时间 ab_hop["incoming_tlc_expiry"] = hex(base_expiry) # 0x5265c00 da_hop["incoming_tlc_expiry"] = hex(base_expiry + delta) # 0xa4cb800 cd_hop["incoming_tlc_expiry"] = hex(base_expiry + 2 * delta) # 0xf731400 bc_hop["incoming_tlc_expiry"] = hex(base_expiry + 3 * delta) # 0x14997000 - + # b call b ,route info:b-c,c-d,d-a,a-b的route payment = ( self.fibers[1] From 909a89f5bbf1dbb4591e41990eb0014ed9617a23 Mon Sep 17 00:00:00 2001 From: gpBlockchain <32102187+gpBlockchain@users.noreply.github.com> Date: Fri, 25 Apr 2025 14:48:31 +0800 Subject: [PATCH 54/57] Gp/testnet1 (#90) * 050 * update debug yml * update debug * fix bug for run premission * update fiber * fix shutdown failed * update test * fix ci occue failed * update testnet --- .github/workflows/fiber.yml | 338 +- Makefile | 16 +- framework/basic_fiber.py | 47 +- framework/fiber_rpc.py | 3 + framework/helper/miner.py | 14 +- framework/helper/udt_contract.py | 6 +- framework/test_fiber.py | 6 +- prepare.sh | 2 +- source/contract/fiber/commitment-lock | Bin 63624 -> 74000 bytes source/contract/fiber/funding-lock | Bin 30352 -> 50320 bytes source/template/fiber/dev_config.yml.j2 | 3 +- source/template/fiber/dev_config_2.yml.j2 | 44 + source/template/fiber/testnet_config_2.yml.j2 | 71 + ...ndonChannel.py => test_abandon_channel.py} | 47 +- .../test_max_tlc_number_in_flight.py | 69 + .../test_max_tlc_value_in_flight.py | 42 + .../accept_channel/test_tlc_expiry_delta.py | 13 + .../test_tlc_fee_proportional_millionths.py | 41 + .../accept_channel/test_tlc_min_value.py | 41 + .../cancel_invoice/test_cancel_invoice.py | 4 +- .../graph_channels/test_graph_channels.py | 30 +- .../fiber/devnet/issue/test_force_stop.py | 0 .../fiber/devnet/issue/test_issue_640.py | 0 .../list_channels/test_list_channels.py | 29 +- .../devnet/list_peers/test_list_peers.py | 19 + .../fiber/devnet/node_info/test_node_info.py | 12 +- .../test_commitment_delay_epoch.py | 6 - .../open_channel/test_commitment_fee_rate.py | 33 +- .../open_channel/test_funding_amount.py | 13 +- .../open_channel/test_funding_fee_rate.py | 5 + .../test_funding_udt_type_script.py | 34 +- .../fiber/devnet/open_channel/test_linked.py | 4 +- .../test_max_tlc_number_in_flight.py | 7 +- .../test_max_tlc_value_in_flight.py | 237 +- .../fiber/devnet/open_channel/test_public.py | 43 +- .../open_channel/test_shutdown_script.py | 32 +- .../open_channel/test_tlc_expiry_delta.py | 73 + .../test_tlc_fee_proportional_millionths.py | 86 +- .../test_tlc_locktime_expiry_delta.py | 125 - .../devnet/open_channel/test_tlc_min_value.py | 147 +- .../devnet/remove_tlc/test_remove_tlc.py | 0 .../{ => module}/test_send_payment.py | 0 .../test_send_payment_with_shutdown.py | 0 .../test_send_payment_with_update_channel.py | 0 .../{ => offline}/test_force_restart.py | 0 .../{ => offline}/test_restart.py | 0 .../offline/test_send_payment_with_stop.py | 27 + .../{ => offline}/test_stop_mid_node.py | 0 .../{ => params}/test_allow_self_payment.py | 1 - .../send_payment/{ => params}/test_amount.py | 0 .../{ => params}/test_custom_records.py | 0 .../send_payment/{ => params}/test_dry_run.py | 0 .../test_final_tlc_expiry_delta.py | 0 .../send_payment/{ => params}/test_hophint.py | 12 +- .../params/test_max_fee_amount.py | 85 + .../{ => params}/test_max_parts.py | 0 .../{ => params}/test_payment_hash.py | 3 +- .../send_payment/params/test_timeout.py | 40 + .../{ => params}/test_tlc_expiry_limit.py | 0 .../send_payment/{ => params}/test_tlc_fee.py | 0 .../{ => params}/test_used_preimage.py | 0 .../send_payment/{ => path}/test_find_path.py | 0 .../{ => path}/test_long_router.py | 0 .../{ => path}/test_mutil_channel.py | 0 .../{ => path}/test_private_channel.py | 2 +- .../send_payment/test_max_fee_amount.py | 52 - .../shutdown_channel/test_channel_id.py | 8 +- .../shutdown_channel/test_close_script.py | 47 +- .../devnet/shutdown_channel/test_force.py | 189 +- .../shutdown_channel/test_node_state.py | 27 +- .../update_channel/test_chain_status.py | 43 +- .../devnet/update_channel/test_enabled.py | 5 +- .../update_channel/test_tlc_expiry_delta.py | 75 +- .../test_tlc_fee_proportional_millionths.py | 4 +- .../update_channel/test_tlc_maximum_value.py | 13 - .../update_channel/test_tlc_minimum_value.py | 76 +- .../update_channel/test_update_channel.py | 5 - .../devnet/watch_tower/test_htlc_expired.py | 151 + .../devnet/watch_tower/test_htlc_mutil.py | 0 .../devnet/watch_tower/test_mutil_shutdown.py | 12 - .../devnet/watch_tower/test_revert_tx.py | 12 - .../devnet/watch_tower/test_watch_tower.py | 84 - .../watch_tower/test_watch_tower_udt.py | 84 - .../fiber/devnet/watch_tower/test_with_tx.py | 12 - .../watch_tower_wit_tlc/test_pending_tlc.py | 3015 +++++++++++++++++ .../fiber/testnet/test_fiber_local_node.py | 351 ++ 86 files changed, 5077 insertions(+), 1100 deletions(-) mode change 100755 => 100644 source/contract/fiber/commitment-lock mode change 100755 => 100644 source/contract/fiber/funding-lock create mode 100644 source/template/fiber/dev_config_2.yml.j2 create mode 100644 source/template/fiber/testnet_config_2.yml.j2 rename test_cases/fiber/devnet/abandon_channel/{TestAbandonChannel.py => test_abandon_channel.py} (78%) create mode 100644 test_cases/fiber/devnet/accept_channel/test_max_tlc_number_in_flight.py create mode 100644 test_cases/fiber/devnet/accept_channel/test_max_tlc_value_in_flight.py create mode 100644 test_cases/fiber/devnet/accept_channel/test_tlc_expiry_delta.py create mode 100644 test_cases/fiber/devnet/accept_channel/test_tlc_fee_proportional_millionths.py create mode 100644 test_cases/fiber/devnet/accept_channel/test_tlc_min_value.py create mode 100644 test_cases/fiber/devnet/issue/test_force_stop.py create mode 100644 test_cases/fiber/devnet/issue/test_issue_640.py create mode 100644 test_cases/fiber/devnet/list_peers/test_list_peers.py create mode 100644 test_cases/fiber/devnet/open_channel/test_tlc_expiry_delta.py delete mode 100644 test_cases/fiber/devnet/open_channel/test_tlc_locktime_expiry_delta.py create mode 100644 test_cases/fiber/devnet/remove_tlc/test_remove_tlc.py rename test_cases/fiber/devnet/send_payment/{ => module}/test_send_payment.py (100%) rename test_cases/fiber/devnet/send_payment/{ => module}/test_send_payment_with_shutdown.py (100%) rename test_cases/fiber/devnet/send_payment/{ => module}/test_send_payment_with_update_channel.py (100%) rename test_cases/fiber/devnet/send_payment/{ => offline}/test_force_restart.py (100%) rename test_cases/fiber/devnet/send_payment/{ => offline}/test_restart.py (100%) create mode 100644 test_cases/fiber/devnet/send_payment/offline/test_send_payment_with_stop.py rename test_cases/fiber/devnet/send_payment/{ => offline}/test_stop_mid_node.py (100%) rename test_cases/fiber/devnet/send_payment/{ => params}/test_allow_self_payment.py (99%) rename test_cases/fiber/devnet/send_payment/{ => params}/test_amount.py (100%) rename test_cases/fiber/devnet/send_payment/{ => params}/test_custom_records.py (100%) rename test_cases/fiber/devnet/send_payment/{ => params}/test_dry_run.py (100%) rename test_cases/fiber/devnet/send_payment/{ => params}/test_final_tlc_expiry_delta.py (100%) rename test_cases/fiber/devnet/send_payment/{ => params}/test_hophint.py (98%) create mode 100644 test_cases/fiber/devnet/send_payment/params/test_max_fee_amount.py rename test_cases/fiber/devnet/send_payment/{ => params}/test_max_parts.py (100%) rename test_cases/fiber/devnet/send_payment/{ => params}/test_payment_hash.py (98%) create mode 100644 test_cases/fiber/devnet/send_payment/params/test_timeout.py rename test_cases/fiber/devnet/send_payment/{ => params}/test_tlc_expiry_limit.py (100%) rename test_cases/fiber/devnet/send_payment/{ => params}/test_tlc_fee.py (100%) rename test_cases/fiber/devnet/send_payment/{ => params}/test_used_preimage.py (100%) rename test_cases/fiber/devnet/send_payment/{ => path}/test_find_path.py (100%) rename test_cases/fiber/devnet/send_payment/{ => path}/test_long_router.py (100%) rename test_cases/fiber/devnet/send_payment/{ => path}/test_mutil_channel.py (100%) rename test_cases/fiber/devnet/send_payment/{ => path}/test_private_channel.py (98%) delete mode 100644 test_cases/fiber/devnet/send_payment/test_max_fee_amount.py delete mode 100644 test_cases/fiber/devnet/update_channel/test_tlc_maximum_value.py create mode 100644 test_cases/fiber/devnet/watch_tower/test_htlc_expired.py create mode 100644 test_cases/fiber/devnet/watch_tower/test_htlc_mutil.py create mode 100644 test_cases/fiber/devnet/watch_tower_wit_tlc/test_pending_tlc.py create mode 100644 test_cases/fiber/testnet/test_fiber_local_node.py diff --git a/.github/workflows/fiber.yml b/.github/workflows/fiber.yml index 9fe7fc41..1a0e1e99 100644 --- a/.github/workflows/fiber.yml +++ b/.github/workflows/fiber.yml @@ -2,46 +2,332 @@ # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python name: fiber test - on: push: - branches: [ "fiber" ] + branches: ["fiber"] pull_request: - branches: [ "fiber" ] + branches: ["fiber"] permissions: contents: read jobs: - build: + prepare: + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + + - name: Run make prepare + run: make prepare + + - name: Tar backup + run: | + tar -czf prepare-backup.tar.gz \ + download \ + source/ckb-cli \ + source/ckb-cli-old + + - name: Backup data + uses: actions/upload-artifact@v4 + with: + name: prepare-backup-${{ runner.os }} + path: prepare-backup.tar.gz + + fiber_test_open_channel: + needs: prepare + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + + - name: Download prepare backup + uses: actions/download-artifact@v4 + with: + name: prepare-backup-${{ runner.os }} + path: ./ + + - name: Extract tarball and restore permissions + run: | + tar -xzf prepare-backup.tar.gz + + - name: Run fiber_test_demo + run: make fiber_test_demo FIBER_TEST_DEMO=test_cases/fiber/devnet/open_channel + + - name: Publish reports + if: failure() + uses: actions/upload-artifact@v4 + with: + name: jfoa-open_channel-reports-${{ runner.os }} + path: ./report + + fiber_test_accept_channel_cancel_invoice_connect_peer_disconnect_peer: + needs: prepare + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + + - name: Download prepare backup + uses: actions/download-artifact@v4 + with: + name: prepare-backup-${{ runner.os }} + path: ./ + + - name: Extract tarball and restore permissions + run: | + tar -xzf prepare-backup.tar.gz + + - name: Run fiber_test_demo + run: make fiber_test_demo FIBER_TEST_DEMO="test_cases/fiber/devnet/accept_channel test_cases/fiber/devnet/cancel_invoice test_cases/fiber/devnet/connect_peer test_cases/fiber/devnet/disconnect_peer" + + - name: Publish reports + if: failure() + uses: actions/upload-artifact@v4 + with: + name: jfoa-accept_channel_to_disconnect_peer-reports-${{ runner.os }} + path: ./report + + fiber_test_get_invoice_graph_channels_graph_nodes_list_channels_new_invoice: + needs: prepare + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + + - name: Download prepare backup + uses: actions/download-artifact@v4 + with: + name: prepare-backup-${{ runner.os }} + path: ./ + + - name: Extract tarball and restore permissions + run: | + tar -xzf prepare-backup.tar.gz + + - name: Run fiber_test_demo + run: make fiber_test_demo FIBER_TEST_DEMO="test_cases/fiber/devnet/get_invoice test_cases/fiber/devnet/graph_channels test_cases/fiber/devnet/graph_nodes test_cases/fiber/devnet/list_channels test_cases/fiber/devnet/new_invoice" + + - name: Publish reports + if: failure() + uses: actions/upload-artifact@v4 + with: + name: jfoa-accept_channel_to_disconnect_peer-reports-${{ runner.os }} + path: ./report + + fiber_test_send_payment_module_offline: + needs: prepare + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + + - name: Download prepare backup + uses: actions/download-artifact@v4 + with: + name: prepare-backup-${{ runner.os }} + path: ./ + + - name: Extract tarball and restore permissions + run: | + tar -xzf prepare-backup.tar.gz + + - name: Run fiber_test_demo + run: make fiber_test_demo FIBER_TEST_DEMO="test_cases/fiber/devnet/send_payment/module test_cases/fiber/devnet/send_payment/offline" + + - name: Publish reports + if: failure() + uses: actions/upload-artifact@v4 + with: + name: jfoa-send_payment-module-offline-reports-${{ runner.os }} + path: ./report + + fiber_test_send_payment_params_path: + needs: prepare + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + + - name: Download prepare backup + uses: actions/download-artifact@v4 + with: + name: prepare-backup-${{ runner.os }} + path: ./ + + - name: Extract tarball and restore permissions + run: | + tar -xzf prepare-backup.tar.gz + + - name: Run fiber_test_demo + run: make fiber_test_demo FIBER_TEST_DEMO="test_cases/fiber/devnet/send_payment/params test_cases/fiber/devnet/send_payment/path" + + - name: Publish reports + if: failure() + uses: actions/upload-artifact@v4 + with: + name: jfoa-send_payment-params-path-reports-${{ runner.os }} + path: ./report + + fiber_test_shutdown_channel_update_channel_issue: + needs: prepare runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 - - name: Set up Python 3.10 - uses: actions/setup-python@v3 - with: - python-version: "3.10" + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + + - name: Download prepare backup + uses: actions/download-artifact@v4 + with: + name: prepare-backup-${{ runner.os }} + path: ./ + + - name: Extract tarball and restore permissions + run: | + tar -xzf prepare-backup.tar.gz + + - name: Run fiber_test_demo + run: make fiber_test_demo FIBER_TEST_DEMO="test_cases/fiber/devnet/shutdown_channel test_cases/fiber/devnet/update_channel test_cases/fiber/devnet/issue" + + - name: Publish reports + if: failure() + uses: actions/upload-artifact@v4 + with: + name: jfoa-send_payment-reports-${{ runner.os }} + path: ./report + + + fiber_test_watch_tower: + needs: prepare + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + + - name: Download prepare backup + uses: actions/download-artifact@v4 + with: + name: prepare-backup-${{ runner.os }} + path: ./ + + - name: Extract tarball and restore permissions + run: | + tar -xzf prepare-backup.tar.gz + + - name: Run fiber_test_demo + run: make fiber_test_demo FIBER_TEST_DEMO="test_cases/fiber/devnet/watch_tower" + - name: Publish reports + if: failure() + uses: actions/upload-artifact@v4 + with: + name: jfoa-watch_tower-reports-${{ runner.os }} + path: ./report + + + fiber_test_watch_tower_tlc: + needs: prepare + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v3 + with: + python-version: "3.10" - - name: Install dependencies - run: make prepare + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Run tests - run: make fiber_test + - name: Download prepare backup + uses: actions/download-artifact@v4 + with: + name: prepare-backup-${{ runner.os }} + path: ./ -# - name: Setup upterm session -# if: always() -# uses: lhotari/action-upterm@v1 + - name: Extract tarball and restore permissions + run: | + tar -xzf prepare-backup.tar.gz - - name: Publish reports - if: failure() - uses: actions/upload-artifact@v4 - with: - name: jfoa-build-reports-${{ runner.os }} - path: ./report + - name: Run fiber_test_demo + run: make fiber_test_demo FIBER_TEST_DEMO="test_cases/fiber/devnet/watch_tower_wit_tlc" + - name: Publish reports + if: failure() + uses: actions/upload-artifact@v4 + with: + name: jfoa-watch_tower-reports-${{ runner.os }} + path: ./report diff --git a/Makefile b/Makefile index 0fe8ada3..50447be8 100644 --- a/Makefile +++ b/Makefile @@ -89,7 +89,6 @@ fiber_test_cases := \ test_cases/fiber/devnet/shutdown_channel \ test_cases/fiber/devnet/update_channel \ test_cases/fiber/devnet/issue \ - test_cases/fiber/devnet/compatibility \ test_cases/fiber/devnet/watch_tower fiber_testnet_cases := \ @@ -139,6 +138,21 @@ fiber_test: exit 1; \ fi +fiber_test_demo: + @failed_cases=; \ + echo "Running tests for $$FIBER_TEST_DEMO"; \ + for test_case in $(FIBER_TEST_DEMO); do \ + echo "Running tests for $$test_case"; \ + if ! bash test.sh "$$test_case"; then \ + echo "$$test_case" >> failed_test_cases.txt; \ + fi \ + done; \ + if [ -s failed_test_cases.txt ]; then \ + echo "Some test cases failed: $$(cat failed_test_cases.txt)"; \ + rm -f failed_test_cases.txt; \ + exit 1; \ + fi + develop_test: @failed_cases=; \ for test_case in $(TestCases); do \ diff --git a/framework/basic_fiber.py b/framework/basic_fiber.py index c0f25054..745e6298 100644 --- a/framework/basic_fiber.py +++ b/framework/basic_fiber.py @@ -299,6 +299,7 @@ def open_channel( fiber2_balance, fiber1_fee=1000, fiber2_fee=1000, + udt=None, ): fiber1.connect_peer(fiber2) time.sleep(1) @@ -335,6 +336,7 @@ def open_channel( "funding_amount": hex(fiber1_balance + fiber2_balance + 62 * 100000000), "tlc_fee_proportional_millionths": hex(fiber1_fee), "public": True, + "funding_udt_type_script": udt, } ) self.wait_for_channel_state( @@ -348,7 +350,7 @@ def open_channel( # "keysend": True, # } # ) - self.send_payment(fiber1, fiber2, fiber2_balance, True, None, 10) + self.send_payment(fiber1, fiber2, fiber2_balance, True, udt, 10) fiber2.get_client().update_channel( { "channel_id": channels["channels"][0]["channel_id"], @@ -372,7 +374,6 @@ def send_payment(self, fiber1, fiber2, amount, wait=True, udt=None, try_count=5) "udt_type_script": udt, } ) - print(f"===debug route:{payment}") if wait: self.wait_payment_state(fiber1, payment["payment_hash"], "Success") return payment["payment_hash"] @@ -429,22 +430,31 @@ def get_fibers_balance_message(self): def get_fiber_balance(self, fiber): channels = fiber.get_client().list_channels({}) + balance_map = {} + channels_balance = 0 channels_offered_tlc_balance = 0 channels_received_tlc_balance = 0 for i in range(len(channels["channels"])): channel = channels["channels"][i] if channel["state"]["state_name"] == "CHANNEL_READY": - channels_balance += int(channel["local_balance"], 16) - channels_offered_tlc_balance += int(channel["offered_tlc_balance"], 16) - channels_received_tlc_balance += int( + key = "ckb" + if channel["funding_udt_type_script"] is not None: + key = channel["funding_udt_type_script"]["args"] + if balance_map.get(key) is None: + balance_map[key] = { + "local_balance": 0, + "offered_tlc_balance": 0, + "received_tlc_balance": 0, + } + balance_map[key]["local_balance"] += int(channel["local_balance"], 16) + balance_map[key]["offered_tlc_balance"] += int( + channel["offered_tlc_balance"], 16 + ) + balance_map[key]["received_tlc_balance"] += int( channel["received_tlc_balance"], 16 ) - return { - "local_balance": channels_balance, - "offered_tlc_balance": channels_offered_tlc_balance, - "received_tlc_balance": channels_received_tlc_balance, - } + return balance_map def calculate_tx_fee(self, balance, fee_list): """ @@ -477,19 +487,22 @@ def wait_tx_pool(self, pending_size, try_size=100): f"status did not reach state {expected_state} within timeout period." ) - def wait_and_check_tx_pool_fee(self, fee_rate, check=True, try_size=120): + def wait_and_check_tx_pool_fee( + self, fee_rate, check=True, try_size=120, up_and_down_rate=0.1 + ): self.wait_tx_pool(1, try_size) pool = self.node.getClient().get_raw_tx_pool() pool_tx_detail_info = self.node.getClient().get_pool_tx_detail_info( pool["pending"][0] ) if check: - assert ( - int(pool_tx_detail_info["score_sortkey"]["fee"], 16) - * 1000 - / int(pool_tx_detail_info["score_sortkey"]["weight"], 16) - == fee_rate - ) + assert int(pool_tx_detail_info["score_sortkey"]["fee"], 16) * 1000 / int( + pool_tx_detail_info["score_sortkey"]["weight"], 16 + ) <= fee_rate * (1 + up_and_down_rate) + + assert int(pool_tx_detail_info["score_sortkey"]["fee"], 16) * 1000 / int( + pool_tx_detail_info["score_sortkey"]["weight"], 16 + ) >= fee_rate * (1 - up_and_down_rate) return pool["pending"][0] def wait_invoice_state( diff --git a/framework/fiber_rpc.py b/framework/fiber_rpc.py index cd0701c2..c56d3d37 100644 --- a/framework/fiber_rpc.py +++ b/framework/fiber_rpc.py @@ -208,6 +208,9 @@ def remove_watch_channel(self, param): def get_peer_id(self): return self.node_info()["addresses"][0].split("/")[-1] + def list_peers(self): + return self.call("list_peers", []) + def call(self, method, params): headers = {"content-type": "application/json"} data = {"id": 42, "jsonrpc": "2.0", "method": method, "params": params} diff --git a/framework/helper/miner.py b/framework/helper/miner.py index 3de02d35..6b27d7d3 100644 --- a/framework/helper/miner.py +++ b/framework/helper/miner.py @@ -55,10 +55,16 @@ def miner_until_tx_committed(node, tx_hash, with_unknown=False): # support > 0x0 when ckb2023 active def miner_with_version(node, version): # get_block_template - block = node.getClient().get_block_template() - node.getClient().submit_block( - block["work_id"], block_template_transfer_to_submit_block(block, version) - ) + for i in range(10): + try: + block = node.getClient().get_block_template() + node.getClient().submit_block( + block["work_id"], + block_template_transfer_to_submit_block(block, version), + ) + break + except Exception as e: + time.sleep(1) pool = node.getClient().tx_pool_info() header = node.getClient().get_tip_header() print( diff --git a/framework/helper/udt_contract.py b/framework/helper/udt_contract.py index d8cdb89a..223e9ab8 100644 --- a/framework/helper/udt_contract.py +++ b/framework/helper/udt_contract.py @@ -57,7 +57,11 @@ def transfer(cls, own_arg, amount) -> (str, str): return ckb_hash_script(own_arg), to_big_uint128_le_compatible(amount) def balance(self, client, own_arg, query_arg): - pass + cells = self.list_cell(client, own_arg, query_arg) + balance = 0 + for cell in cells: + balance += cell["balance"] + return balance def list_cell(self, client, own_arg, query_arg): code_hash = get_ckb_contract_codehash( diff --git a/framework/test_fiber.py b/framework/test_fiber.py index 297d64c5..7b54d0ef 100644 --- a/framework/test_fiber.py +++ b/framework/test_fiber.py @@ -18,12 +18,12 @@ class FiberConfigPath(Enum): CURRENT_DEV = ( - "/source/template/fiber/dev_config.yml.j2", + "/source/template/fiber/dev_config_2.yml.j2", "download/fiber/current/fnn", ) CURRENT_TESTNET = ( - "/source/template/fiber/config.yml.j2", - "download/fiber/0.4.2/fnn", + "/source/template/fiber/testnet_config_2.yml.j2", + "download/fiber/0.5.0/fnn", ) V042_TESTNET = ("/source/template/fiber/config.yml.j2", "download/fiber/0.4.2/fnn") diff --git a/prepare.sh b/prepare.sh index b2e1f166..4d1c4bea 100644 --- a/prepare.sh +++ b/prepare.sh @@ -3,7 +3,7 @@ cp download/0.117.0/ckb-cli ./source/ckb-cli cp download/0.110.2/ckb-cli ./source/ckb-cli-old git clone https://github.com/nervosnetwork/fiber cd fiber -git checkout v0.4.2 +git checkout v0.5.0-rc1 cargo build cp target/debug/fnn ../download/fiber/current/fnn cd migrate diff --git a/source/contract/fiber/commitment-lock b/source/contract/fiber/commitment-lock old mode 100755 new mode 100644 index 3bc39aed558ba2a19e2c4a9cc93010cb5754ce86..6d264b8b1ef9a0403b924b4c08cf48067428cbe1 GIT binary patch delta 29188 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 z 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 delta 20407 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 Tya~UAAGk#vnc&LOvPu69NUX8J diff --git a/source/contract/fiber/funding-lock b/source/contract/fiber/funding-lock old mode 100755 new mode 100644 index c139a68202a0e868fa84dc8f1ae9b499f38f5b2c..22067027d28c49dc9985493852acdacfe677e35b GIT binary patch literal 50320 zcmcG14P2B}*7%)|XBY?#8a~vl9T;Jelz;&Q*IHnC#-w!1)Hd5{hKC2m4^(6@-7LPI z5l~DUKGbS=L}76%Ute3`h1H% z>*@2|pZRo9x*Z9G-#xz}&o6#Uo(}pddTk{slFW>ar>B zCih>=FNI&ZfaeIm$f#&-Ol;h=_yj{52aKIHoqp(i9-;nTA4?>>^$L0Jqvg@+U7__x z!!M!uImTsdcJ_*F(ux(v9QBF}b=nGJR{HAc>YT;AdKsUk&Rnhb_%81Ej?FhJf4yF~ z{8>*@Vng%YU$5h$uP<9ve*2rhr$1xN;$K+Fb2)svdiC1XY98}cgYbPaqOU(ygjt_^D|f zXa6+Qe&7ttmO=#yqlPhUF~{AUFRwup;}6ip#QpBJH6qkU8# z+`sq->w@Q-;qbsw;P(RDV+48($MeW=J}khnorlBE0Bqa{@Gk%sGzxs;2=M;_81|px z{96HrX*-?qZFoD+0?kPM7Y^+R0Nil;nE)TDpCSQ{ zeQh}XqW}*X0sbVw?;inP25=m6hx2(A;COBr4&O!LbVec$Faq6QfNmr^zXfpI{$G4t zW*AoIxP~Qq#0vz$&vW9x;J5I^<6}6ybQE|Yz_I?r>0bw!FZ}-E`e&`kdOl|{Orq(l z4H^7kOO$t!b}R;&_y^CsxW9(O!vGeEaoOOW+=D!ymJrFM8=?~8614H!jMz9sbewh? zmzJ)L)J{uF&xlQkkB^Ds9)xKm+W<2V2g6y6Z>yJOa{PmtIefMlaLb=3@}2q3tF-)| zv>y+asZkNJ&WMW3AdIALt8)z5Ib@J!tq=#=A}|W516dEu*O1vwS3^6W!2=(D!o`1a z@=VIkHG-1jpQQK!cw)bE<%h#^DoWQx>y4-7pW=(BLCkl!yn6tS$7eD$j%1dEp@sJ6 z9Lm1{g8zT$Ps>*<<2mCp9ww=HYEqgUfQ&zkA$FNZ<>8#^KKPxVnU$lBCYt>ReTj30 zMSRxOoV6>VPa$K7N}wtLl*hSD`h#gWlZ4bz%p$SexELZA+s3H_{(H3xym~Mpr+S~G z?N~<31$QCzYxsLO{6&D{oRsh$UU zJ{5vEjvK?_DFD|3{7Ei5b0uB7K=BSb2hfcq55sZ(IGitrPZ|N<0`Ry|;5dd1=hF)G zIBy*Zj_0!B@GpUWr1t+F2F^(OWB5q=WB5q=KRyaQhL1%51i*hYf}R*YlAk;V@R9V- zfaGVS{#ylb+<(LEux1qa`cdG8qrgi>fxklGa}8_eLF`%$OSH@tS+0mOT%X@T;y9B1 zF?=NZWB5q+Oab_e5!#F4c&-^Pmj(E5MuCrHe;v?2JOVw25lzk7MI-xrw9j$pJWy`@`w;Mu8WM0xuc`ZW;w%JPN#Y6!>QV$9_Aa^NgVX zYDhL?Mu8`e0w2lWa)8fBcE<3L?7R}-xW67Vtc8hL-1_(rrlmAIn~acRAheC{(>U{) ziCf70ct3sqnLcB-5_mLDgyCl&eWsX6d8r?HJ}F`mIw~Xc{cyNcj?mqd#{EHRBXn{z z`RjkXPcdAM4CF+EfZ=dofX@PWVmXm>vVuINmE<{`-U#$#f!>g|B0J|7>;Y#+a@C*6 zeS#WeH9YY+;ta5c%*hT{JaK8^I>V3&yGNMOS;`l$iSW-wTE=q){&|;{ zAzX_;yFqP?8xF6g@c+cL%M45T=(MTXd*dC5=DW1mzTR;!J8 z5&Vm5k2mF3(j|vzKgBqr|1)BVtVOY(+^Ij--A&iW*||^teooS3Lls8D`ON@cBEOs* ze)-BAypA`dr>oaw<}4oKf*(^ooc?5R(XTe9iCd<3R>C@uC;A8Bzv0KN-g4N8%v`x_ z?TGc_N3XYfab`x2Ivcj)a_+3UlGd9<#JS5O^_KBa`HtqHI$F=UUokm(1#A(onN4G` zVOch;NY}#7GlueH?S(~rL+d&BYM)W3!5%flMYs#ze+O#zjtxjE_u+ij0biijLAo#YDwM z#YIhvijPW&j*O0qj*iww$3(|Q$3;(zj*m`&MRt@nTC3H@Xk)do?uI3JLQG^#R7`Y? zHYO$}HYP4+T1+;s_XVI4<9_m3A9o%YzXg7?0WY?11pGe0F9y67o*+Z- zUSAEMkKy+_z&FDa<5QhFm>E$EETad0JGa9gI6Q&o4jpi+g7LUL!)4(5_z2~m9griA zf}aO?9pIA*{&9C5as3>?F9v+dDEM-~Zvy=EQSkc#Uki9W!LJ<7{~X{u0gr7B`sYd9 z2qFR01NgYt5IPM{z;BT(bQ!5!QQ=Xktn%FEwf(Lg-mi_pM#C{O3Qx#DW+-?idZrVNac)&Jn0(=4BPZF8>QOu3hfWHE?>Kd|+1pP)F zpFVQ%JD|aP-#~N6*oXvtJmB#-L(;?{1X$n40pA69J^YRB=H`0hzC1p(533y=zXkA% ze+0h|@O6N%BeuIkzmEYQSx5GAK>m;zh4t$OyzWQvJ`6&C{1JR4;In@O|2W`_e+0i0 z@PGah{1(9P2mCr>zb)=#7wf+d@MHIkj{g|&ivgcIO8suYe=rJO4Rw4Ja7GNlHJ$qA zNHIJHXy&~+x{u5Q{9M3eeMj=49Kg>3{Ae-09PlZl@W(8r>=Y!?Rl#2u^Bj+wl^-*B zRg|zw(HDF(v?sND&K0Er@$yVBp=t}JQEMui0?!W&s62)1W=}s9+~p;BD^pDbi+0@h zn^V-$)0)@p{6Re*uK=s_VCiv0<_2nmz&ZFO-(hU zJ1v}~R2IWrVNBf{M=?&8Xgp2jTU>lBtmh?x^=_Bc<);X25Ifnzu=%XAJ&6sjkhgd{ zs#i*LTGNv=t23obSg_2qt4!_Hj?lh{N{(-ohZljB{Qc2Roi_J^m#xR{Ww!SW+}`SG z_eqcL4!_B;;XPr<&j+*?d(QH0Fz~Hkohb$={9` zrzBAZkAu4@cIL5DjXtSp(Y=A{{$_&rDya}a4FPavz_E4?#Ex3+ezV%;pT zr^*W{8A~-^U7^XVL5w`O%gL#!9&>7YCH4pWGp~9I1 z1*h*_(16UwUfGz|@%bvA@z#vpDOU-Mau+;HQj*i9yrhhKu|oQO-D?XNi7J4Vy>TrH zQ;x-ynXjrCg-DC*Xzt!AFLTyeR3Q)MgDb8m!7D`HMG2wh?|f)p5F#;mD(=>dJtLp) zpSe}V5Ltv1<{(Ok=T^$8ZGFWpFQHl(^sWoZ>CYbU67pH`vkO0e1;=UJ}ys3 zyToTI(m+}wvoA!u#OD&EDa7Ss~p7NE?{BjP9_mRv@iFzHg-5 z*TgBj^aLC7IHS8DoOMRiyHCAYmU4U7)|9Ke>LwEY>NjL@`M)lVm4wM+^TRlq;-11d z$>jR(y*<{OD$jh6#=bD0O+HnRi_!OT-GnJVRf%F08%O+s*{YebGnD*ouhB&A4D)3* zF+0OF@scp59_U_#5E$#<{s+6ACI7@OQ0ImzydSRJNv{X-dw-ay2pV z8wtNNB%w+PiL{Au9pfY(L!x&lR@P1MmZ%hYcM~fl4%AI}Ya-ZPO7N%}tnusc9vRrB z5bT0&l9myEGor82vD1RbTH}JiW<1suS+)-QpszjMAkksV%VL?7DtHz)z!Uttuu+i` z_*Z~W^GLBeBKqQPPVWhZHXY%WJ_Vo)?TfnH+zI?4o>M69`8=b(+uF1DW@BI71h9W3 zwt3$4x`}U1pgpb6g|_eDr)MbdDyYOd&Strl4UdT$v;Gp9j*%4VLL{=8v65=$OxY1d zn#X6G&zJ4}sF3wXfJJ~sfJN?DnQlJ7S%CAXaHbIjE9P%b_GtBZ_LZkMO9Rf7?QJh) zE1jQE;(Xh>camT0P3Uxl-#4K*e+vroEj z0_k=22|^A1p%;DpxV0prL?w7BE`Y8h>4>&)W4!1{@HT|bv=_i+jF9Ged=1aIT zFF9z>91^wnJO$eCIMlz@B4N)83uv~c-UJ%UikRyDxor!u#-J(84o0G}kd2?J4a_=s zjnM~$+Np8g`{AW~OBH;pF*&z7H~ebErRa{h7NxJZ1Y6tHf-vN?vZF9Ky>)5wQmm&D z^t?;d^F22`-xKxxTzHP^X~}o$xv=fIK|O^fL{DZh)icXo&pcaoKc;r-iEQB;_ioH! zoB7a;@Q)(8qT3fMiAT6}6g{DPU;r|vwWZ5a)2->C<8lE7eT4mBze~dj`$Y|F1(-9$ z(Ve#j_x&cOvp=*gcu+%LPc-a!mT1VQxofE3L#Uk^I_j)N5k;r_Q`_cVT=4b6Z=U;i z=#AhX0>wVD&w?36>Z#j5!FMJdxvy#R_KqybD@!pR+M2>2V%WnpYfUUAZD&|(KjaN<(r+MdxF{99)XdbRfZasfI|}f8 zP&YBWg=7vjt>GP{W=Ddvrne;GVmPhI%z&CJ#S#0mNT=YFpI~Ve&Z72y2=ioEJi942WF|AVT6aEb@FJ+ z#p|y1^YdAWE?*~^nLktV$ZCX;#6mKa&d;r;Xd%u-);6*{9N*0*(`OMItU{b{h{CyT z?kk?SqWYqU5^D+dj|=rspLh;>ag1HNpu{V@=ro<(^C}4yf9^EQSI#w#-W)8rRw;Om zBJovR3GNDQ4=U0L`1wM(_WWQVKJ$stM7x|px zs_arf-p6nNA}}YpAT4AS$S+fMqZe(>Z>%^}S=g*Vg?0up3ZE9gQxQTz-WI;x;J=ys zy#j5Ttxy~QE)8Y2*nQ|snRg4L^Xc%u1X9G25;BeSq~)dAt4y8Mt)cy?x8_`*``vU`c&rNeSID3JUS11LRySqVA z!#;F1I#b0UA8uWUC&zk@6<8rPmFD+(*D_CqmrfD6Q%mQYrJmg?#>*$5Q{Z1K#{9AD zl9VC*Wg`Evn1AfmXugEyzOGhiA}2{SF)0#F{3MBIT|VmzYfJBTWtYmo`vIk+Q6G^0 zUC!LK^=t20_uZ1YOILxFxx2}Th4pBMz&f=`{2~Z?25P5FL7{k63w{N;_8QDz-j2D? z|8O{WMmJYDNn2(4?1>NaQy1X!+Tj%u8y=5&@j>GL! zx!E?43wB`J#^#j@%5k1xTXzrVqyo0>m&}|%)Zwl9`}<$IM#oKXLMgu^}nelJzZ8-V>f&f3@^d zoh0Q;3zKrn!re`xp)9fCjKDf&<{e*uBD}=c!p!m>mnzJ4uC+NYu*O_dzK-OjO{r40 zg5@SQpt(0w`_4MN$3fOC+DgAGsV>-9V674+u%~+Y5A$cu z!CXYGo1z)W5y;;tI>|SB!(NlMK#Esw?P5C2)>huF_xGAfBH`1>%)CV{n|*r)3p76=R4?bm+vHw zeR`Ftr@FiL%E_+N?Pnbe`WD`Nu4ivac!{d_`rQIvVapnjkYe^iQf?w_b)+!-yz^rkRC-0)C2f~F7Ib_2j1hgU+Elwi^2pg@abC1@$S4ymQX|)4Re0fklfqmkMmF@k zBqa6(cXzr6BVYbuz7BgV@SInYJZ2{LSa;4|LhT01Pca`!>VV%>E8_A^azWAZA)~8- zQD}#qAWsx!DS=!7c7fZ;CKkg)l@lz81_Ur|-cdUcqhhRN%Q)mKa;2+Ma-R;y5m5q)jhB zbjrHnw6I0*({kHCh&e33{e2m0R_=QlN7OlBn_vOACA=s|_b|jKT5Ej?%dnhmKMG;Z zOwWM1!5`iBUtU%%4BeM4V|UJU@{&OI;pByDx2g8`ojDN1I-`D5x1V8~fvf67J}n0~ zs~Q4OL0uGrM1xB!6rS%XEQw{MT95O{fAgvx zt^NlBeSBW^FmLeiu{HEcz5j0WIEFNP5A<`H3XjMB?tM&SJNkg z@cwU2^?`oPZuqS|(64%7CNe9FB{pe5l1hf8nmVApoF+9_G1B*hUZt)|t577V6sA7O z4sC`)Vy%p;gp$m=&HJMF*WTZ#JUpar6OQ#4qHj+!&8iLlh%uN^|Zsv_4>KGvm=#?lItNu@;+e#G~`a%;4ADOBc{#9sE1doNGedtBO!_t`2b( zitz7uZ%ZoEahpH!F41vYKk?J#bs_VD4X}swDJoH}4LHoOj}+Fj1Fy~XkeI6!64_sc z0o%o=kj8Qoy>?b1F$a1`3Ty2IRt+#pw=GGe+6|BkgzC%9n%Ym%VPz8VY+H2v)(QW^ zy0**<*SA-LrfXu+&H>PnzaV|+k-};QeUY?{J;!P+e?S`Gs@dIz#M%thR<`Mn_wUqk zOD>@Dq)(GHmQR7E3++_yWW-i&R@`4MLbocnGPkR7iv;C*kUERDWCJo+nTEB*gv@~( zhP8yyxwnK+4R47ZwzNmLNHqDMqB5lsWG6XggB49^s1;3M#SLhv6*q`< zLu89o!)2esvNJBUVqcIQ^GAipLSP^8Q|P}gRM;E~dw@3q&H{Ws>;rxZFdp^+yI>Cx z!;P>9h+(iVa;yUO0lNTJ1N(qbzvw2wVXO!kW(WLv*ayV49k34w^-Z{bFYEzApE72X z5_SN)P-8LnueJik=l_X5(2u5KQ5(!phsv6NjN4X5wc!!5ZHIt56yjF*QyAX56pDctuCD}39Z{OAl>NNvqE=#5ftO@nmLNvkPhL)4MD%0kuyF%U5VKtdz> zz|^+wSbwY~yy%#c@> zor)ljwy*8|As^P8f^=MKvsAS~7%+PX1NS|mReXX_n#yDv_@FYcwcgaW_;~+)AI6!+ zeCT8F<0Cdi3ys2bQy@$qi;yfcYL()4@b{s;so&1II``6oj)g5>^|jvY?77^1?aCHI z;Xwh_Z|B-hOb;(B&g49p>E_&CI6qNLFZ_offxOSqAYH$(jJ2AWe8#Ko>iK@y;hmvk zx@BCBHI?xSvT%1Prb|-S2WaQ(Bn)Gl$V#}&+kc}KwkK)lvl7W;z+-U(Dr3Vo~x`u*NV2nx^KIMEApP9D%vDn|FqV>L_)dE18$M~C74?laC4s4=r>&} z=f2+mtMwBN^4hYtaZ_#wFF&%pfaf;I!*2U&&!>RZwJNas#mq3y%uF#C-yWDCa3x9~ z72_l4O1-CWiy9uZsS+n^$LbSRN+h2km^a)UumyC7X{&S+o1z18Ew%U9YHp~t?G-`X z>q?nT@mIus^O@3Hzs2Cw;@z>upzJJe;WBui>o@7WTjU*^^u-;;4la%N&aSWlA6u^xUHZPTp#W*; z`%AFDn86CQ6*7pS_myY^YQr>n5b+wM5(3luV@M5J`=L#^FNy1B#&8DS%f|4DKIyje z!0$Y8xDbnA?CTWhjS^c$W0(fP*e~9GgS)zCf>zj*sEJ$-dEY~@5`P&GA}$IWu*Mb&1!~&Ohm1tbI|jzH|9x$bkYI@LsBY8QmpXLCZN+7;a*X z4Rn`F|2)CetRj?oxuhJDO(<)!os>;~Bp4x??q01SWo98MH!Y^+1+=_~(zCRjLGjPQ zUKN8+ql6~dt0H>#hF^g_wr*+PTPUI@{3eFNx$GaqlyqPEF+#`AAiVT&;*Q(LCebpS zl8f!7Wu&Kgs%sOax6?9uj^d}$`jpd7GU3HMNqAYFA!Wf? zUdXLzkPPaB^)%U^s-X67N+6h;2S{y)fs{iYB;~wkVeKt$dw-VetqpW zu7z12(B^fcf7^0vv636dPc%$y8^>?EC_A7;+=`B|DjlB5%pTBI<8`0HdPZtfb|h*) zIgV^kc}P^BOhW-xg~=Z1jg7Xd?_iAFK-{fng=*~e0c~K@WG&>9;Ze9q zIMP0;kf;`Dk*)9uDm)@Xs@&B`TX_UVv-87zHLTGQ*{HDR_ z86gD2P@aw%g%2}QZqwKa&>s?2VgCR(RyjfTFytTK+GU(lrXch}9Ff6Hb8=D#6>6ae zaldm$xvjvSq+*Jt0<5^e+9myRt}M`ZoWXYCJFrSFN%B{fXVyfIV5R2`PO#NQ#e*wz3KV_e#{R=*6tiyTvnB8sAgAd4nO zu81h|)(eccgY3xQU6^76!xV!yZ63@uV!axFGK1w^pk+^L&0bnoQT#bl4)LYse^R`R z(tk(sa$5eDmKC(z?d%;mVK8ukj89nl`UQ~=Uwh=@uw`7gUGWCkC-MbVuc9xyf?H(w zaoWeHwPdDGXFat~y{X=5pR1K#ueWaLoR}usr*+UihEK*~`$Tqyfqf!-xNq!;yIq4K zbD$69vCV#I5xwMb>LvBmOZFB`8ArUtk_cy- zd~ycy5+CpqDV*wJFYy`l60&P$`LD^AO)Z-`ru4u)GLWEj>jh#N=HJ9JiT^{&!Vko< zj`u0PkCy*N%U3D=7nI&v{?{FM0mYSyW)fMk{JoDncgbY&j0DFWSV5$}D-H6n#lrrD z-5+sF6_Ubf8wUKi^#S8rd^%vI^zZBa_*{uI!c9aL|Fu3K$OGd<*$Vq2h85^M;5hyZ z^8Kxgum1199A7rV#n%?k~reUF_oPsQ(4|dbs%N-*!I_o$H7$3NN|u z2scwMk0=ck@E&V#v;Zee!9tZoJoBII4<}`31$Az3wD_~E&%z8_d8XUG00pIhkWgR>0_BVxJ?QtJK}9px*rYqghsqeDxOO?wj|@b zq~hP|Ed{O{o-pH@1=fx;knyWbebqN>d$wgxcSN_xb#Ygk<=o;=l=Ve0YyG(yF=FfG zu$q$7+0TAh=?JkD8zvgI@GVT3@{p;ZDw*e&RVWLLX&FaynX=jnABW!+d;;3JQkPSk z;|c8!L(bI#`+>E~TeA{;@_5_A92sZKfmjp7 z9;{QHKPfYBpN^hxA$@N2+Q|l`&Y}IRmV}(x%5qoUQxwMCh4Nt^M0P9yc7hZr7;Z%s z9t%ZsR10imhbFpl<_v{2>L>cNoep*jvCe|k81@~O*r-YGE_vsOlILG_myFt7l0%4O5(!?$l7oJ>WcAbT zlFe`?>}vI%!B$svxXVub;=aLFhrA=UT9lo*2Fq3f4-$!fy4{ISxJ#ew*s7~90r|NX zIEEq8Vn4yMrczmsQ%~E&OGw`eyQb>k)J&A+ zSd3eVqb8OFw<#hg!QM@ZEWgf|bi!*~PJmb9oDR z$`#B}-sAEWGA}Uf+tgbe5BaY$U9G-U+i|jG&h5G1FX&zP?Q>VtE{WFL#I1E&FZN_Y zN&>bX_12~)j$tC4{@Poa;IiK{8ub*gpC$%w6vfYgc5Q-vz}Kakm`&2RhuBbd%xOb> z6I0t>i@5PIP?M|Nqwr@n>bKAy+V=TJuNz>1cG~!S zal&A)<;})Y@SQ977EXbl+axu7C~Jlrqs=Vr{PPeM-EWL)(~lu;$!@927=!#fxEm!3 z6|+fP-I;~+1qnL35_%c#bwS&mt=N8m_J6&*c1?9){{XN-4UQMRFS%@B`P~?|`me(* zwh4J2PzfwrwN0fsIuKD>T@l_L-4g+K+INVi>&P^(@xq~VADq<$gN~E>kS6pB+;{x~X~I83nkj!nnuxcM zP{*w(mzH>$D{zLbX^lWHRWCe1K58$)wVxtbZc2SHX0$c+2~Rr76|m@hUn zL4$p5F@I)b*n@Pmpu2EuRSGsU0?$c$d=sLnC$JX76pLy3`yY=(6%`N7dI*ms8XMG` zj5rR7{bT0dkA4UI#y%O;-)V9atBxgt@;W-RR?DlJhEBatYrQLQIRjm?@hYnGe! zn$q2^7n?Y1(C;h*|2ONInI@@C-XL|i-uVnHa~8E;-m@<2`6tX8v|irLpKrYe1Lk;w zT2KE6m-TwJE{^(t|2*rRpw`PfHS#Q&(cwF@zQhXiV5dBg_`}n)Q)Xj3&Z7PIILt+O z9u)om{FK>)c2s|@v552Feuy#e!aVr5O>nvfv(bL{c~Eu?atS&QlJ^R<>2Ov>=D{j) z9<+$_peX(P6Ik|aDm(8H&QqKRMcMu#F4_7vjl~;ovVlZVGR%XbMO*yZhiq$`z!k`vPgKA0f?Ke}j8L&biR@E<6`jJUZsFp>yF`lQCbK$y;w+rxG2-bL`qxU^8zS9VOCe`fwn^b44H7asDGc2C--;|e z$Vj5#?Ei$1*)(fljE-A#!DkC!sJj4n6`JAgnod-havfDGv|XKu_=#*LtSnO$NR|St zN<|#3Fxgj<<*katn?Kv^JJZ(gA>o8hd7*9raL@sV3?!510*BAQ&(h?FU-@$Ln3>!Q zpLl<}BWWhLrBfQ(KBtT8+V0Jnx62E<3fhUsb^KjO*;=P6@*F7BaW_oLLR|p7B?NnN zGehC6p*RWUr+9YrnayyDP5h(pAxpVoTUrYfUJ|A}ZIV`{3esGgX?tSwnpVEPys`aI zS7=6a20kVBx#V}%^{ysCXGIu!|$^3^u$cnQb5@QrTq&MPVfuh$0SorD|9#5?01+u>TVsbANq9}x5YHzB9`4dneBA*Xr` zvhR&jUnWK6VEYR8u@sZHfrY7m=AV?)CL);5Yp zNd0y9=*)las+YJ`qrMCBKG0Fr^#NEZU|k=O?bVM>znqi0*1Fbf-Q^{zOEaOra85sR zB(vd2c6GO0)fjiC@5DCIXso`(jbYSITZEi)wFwpB%pa^TwhH(`4~+Rx_1Pq}e*BmeZgmNq z@ol70w&p~XbG`8{tkr0(sTR(oL|WGAq6<0Zq|0j{bRZ)JWW+xp`RsihNx$8uGT~K4 z?VYO%airvZ+M1466}$1OLWDGZh*uSp#7Jr`^@CN#g&t4VI=HHEY;#6Z_QN_fDF5xB zURAv2wyJ>kPx=8?6<@=u;$N_;_y?>i-h)-eo9K*X{d_Ke_t>k)WwD;9Ft!A7*J{Rc z?FW5u3>8-sMDu?u=J~VS{k!F!Ecc3atPCg%<2)r?Tdfaw`7X$hFu`Fcyl|E#*?=sY(7iJ zJG_aBBR2n*xC<@D0K}4rUHcY|FH;Fk&6{FWC%ciM1lIAUCtE^!ARp3=I|KH*p!lHE&=V~lsblMNT`z}PMziotB4o3X`pB zS^ux`>22BuXMEZP?;uQq_$2CLe@|SAh?cWFtg%3R+7Es=Xg7#YV7Cfqe5w;$<%~~% z;k?B7BytKl;A$0`j`K0Fzt}F|2YtW{jZXr^CtOdcvB7)j;%W|MLENFW2IJEpUC295 zy1b=?4rGXZ{D9=l`y|&R5o#}vP$TAg`prkuA=iTl1-TyC12jUeM-34=YBfZd(AD^0}Jf_0dl>sA=mpCC3$oZ@|b~*w`K0B-5na^GTjhaEDPRM7k56@>s zk5>QwKgwsto`L^r$d%8&Pb?e~?v~FUA+QekBEn!k+a$u6nx|=RT@v%z4(Ms7!)q{~ z#r&C$dxqt+xV4O+8q8-u3Rv1TMnn;b#wsR6VeP0At-_S;z2vK_zgqCEwRi9L z^|z(1;hhneqpwN73%?$5E4sg+)!HdinrccLFV|nQUiV&H@Ll7r`hIB}_q9F3Y_bo) zn0fhpM2kO{+YqTtFJvJr)Uoh}KM(5K%4Oin!Oo+7obol0Hy}XSo~dp^JU2% z_}zaSyj@Z!0Skzh&zlDCp2L?HkT=NzEEl~OLoR%+CfsZiu*B^1Q_lEvYa6Cui6eh& zz$x)ONVJ2*4v^S8Ok$YKDN!F83|~q(Vob{|Mg^4Mu7AvyJ+w=u_g_A2o(UD^41h#5fgnvM>!o|vyX))QDwJJb^)O_aNE zZ<14-nr5-E3K{1Z-j*;L+Fu6mAKD(_2Yy(Z(xnQ3&Xm1L&8_y5R2Vpf%?twYKow>;X*Rj=s{S7wTJec+eMOxNw8ZK#luHV9M~ z^)1LJBb=FOjpPZuauzLzDl`_z&Vw1J@ck1sGu0pcvCNdY;iPjMCUoQ7GSfzJ7m}`>H;#~*igQC#`|!*ZedNqc z;mv>&KQyQ7}YbW~X06 z4a1=c;vVOa8kSi!pY?)s<5E!u1ILc5a&6f+AkLXHXgzG<=e~@9%u;eQZO}`@%Ncg9iFN>!=mRSU5|EHKQi!l8o zG=DuuSNlr6Gj8&JMdh9GXW_mvkvE;nqxBLg zZ^o@2nn>kAJ?EVvM;w6(XU4PJDKB^@Le!!84ACL)ek$){mkx=6uKRKkMVnS_%k<9i zdjal~?GS?`743{t3<&Uh5sNj}1$m#sc2q&qXVniJo;RG#fY56!U zN7M2#S|9$o?I6E8ke#0p29oOzVr;=vU(Le6q1)6JcJ@|)xx3Wl?#TJ zSIiw+&YCl{ynIvomc_8v7jq@cvQ0T#UT~J0mcEs~ZxH8L@>b5iK^$80QToS&ICbVn zIUf(=5*H`))x0z#yeFbN`pOg~%uynD_448Gp(ML91!4yPx_ z;Pm7eoSro2qF|+VQ5T%Qq``T|)0<_-D*n^FtdoZUGYxd@1ZP{5> zqPCXhuuF(fit%cc3EA$dm#1{)R9M$vET_V!5uzsZvuGPqi7t+G?k?AEAHRjgITab# z13$9cN9>U)wkm~iO4P&YD?W`7TV>zqYL)tj5p$|@a2ny~Ig87kBvTFMRBSP;Bfg}5 z(3w*`Ac520ci%+4?{glLWN;=iNX-jQ>clC8+LLBh8`1X-Ke^{8=8o#$yJc1)L?7qO ztl+NG&^9>dyc)}nuxjPj6WuB;{T0|MihF)%LZ zjo$YIO*lf-E~^qV6h{rAsaY!S)`>97p9!pEsVi&AJ4ot<(t7%8D$lK+`Zd=KKFW=E zQFBM!C^z1TzfY$(-o;Wwf_URytd|%0^KQKVAHSJA+C6tMs_U!8d+y@jmYwc*f5MC8 znMwmw7G1)n*`><#&T2k(1@}F4tX9d~Y~d*jrLN_}#6<-qV77@waf}5d2)h zKRs0!Av6ksTKwjH;q&fN@L7J5K?ZNz!yQ}mcDPIKDOVZCqu3S|KORmX;p<>=lOnqhKMCs3F9_?qo^q$eTF%lqNDnBJY4V1}+V+!z_x0Z&EAaZY;ftk7p7lWWjiN zCxLLscXJ7U`%_A{FIMAr1+gtoPux2Am1o>4fticn0dQ~2Tt{3{G<=J$>;f(sxznEW-P=p<^qQ}?4+KM| z9}3@wJGp`0htyyCkp8u5Sc^-di{L9iB_(XMQ~qYFz?67ingi{FZ|sTL3wnsDGxu~1 zaLdc3TBbZv>lGs5R#>H>&@!i2IsU2HHw#wb*b0*+F7F~*(fny8_ywkb=W|Z(=myXUt-?y+qI@k9ASwo<0 zU|ZTl`Y)6a(KSBQ-6(PK~kG5bZi*iFPQTmSae)f_*H} z2l;7L*po~hG5D$4z$f7EW~vn5B69ZHWE`v~((%!hE`BIo;%Zmx@i*Y(+6P8!v(Gr-v1T^ zIGcyJ61!@oP!D{;InJBT-RTYRzHB7Uqt$w%E8|DXP0x_B5P*9Z_M`pqQ?)@DH?)@> z^9JjQbb>FX3mi(PpX+M-oObLB;YATbK8=-*dDfyNMvM!j&GWe5-RT2%mAemMk0#?- zA9dd<)0OJ3+V+!S0sNCxM%*q+bPXsACDDqIle&?x9Izg@@W!;Z^8JZZLRJO;7z(K(b_{Q-O#tccbS zb7TxCa;#at*n^}e_#bc&!1%pMdZMF$XiqdHxbg<* z2Y4&Y)erg^Rv{Q-tTRG7F1xStT|I~lL*kg&s;24Sx6n$6V`AAd4gB^bBXz~Grng4+ z+uyx8f?s|^S|MB^tq{H&+6w*Dk>2~_GC%Qtxa3etTv7N&E%QmD?JoE_&c!#q>r3IS zLovgtc+)jQ>#H*lTZ>SOCdMf7WQ)5NI7)@BI?tJN!2+}mlbI8m7+AHyeIU(@*H9oH z0}T7MCi2Zu-tUs8<9E4To}BkTOVc@2Vl>Dy05mlS6(`2EUL#c7NVJcju8mdPFY z7RfC9ziw=|;BWj8yZe`mUquyZ9HsZQMAdP~iJSkb3#TsegnVgJdyx1)b`-ZKv7r?> zi`q2golCst4|X7>X9w|p$9LqJhn$#jQ-o0O9D!R%e8}^u0v{-}vm2qsw_u;^)}9Wq zK!0E?{vHeS6hQlleM4Hn61QO8HDmWJ$k6)04*f1$i8P812S9%y*0Ef7@e$^__|$yy zQ+&)!$CoZX=$d;Z%L6+XCNL6jgQ{dae046C;XFK*X7+pdzeuTV+T9e-t7^X?=GmwOOq2=$ z%gpem8_5+D7X}((3@L;n_@8TJhjsK@y<9289l9gD2) z9KPazmvi38>kVX~M`J_g;HL~fb?}S}ol^zR!p6`!-VSBEPnX{n0@X9C@{ zzs|0LXW@ar&i3i{>lt@*VxO$p4|am&up=}Hc7|f$RyX+~eqnP8!ta01Lr-rm2#_61 zN7aE}ZZ15QgOUS3+dO}BYv8k+-zoEU6xz#SpQ#9V>;@kD2TzS<%}3E^n`Ot&q4Q6dVZqPse)Dk%0M|&y=MFepWWMOxZ58`$-Dza*3>YlBBRXMiR`-E=yLlf)<7L z6oAZ=JW(cVPL~w;R|mj}GjPh0oGJ5JJiDwlV18LtnX*e}F9!}qz+pGg?;pb9C_v7M z9NOJD^k5Fl202^-9i*Ux6m*b+4$>hy0A!w2)IsW|gA_Qd9OTdqIsk_v;IJF$_YdK4 z6d>nB4()CndN7BaK@L5jgA8<#fetdzK{iAOfXtJLI>_8~kO7Am207dW9e_g-aM%s> z`-gBi3XpRmhjup(J($CbgBKawbUU2oAeq)vB8xAPz&?t!a2lM{M0?6)zb%o;Vm#%t zX(jT)hdr6g@NK%P6!7f*Me4%%5+)qJjTy3uK-zy5wMjKko>rP0*HK*_Ql^?O7~(2Z zyR@FQNUJMB=7kD_4gS+Ut^F-z&5i3JBS&$c`T1V%gNnwAgw15E@e`+RRxMDZoy=`) zkFMDBit3XMhQzqSo`jkd{4x%zxQnOXb>``$2n;w6p1l zwrHEX;hRn-Luh&JE45dm%Y#6F4+DlbA=QHGoVBvP(sooQF`tvC)mA+uwxbnb4V%ng z$_=)H+eq7d1gREY=f10GuF&e?WQJVn>kTphTi*g#r>P_@BW|y`t@0bVae-S zT^}+(b)DO>{m6E$ZW>5eq(yJn&h!*5+K+W_2Ae#tFem_e30ic6TUef3ZcAw=TI|r; zKZiC8hUdz|%Xfl}ZRcMEin75%^dRok)eM9sewVP>$Mw)9!B>Mf-D>;`we;;V|OxUbS| z(ksYzEL-H-Spjx_MfFDyL+h(rsE=(|yM4!YE!-u-er1JLFBZA}dArv1J&Gz*J;@k$ z5LNqDI;v{DQA0&zR1@35s{xkU!6UQwwXTZyzYuYp7l1I2vV!=>d$m#3E7 zW~KvY=*w4aM}tNEO)Fjk{dc|sPy>d(D(Y{otgY1A8%fU$a<2y{vJ4!0_8 z8^IRGzJ{3K0e-DL2JmNZa9&G4udr5V;f-o+J+161iI&|3QUycC=FSRRX${oDo(}di zV~qLfUamjyrOJmZwX9C;f2Ut=Fw`0&01nHZ*ZNdBEw_QMn}Iq4Z16j=r!sIldmJ)T01BPxB zGiEbJndokeO#zCp_yA)}Vvz1?XaqE=rdxveLlHD=mQ5%{WqvVH0b_onvvHJ6Okz+F zHIIo8ULwMNZ&f#F$7C}5&yUZisjk!a+;i@^=bm%!o9g=CTE)6o&$)fTAJU29nuCD>%grhZNZsD9))(di-sj3v+aph^%pt$kM2*k*49g+gP$foGe;$gmYi z72Tgz3HV-yMhLS2Pgec*Ja@QTsmX)f;I~l+<5bIIv(r|V-~kk^{4`PzfHk+) zGxD>D3W~~$gy|n>_HB!R^`_JUjd=Bp3P+2E{ee6Ir|m4`)%ma}f(3HcwAwmXm0Vrw zXewvb{*=EhSmOb06A$-F87?b@_%qP#oF^H@0{@{(`fWsKXmtrfW;OV0 zROM$_e~t2;E7R1}MpF49I{752qR6Sqm2!fZWGrgIX+wNbRH4CX?Le}S3S=0*DcpU( zTdCbdH7!BOI7pP5=TxmdzkUt%J*q{ifR~s}`1%$xJNa;Ln^dw6z#>Gb?IAgf`c&u+ zBE)WJNd!anyE(Cp>bC_R6S-Hs1*@o92gAZ@uVSZqlLq_3!MD+0s3kvJR9U3d!kZv{ z6!aJ}KrJP|T&=>PEocw0?^HHtS`lAC>egJXHK0|cs2Xq>wV?#>fvh|XKO6Cf!A{;s zIa6WA_L*E9yQjd&!jfYHdWOmDtRIE2xIV4UE>nQc$C_Y)ZEv2qX3iV zQyX57C_&S5Yg&2H3CP!H*U-|2sGN6#Z&k~w)P1$x1}h%Sz^c?h>Qv8B`gy8cbZD1; zJ*QAAtyZ+l^Sydr22TK;gPxyBKLecs-!Uf9rxCxV>5w_3f|$xOSLG&X z*hX}RBqYg#_fZSB=@Xkp#+syr6E7jGKZ ztKpiI+QjB$XRj5nC{Sv~t6bE*(XByYzuO9w+VKqk6?#AGS9!TQU z<@0GbWLJjP3H^?Pep4IdvwBY3jHGf3?~^rc&FGuZI7O&~RiS;2t%Aj+TA|Ufh6=kA z`n`r|`kg}~VrWXWr`)ICQ`LHu=Rr?A^&a8RpW*EG70hBtmxpwr&C`#oo;}Jtm1^^| zup<4#!SODJuWuKfOPh=8`m`D4)glrl>w5+^OSOj3|IBxYa=euY4whA1WCz=!{c@&Fte1sjX=GbrvX_r<5#>21n_{mdLfksYv_+h;_~e$#ElORe z+Ix=ft*P^gWhy+J!fny=2(NOunN)APwnc#s@?W=!om+$_!x$x}gLK0qpdL{#JSwV3d#NkdtqfrB!T$uGlKwC{IEK;d7sT4y;P~HsRw_u*&*`5wdc0 zp~ju(aN_2M&50A}NQ`X+YW@V-vPe0x5c-i!Ha7`%h-N-B*$#qwnyU3%mTtbp$D3@G z1qbrBtf1U0bQUZXWJAvdezgIxaf zHSyuxtgf~d@u^j*;jgr%#4oN|9KPh*tFvCKeC?jsuPqy5yM3(nRv5;z^y1B0!x+a? z9B*jQVN^>UACB3E@oE#jOVtkh^NeQ7Ro`r@+ z4%|s1M_|>%NPI@)GZ>#K`tZJSwumG;%aWAlrB7$l8#1>*uZ;~aa4^1G@GR5C8_hU@ z4Ywz=6%O@&J(auS?cjUx2zqX#_Ff#A?}b;jd@rn*xfQH7V%@MlcBQj@$Mv!wKKb^< z#gmQkJy96X!)gi>z26A8#%d$ug{;MGWUUUP-MZ7rPTynH&Pb!@d(FvZt)~F50ldj* zpWb5hj~r`vss15%Oebp@1Z+K0jYO*P0obzvj{`gr*r`9pZUb!tXd6J=0NREC+JM&p z-edr6!;ffR18rdIk!mDTjSs+{4R{>jiNH?%G4|giz!QO;`eW>7(AI-C&cWEJ2W@=-ZNO^)Z_!I%s!JM-Cw&3Nwb&7d* ztk~uzXb|=nHIhb<);?8xi!)m0Q=$jd)D?P?J=xF$& zm6-K0DX!8OvF(@%_jnE9gUp6m`DJu&UCpZ@8DrRbT2eOJ1)Fg|-&fkm@$cx>yY*D= zKgtsH<70hgerczUmiZsy`b$ov-jZK*bpdO@jDV6Zm`|Z2FwgkT0RK$%judT$Pv3kT z){nU89gDG6f2|hJyp$>??i{&$kvNbWd1=MF3$QYg>`BBZe~2=nioDlQmH%$jkR!L| zy!+kj@l+b`UBXU`5xhfh5c}=L$q1Oi%o~y4o8Oak<&jU5UddV@&yip?-=|}3Oe8~} z5>ui+u{J`dzUxZVC&xyz?~|OwNV1*S-Yj8p=kjUb(z};W1D8|ZnG*F0T!Z?qD^Z`o zHEt(aiz4;!lXGvD7}b19iOEOT?4zqbfg9wbt3H9-4|JDCnpC>|d~}0-bo=}0s!!k! z@X=MDzzqT26_LRz-4GvL^1jYjUOd(E(sx>3`cBJB-)VVuIjJbGPA#v_S6)2T^3r!& zUiwbUOW$dE={qg2E^95yt5eHM^QGmb`7)7al@3#)KGpJSbc1|!`+@FjC@-bkZ@UiM z)4WMZu$ng$*1^83kZuZ18?+KMF>8IQD|FeQl)*_R zTjY4Wk3EHz2Tho52hFj2aOZI3pqHbWWX0=XU;EwB01>gOR!;a>||# z$m<5acWE-QJVzxibNU;UcU3;B#7Biv8Dbp)3{M{{dZ?B zoozbbkleMnV|fcPSGt&|H~juFLr-^MzqTKEPd{B{;bY-JL52FVV!lAH8(mQ0V5iWP z2`Ej6DG3~O@KQ?;xNFliqjo1P$wM$a?2+?#ncOqk%=K(50SFoR;GAikTBu8onQKdGv)D=T3B;?5J-! zbA=34cf(KS2=`R$H~J)da~#XJTZ#DxB|3(|N<4b*mN_VhS|aQZX@0%9!d0Fv4h+(b z+nAG@wZgqjvuUTB%+?G~xfCybb>YbqlN{SUD> zKgF`s^N9?3Zf_iR6t$_dSDAJ9y%ez!R@gZG({V1dwLCjos&G_G8QvMrGO64dFG+D< zT?<`K_q?3|m*tKb(%cUb!(?+8?R#B1cCT6IFK_AbaT__S6sJkP^u|1=y$m#wf~J(> zN9Ej43(1=bzx!zokyur6UgcC+qxF+W`ogKh-YL+N3EJ>YyfkNNw+dZ<03 zYJ($Qy840N1KKSTd9xOJt9ib3lPx&Sv~sIYf5HxNy*|;85s?fj8Jho8;iUaU4KIRK zSN)T_+d*mp5uM>Q-I~t+)<}I#r+trE2W^5BRC}!YfEypPkPdbTVNal$u+?S;XBGG4 zak(3KR~UJqpUkDRO3+^2Ilb#??)NOtlgF)z(T*F?G}woYJ?`Zh9`Rl-6tNd{6RU<8 zJ5B)3T0rUL9}+zha}PHwKRBu2*aiz*<$$LGeOOp=*cxV3vB@U+pnEiEzcs+u_lQF= zQk-ugD&`@^I;1$*##yrxXev!a071+Bin=S3EHk_tAfbDzLM+)~#awKdpQblJ5#ZZ4mLS+k)3 z)=~V+SDB}SZ3W*yyt{nkJi7l_^{Qlfxinvj+G@wDUg?m1ibTZMw=&zawtokmWjU*o zlM^dGQ)T+9HmTVDTTZ7>n-BXD?>yt2<=fFPJPf-OEVY-nE}f5^7|%EZcUSIsRrlKY zrPG$}Se9hV$kfk8DOlDju{2tsIkJySy5P?xOL=Z&@Lt_~>6<%pj|qA7^5|^EkrONF zZw>nDV>Y_Kj^;2QZ7=EbQM=7naZ4@6y6z)Vu>9qHWJqzdwBh&ueA$2DSlNLaj0Y9f zP|W$#dEhPkoU{Bam+66AG#F?ZueREwa~Z9a)au~m&0B(bbL_3Tw`L1m0TKI8VEd>@ z`n1!+UN=5>DW%lk1^ofN=1KFn^NT_yHxWtWK ztPeEn$}x77;Thu`ElC#cyf`3;+A`)O_J0KWJbF`204!aLCr5YPe)mMWX^?GjigK}; z;0_Ykg}=o9WiWjFLl_Hq;3)3@8o--K$O(sex12eME3hG2^J7bsC8nXCMU^CKU5)+Cq3E z8IXHO>>kAk&I5!sK$neIZi-JHC5%-ZOmM??>y z0yAy~dx$x4*H$3j$k*XS7nZSd)0ON!F=Fgz`U7?G&J8oR7mtA#{KVG+8Xhl~W=W0T z=67i}`2^&6x7E!VnJ$ayt-}1;P$C7%XND4XGN-fuR?@M*ftKmRp=GX**{XiJS<)Gw zmOZ@NUbT^-w`I(5Y8oS55X{f^X-v=rrVoQ^%)sJ$=)!E~t{kNMy~7~tJ(l0ukGg#) zfw~G8cL17J+V^KM(_FAG*;TMF8C&ED_u{ldHzE#19pm+#C5Sv|KdN4ut_E$Y;7Dn&}IJK7ZK5jO*;J?r$$7a z#dQZf$wJ{niiHUu5r6N|zdL~TA{pOE>5K*v8s*AK!`Y5H+&}10<3NrBb~Iu@x*|Bz zv6GZI^h6se)o!)%OQ*tFE{zNDn(U_)8AaT=0)B04h?qG?r$}>R#Ku}uDM^QLL!xw& z&}bQFn)aiy^yzNdWatsw_LHLB8*pd3LEW)US8qSYp?_g){ejOn>V&*qr6tuK!T2|wY|kKC4GB^u!Muf2A?}1pxr(ut;VrE_ujd}fQ!?uqxvmY7 z|D0SQEr~D}4sne)_IHUJYYD|tc`|a0#5E47>ZZ3Ca_Xl_@_Urqdv~xVwAaPa9&b__ z?RCkt$LH_eslDIYnbdFpZ0}ARdkg(>@9~cOU;2sspZbOQ2mOQmQ|_L><;h=|zuvD6 zW1KR2X6FhO=dKvB;q*O1Chm29?lB?;v7wb7e(lSI!C9e4iJdbLxs>ao_kVGQsQkvk z_1&;Sj7euJR5;wD>^+=sHyVk&%h)9!jUb$Hs;6=AzV3Zg10<>eoKD$vfjnmHA%&*) z5Z-IjRd0!1$UVppHtHsC8JGX&w6J2uu$&0?^)Yg*K!mmkX1iTzk6bRHe6ecLW39w9PIf_YgWxTo>l{4RbCyIu%6L9RKsB=FYuo?#h7>^aGE z^Mo~R%v2A5w6$lLee2?a4WH*Kt*s1?e&sNGf&GO|TbBzjw=>oGmHB)f#R4o(dAW_j z&3d{DZ{yp|H}m&lXYldLd^_y0TxRH!>%t+4Xh@=}+aI|yR=M>^NFEopP2M{o_)pw> zF-q$}lIxN^6R@Uz0C`}5#(|&yx$r2@g*GKEoOvKaSQE}v<|`7%9(pq>-=E_o-~Ntr z$u~aJf*QW-4i7})UB0p8=0GgVFTh&R7>Jeh3$RE-AeQtCu!_G7#47y-STUamVp)d$ zV`(&;)_MfQOzlNf)`sc4LErO^<$>L@a}CGi4$g$Il^DIN@0N^{oy;+M|90c`s&3Rg zdwd`KsY#GpW5jc%I<*E2(oMKzb=AD$KD}9vH-}6&8+4@R6q_@NaHw2WoMJw;=yOsC5+o^tJxs&Rs%Y9Ubzw?t+ z)zbbmR7YXYP@j|+Oudt8?YTaxA8qf8+0sqpvgCLR;-nQi*%|2WS684`MNlChgtnt|6!U=0NkjV5lqBzg1(8%;UvNEFa{3ADc zJ4S1`)0Mb zB>gkwyNisI9vM~j{J%om0)8uL_Wu@&p6Y$QQJEh)$QGJJ_v~x-F5)`f z1s(Y$I$!@E{?f0SHLBPHPyh7VgQTBhv~Qs%^%>)X`=CUF`ha+Be`MNT@$vAfg*6v{ zc;c^FwJ#(NPjDA~(iGQH=Z!nvQENK(f3GiWHZT6|u1_3N#lk=Rro-L6>iY62SEGl$ zIJxy-OFzwM{?9jN4fw;amQMXm&LXaG$p^7F|KoG}uU>gzJwN1l!@v1|_vhnJ=g-fd z-k+}@&!2yKz+X>~xJ#*O=>Ocj!s18v$B_?Tc>MTVC4+SV$$DRJov=9uzWnF^_~Tms z{YR2YCv7;M|Ll>O)A|V`PW_uscqH<})8Bt^qPzKvFUD7WmT~spCtlcexb@fnzDf7= zT<664+GXR9&^Le%+%J9mhyC|+tA|8iy4tcO|Kxi!zL+{HWX)fFPeKN-x%uV9v|hqL z@x@Q?iM4xH*35e?=xg8KQ_onRdgiIwbKg2W)9{xo>2ZPZho89b+w;*we@D_A9((0T z>=SBTrTAi?ZHHlLHshLLEQd3u@B&4M#0i3kx#pG4C zWl2c3Xm#qUR9gbRCE8MLBx==5+Ee}Cr@jCCZ@&X7Z4>wSve?XMEo8I5`P_SF-hu6A zqz_-`OICcJhFE-`2H0cxd3>6he*DR13mX3HNDa?gcqhJ}jvtDRgDO4-64k%rvp)I< zwfFr#xc?Ez|3v&iG5q+?f34v^|DlF4Jb+*zz7feN%#Xj;XLr^f((nV&Ra)9F+W-D| z+>Y0&wC$~kPm(Q&%7Oo?}7OL E0~+ckqyPW_ delta 11426 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 diff --git a/source/template/fiber/dev_config.yml.j2 b/source/template/fiber/dev_config.yml.j2 index 8c1a82c5..467d7ea8 100644 --- a/source/template/fiber/dev_config.yml.j2 +++ b/source/template/fiber/dev_config.yml.j2 @@ -6,7 +6,8 @@ fiber: gossip_store_maintenance_interval_ms: 1000 gossip_network_maintenance_interval_ms: 1000 open_channel_auto_accept_min_ckb_funding_amount: {{ fiber_open_channel_auto_accept_min_ckb_funding_amount | default(10000000000)}} - watchtower_check_interval_seconds: {{ watchtower_check_interval_seconds | default(60) }} + watchtower_check_interval_seconds: {{ fiber_watchtower_check_interval_seconds | default(60) }} + tlc_expiry_delta: {{ fiber_tlc_expiry_delta | default(86400000) }} rpc: listening_addr: {{ rpc_listening_addr | default("127.0.0.1:8227") }} diff --git a/source/template/fiber/dev_config_2.yml.j2 b/source/template/fiber/dev_config_2.yml.j2 new file mode 100644 index 00000000..4f879049 --- /dev/null +++ b/source/template/fiber/dev_config_2.yml.j2 @@ -0,0 +1,44 @@ +fiber: + listening_addr: {{ fiber_listening_addr | default("/ip4/127.0.0.1/tcp/8228") }} + chain: dev.toml + announce_listening_addr: true + announce_private_addr: true + gossip_store_maintenance_interval_ms: 1000 + gossip_network_maintenance_interval_ms: 1000 + open_channel_auto_accept_min_ckb_funding_amount: {{ fiber_open_channel_auto_accept_min_ckb_funding_amount | default(10000000000)}} + watchtower_check_interval_seconds: {{ fiber_watchtower_check_interval_seconds | default(60) }} + tlc_expiry_delta: {{ fiber_tlc_expiry_delta | default(86400000) }} + +rpc: + listening_addr: {{ rpc_listening_addr | default("127.0.0.1:8227") }} + enabled_modules: + - cch + - channel + - payment + - graph + - info + - invoice + - peer + - dev +ckb: + rpc_url: {{ ckb_rpc_url | default("http://127.0.0.1:8114") }} + {% if ckb_udt_whitelist is defined %} + udt_whitelist: + - name: XUDT + + script: + code_hash: {{ xudt_script_code_hash }} + hash_type: type + args: 0x32e555f3ff8e135cece1351a6a2971518392c1e30375c1e006ad0ce8eac07947 + cell_deps: + - type_id: + code_hash: 0x00000000000000000000000000000000000000000000000000545950455f4944 + hash_type: type + args: 0xd0e6998c64e5e3ac7f04f1c05cc41c5c36af05db696333a762d4f1ef2f407468 + auto_accept_amount: {{ fiber_auto_accept_amount | default(1000000000)}} + {% endif %} + +services: + - fiber + - rpc + - ckb diff --git a/source/template/fiber/testnet_config_2.yml.j2 b/source/template/fiber/testnet_config_2.yml.j2 new file mode 100644 index 00000000..3f033864 --- /dev/null +++ b/source/template/fiber/testnet_config_2.yml.j2 @@ -0,0 +1,71 @@ +fiber: + listening_addr: {{ fiber_listening_addr | default("/ip4/127.0.0.1/tcp/8228") }} + chain: testnet + bootnode_addrs: + - "/ip4/54.179.226.154/tcp/8228/p2p/Qmes1EBD4yNo9Ywkfe6eRw9tG1nVNGLDmMud1xJMsoYFKy" + - "/ip4/54.179.226.154/tcp/18228/p2p/QmdyQWjPtbK4NWWsvy8s69NGJaQULwgeQDT5ZpNDrTNaeV" + announce_listening_addr: true + announce_private_addr: true + scripts: + - name: FundingLock + script: + code_hash: 0x6c67887fe201ee0c7853f1682c0b77c0e6214044c156c7558269390a8afa6d7c + hash_type: type + args: 0x + cell_deps: + - type_id: + code_hash: 0x00000000000000000000000000000000000000000000000000545950455f4944 + hash_type: type + args: 0x3cb7c0304fe53f75bb5727e2484d0beae4bd99d979813c6fc97c3cca569f10f6 + - cell_dep: + out_point: + tx_hash: 0x5a5288769cecde6451cb5d301416c297a6da43dc3ac2f3253542b4082478b19b # ckb_auth + index: 0x0 + dep_type: code + - name: CommitmentLock + script: + code_hash: 0x740dee83f87c6f309824d8fd3fbdd3c8380ee6fc9acc90b1a748438afcdf81d8 + hash_type: type + args: 0x + cell_deps: + - type_id: + code_hash: 0x00000000000000000000000000000000000000000000000000545950455f4944 + hash_type: type + args: 0xf7e458887495cf70dd30d1543cad47dc1dfe9d874177bf19291e4db478d5751b + - cell_dep: + out_point: + tx_hash: 0x5a5288769cecde6451cb5d301416c297a6da43dc3ac2f3253542b4082478b19b #ckb_auth + index: 0x0 + dep_type: code +rpc: + listening_addr: {{ rpc_listening_addr | default("127.0.0.1:8227") }} + enabled_modules: + - cch + - channel + - payment + - graph + - info + - invoice + - peer + - dev + +ckb: + rpc_url: "https://testnet.ckb.dev" + open_channel_auto_accept_min_ckb_funding_amount: {{ fiber_open_channel_auto_accept_min_ckb_funding_amount | default(10000000000)}} + udt_whitelist: + - name: RUSD + script: + code_hash: 0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a + hash_type: type + args: 0x878fcc6f1f08d48e87bb1c3b3d5083f23f8a39c5d5c764f253b55b998526439b + cell_deps: + - type_id: + code_hash: 0x00000000000000000000000000000000000000000000000000545950455f4944 + hash_type: type + args: 0x97d30b723c0b2c66e9cb8d4d0df4ab5d7222cbb00d4a9a2055ce2e5d7f0d8b0f + auto_accept_amount: {{ fiber_auto_accept_amount | default(1000000000)}} + +services: + - fiber + - rpc + - ckb diff --git a/test_cases/fiber/devnet/abandon_channel/TestAbandonChannel.py b/test_cases/fiber/devnet/abandon_channel/test_abandon_channel.py similarity index 78% rename from test_cases/fiber/devnet/abandon_channel/TestAbandonChannel.py rename to test_cases/fiber/devnet/abandon_channel/test_abandon_channel.py index d26a061c..bcab757b 100644 --- a/test_cases/fiber/devnet/abandon_channel/TestAbandonChannel.py +++ b/test_cases/fiber/devnet/abandon_channel/test_abandon_channel.py @@ -44,7 +44,6 @@ def test_tmp_id(self): assert len(channel["channels"]) == 0 def test_abandon_channel_accept(self): - # self.open_channel(self.fiber1, self.fiber2, 1000 * 100000000, 10000 * 100000000) channel = self.fiber1.get_client().open_channel( { "peer_id": self.fiber2.get_peer_id(), @@ -60,23 +59,22 @@ def test_abandon_channel_accept(self): } ) # self.wait_and_check_tx_pool_fee(1000, False, 120) - + time.sleep(1) # Test AbandonChannel channel_id = self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ] - response = self.fiber1.get_client().abandon_channel({"channel_id": channel_id}) - time.sleep(1) - channel = self.fiber1.get_client().list_channels({}) - assert len(channel["channels"]) == 0 - channel = self.fiber2.get_client().list_channels({}) - tx = self.node.getClient().get_transaction( - channel["channels"][0]["channel_outpoint"][:-8] + with pytest.raises(Exception) as exc_info: + response = self.fiber1.get_client().abandon_channel( + {"channel_id": channel_id} + ) + expected_error_message = " our signature has been sent. It cannot be abandoned" + assert expected_error_message in exc_info.value.args[0], ( + f"Expected substring '{expected_error_message}' " + f"not found in actual string '{exc_info.value.args[0]}'" ) - assert tx["tx_status"]["status"] == "unknown" - @pytest.mark.skip("资金会丢失") def test_abandon_channel_when_tx_send(self): channel = self.fiber1.get_client().open_channel( { @@ -86,26 +84,23 @@ def test_abandon_channel_when_tx_send(self): } ) self.wait_and_check_tx_pool_fee(1000, False, 120) - channel = self.fiber1.get_client().list_channels({}) - response = self.fiber1.get_client().abandon_channel( - {"channel_id": channel["channels"][0]["channel_id"]} - ) - time.sleep(5) - channel = self.fiber1.get_client().list_channels({}) - assert len(channel["channels"]) == 0 - channel = self.fiber2.get_client().list_channels({}) - print(channel) - assert channel["channels"][0]["state"]["state_name"] == "AWAITING_CHANNEL_READY" - tx = self.node.getClient().get_transaction( - channel["channels"][0]["channel_outpoint"][:-8] + with pytest.raises(Exception) as exc_info: + channel = self.fiber1.get_client().list_channels({}) + response = self.fiber1.get_client().abandon_channel( + {"channel_id": channel["channels"][0]["channel_id"]} + ) + + expected_error_message = "is in state AwaitingTxSignatures" + assert expected_error_message in exc_info.value.args[0], ( + f"Expected substring '{expected_error_message}' " + f"not found in actual string '{exc_info.value.args[0]}'" ) - assert tx["tx_status"]["status"] == "committed" def test_chain_status_ready_or_shutdown_close(self): self.open_channel(self.fiber1, self.fiber2, 1000 * 100000000, 1) channels = self.fiber1.get_client().list_channels({}) - # ChannelReady + # Channel Ready with pytest.raises(Exception) as exc_info: response = self.fiber1.get_client().abandon_channel( {"channel_id": channels["channels"][0]["channel_id"]} @@ -139,7 +134,7 @@ def test_chain_status_ready_or_shutdown_close(self): ) # closed self.wait_for_channel_state( - self.fiber1.get_client(), self.fiber2.get_peer_id(), "Closed", True + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CLOSED", 120, True ) with pytest.raises(Exception) as exc_info: response = self.fiber1.get_client().abandon_channel( diff --git a/test_cases/fiber/devnet/accept_channel/test_max_tlc_number_in_flight.py b/test_cases/fiber/devnet/accept_channel/test_max_tlc_number_in_flight.py new file mode 100644 index 00000000..e6ede799 --- /dev/null +++ b/test_cases/fiber/devnet/accept_channel/test_max_tlc_number_in_flight.py @@ -0,0 +1,69 @@ +import time + +import pytest + +from framework.basic_fiber import FiberTest + + +class TestMaxTlcNumberInFlight(FiberTest): + + def test_max_tlc_number_in_flight(self): + """ + + Returns: + + """ + # 1. Open a new channel with fiber1 as the client and fiber2 as the peer + temporary_channel = self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(62 * 100000000), + "public": True, + } + ) + time.sleep(1) + # 2. Accept the channel with fiber2 as the client + self.fiber2.get_client().accept_channel( + { + "temporary_channel_id": temporary_channel["temporary_channel_id"], + "funding_amount": hex(1000 * 100000000), + "max_tlc_number_in_flight": hex(1), + } + ) + # 3. Wait for the channel state to be "CHANNEL_READY" + self.wait_for_channel_state( + self.fiber2.get_client(), self.fiber1.get_peer_id(), "CHANNEL_READY" + ) + # node1 send_payment to node2 + self.fiber2.get_client().add_tlc( + { + "channel_id": self.fiber2.get_client().list_channels({})["channels"][0][ + "channel_id" + ], + "amount": hex(100), + # "payment_hash": invoice_list[i]['invoice']['data']['payment_hash'], + "payment_hash": self.generate_random_preimage(), + "expiry": hex((int(time.time()) + 40) * 1000), + "hash_algorithm": "sha256", + } + ) + + with pytest.raises(Exception) as exc_info: + self.fiber2.get_client().add_tlc( + { + "channel_id": self.fiber2.get_client().list_channels({})[ + "channels" + ][0]["channel_id"], + "amount": hex(100), + # "payment_hash": invoice_list[i]['invoice']['data']['payment_hash'], + "payment_hash": self.generate_random_preimage(), + "expiry": hex((int(time.time()) + 40) * 1000), + "hash_algorithm": "sha256", + } + ) + + expected_error_message = "TemporaryChannelFailure" + assert expected_error_message in exc_info.value.args[0], ( + f"Expected substring '{expected_error_message}' " + f"not found in actual string '{exc_info.value.args[0]}'" + ) diff --git a/test_cases/fiber/devnet/accept_channel/test_max_tlc_value_in_flight.py b/test_cases/fiber/devnet/accept_channel/test_max_tlc_value_in_flight.py new file mode 100644 index 00000000..2c9a3e81 --- /dev/null +++ b/test_cases/fiber/devnet/accept_channel/test_max_tlc_value_in_flight.py @@ -0,0 +1,42 @@ +import time + +from framework.basic_fiber import FiberTest + + +class TestMaxTlcValueInFlight(FiberTest): + + def test_max_tlc_value_in_flight_exist(self): + """ + max_tlc_value_in_flight = 1 ckb + Returns: + + """ + # 1. Open a new channel with fiber1 as the client and fiber2 as the peer + temporary_channel = self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(62 * 100000000), + "public": True, + } + ) + time.sleep(1) + # 2. Accept the channel with fiber2 as the client + self.fiber2.get_client().accept_channel( + { + "temporary_channel_id": temporary_channel["temporary_channel_id"], + "funding_amount": hex(1000 * 100000000), + "max_tlc_value_in_flight": hex(1 * 100000000), + } + ) + # 3. Wait for the channel state to be "CHANNEL_READY" + self.wait_for_channel_state( + self.fiber2.get_client(), self.fiber1.get_peer_id(), "CHANNEL_READY" + ) + # node1 send_payment to node2 + self.send_payment(self.fiber2, self.fiber1, 1 * 100000000) + self.send_payment(self.fiber2, self.fiber1, 1 * 100000000) + payment_hash = self.send_payment( + self.fiber2, self.fiber1, 1 * 100000000 + 1, False + ) + self.wait_payment_state(self.fiber2, payment_hash, "Failed") + self.send_payment(self.fiber1, self.fiber2, 1 * 100000000 + 1) diff --git a/test_cases/fiber/devnet/accept_channel/test_tlc_expiry_delta.py b/test_cases/fiber/devnet/accept_channel/test_tlc_expiry_delta.py new file mode 100644 index 00000000..ad5ed198 --- /dev/null +++ b/test_cases/fiber/devnet/accept_channel/test_tlc_expiry_delta.py @@ -0,0 +1,13 @@ +from framework.basic_fiber import FiberTest + + +class TestTlcExpiryDelta(FiberTest): + pass + + # def test_tlc_expiry_delta(self): + # """ + # todo + # Returns: + # + # """ + # 1. Open a new channel with fiber1 as the client and fiber2 as the peer diff --git a/test_cases/fiber/devnet/accept_channel/test_tlc_fee_proportional_millionths.py b/test_cases/fiber/devnet/accept_channel/test_tlc_fee_proportional_millionths.py new file mode 100644 index 00000000..1774ff4e --- /dev/null +++ b/test_cases/fiber/devnet/accept_channel/test_tlc_fee_proportional_millionths.py @@ -0,0 +1,41 @@ +import time + +from framework.basic_fiber import FiberTest + + +class TestTlcFeeProportionalMillionths(FiberTest): + + def test_tlc_fee_proportional_millionths(self): + # 1. Open a new channel with fiber1 as the client and fiber2 as the peer + temporary_channel = self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(62 * 100000000), + "public": True, + } + ) + time.sleep(1) + # 2. Accept the channel with fiber2 as the client + self.fiber2.get_client().accept_channel( + { + "temporary_channel_id": temporary_channel["temporary_channel_id"], + "funding_amount": hex(1000 * 100000000), + "tlc_fee_proportional_millionths": hex(1 * 100000000), + } + ) + # 3. Wait for the channel state to be "CHANNEL_READY" + self.wait_for_channel_state( + self.fiber2.get_client(), self.fiber1.get_peer_id(), "CHANNEL_READY" + ) + + self.fiber3 = self.start_new_fiber(self.generate_account(10000)) + self.open_channel(self.fiber3, self.fiber2, 1000 * 100000000, 1) + payment_hash = self.send_payment(self.fiber3, self.fiber1, 1 * 10000000) + payment = self.fiber3.get_client().get_payment( + { + "payment_hash": payment_hash, + } + ) + print("payment", payment) + fee = self.calculate_tx_fee(1 * 10000000, [100000000]) + assert int(payment["fee"], 16) == fee diff --git a/test_cases/fiber/devnet/accept_channel/test_tlc_min_value.py b/test_cases/fiber/devnet/accept_channel/test_tlc_min_value.py new file mode 100644 index 00000000..bf2a3305 --- /dev/null +++ b/test_cases/fiber/devnet/accept_channel/test_tlc_min_value.py @@ -0,0 +1,41 @@ +import time + +import pytest + +from framework.basic_fiber import FiberTest + + +class TestTlcMinValue(FiberTest): + + def test_tlc_min_value(self): + # 1. Open a new channel with fiber1 as the client and fiber2 as the peer + temporary_channel = self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(62 * 100000000), + "public": True, + } + ) + time.sleep(1) + # 2. Accept the channel with fiber2 as the client + self.fiber2.get_client().accept_channel( + { + "temporary_channel_id": temporary_channel["temporary_channel_id"], + "funding_amount": hex(1000 * 100000000), + "tlc_min_value": hex(1 * 100000000), + } + ) + # 3. Wait for the channel state to be "CHANNEL_READY" + self.wait_for_channel_state( + self.fiber2.get_client(), self.fiber1.get_peer_id(), "CHANNEL_READY" + ) + # node1 send_payment to node2 + self.send_payment(self.fiber2, self.fiber1, 1 * 100000000) + with pytest.raises(Exception) as exc_info: + self.send_payment(self.fiber2, self.fiber1, 1 * 100000000 - 1, False) + expected_error_message = "Failed to build route" + assert expected_error_message in exc_info.value.args[0], ( + f"Expected substring '{expected_error_message}' " + f"not found in actual string '{exc_info.value.args[0]}'" + ) + self.send_payment(self.fiber1, self.fiber2, 1 * 100000000 - 1) diff --git a/test_cases/fiber/devnet/cancel_invoice/test_cancel_invoice.py b/test_cases/fiber/devnet/cancel_invoice/test_cancel_invoice.py index 870cf77c..cbf240dd 100644 --- a/test_cases/fiber/devnet/cancel_invoice/test_cancel_invoice.py +++ b/test_cases/fiber/devnet/cancel_invoice/test_cancel_invoice.py @@ -449,6 +449,7 @@ def test_cancel_invoice_that_statue_is_receive(self): int(before_channel["channels"][0]["local_balance"], 16) - invoice_balance ) + @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/654") def test_batch_cancel(self): """ Test case for batch canceling invoices. @@ -466,7 +467,8 @@ def test_batch_cancel(self): """ cancel_size = 50 channel_length = 4 - + # cancel_size = 5 + # channel_length = 3 # Step 1: Start new fibers for i in range(channel_length - 2): self.start_new_fiber(self.generate_account(10000)) diff --git a/test_cases/fiber/devnet/graph_channels/test_graph_channels.py b/test_cases/fiber/devnet/graph_channels/test_graph_channels.py index 142ac22b..15d1da17 100644 --- a/test_cases/fiber/devnet/graph_channels/test_graph_channels.py +++ b/test_cases/fiber/devnet/graph_channels/test_graph_channels.py @@ -276,33 +276,33 @@ def test_update_channel_info(self): node_info = self.fiber1.get_client().node_info() print("node1_channels:", node1_channels) key = ( - "fee_rate_of_node1" + "update_info_of_node1" if node3_channels["channels"][0]["node1"] == node_info["node_id"] - else "fee_rate_of_node2" + else "update_info_of_node2" ) print("key:", key) assert ( - node1_channels["channels"][0][key] + node1_channels["channels"][0][key]["fee_rate"] == update_channel_param["tlc_fee_proportional_millionths"] ) print("node2_channels", node2_channels) key = ( - "fee_rate_of_node1" + "update_info_of_node1" if node3_channels["channels"][0]["node1"] == node_info["node_id"] - else "fee_rate_of_node2" + else "update_info_of_node2" ) assert ( - node2_channels["channels"][0][key] + node2_channels["channels"][0][key]["fee_rate"] == update_channel_param["tlc_fee_proportional_millionths"] ) print("node3_channels", node3_channels) key = ( - "fee_rate_of_node1" + "update_info_of_node1" if node3_channels["channels"][0]["node1"] == node_info["node_id"] - else "fee_rate_of_node2" + else "update_info_of_node2" ) assert ( - node3_channels["channels"][0][key] + node3_channels["channels"][0][key]["fee_rate"] == update_channel_param["tlc_fee_proportional_millionths"] ) @@ -365,29 +365,29 @@ def test_channel_info_check(self): # created_timestamp # fee_rate_of_node2 assert ( - node1_channels["channels"][0]["fee_rate_of_node2"] + node1_channels["channels"][0]["update_info_of_node2"]["fee_rate"] == node2_info["tlc_fee_proportional_millionths"] ) assert ( - node2_channels["channels"][0]["fee_rate_of_node2"] + node2_channels["channels"][0]["update_info_of_node2"]["fee_rate"] == node2_info["tlc_fee_proportional_millionths"] ) assert ( - node3_channels["channels"][0]["fee_rate_of_node2"] + node3_channels["channels"][0]["update_info_of_node2"]["fee_rate"] == node2_info["tlc_fee_proportional_millionths"] ) # fee_rate_of_node1 assert ( - node1_channels["channels"][0]["fee_rate_of_node1"] + node1_channels["channels"][0]["update_info_of_node1"]["fee_rate"] == node1_info["tlc_fee_proportional_millionths"] ) assert ( - node2_channels["channels"][0]["fee_rate_of_node1"] + node2_channels["channels"][0]["update_info_of_node1"]["fee_rate"] == node1_info["tlc_fee_proportional_millionths"] ) assert ( - node3_channels["channels"][0]["fee_rate_of_node1"] + node3_channels["channels"][0]["update_info_of_node1"]["fee_rate"] == node1_info["tlc_fee_proportional_millionths"] ) diff --git a/test_cases/fiber/devnet/issue/test_force_stop.py b/test_cases/fiber/devnet/issue/test_force_stop.py new file mode 100644 index 00000000..e69de29b diff --git a/test_cases/fiber/devnet/issue/test_issue_640.py b/test_cases/fiber/devnet/issue/test_issue_640.py new file mode 100644 index 00000000..e69de29b diff --git a/test_cases/fiber/devnet/list_channels/test_list_channels.py b/test_cases/fiber/devnet/list_channels/test_list_channels.py index e2ad1382..35c999e5 100644 --- a/test_cases/fiber/devnet/list_channels/test_list_channels.py +++ b/test_cases/fiber/devnet/list_channels/test_list_channels.py @@ -233,6 +233,9 @@ def test_check_channel_result(self): print("channel_outpoint:", channels["channels"][0]["channel_outpoint"]) assert open_tx_hash in channels["channels"][0]["channel_outpoint"] + # peer_id + assert channels["channels"][0]["peer_id"] == self.fiber2.get_peer_id() + # funding_udt_type_script assert channels["channels"][0][ "funding_udt_type_script" @@ -257,21 +260,35 @@ def test_check_channel_result(self): assert int(channels["channels"][0]["created_at"], 16) / 1000 > begin_time assert int(channels["channels"][0]["created_at"], 16) / 1000 < time.time() + # enabled + assert channels["channels"][0]["enabled"] is True + + # tlc_expiry_delta + assert channels["channels"][0]["tlc_expiry_delta"] == hex(86400000) + + # tlc_fee_proportional_millionths + assert channels["channels"][0]["tlc_fee_proportional_millionths"] == hex(1000) + # force shutdown latest_commitment_transaction_hash == hash self.fiber1.get_client().shutdown_channel( { "channel_id": channels["channels"][0]["channel_id"], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 120) assert tx_hash == channels["channels"][0]["latest_commitment_transaction_hash"] + # + channel = self.fiber1.get_client().list_channels( + { + "peer_id": self.fiber2.get_peer_id(), + } + ) + assert len(channel["channels"]) == 0 + channel = self.fiber1.get_client().list_channels( + {"peer_id": self.fiber2.get_peer_id(), "include_closed": True} + ) + assert len(channel["channels"]) == 1 def test_close_channels(self): temporary_channel_id = self.fiber1.get_client().open_channel( diff --git a/test_cases/fiber/devnet/list_peers/test_list_peers.py b/test_cases/fiber/devnet/list_peers/test_list_peers.py new file mode 100644 index 00000000..0607aa5b --- /dev/null +++ b/test_cases/fiber/devnet/list_peers/test_list_peers.py @@ -0,0 +1,19 @@ +from framework.basic_fiber import FiberTest + + +class TestListPeers(FiberTest): + + def test_01(self): + peer = self.fiber1.get_client().list_peers() + assert ( + peer["peers"][0]["pubkey"] + == self.fiber2.get_client().node_info()["node_id"] + ) + assert ( + peer["peers"][0]["addresses"] + == self.fiber2.get_client().node_info()["addresses"] + ) + assert ( + peer["peers"][0]["peer_id"] + == self.fiber2.get_client().node_info()["addresses"][0].split("/")[-1] + ) diff --git a/test_cases/fiber/devnet/node_info/test_node_info.py b/test_cases/fiber/devnet/node_info/test_node_info.py index b463b7d6..4e83ed6d 100644 --- a/test_cases/fiber/devnet/node_info/test_node_info.py +++ b/test_cases/fiber/devnet/node_info/test_node_info.py @@ -7,6 +7,7 @@ class TestNodeInfo(FiberTest): + @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/631") def test_commit_hash(self): """ @@ -69,6 +70,7 @@ def test_commit_hash(self): assert node_info["tlc_min_value"] == hex(0) # tlc_max_value + # https://github.com/nervosnetwork/fiber/issues/631 assert node_info["tlc_max_value"] == hex(0) # tlc_fee_proportional_millionths @@ -148,11 +150,11 @@ def test_channel_count(self): == int(after_node1_info["channel_count"], 16) - 1 ) - def test_pending_channel_count(self): - """ - check pending_channel_count - Returns: - """ + # def test_pending_channel_count(self): + # """ + # check pending_channel_count + # Returns: + # """ @pytest.mark.skip("") def test_network_sync_status(self): diff --git a/test_cases/fiber/devnet/open_channel/test_commitment_delay_epoch.py b/test_cases/fiber/devnet/open_channel/test_commitment_delay_epoch.py index 7e07d46d..6693efc1 100644 --- a/test_cases/fiber/devnet/open_channel/test_commitment_delay_epoch.py +++ b/test_cases/fiber/devnet/open_channel/test_commitment_delay_epoch.py @@ -20,12 +20,6 @@ def test_epoch(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) diff --git a/test_cases/fiber/devnet/open_channel/test_commitment_fee_rate.py b/test_cases/fiber/devnet/open_channel/test_commitment_fee_rate.py index af781d1c..f570159c 100644 --- a/test_cases/fiber/devnet/open_channel/test_commitment_fee_rate.py +++ b/test_cases/fiber/devnet/open_channel/test_commitment_fee_rate.py @@ -31,7 +31,7 @@ def test_commitment_fee_rate_very_big(self): f"not found in actual string '{exc_info.value.args[0]}'" ) - @pytest.mark.skip("todo") + # @pytest.mark.skip("todo") def test_commitment_fee_rate_exist(self): """ commitment_fee_rate != default.value @@ -83,7 +83,7 @@ def test_commitment_fee_rate_is_1(self): f"not found in actual string '{exc_info.value.args[0]}'" ) - @pytest.mark.skip("commitment_fee_rate 不准确") + # @pytest.mark.skip("commitment_fee_rate 不准确") def test_check_commitment_fee_rate_is_none(self): """ @@ -142,18 +142,12 @@ def test_check_commitment_fee_rate_is_none(self): self.fiber1.get_client().shutdown_channel( { "channel_id": N1N2_CHANNEL_ID, - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) self.wait_and_check_tx_pool_fee(1000) - @pytest.mark.skip("commitment_fee_rate 不准确") + # @pytest.mark.skip("commitment_fee_rate 不准确") def test_check_commitment_fee_rate(self): """ 验证我方的commit fee @@ -211,23 +205,13 @@ def test_check_commitment_fee_rate(self): self.fiber1.get_client().shutdown_channel( { "channel_id": N1N2_CHANNEL_ID, - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) self.wait_and_check_tx_pool_fee(commitment_fee_rate) - @pytest.mark.skip("目前双方commit fee 一致") def test_other_node_check_commitment_fee_rate(self): """ - 因为无法查询到shutdown_channel(force)的交易 - 因为dev 节点只会有我发的交易,所以通过监控pool池 查交易 - 验证对方的commit fee Returns: """ commitment_fee_rate = 21978021 @@ -282,13 +266,10 @@ def test_other_node_check_commitment_fee_rate(self): self.fiber2.get_client().shutdown_channel( { "channel_id": N1N2_CHANNEL_ID, - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account2["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) - self.wait_and_check_tx_pool_fee(1000) + tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 100) + self.Miner.miner_until_tx_committed(self.node, tx_hash) + tx_message = self.get_tx_message(tx_hash) + assert tx_message["fee"] == 9999999 diff --git a/test_cases/fiber/devnet/open_channel/test_funding_amount.py b/test_cases/fiber/devnet/open_channel/test_funding_amount.py index a22e5d46..0faed925 100644 --- a/test_cases/fiber/devnet/open_channel/test_funding_amount.py +++ b/test_cases/fiber/devnet/open_channel/test_funding_amount.py @@ -7,6 +7,7 @@ class FundingAmount(FiberTest): # FiberTest.debug = True + start_fiber_config = {"fiber_auto_accept_amount": "0"} def test_funding_amount_ckb_is_zero(self): """ @@ -44,12 +45,12 @@ def test_funding_amount_udt_is_zero(self): } ) time.sleep(1) - self.fiber2.get_client().accept_channel( - { - "temporary_channel_id": temporary_channel_id["temporary_channel_id"], - "funding_amount": "0x0", - } - ) + # self.fiber2.get_client().accept_channel( + # { + # "temporary_channel_id": temporary_channel_id["temporary_channel_id"], + # "funding_amount": "0x0", + # } + # ) self.wait_for_channel_state( self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY", 120 ) diff --git a/test_cases/fiber/devnet/open_channel/test_funding_fee_rate.py b/test_cases/fiber/devnet/open_channel/test_funding_fee_rate.py index 4af46685..78211772 100644 --- a/test_cases/fiber/devnet/open_channel/test_funding_fee_rate.py +++ b/test_cases/fiber/devnet/open_channel/test_funding_fee_rate.py @@ -52,6 +52,11 @@ def test_funding_fee_rate_too_big(self): # "tlc_fee_proportional_millionths": "0x4B0", } ) + tx_hash = self.wait_and_check_tx_pool_fee(int("0xffffffffffffffff", 16), False) + tx_message = self.get_tx_message(tx_hash) + print(tx_message) + return + # self.wait_and_check_tx_pool_fee(int("0xffffffffffffffff", 16)) self.wait_for_channel_state( self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY", 120 diff --git a/test_cases/fiber/devnet/open_channel/test_funding_udt_type_script.py b/test_cases/fiber/devnet/open_channel/test_funding_udt_type_script.py index 7e27305d..8b43a4b1 100644 --- a/test_cases/fiber/devnet/open_channel/test_funding_udt_type_script.py +++ b/test_cases/fiber/devnet/open_channel/test_funding_udt_type_script.py @@ -61,7 +61,6 @@ def test_funding_udt_type_script_not_exist(self): def test_funding_udt_type_script_is_white(self): """ 1. funding_udt_type_script 在节点上 - todo: add more check balance Returns: """ # open chanel for fiber @@ -73,7 +72,6 @@ def test_funding_udt_type_script_is_white(self): ) # connect 2 fiber self.fiber1.connect_peer(self.fiber2) - # todo wait peer connet time.sleep(1) # open channel temporary_channel_id = self.fiber1.get_client().open_channel( @@ -145,8 +143,8 @@ def test_funding_udt_type_script_is_white(self): "fee_rate": "0x3FC", } ) - # todo wait close tx commit - time.sleep(20) + tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 100) + self.Miner.miner_until_tx_committed(self.node, tx_hash) after_account1 = self.udtContract.list_cell( self.node.getClient(), account["lock_arg"], account["lock_arg"] @@ -156,9 +154,27 @@ def test_funding_udt_type_script_is_white(self): ) assert after_account1[-1]["balance"] == 90000000000 - print(after_account2) assert after_account2[-1]["balance"] == 10000000000 - before_balance1 = self.Ckb_cli.wallet_get_live_cells( - account["address"]["testnet"] - ) - print("before_balance1:", before_balance1) + tx_message = self.get_tx_message(tx_hash) + if tx_message["output_cells"][1]["udt_capacity"] == 90000000000: + assert tx_message["output_cells"][1]["udt_capacity"] == 90000000000 + assert ( + tx_message["output_cells"][1]["args"] + == self.fiber1.get_account()["lock_arg"] + ) + assert tx_message["output_cells"][0]["udt_capacity"] == 10000000000 + assert ( + tx_message["output_cells"][0]["args"] + == self.fiber2.get_account()["lock_arg"] + ) + else: + assert tx_message["output_cells"][0]["udt_capacity"] == 90000000000 + assert ( + tx_message["output_cells"][0]["args"] + == self.fiber1.get_account()["lock_arg"] + ) + assert tx_message["output_cells"][1]["udt_capacity"] == 10000000000 + assert ( + tx_message["output_cells"][1]["args"] + == self.fiber2.get_account()["lock_arg"] + ) diff --git a/test_cases/fiber/devnet/open_channel/test_linked.py b/test_cases/fiber/devnet/open_channel/test_linked.py index de0940a4..6e2051d2 100644 --- a/test_cases/fiber/devnet/open_channel/test_linked.py +++ b/test_cases/fiber/devnet/open_channel/test_linked.py @@ -113,7 +113,9 @@ def test_linked_peer(self): } ) # todo wait close tx commit - time.sleep(20) + tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 100) + self.Miner.miner_until_tx_committed(self.node, tx_hash) + after_balance1 = self.Ckb_cli.wallet_get_capacity( self.account1["address"]["testnet"] ) diff --git a/test_cases/fiber/devnet/open_channel/test_max_tlc_number_in_flight.py b/test_cases/fiber/devnet/open_channel/test_max_tlc_number_in_flight.py index 0b0481d0..12c7e8f7 100644 --- a/test_cases/fiber/devnet/open_channel/test_max_tlc_number_in_flight.py +++ b/test_cases/fiber/devnet/open_channel/test_max_tlc_number_in_flight.py @@ -176,8 +176,9 @@ def test_max_tlc_number_in_flight_zero(self): "fee_rate": "0x3FC", } ) - # todo wait close tx commit - time.sleep(20) + + tx_hash = self.wait_and_check_tx_pool_fee(1000, False) + self.Miner.miner_until_tx_committed(self.node, tx_hash) after_balance1 = self.Ckb_cli.wallet_get_capacity( self.account1["address"]["testnet"] ) @@ -273,8 +274,6 @@ def test_max_tlc_number_in_flight_not_eq_default(self): f"Expected substring '{expected_error_message}' " f"not found in actual string '{exc_info.value.args[0]}'" ) - self.fiber1.get_client().list_channels({}) - self.fiber2.get_client().list_channels({}) def test_max_tlc_number_in_flight_not_eq_default_other_node(self): """ diff --git a/test_cases/fiber/devnet/open_channel/test_max_tlc_value_in_flight.py b/test_cases/fiber/devnet/open_channel/test_max_tlc_value_in_flight.py index e5f9f747..eea2edbb 100644 --- a/test_cases/fiber/devnet/open_channel/test_max_tlc_value_in_flight.py +++ b/test_cases/fiber/devnet/open_channel/test_max_tlc_value_in_flight.py @@ -74,81 +74,7 @@ def test_max_tlc_value_in_flight_is_zero(self): "invoice": invoice["invoice_address"], } ) - self.wait_payment_state(self.fiber1, payment["payment_hash"], "Failed") - # expected_error_message = "TemporaryChannelFailure" - # assert expected_error_message in payment["failed_error"] - - # invoice_balance = hex(160 * 100000000) - # payment_preimage = self.generate_random_preimage() - # invoice = self.fiber1.get_client().new_invoice( - # { - # "amount": invoice_balance, - # "currency": "Fibd", - # "description": "test invoice generated by node2", - # "expiry": "0xe10", - # "final_cltv": "0x28", - # "payment_preimage": payment_preimage, - # "hash_algorithm": "sha256", - # "udt_type_script": { - # "code_hash": self.udtContract.get_code_hash(True, self.node.rpcUrl), - # "hash_type": "type", - # "args": self.udtContract.get_owner_arg_by_lock_arg( - # self.account1["lock_arg"] - # ), - # }, - # } - # ) - # before_channel = self.fiber2.get_client().list_channels({}) - # print("node2 send 1 ckb failed ") - # with pytest.raises(Exception) as exc_info: - # self.fiber2.get_client().send_payment( - # { - # "invoice": invoice["invoice_address"], - # } - # ) - # expected_error_message = "no path found" - # assert expected_error_message in exc_info.value.args[0], ( - # f"Expected substring '{expected_error_message}' " - # f"not found in actual string '{exc_info.value.args[0]}'" - # ) - - channels = self.fiber1.get_client().list_channels( - {"peer_id": self.fiber2.get_peer_id()} - ) - N1N2_CHANNEL_ID = channels["channels"][0]["channel_id"] - self.fiber1.get_client().graph_channels() - - before_balance1 = self.Ckb_cli.wallet_get_capacity( - self.account1["address"]["testnet"] - ) - before_balance2 = self.Ckb_cli.wallet_get_capacity( - self.account2["address"]["testnet"] - ) - # shut down - self.fiber1.get_client().shutdown_channel( - { - "channel_id": N1N2_CHANNEL_ID, - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", - } - ) - # todo wait close tx commit - time.sleep(20) - after_balance1 = self.Ckb_cli.wallet_get_capacity( - self.account1["address"]["testnet"] - ) - after_balance2 = self.Ckb_cli.wallet_get_capacity( - self.account2["address"]["testnet"] - ) - print("before_balance1:", before_balance1) - print("before_balance2:", before_balance2) - print("after_balance1:", after_balance1) - print("after_balance2:", after_balance2) def test_max_tlc_value_in_flight_overflow(self): with pytest.raises(Exception) as exc_info: @@ -277,44 +203,6 @@ def test_ckb_max_tlc_value_in_flight_too_min(self): after_channel_2["channels"][0]["local_balance"], 16 ) == int(invoice_balance, 16) - channels = self.fiber1.get_client().list_channels( - {"peer_id": self.fiber2.get_peer_id()} - ) - N1N2_CHANNEL_ID = channels["channels"][0]["channel_id"] - self.fiber1.get_client().graph_channels() - - before_balance1 = self.Ckb_cli.wallet_get_capacity( - self.account1["address"]["testnet"] - ) - before_balance2 = self.Ckb_cli.wallet_get_capacity( - self.account2["address"]["testnet"] - ) - # shut down - self.fiber1.get_client().shutdown_channel( - { - "channel_id": N1N2_CHANNEL_ID, - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", - } - ) - # todo wait close tx commit - time.sleep(20) - after_balance1 = self.Ckb_cli.wallet_get_capacity( - self.account1["address"]["testnet"] - ) - after_balance2 = self.Ckb_cli.wallet_get_capacity( - self.account2["address"]["testnet"] - ) - print("before_balance1:", before_balance1) - print("before_balance2:", before_balance2) - print("after_balance1:", after_balance1) - print("after_balance2:", after_balance2) - assert after_balance2 - before_balance2 == 62 - def test_udt_max_tlc_value_in_flight_too_min(self): """ max_tlc_value_in_flight == 1 @@ -393,12 +281,12 @@ def test_udt_max_tlc_value_in_flight_too_min(self): ) before_channel = self.fiber1.get_client().list_channels({}) - self.fiber1.get_client().send_payment( + payment = self.fiber1.get_client().send_payment( { "invoice": invoice["invoice_address"], } ) - time.sleep(10) + self.wait_payment_state(self.fiber1, payment["payment_hash"], "Success") after_channel = self.fiber1.get_client().list_channels({}) assert int(before_channel["channels"][0]["local_balance"], 16) - int( after_channel["channels"][0]["local_balance"], 16 @@ -423,51 +311,17 @@ def test_udt_max_tlc_value_in_flight_too_min(self): time.sleep(1) print("node2 send 1 ckb failed ") before_channel_2 = self.fiber2.get_client().list_channels({}) - self.fiber2.get_client().send_payment( + payment = self.fiber2.get_client().send_payment( { "invoice": invoice["invoice_address"], } ) - time.sleep(10) + self.wait_payment_state(self.fiber2, payment["payment_hash"], "Success") after_channel_2 = self.fiber2.get_client().list_channels({}) assert int(before_channel_2["channels"][0]["local_balance"], 16) - int( after_channel_2["channels"][0]["local_balance"], 16 ) == int(invoice_balance, 16) - channels = self.fiber1.get_client().list_channels( - {"peer_id": self.fiber2.get_peer_id()} - ) - N1N2_CHANNEL_ID = channels["channels"][0]["channel_id"] - self.fiber1.get_client().graph_channels() - - before_balance1 = self.Ckb_cli.wallet_get_capacity( - self.account1["address"]["testnet"] - ) - before_balance2 = self.Ckb_cli.wallet_get_capacity( - self.account2["address"]["testnet"] - ) - # shut down - self.fiber1.get_client().shutdown_channel( - { - "channel_id": N1N2_CHANNEL_ID, - "close_script": self.get_account_script(self.fiber1.account_private), - "fee_rate": "0x3FC", - } - ) - # todo wait close tx commit - time.sleep(20) - after_balance1 = self.Ckb_cli.wallet_get_capacity( - self.account1["address"]["testnet"] - ) - after_balance2 = self.Ckb_cli.wallet_get_capacity( - self.account2["address"]["testnet"] - ) - print("before_balance1:", before_balance1) - print("before_balance2:", before_balance2) - print("after_balance1:", after_balance1) - print("after_balance2:", after_balance2) - assert after_balance2 - before_balance2 == 143 - # @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/450") def test_debug_ckb_max_tlc_value_in_flight_not_eq_default(self): self.fiber1.get_client().open_channel( @@ -510,7 +364,6 @@ def test_debug_ckb_max_tlc_value_in_flight_not_eq_default(self): "invoice": invoice["invoice_address"], } ) - self.wait_payment_state(self.fiber1, payment["payment_hash"], "Failed") # @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/450") @@ -649,40 +502,6 @@ def test_ckb_max_tlc_value_in_flight_not_eq_default(self): after_channel_2["channels"][0]["local_balance"], 16 ) == int(invoice_balance, 16) - channels = self.fiber1.get_client().list_channels( - {"peer_id": self.fiber2.get_peer_id()} - ) - N1N2_CHANNEL_ID = channels["channels"][0]["channel_id"] - self.fiber1.get_client().graph_channels() - - before_balance1 = self.Ckb_cli.wallet_get_capacity( - self.account1["address"]["testnet"] - ) - before_balance2 = self.Ckb_cli.wallet_get_capacity( - self.account2["address"]["testnet"] - ) - # shut down - self.fiber1.get_client().shutdown_channel( - { - "channel_id": N1N2_CHANNEL_ID, - "close_script": self.get_account_script(self.fiber1.account_private), - "fee_rate": "0x3FC", - } - ) - # todo wait close tx commit - time.sleep(20) - after_balance1 = self.Ckb_cli.wallet_get_capacity( - self.account1["address"]["testnet"] - ) - after_balance2 = self.Ckb_cli.wallet_get_capacity( - self.account2["address"]["testnet"] - ) - print("before_balance1:", before_balance1) - print("before_balance2:", before_balance2) - print("after_balance1:", after_balance1) - print("after_balance2:", after_balance2) - assert after_balance2 - before_balance2 == 62.90000057220459 - # @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/450") def test_udt_max_tlc_value_in_flight_not_eq_default(self): """ @@ -834,51 +653,3 @@ def test_udt_max_tlc_value_in_flight_not_eq_default(self): assert int(before_channel["channels"][0]["local_balance"], 16) - int( after_channel_2["channels"][0]["local_balance"], 16 ) == int(invoice_balance, 16) - - channels = self.fiber1.get_client().list_channels( - {"peer_id": self.fiber2.get_peer_id()} - ) - N1N2_CHANNEL_ID = channels["channels"][0]["channel_id"] - self.fiber1.get_client().graph_channels() - - before_balance1 = self.Ckb_cli.wallet_get_capacity( - self.account1["address"]["testnet"] - ) - before_balance2 = self.Ckb_cli.wallet_get_capacity( - self.account2["address"]["testnet"] - ) - # shut down - self.fiber1.get_client().shutdown_channel( - { - "channel_id": N1N2_CHANNEL_ID, - "close_script": self.get_account_script(self.fiber1.account_private), - "fee_rate": "0x3FC", - } - ) - # todo wait close tx commit - time.sleep(20) - after_balance1 = self.Ckb_cli.wallet_get_capacity( - self.account1["address"]["testnet"] - ) - after_balance2 = self.Ckb_cli.wallet_get_capacity( - self.account2["address"]["testnet"] - ) - print("before_balance1:", before_balance1) - print("before_balance2:", before_balance2) - print("after_balance1:", after_balance1) - print("after_balance2:", after_balance2) - assert after_balance2 - before_balance2 == 143 - account2_balance = self.udtContract.list_cell( - self.node.getClient(), - own_arg=self.account1["lock_arg"], - query_arg=self.account2["lock_arg"], - ) - account1_balance = self.udtContract.list_cell( - self.node.getClient(), - own_arg=self.account1["lock_arg"], - query_arg=self.account1["lock_arg"], - ) - print("account1:", account1_balance) - print("account2:", account2_balance) - assert account1_balance[-1]["balance"] == 99900000001 - assert account2_balance[-1]["balance"] == 99999999 diff --git a/test_cases/fiber/devnet/open_channel/test_public.py b/test_cases/fiber/devnet/open_channel/test_public.py index 12bf36b8..7230d5c3 100644 --- a/test_cases/fiber/devnet/open_channel/test_public.py +++ b/test_cases/fiber/devnet/open_channel/test_public.py @@ -22,14 +22,11 @@ def test_public_none(self): # "tlc_fee_proportional_millionths": "0x4B0", } ) - time.sleep(1) self.wait_for_channel_state( self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY", 120 ) - time.sleep(5) + time.sleep(2) # transfer - self.fiber1.get_client().graph_channels() - self.fiber1.get_client().graph_nodes() payment_preimage = self.generate_random_preimage() invoice_balance = 100 * 100000000 invoice = self.fiber2.get_client().new_invoice( @@ -83,7 +80,8 @@ def test_public_none(self): } ) # todo wait close tx commit - time.sleep(20) + tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 100) + self.Miner.miner_until_tx_committed(self.node, tx_hash) after_balance1 = self.Ckb_cli.wallet_get_capacity( self.account1["address"]["testnet"] ) @@ -95,19 +93,15 @@ def test_public_none(self): print("after_balance1:", after_balance1) print("after_balance2:", after_balance2) - @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/170") def test_public_false(self): """ https://github.com/nervosnetwork/fiber/issues/268 https://github.com/nervosnetwork/fiber/issues/170 public : false - - todo: add check 多路支付会失败 - Returns: """ self.fiber1.connect_peer(self.fiber2) - time.sleep(5) + time.sleep(1) temporary_channel_id = self.fiber1.get_client().open_channel( { "peer_id": self.fiber2.get_peer_id(), @@ -116,14 +110,16 @@ def test_public_false(self): # "tlc_fee_proportional_millionths": "0x4B0", } ) - time.sleep(1) self.wait_for_channel_state( self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY", 120 ) - time.sleep(5) + time.sleep(2) + + # graph_channels is none + channels = self.fiber1.get_client().graph_channels({}) + assert channels["channels"] == [] + # transfer - self.fiber1.get_client().graph_channels() - self.fiber1.get_client().graph_nodes() payment_preimage = self.generate_random_preimage() invoice_balance = 100 * 100000000 invoice = self.fiber2.get_client().new_invoice( @@ -139,12 +135,13 @@ def test_public_false(self): ) before_channel = self.fiber1.get_client().list_channels({}) - self.fiber1.get_client().send_payment( + payment = self.fiber1.get_client().send_payment( { "invoice": invoice["invoice_address"], } ) - time.sleep(10) + self.wait_payment_state(self.fiber1, payment["payment_hash"], "Success") + after_channel = self.fiber1.get_client().list_channels({}) assert ( int(before_channel["channels"][0]["local_balance"], 16) @@ -177,7 +174,9 @@ def test_public_false(self): } ) # todo wait close tx commit - time.sleep(20) + tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 100) + self.Miner.miner_until_tx_committed(self.node, tx_hash) + after_balance1 = self.Ckb_cli.wallet_get_capacity( self.account1["address"]["testnet"] ) @@ -197,7 +196,7 @@ def test_public_true(self): """ self.fiber1.connect_peer(self.fiber2) - time.sleep(5) + time.sleep(1) temporary_channel_id = self.fiber1.get_client().open_channel( { "peer_id": self.fiber2.get_peer_id(), @@ -229,12 +228,13 @@ def test_public_true(self): ) before_channel = self.fiber1.get_client().list_channels({}) - self.fiber1.get_client().send_payment( + payment = self.fiber1.get_client().send_payment( { "invoice": invoice["invoice_address"], } ) - time.sleep(10) + self.wait_payment_state(self.fiber1, payment["payment_hash"], "Success") + after_channel = self.fiber1.get_client().list_channels({}) assert ( int(before_channel["channels"][0]["local_balance"], 16) @@ -267,7 +267,8 @@ def test_public_true(self): } ) # todo wait close tx commit - time.sleep(20) + tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 100) + self.Miner.miner_until_tx_committed(self.node, tx_hash) after_balance1 = self.Ckb_cli.wallet_get_capacity( self.account1["address"]["testnet"] ) diff --git a/test_cases/fiber/devnet/open_channel/test_shutdown_script.py b/test_cases/fiber/devnet/open_channel/test_shutdown_script.py index 0dd2ae40..5f0ae615 100644 --- a/test_cases/fiber/devnet/open_channel/test_shutdown_script.py +++ b/test_cases/fiber/devnet/open_channel/test_shutdown_script.py @@ -97,8 +97,9 @@ def test_script_udt_none(self): "fee_rate": "0x3FC", } ) - # todo wait close tx commit - time.sleep(40) + tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 100) + self.Miner.miner_until_tx_committed(self.node, tx_hash) + after_balance1 = self.node.getClient().get_cells_capacity( { "script": { @@ -168,8 +169,9 @@ def test_shutdown_script(self): "fee_rate": "0x3FC", } ) - # todo wait close tx commit - time.sleep(40) + tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 100) + self.Miner.miner_until_tx_committed(self.node, tx_hash) + after_balance1 = self.node.getClient().get_cells_capacity( { "script": { @@ -204,6 +206,8 @@ def test_shutdown_script_udt(self): ), } ) + tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 100) + open_channel_message = self.get_tx_message(tx_hash) self.wait_for_channel_state( self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY", 120 ) @@ -221,41 +225,41 @@ def test_shutdown_script_udt(self): "script": { "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", "hash_type": "type", - "args": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9f9bd7e06f3ecf4be0f2fcd2188b23f1b9f9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + "args": self.account1["lock_arg"], }, "script_type": "lock", "script_search_mode": "prefix", } ) # shut down - self.fiber2.get_client().shutdown_channel( + self.fiber1.get_client().shutdown_channel( { "channel_id": N1N2_CHANNEL_ID, "close_script": { "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", "hash_type": "type", - "args": self.account2["lock_arg"], + "args": self.account1["lock_arg"], }, "fee_rate": "0x3FC", } ) - # todo wait close tx commit - time.sleep(40) + tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 100) + self.Miner.miner_until_tx_committed(self.node, tx_hash) + message = self.get_tx_message(tx_hash) + print("message:", message) + assert message["fee"] < 10000 after_balance1 = self.node.getClient().get_cells_capacity( { "script": { "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", "hash_type": "type", - "args": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9f9bd7e06f3ecf4be0f2fcd2188b23f1b9f9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce89bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + "args": self.account1["lock_arg"], }, "script_type": "lock", "script_search_mode": "prefix", } ) - print("before_balance1:", before_balance1) - print("after_balance1:", after_balance1) assert ( int(after_balance1["capacity"], 16) - int(before_balance1["capacity"], 16) - == 732 * 100000000 + >= 700 * 100000000 ) - # todo check udt balance diff --git a/test_cases/fiber/devnet/open_channel/test_tlc_expiry_delta.py b/test_cases/fiber/devnet/open_channel/test_tlc_expiry_delta.py new file mode 100644 index 00000000..3f64c35c --- /dev/null +++ b/test_cases/fiber/devnet/open_channel/test_tlc_expiry_delta.py @@ -0,0 +1,73 @@ +import time + +import pytest + +from framework.basic_fiber import FiberTest + + +class TestTlcLocktimeExpiryDelta(FiberTest): + start_fiber_config = {"fiber_watchtower_check_interval_seconds": 5} + + @pytest.mark.skip("todo") + def test_tlc_expiry_delta_none(self): + """ + tlc_expiry_delta = none + Returns: + """ + # self.test_linked_peer() + + def test_tlc_expiry_delta_is_zero_or_bigger(self): + """ + tlc_expiry_delta = 0 + Returns: + """ + with pytest.raises(Exception) as exc_info: + temporary_channel_id = self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(200 * 100000000), + "public": True, + "tlc_expiry_delta": "0x0", + # "tlc_fee_proportional_millionths": "0x4B0", + } + ) + expected_error_message = ( + "TLC expiry delta is too small, expect larger than 900000" + ) + assert expected_error_message in exc_info.value.args[0], ( + f"Expected substring '{expected_error_message}' " + f"not found in actual string '{exc_info.value.args[0]}'" + ) + temporary_channel_id = self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(200 * 100000000), + "public": True, + "tlc_expiry_delta": "0xffffffff", + # "tlc_fee_proportional_millionths": "0x4B0", + } + ) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY", 120 + ) + + @pytest.mark.skip("todo") + def test_tlc_expiry_delta_is_1(self): + """ + tlc_expiry_delta = 1 + Returns: + todo: + qa: open_channel 的 tlc_expiry_delta 作用是什么呢? 有点没理解 @by 我要怎么才能测到这个参数 + A 给 B 发送一个 tlc,如果 B 知道原相,那 B 可以取走 tlc 里面的资金,否则过了时间 tlc_expiry_delta 之后,A 可以取回 tlc 里面的资金。 + 那a 可以怎么取回tlc的资金 + 要在 watchtower 里面做,我们现在似乎没有这个功能 + """ + + @pytest.mark.skip("todo") + def test_tlc_expiry_delta_not_eq_default(self): + """ + tlc_expiry_delta != default + + Returns: + + """ diff --git a/test_cases/fiber/devnet/open_channel/test_tlc_fee_proportional_millionths.py b/test_cases/fiber/devnet/open_channel/test_tlc_fee_proportional_millionths.py index dcb121be..0b18987a 100644 --- a/test_cases/fiber/devnet/open_channel/test_tlc_fee_proportional_millionths.py +++ b/test_cases/fiber/devnet/open_channel/test_tlc_fee_proportional_millionths.py @@ -106,6 +106,49 @@ def test_tlc_fee_proportional_millionths_overflow(self): f"not found in actual string '{exc_info.value.args[0]}'" ) + def test_tlc_fee_proportional_millionths_100_100000000_1000_1000(self): + """ + tlc_fee_proportional_millionths :100 * 100000000 * 1000 * 1000 + 发送 1,收取100ckb的手续费 + Returns: + """ + temporary_channel_id = self.fiber2.get_client().open_channel( + { + "peer_id": self.fiber1.get_peer_id(), + "funding_amount": hex(1000 * 100000000), + "public": True, + # "tlc_min_value": hex(2 * 100000000) + # "funding_fee_rate": "0xffff", + "tlc_fee_proportional_millionths": hex(100 * 100000000 * 1000 * 1000), + } + ) + self.wait_for_channel_state( + self.fiber2.get_client(), self.fiber1.get_peer_id(), "CHANNEL_READY", 120 + ) + channel = self.fiber1.get_client().list_channels({}) + assert channel["channels"][0]["tlc_fee_proportional_millionths"] == "0x3e8" + channel = self.fiber2.get_client().list_channels({}) + assert channel["channels"][0]["tlc_fee_proportional_millionths"] == hex( + 100 * 100000000 * 1000000 + ) + fiber3 = self.start_new_fiber(self.generate_account(10000)) + fiber3.connect_peer(self.fiber2) + time.sleep(1) + fiber3.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(1000 * 100000000), + "public": True, + # "tlc_min_value": hex(2 * 100000000) + # "funding_fee_rate": "0xffff", + "tlc_fee_proportional_millionths": "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + } + ) + self.wait_for_channel_state( + fiber3.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY", 120 + ) + self.send_payment(fiber3, self.fiber1, 1) + def test_ckb_tlc_fee_proportional_millionths_not_eq_default(self): """ tlc_fee_proportional_millionths != default @@ -577,4 +620,45 @@ def test_udt_tlc_fee_proportional_millionths_not_eq_default(self): after_channel_12["channels"][0]["local_balance"], 16 ) == int(invoice_balance, 16) - # todo tlc_fee_proportional_millionths add more test case + def test_ckb_tlc_fee_proportional_millionths_is_zero(self): + """ + tlc_fee_proportional_millionths == 0 + + Returns: + """ + account3_private_key = self.generate_account(1000) + new_fiber = self.start_new_fiber(account3_private_key) + self.fiber2.connect_peer(new_fiber) + time.sleep(1) + temporary_channel_id = self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(500 * 100000000), + "public": True, + # "funding_fee_rate": "0xffff", + # "tlc_fee_proportional_millionths": hex(1000000), + } + ) + time.sleep(1) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY", 120 + ) + fiber2_tlc_fee = 0 + temporary_channel_id = self.fiber2.get_client().open_channel( + { + "peer_id": new_fiber.get_peer_id(), + "funding_amount": hex(1000 * 100000000), + "public": True, + "tlc_fee_proportional_millionths": hex(fiber2_tlc_fee), + # "tlc_min_value": hex(2 * 100000000) + # "funding_fee_rate": "0xffff", + # "tlc_fee_proportional_millionths": "0x4B0", + } + ) + time.sleep(1) + self.wait_for_channel_state( + self.fiber2.get_client(), new_fiber.get_peer_id(), "CHANNEL_READY", 120 + ) + payment_hash = self.send_payment(self.fiber1, new_fiber, 100 * 100000000) + payment = self.fiber1.get_client().get_payment({"payment_hash": payment_hash}) + assert payment["fee"] == "0x0" diff --git a/test_cases/fiber/devnet/open_channel/test_tlc_locktime_expiry_delta.py b/test_cases/fiber/devnet/open_channel/test_tlc_locktime_expiry_delta.py deleted file mode 100644 index 0a177cf4..00000000 --- a/test_cases/fiber/devnet/open_channel/test_tlc_locktime_expiry_delta.py +++ /dev/null @@ -1,125 +0,0 @@ -import time - -import pytest - -from framework.basic_fiber import FiberTest - - -class TestTlcLocktimeExpiryDelta(FiberTest): - - @pytest.mark.skip("todo") - def test_tlc_locktime_expiry_delta_none(self): - """ - tlc_locktime_expiry_delta = none - Returns: - """ - # self.test_linked_peer() - - def test_tlc_locktime_expiry_delta_is_zero(self): - """ - tlc_locktime_expiry_delta = 0 - Returns: - """ - - temporary_channel_id = self.fiber1.get_client().open_channel( - { - "peer_id": self.fiber2.get_peer_id(), - "funding_amount": hex(200 * 100000000), - "public": True, - "tlc_locktime_expiry_delta": "0x0", - # "tlc_fee_proportional_millionths": "0x4B0", - } - ) - self.wait_for_channel_state( - self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY", 120 - ) - time.sleep(1) - # transfer - self.fiber1.get_client().graph_channels() - self.fiber1.get_client().graph_nodes() - payment_preimage = self.generate_random_preimage() - invoice_balance = 100 * 100000000 - invoice = self.fiber2.get_client().new_invoice( - { - "amount": hex(invoice_balance), - "currency": "Fibd", - "description": "test invoice generated by node2", - "expiry": "0xe10", - "final_cltv": "0x28", - "payment_preimage": payment_preimage, - "hash_algorithm": "sha256", - } - ) - before_channel = self.fiber1.get_client().list_channels({}) - - self.fiber1.get_client().send_payment( - { - "invoice": invoice["invoice_address"], - } - ) - time.sleep(10) - after_channel = self.fiber1.get_client().list_channels({}) - assert ( - int(before_channel["channels"][0]["local_balance"], 16) - - int(after_channel["channels"][0]["local_balance"], 16) - == invoice_balance - ) - - channels = self.fiber1.get_client().list_channels( - {"peer_id": self.fiber2.get_peer_id()} - ) - N1N2_CHANNEL_ID = channels["channels"][0]["channel_id"] - self.fiber1.get_client().graph_channels() - - before_balance1 = self.Ckb_cli.wallet_get_capacity( - self.account1["address"]["testnet"] - ) - before_balance2 = self.Ckb_cli.wallet_get_capacity( - self.account2["address"]["testnet"] - ) - # shut down - self.fiber1.get_client().shutdown_channel( - { - "channel_id": N1N2_CHANNEL_ID, - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", - } - ) - # todo wait close tx commit - time.sleep(20) - after_balance1 = self.Ckb_cli.wallet_get_capacity( - self.account1["address"]["testnet"] - ) - after_balance2 = self.Ckb_cli.wallet_get_capacity( - self.account2["address"]["testnet"] - ) - print("before_balance1:", before_balance1) - print("before_balance2:", before_balance2) - print("after_balance1:", after_balance1) - print("after_balance2:", after_balance2) - assert after_balance2 - before_balance2 == 162 - - @pytest.mark.skip("todo") - def test_tlc_locktime_expiry_delta_is_1(self): - """ - tlc_locktime_expiry_delta = 1 - Returns: - todo: - qa: open_channel 的 tlc_locktime_expiry_delta 作用是什么呢? 有点没理解 @by 我要怎么才能测到这个参数 - A 给 B 发送一个 tlc,如果 B 知道原相,那 B 可以取走 tlc 里面的资金,否则过了时间 tlc_locktime_expiry_delta 之后,A 可以取回 tlc 里面的资金。 - 那a 可以怎么取回tlc的资金 - 要在 watchtower 里面做,我们现在似乎没有这个功能 - """ - - @pytest.mark.skip("todo") - def test_tlc_locktime_expiry_delta_not_eq_default(self): - """ - tlc_locktime_expiry_delta != default - - Returns: - - """ diff --git a/test_cases/fiber/devnet/open_channel/test_tlc_min_value.py b/test_cases/fiber/devnet/open_channel/test_tlc_min_value.py index 4d270bca..5d722e6c 100644 --- a/test_cases/fiber/devnet/open_channel/test_tlc_min_value.py +++ b/test_cases/fiber/devnet/open_channel/test_tlc_min_value.py @@ -81,8 +81,10 @@ def test_tlc_min_value_none(self): "fee_rate": "0x3FC", } ) - # todo wait close tx commit - time.sleep(20) + + tx_hash = self.wait_and_check_tx_pool_fee(1000, False) + self.Miner.miner_until_tx_committed(self.node, tx_hash) + after_balance1 = self.Ckb_cli.wallet_get_capacity( self.account1["address"]["testnet"] ) @@ -183,7 +185,6 @@ def test_tlc_min_value_is_not_zero(self): "fee_rate": "0x3FC", } ) - # todo wait close tx commit tx_hash = self.wait_and_check_tx_pool_fee(1000, False) tx_response = self.get_tx_message(tx_hash) print("tx response:", tx_response) @@ -434,143 +435,3 @@ def test_udt_tlc_min_value_not_eq_default(self): ), "udt_capacity": 18016000000, } in tx_response["output_cells"] - - # fiber remove tlc_max_value option - # - # def test_udt_tlc_min_value_gt_tlc_max_value(self): - # """ - # tlc_min_value > tlc_max_value - # Returns: - # """ - # - # self.fiber1.get_client().open_channel( - # { - # "peer_id": self.fiber2.get_peer_id(), - # "funding_amount": hex(1000 * 100000000), - # "public": True, - # "tlc_min_value": hex(160 * 100000000), - # "tlc_max_value": hex(160 * 100000000 - 1), - # "funding_udt_type_script": { - # "code_hash": self.udtContract.get_code_hash(True, self.node.rpcUrl), - # "hash_type": "type", - # "args": self.udtContract.get_owner_arg_by_lock_arg( - # self.account1["lock_arg"] - # ), - # }, - # # "tlc_fee_proportional_millionths": "0x4B0", - # } - # ) - # - # time.sleep(1) - # self.wait_for_channel_state( - # self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY", 120 - # ) - # time.sleep(5) - # # transfer - # self.fiber1.get_client().graph_channels() - # self.fiber1.get_client().graph_nodes() - # payment_preimage = self.generate_random_preimage() - # channels = self.fiber1.get_client().list_channels({}) - # invoice_balance = hex(500 * 100000000) - # - # invoice = self.fiber2.get_client().new_invoice( - # { - # "amount": invoice_balance, - # "currency": "Fibd", - # "description": "test invoice generated by node2", - # "expiry": "0xe10", - # "final_cltv": "0x28", - # "payment_preimage": payment_preimage, - # "hash_algorithm": "sha256", - # "udt_type_script": { - # "code_hash": self.udtContract.get_code_hash(True, self.node.rpcUrl), - # "hash_type": "type", - # "args": self.udtContract.get_owner_arg_by_lock_arg( - # self.account1["lock_arg"] - # ), - # }, - # } - # ) - # before_channel = self.fiber1.get_client().list_channels({}) - # - # self.fiber1.get_client().send_payment( - # { - # "invoice": invoice["invoice_address"], - # } - # ) - # time.sleep(10) - # after_channel = self.fiber1.get_client().list_channels({}) - # assert int(before_channel["channels"][0]["local_balance"], 16) - int( - # after_channel["channels"][0]["local_balance"], 16 - # ) == int(invoice_balance, 16) - # - # invoice_balance = hex(160 * 100000000) - # payment_preimage = self.generate_random_preimage() - # invoice = self.fiber1.get_client().new_invoice( - # { - # "amount": invoice_balance, - # "currency": "Fibd", - # "description": "test invoice generated by node2", - # "expiry": "0xe10", - # "final_cltv": "0x28", - # "payment_preimage": payment_preimage, - # "hash_algorithm": "sha256", - # "udt_type_script": { - # "code_hash": self.udtContract.get_code_hash(True, self.node.rpcUrl), - # "hash_type": "type", - # "args": self.udtContract.get_owner_arg_by_lock_arg( - # self.account1["lock_arg"] - # ), - # }, - # } - # ) - # before_channel = self.fiber2.get_client().list_channels({}) - # print("node2 send 1 ckb failed ") - # with pytest.raises(Exception) as exc_info: - # self.fiber2.get_client().send_payment( - # { - # "invoice": invoice["invoice_address"], - # } - # ) - # expected_error_message = "Failed to build route" - # assert expected_error_message in exc_info.value.args[0], ( - # f"Expected substring '{expected_error_message}' " - # f"not found in actual string '{exc_info.value.args[0]}'" - # ) - # - # channels = self.fiber1.get_client().list_channels( - # {"peer_id": self.fiber2.get_peer_id()} - # ) - # N1N2_CHANNEL_ID = channels["channels"][0]["channel_id"] - # self.fiber1.get_client().graph_channels() - # - # before_balance1 = self.Ckb_cli.wallet_get_capacity( - # self.account1["address"]["testnet"] - # ) - # before_balance2 = self.Ckb_cli.wallet_get_capacity( - # self.account2["address"]["testnet"] - # ) - # # shut down - # self.fiber1.get_client().shutdown_channel( - # { - # "channel_id": N1N2_CHANNEL_ID, - # "close_script": { - # "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - # "hash_type": "type", - # "args": self.account1["lock_arg"], - # }, - # "fee_rate": "0x3FC", - # } - # ) - # # todo wait close tx commit - # time.sleep(20) - # after_balance1 = self.Ckb_cli.wallet_get_capacity( - # self.account1["address"]["testnet"] - # ) - # after_balance2 = self.Ckb_cli.wallet_get_capacity( - # self.account2["address"]["testnet"] - # ) - # print("before_balance1:", before_balance1) - # print("before_balance2:", before_balance2) - # print("after_balance1:", after_balance1) - # print("after_balance2:", after_balance2) diff --git a/test_cases/fiber/devnet/remove_tlc/test_remove_tlc.py b/test_cases/fiber/devnet/remove_tlc/test_remove_tlc.py new file mode 100644 index 00000000..e69de29b diff --git a/test_cases/fiber/devnet/send_payment/test_send_payment.py b/test_cases/fiber/devnet/send_payment/module/test_send_payment.py similarity index 100% rename from test_cases/fiber/devnet/send_payment/test_send_payment.py rename to test_cases/fiber/devnet/send_payment/module/test_send_payment.py diff --git a/test_cases/fiber/devnet/send_payment/test_send_payment_with_shutdown.py b/test_cases/fiber/devnet/send_payment/module/test_send_payment_with_shutdown.py similarity index 100% rename from test_cases/fiber/devnet/send_payment/test_send_payment_with_shutdown.py rename to test_cases/fiber/devnet/send_payment/module/test_send_payment_with_shutdown.py diff --git a/test_cases/fiber/devnet/send_payment/test_send_payment_with_update_channel.py b/test_cases/fiber/devnet/send_payment/module/test_send_payment_with_update_channel.py similarity index 100% rename from test_cases/fiber/devnet/send_payment/test_send_payment_with_update_channel.py rename to test_cases/fiber/devnet/send_payment/module/test_send_payment_with_update_channel.py diff --git a/test_cases/fiber/devnet/send_payment/test_force_restart.py b/test_cases/fiber/devnet/send_payment/offline/test_force_restart.py similarity index 100% rename from test_cases/fiber/devnet/send_payment/test_force_restart.py rename to test_cases/fiber/devnet/send_payment/offline/test_force_restart.py diff --git a/test_cases/fiber/devnet/send_payment/test_restart.py b/test_cases/fiber/devnet/send_payment/offline/test_restart.py similarity index 100% rename from test_cases/fiber/devnet/send_payment/test_restart.py rename to test_cases/fiber/devnet/send_payment/offline/test_restart.py diff --git a/test_cases/fiber/devnet/send_payment/offline/test_send_payment_with_stop.py b/test_cases/fiber/devnet/send_payment/offline/test_send_payment_with_stop.py new file mode 100644 index 00000000..c690c4e3 --- /dev/null +++ b/test_cases/fiber/devnet/send_payment/offline/test_send_payment_with_stop.py @@ -0,0 +1,27 @@ +from framework.basic_fiber import FiberTest + + +class TestSendPaymentWithStop(FiberTest): + pass + # debug = True + # def test_0000(self): + # self.start_new_fiber(self.generate_account(10000)) + # self.start_new_fiber(self.generate_account(10000)) + # for i in range(len(self.fibers) - 1): + # self.open_channel(self.fibers[i], self.fibers[i + 1], 1000 * 100000000, 1) + # + # for i in range(100): + # self.send_payment(self.fibers[0], self.fibers[-1], 1, False) + # self.fibers[0].stop() + # self.fibers[-1].stop() + + # def test_cccc(self): + # self.start_new_mock_fiber("") + # self.start_new_mock_fiber("") + # # for i in range(1000): + # # self.send_payment(self.fibers[0], self.fibers[-1], 1, False) + # # self.send_payment(self.fibers[-1], self.fibers[0], 1, False) + # # self.fiber1.stop() + # self.fibers[1].get_client().list_channels({ + # "peer_id":self.fibers[2].get_peer_id() + # }) diff --git a/test_cases/fiber/devnet/send_payment/test_stop_mid_node.py b/test_cases/fiber/devnet/send_payment/offline/test_stop_mid_node.py similarity index 100% rename from test_cases/fiber/devnet/send_payment/test_stop_mid_node.py rename to test_cases/fiber/devnet/send_payment/offline/test_stop_mid_node.py diff --git a/test_cases/fiber/devnet/send_payment/test_allow_self_payment.py b/test_cases/fiber/devnet/send_payment/params/test_allow_self_payment.py similarity index 99% rename from test_cases/fiber/devnet/send_payment/test_allow_self_payment.py rename to test_cases/fiber/devnet/send_payment/params/test_allow_self_payment.py index 2172b980..0708be5c 100644 --- a/test_cases/fiber/devnet/send_payment/test_allow_self_payment.py +++ b/test_cases/fiber/devnet/send_payment/params/test_allow_self_payment.py @@ -382,4 +382,3 @@ def test_a1_to_b1_to_c1_a2_2(self): } ) self.wait_payment_state(self.fiber1, payment1["payment_hash"], "Success") - # after fix todo add check diff --git a/test_cases/fiber/devnet/send_payment/test_amount.py b/test_cases/fiber/devnet/send_payment/params/test_amount.py similarity index 100% rename from test_cases/fiber/devnet/send_payment/test_amount.py rename to test_cases/fiber/devnet/send_payment/params/test_amount.py diff --git a/test_cases/fiber/devnet/send_payment/test_custom_records.py b/test_cases/fiber/devnet/send_payment/params/test_custom_records.py similarity index 100% rename from test_cases/fiber/devnet/send_payment/test_custom_records.py rename to test_cases/fiber/devnet/send_payment/params/test_custom_records.py diff --git a/test_cases/fiber/devnet/send_payment/test_dry_run.py b/test_cases/fiber/devnet/send_payment/params/test_dry_run.py similarity index 100% rename from test_cases/fiber/devnet/send_payment/test_dry_run.py rename to test_cases/fiber/devnet/send_payment/params/test_dry_run.py diff --git a/test_cases/fiber/devnet/send_payment/test_final_tlc_expiry_delta.py b/test_cases/fiber/devnet/send_payment/params/test_final_tlc_expiry_delta.py similarity index 100% rename from test_cases/fiber/devnet/send_payment/test_final_tlc_expiry_delta.py rename to test_cases/fiber/devnet/send_payment/params/test_final_tlc_expiry_delta.py diff --git a/test_cases/fiber/devnet/send_payment/test_hophint.py b/test_cases/fiber/devnet/send_payment/params/test_hophint.py similarity index 98% rename from test_cases/fiber/devnet/send_payment/test_hophint.py rename to test_cases/fiber/devnet/send_payment/params/test_hophint.py index c278d93d..e353bf6a 100644 --- a/test_cases/fiber/devnet/send_payment/test_hophint.py +++ b/test_cases/fiber/devnet/send_payment/params/test_hophint.py @@ -206,9 +206,7 @@ def test_not_hophit_issue620(self): print(f"b-a,channel:{channels}") ba_channel_outpoint = channels["channels"][0]["channel_outpoint"] print(f"b-a, channel_outpoint:{ba_channel_outpoint}") - assert ( - payment["router"]["nodes"][0]["channel_outpoint"] == ba_channel_outpoint - ) + assert payment["router"][0]["channel_outpoint"] == ba_channel_outpoint except Exception as e: # 如果走的是b-c-d-a,不通过hophit应该发送失败 error_message = str(e) @@ -289,8 +287,8 @@ def test_use_hophit(self): .node_info()["node_id"], # 填的是 d 的 pubkey,表示在 d 节点使用 channel_outpoint 到 a "channel_outpoint": da_channel_outpoint, - "fee_rate": 1000, - "tlc_expiry_delta": 1000, + "fee_rate": hex(1000), + "tlc_expiry_delta": hex(1000), } ], } @@ -367,8 +365,8 @@ def test_use_hophit_simple(self): "node_id" ], # 填的是 d 的 pubkey,表示在 d 节点使用 channel_outpoint 到 a "channel_outpoint": da_channel_outpoint, - "fee_rate": 1000, - "tlc_expiry_delta": 1000, + "fee_rate": hex(1000), + "tlc_expiry_delta": hex(1000), } ], } diff --git a/test_cases/fiber/devnet/send_payment/params/test_max_fee_amount.py b/test_cases/fiber/devnet/send_payment/params/test_max_fee_amount.py new file mode 100644 index 00000000..29a10023 --- /dev/null +++ b/test_cases/fiber/devnet/send_payment/params/test_max_fee_amount.py @@ -0,0 +1,85 @@ +import pytest + +from framework.basic_fiber import FiberTest + + +class TestMaxFeeAmount(FiberTest): + + def test_fee(self): + account_private = self.generate_account(1000) + self.fiber3 = self.start_new_fiber(account_private) + self.fiber3.connect_peer(self.fiber2) + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(100000000 * 100000000), + "public": True, + } + ) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" + ) + self.fiber2.get_client().open_channel( + { + "peer_id": self.fiber3.get_peer_id(), + "funding_amount": hex(100000000 * 100000000), + "public": True, + } + ) + self.wait_for_channel_state( + self.fiber3.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" + ) + + # send to node1 -> node2 no fee max_fee = 0 + payment1 = self.fiber1.get_client().send_payment( + { + "target_pubkey": self.fiber2.get_client().node_info()["node_id"], + "amount": hex(1000000 * 100000000), + "keysend": True, + "max_fee_amount": hex(0), + "dry_run": True, + } + ) + # send to node1 - > node3 need fee 10000 but max_fee = 0 + with pytest.raises(Exception) as exc_info: + payment1 = self.fiber1.get_client().send_payment( + { + "target_pubkey": self.fiber3.get_client().node_info()["node_id"], + "amount": hex(1000000 * 100000000), + "keysend": True, + "max_fee_amount": hex(0), + "dry_run": True, + } + ) + expected_error_message = "Failed to build route" + assert expected_error_message in exc_info.value.args[0], ( + f"Expected substring '{expected_error_message}' " + f"not found in actual string '{exc_info.value.args[0]}'" + ) + + payment1 = self.fiber1.get_client().send_payment( + { + "target_pubkey": self.fiber3.get_client().node_info()["node_id"], + "amount": hex(1000000 * 100000000), + "keysend": True, + "dry_run": True, + } + ) + payment1 = self.fiber1.get_client().send_payment( + { + "target_pubkey": self.fiber3.get_client().node_info()["node_id"], + "amount": hex(1000000 * 100000000), + "keysend": True, + "max_fee_amount": payment1["fee"], + } + ) + self.wait_payment_state(self.fiber1, payment1["payment_hash"]) + + # + # + # def test_max_fee_amount_is_none(self): + # """ + # max_fee_amount == node 代表什么 + # Returns: + # + # """ diff --git a/test_cases/fiber/devnet/send_payment/test_max_parts.py b/test_cases/fiber/devnet/send_payment/params/test_max_parts.py similarity index 100% rename from test_cases/fiber/devnet/send_payment/test_max_parts.py rename to test_cases/fiber/devnet/send_payment/params/test_max_parts.py diff --git a/test_cases/fiber/devnet/send_payment/test_payment_hash.py b/test_cases/fiber/devnet/send_payment/params/test_payment_hash.py similarity index 98% rename from test_cases/fiber/devnet/send_payment/test_payment_hash.py rename to test_cases/fiber/devnet/send_payment/params/test_payment_hash.py index 20d993f2..8b3a1bc0 100644 --- a/test_cases/fiber/devnet/send_payment/test_payment_hash.py +++ b/test_cases/fiber/devnet/send_payment/params/test_payment_hash.py @@ -58,9 +58,10 @@ def test_payment_hash_not_exist(self): "currency": parse_invoice["invoice"]["currency"], "payment_hash": self.generate_random_preimage(), "amount": invoice["invoice"]["amount"], - "dry_run": True, + # "dry_run": True, } ) + self.wait_payment_state(self.fiber1, payment1["payment_hash"], "Failed") def test_rand_hash_Musig2VerifyError(self): account_private = self.generate_account(1000) diff --git a/test_cases/fiber/devnet/send_payment/params/test_timeout.py b/test_cases/fiber/devnet/send_payment/params/test_timeout.py new file mode 100644 index 00000000..a5a6a2a7 --- /dev/null +++ b/test_cases/fiber/devnet/send_payment/params/test_timeout.py @@ -0,0 +1,40 @@ +from framework.basic_fiber import FiberTest + + +class TestTimeout(FiberTest): + + def test_01(self): + self.fiber3 = self.start_new_fiber(self.generate_account(1000)) + self.open_channel(self.fiber1, self.fiber2, 1000 * 100000000, 1000 * 100000000) + self.open_channel(self.fiber2, self.fiber3, 1000 * 100000000, 1000 * 100000000) + payment = self.fiber1.get_client().send_payment( + { + "target_pubkey": self.fiber3.get_client().node_info()["node_id"], + "amount": hex(10 * 100000000), + "keysend": True, + "timeout": hex(0), + } + ) + self.wait_payment_state(self.fiber1, payment["payment_hash"], "Success", 100) + + # for i in range(100): + # payment = self.fiber1.get_client().send_payment({ + # "target_pubkey": self.fiber3.get_client().node_info()["node_id"], + # "amount": hex(1 * 10000000), + # "keysend": True, + # "timeout": hex(0) + # }) + # payments.append(payment) + # for i in range(100): + # payment = payments[i] + # print(i, payment) + # self.wait_payment_finished(self.fiber1, payment["payment_hash"], 1000) + # payment_result = [] + # for i in range(100): + # ret = self.fiber1.get_client().get_payment({ + # "payment_hash": payments[i]["payment_hash"] + # }) + # payment_result.append(ret) + # for i in range(100): + # ret = payment_result[i] + # print(i, ret) diff --git a/test_cases/fiber/devnet/send_payment/test_tlc_expiry_limit.py b/test_cases/fiber/devnet/send_payment/params/test_tlc_expiry_limit.py similarity index 100% rename from test_cases/fiber/devnet/send_payment/test_tlc_expiry_limit.py rename to test_cases/fiber/devnet/send_payment/params/test_tlc_expiry_limit.py diff --git a/test_cases/fiber/devnet/send_payment/test_tlc_fee.py b/test_cases/fiber/devnet/send_payment/params/test_tlc_fee.py similarity index 100% rename from test_cases/fiber/devnet/send_payment/test_tlc_fee.py rename to test_cases/fiber/devnet/send_payment/params/test_tlc_fee.py diff --git a/test_cases/fiber/devnet/send_payment/test_used_preimage.py b/test_cases/fiber/devnet/send_payment/params/test_used_preimage.py similarity index 100% rename from test_cases/fiber/devnet/send_payment/test_used_preimage.py rename to test_cases/fiber/devnet/send_payment/params/test_used_preimage.py diff --git a/test_cases/fiber/devnet/send_payment/test_find_path.py b/test_cases/fiber/devnet/send_payment/path/test_find_path.py similarity index 100% rename from test_cases/fiber/devnet/send_payment/test_find_path.py rename to test_cases/fiber/devnet/send_payment/path/test_find_path.py diff --git a/test_cases/fiber/devnet/send_payment/test_long_router.py b/test_cases/fiber/devnet/send_payment/path/test_long_router.py similarity index 100% rename from test_cases/fiber/devnet/send_payment/test_long_router.py rename to test_cases/fiber/devnet/send_payment/path/test_long_router.py diff --git a/test_cases/fiber/devnet/send_payment/test_mutil_channel.py b/test_cases/fiber/devnet/send_payment/path/test_mutil_channel.py similarity index 100% rename from test_cases/fiber/devnet/send_payment/test_mutil_channel.py rename to test_cases/fiber/devnet/send_payment/path/test_mutil_channel.py diff --git a/test_cases/fiber/devnet/send_payment/test_private_channel.py b/test_cases/fiber/devnet/send_payment/path/test_private_channel.py similarity index 98% rename from test_cases/fiber/devnet/send_payment/test_private_channel.py rename to test_cases/fiber/devnet/send_payment/path/test_private_channel.py index 70088fe9..f680718d 100644 --- a/test_cases/fiber/devnet/send_payment/test_private_channel.py +++ b/test_cases/fiber/devnet/send_payment/path/test_private_channel.py @@ -52,7 +52,7 @@ def test_private_channel(self): self.wait_for_channel_state( self.fibers[3].get_client(), self.fibers[0].get_peer_id(), "CHANNEL_READY" ) - + time.sleep(1) for i in range(1, len(self.fibers)): self.send_payment(self.fibers[0], self.fibers[i], 1 * 100000000) diff --git a/test_cases/fiber/devnet/send_payment/test_max_fee_amount.py b/test_cases/fiber/devnet/send_payment/test_max_fee_amount.py deleted file mode 100644 index 7c6f456e..00000000 --- a/test_cases/fiber/devnet/send_payment/test_max_fee_amount.py +++ /dev/null @@ -1,52 +0,0 @@ -import pytest - -from framework.basic_fiber import FiberTest - - -class TestMaxFeeAmount(FiberTest): - # FiberTest.debug = True - - # @pytest.mark.skip("send to node1 -> node2 no fee 设置 max_fee = 0 会报错") - def test_fee(self): - account_private = self.generate_account(1000) - self.fiber3 = self.start_new_fiber(account_private) - self.fiber3.connect_peer(self.fiber2) - self.fiber1.get_client().open_channel( - { - "peer_id": self.fiber2.get_peer_id(), - "funding_amount": hex(100000000 * 100000000), - "public": True, - } - ) - self.wait_for_channel_state( - self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" - ) - self.fiber2.get_client().open_channel( - { - "peer_id": self.fiber3.get_peer_id(), - "funding_amount": hex(100000000 * 100000000), - "public": True, - } - ) - self.wait_for_channel_state( - self.fiber3.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" - ) - - # send to node1 -> node2 no fee max_fee = 0 - payment1 = self.fiber1.get_client().send_payment( - { - "target_pubkey": self.fiber2.get_client().node_info()["node_id"], - "amount": hex(1000000 * 100000000), - "keysend": True, - "max_fee_amount": hex(0), - "dry_run": True, - } - ) - # send to node1 - > node3 need fee 10000 but max_fee = 0 - - def test_max_fee_amount_is_none(self): - """ - max_fee_amount == node 代表什么 - Returns: - - """ diff --git a/test_cases/fiber/devnet/shutdown_channel/test_channel_id.py b/test_cases/fiber/devnet/shutdown_channel/test_channel_id.py index 730625af..e5bc6b82 100644 --- a/test_cases/fiber/devnet/shutdown_channel/test_channel_id.py +++ b/test_cases/fiber/devnet/shutdown_channel/test_channel_id.py @@ -63,8 +63,12 @@ def test_channel_id_exist(self): "fee_rate": "0x3FC", } ) - # todo wait close tx commit - time.sleep(20) + + tx_hash = self.wait_and_check_tx_pool_fee(1000, False) + self.Miner.miner_until_tx_committed(self.node, tx_hash) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CLOSED", 120, True + ) node_info = self.fiber1.get_client().node_info() print("node info :", node_info) assert node_info["channel_count"] == "0x0" diff --git a/test_cases/fiber/devnet/shutdown_channel/test_close_script.py b/test_cases/fiber/devnet/shutdown_channel/test_close_script.py index eec7b504..968fa55f 100644 --- a/test_cases/fiber/devnet/shutdown_channel/test_close_script.py +++ b/test_cases/fiber/devnet/shutdown_channel/test_close_script.py @@ -57,11 +57,14 @@ def test_secp256k1_blake160_sighash_all(self): "fee_rate": "0x3FC", } ) - # todo wait close tx commit - time.sleep(20) - node_info = self.fiber1.get_client().node_info() - print("node info :", node_info) - assert node_info["channel_count"] == "0x0" + tx_hash = self.wait_and_check_tx_pool_fee(1000, False) + self.Miner.miner_until_tx_committed(self.node, tx_hash) + # for i in range(4): + # self.Miner.miner_with_version(self.node, "0x0") + # time.sleep(5) + # node_info = self.fiber1.get_client().node_info() + # print("node info :", node_info) + # assert node_info["channel_count"] == "0x0" after_balance1 = self.Ckb_cli.wallet_get_capacity( self.account1["address"]["testnet"] ) @@ -74,7 +77,7 @@ def test_secp256k1_blake160_sighash_all(self): print("after_balance2:", after_balance2) assert after_balance2 - before_balance2 == 62.0 - @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/431") + # @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/431") def test_not_secp256k1_blake160_sighash_all(self): temporary_channel_id = self.fiber1.get_client().open_channel( { @@ -113,8 +116,12 @@ def test_not_secp256k1_blake160_sighash_all(self): "fee_rate": "0x3FC", } ) - # todo wait close tx commit - time.sleep(20) + + tx_hash = self.wait_and_check_tx_pool_fee(1000, False) + self.Miner.miner_until_tx_committed(self.node, tx_hash) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CLOSED", 120, True + ) node_info = self.fiber1.get_client().node_info() print("node info :", node_info) assert node_info["channel_count"] == "0x0" @@ -229,8 +236,9 @@ def test_ckb_arg_change_long_enough(self): "fee_rate": "0x3FC", } ) - # todo wait close tx commit - time.sleep(40) + tx_hash = self.wait_and_check_tx_pool_fee(1000, False) + self.Miner.miner_until_tx_committed(self.node, tx_hash) + time.sleep(5) after_account_balance1 = self.node.getClient().get_cells_capacity( { "script": { @@ -242,9 +250,9 @@ def test_ckb_arg_change_long_enough(self): "script_search_mode": "prefix", } ) - node_info = self.fiber1.get_client().node_info() - print("node info :", node_info) - assert node_info["channel_count"] == "0x0" + # node_info = self.fiber1.get_client().node_info() + # print("node info :", node_info) + # assert node_info["channel_count"] == "0x0" after_balance1 = self.Ckb_cli.wallet_get_capacity( self.account1["address"]["testnet"], self.node.getClient().url ) @@ -313,7 +321,9 @@ def test_ckb_arg_change_short(self): } ) # todo wait close tx commit - time.sleep(40) + tx_hash = self.wait_and_check_tx_pool_fee(1000, False) + self.Miner.miner_until_tx_committed(self.node, tx_hash) + after_balance1 = self.node.getClient().get_cells_capacity( { "script": { @@ -385,8 +395,8 @@ def test_arg_udt_change_short(self): "fee_rate": "0x3FC", } ) - # todo wait close tx commit - time.sleep(40) + tx_hash = self.wait_and_check_tx_pool_fee(1000, False) + self.Miner.miner_until_tx_committed(self.node, tx_hash) after_balance1 = self.node.getClient().get_cells_capacity( { "script": { @@ -524,8 +534,9 @@ def test_arg_udt_change_short(self): "fee_rate": "0x3FC", } ) - # todo wait close tx commit - time.sleep(40) + tx_hash = self.wait_and_check_tx_pool_fee(1000, False) + self.Miner.miner_until_tx_committed(self.node, tx_hash) + after_balance1 = self.node.getClient().get_cells_capacity( { "script": { diff --git a/test_cases/fiber/devnet/shutdown_channel/test_force.py b/test_cases/fiber/devnet/shutdown_channel/test_force.py index 21003925..b0b72423 100644 --- a/test_cases/fiber/devnet/shutdown_channel/test_force.py +++ b/test_cases/fiber/devnet/shutdown_channel/test_force.py @@ -49,21 +49,34 @@ def test_node_offline(self): latest_commitment_transaction_hash = list_channels["channels"][0][ "latest_commitment_transaction_hash" ] + with pytest.raises(Exception) as exc_info: + self.fiber1.get_client().shutdown_channel( + { + "channel_id": N1N2_CHANNEL_ID, + "close_script": { + "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + "hash_type": "type", + "args": self.account1["lock_arg"], + }, + "fee_rate": "0x3FC", + } + ) + expected_error_message = "Messaging failed because channel is closed" + assert expected_error_message in exc_info.value.args[0], ( + f"Expected substring '{expected_error_message}' " + f"not found in actual string '{exc_info.value.args[0]}'" + ) + # shut down self.fiber1.get_client().shutdown_channel( { "channel_id": N1N2_CHANNEL_ID, - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) tx_hash = self.wait_and_check_tx_pool_fee(1000, False) assert latest_commitment_transaction_hash == tx_hash + self.Miner.miner_until_tx_committed(self.node, tx_hash) def test_node_online(self): temporary_channel_id = self.fiber1.get_client().open_channel( @@ -138,19 +151,23 @@ def test_node_online(self): before_balance2 = self.Ckb_cli.wallet_get_capacity( self.account2["address"]["testnet"] ) + list_channels = self.fiber1.get_client().list_channels({}) + latest_commitment_transaction_hash = list_channels["channels"][0][ + "latest_commitment_transaction_hash" + ] # shut down self.fiber1.get_client().shutdown_channel( { "channel_id": N1N2_CHANNEL_ID, - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) + tx_hash = self.wait_and_check_tx_pool_fee(1000, False) + assert latest_commitment_transaction_hash == tx_hash + self.Miner.miner_until_tx_committed(self.node, tx_hash) + + self.fiber1.get_client().list_channels({}) + self.fiber2.get_client().list_channels({}) # def test_node_online(self): # temporary_channel_id = self.fiber1.get_client().open_channel( @@ -196,14 +213,14 @@ def test_node_online(self): # # todo check graph_channels # # todo check list channel - def test_NegotiatingFunding(self): - pass - - def test_CollaboratingFundingTx(self): - pass - - def test_SigningCommitment(self): - pass + # def test_NegotiatingFunding(self): + # pass + # + # def test_CollaboratingFundingTx(self): + # pass + # + # def test_SigningCommitment(self): + # pass def test_AwaitingTxSignatures(self): temporary_channel_id = self.fiber1.get_client().open_channel( @@ -230,12 +247,6 @@ def test_AwaitingTxSignatures(self): self.fiber1.get_client().shutdown_channel( { "channel_id": N1N2_CHANNEL_ID, - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -276,12 +287,6 @@ def test_AwaitingChannelReady(self): self.fiber1.get_client().shutdown_channel( { "channel_id": N1N2_CHANNEL_ID, - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -295,6 +300,25 @@ def test_AwaitingChannelReady(self): def test_ChannelReady(self): pass + # debug = True + + # def test_bb1(self): + # self.fiber1.get_client().list_channels({ + # "include_closed": True, + # }) + # self.fiber2.get_client().list_channels({}) + # + # def test_get_message(self): + # msg = self.get_tx_message("0x85c2334a63dc0850b425eb2e9346f57af6a47c18c895aabbdaf7d02fa445109b") + # print(msg) + # + # def test_bb(self): + # f1_b = self.get_fiber_balance(self.fiber1) + # f2_b = self.get_fiber_balance(self.fiber2) + # print(f1_b) + # print(f2_b) + # self.node.getClient().generate_epochs("0x2") + # @pytest.mark.skip("have bug") def test_in_tx(self): temporary_channel_id = self.fiber1.get_client().open_channel( @@ -311,14 +335,14 @@ def test_in_tx(self): ) payment = self.fiber1.get_client().send_payment( { - "amount": hex(100), + "amount": hex(1 * 100000000), "target_pubkey": self.fiber2.get_client().node_info()["node_id"], "keysend": True, } ) self.wait_payment_state(self.fiber1, payment["payment_hash"]) - amount = 1 + amount = 10 * 100000000 invoice = self.fiber2.get_client().new_invoice( { "amount": hex(amount), @@ -346,12 +370,6 @@ def test_in_tx(self): self.fiber1.get_client().shutdown_channel( { "channel_id": N1N2_CHANNEL_ID, - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -362,6 +380,73 @@ def test_in_tx(self): # todo check assert payment["status"] == "Inflight" + def test_in_tx_force_2(self): + self.open_channel(self.fiber1, self.fiber2, 1000 * 100000000, 1) + for i in range(100): + self.send_payment(self.fiber1, self.fiber2, 1 * 100000000, False) + self.fiber1.get_client().shutdown_channel( + { + "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ], + "force": True, + } + ) + + # def test_in_key_send_tx2(self): + # self.open_channel(self.fiber1, self.fiber2, 1000 * 100000000, 1) + # + # for i in range(100): + # self.send_payment(self.fiber1, self.fiber2, 1 * 100000000, False) + # self.fiber1.get_client().shutdown_channel({ + # "channel_id": self.fiber1.get_client().list_channels({})["channels"][0]["channel_id"], + # "close_script": { + # "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + # "hash_type": "type", + # "args": self.account1["lock_arg"], + # }, + # "fee_rate": "0x3FC", + # }) + # self.wait_for_channel_state(self.fiber1.get_client(), self.fiber2.get_peer_id(), "CLOSED", 120, True) + + # def test_in_invoice_send_tx2(self): + # self.open_channel(self.fiber1, self.fiber2, 1000 * 100000000, 1) + # invocies = [] + # for i in range(50): + # invocies.append( + # self.fibers[1].get_client().new_invoice( + # {"amount": hex(1 * 100000000), + # "currency": "Fibd", + # "description": "test invoice generated by node2", + # "expiry": "0xe10", + # "final_cltv": "0x28", + # "payment_preimage": self.generate_random_preimage(), + # "hash_algorithm": "sha256", + # } + # ) + # ) + # # transfer + # for i in range(50): + # self.fiber1.get_client().send_payment({ + # "invoice": invocies[i]["invoice_address"], + # }) + # self.fiber1.get_client().shutdown_channel({ + # "channel_id": self.fiber1.get_client().list_channels({})["channels"][0]["channel_id"], + # "close_script": { + # "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + # "hash_type": "type", + # "args": self.account1["lock_arg"], + # }, + # "fee_rate": "0x3FC", + # }) + # self.wait_for_channel_state(self.fiber1.get_client(), self.fiber2.get_peer_id(), "SHUTTING_DOWN", 120, True) + # self.fiber1.get_client().shutdown_channel({ + # "channel_id": self.fiber1.get_client().list_channels({})["channels"][0]["channel_id"], + # "force": True + # }) + # tx_hash = self.wait_and_check_tx_pool_fee(1000, False) + # self.Miner.miner_until_tx_committed(self.node, tx_hash) + # @pytest.mark.skip("https://github.com/nervosnetwork/fiber/issues/333") def test_in_tx_offline(self): temporary_channel_id = self.fiber1.get_client().open_channel( @@ -400,12 +485,6 @@ def test_in_tx_offline(self): self.fiber2.get_client().shutdown_channel( { "channel_id": N1N2_CHANNEL_ID, - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account2["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -470,12 +549,6 @@ def test_ShuttingDown(self): self.fiber2.get_client().shutdown_channel( { "channel_id": N1N2_CHANNEL_ID, - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account2["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -533,12 +606,6 @@ def test_force_ckb(self): self.fiber1.get_client().shutdown_channel( { "channel_id": N1N2_CHANNEL_ID, - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -606,12 +673,6 @@ def test_force_udt(self): self.fiber1.get_client().shutdown_channel( { "channel_id": N1N2_CHANNEL_ID, - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) diff --git a/test_cases/fiber/devnet/shutdown_channel/test_node_state.py b/test_cases/fiber/devnet/shutdown_channel/test_node_state.py index afef775f..c84d7566 100644 --- a/test_cases/fiber/devnet/shutdown_channel/test_node_state.py +++ b/test_cases/fiber/devnet/shutdown_channel/test_node_state.py @@ -20,7 +20,7 @@ class TestNodeState(FiberTest): # @pytest.mark.skip("交易发送一半,如果交易卡在Inflight,下一笔交易好像也发不出去") def test_shutdown_in_send_payment(self): """ - payment state 卡在 Inflight + payment state 最终会failed Returns: @@ -69,18 +69,17 @@ def test_shutdown_in_send_payment(self): # node1 send payment to node4 node4_info = self.fiber4.get_client().node_info() fiber4_pub = node4_info["node_id"] - payment = self.fiber1.get_client().send_payment( - { - "target_pubkey": fiber4_pub, - "amount": hex(1 * 100000000), - "keysend": True, - # "invoice": "0x123", - } - ) + for i in range(10): + payment = self.fiber1.get_client().send_payment( + { + "target_pubkey": fiber4_pub, + "amount": hex(1 * 100000000), + "keysend": True, + # "invoice": "0x123", + } + ) - self.wait_payment_state(self.fiber1, payment["payment_hash"], "Success", 120) - channels = self.fiber4.get_client().list_channels({}) - assert channels["channels"][0]["local_balance"] == hex(1 * 100000000) + # self.wait_payment_state(self.fiber1, payment["payment_hash"], "Success", 120) N3N4_CHANNEL_ID = self.fiber4.get_client().list_channels({})["channels"][0][ "channel_id" ] @@ -106,10 +105,12 @@ def test_shutdown_in_send_payment(self): ) tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 120) tx_message = self.get_tx_message(tx_hash) + time.sleep(1) payment = self.fiber1.get_client().get_payment( {"payment_hash": payment["payment_hash"]} ) - assert payment["status"] == "Inflight" + print("payment:", payment) + self.wait_payment_state(self.fiber1, payment["payment_hash"], "Failed", 120) assert { "args": self.fiber4.get_account()["lock_arg"], "capacity": 6300000000, diff --git a/test_cases/fiber/devnet/update_channel/test_chain_status.py b/test_cases/fiber/devnet/update_channel/test_chain_status.py index 364fb4b4..13cc1796 100644 --- a/test_cases/fiber/devnet/update_channel/test_chain_status.py +++ b/test_cases/fiber/devnet/update_channel/test_chain_status.py @@ -1,3 +1,5 @@ +import time + import pytest from framework.basic_fiber import FiberTest @@ -20,10 +22,6 @@ def test_chain_status_pending(self): "AWAITING_TX_SIGNATURES", ) - # self.fiber2.get_client().update_channel({ - # "channel_id": self.fiber1.get_client().list_channels({})["channels"][0]["channel_id"], - # "tlc_fee_proportional_millionths": hex(2000), - # }) self.fiber1.get_client().update_channel( { "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ @@ -35,4 +33,41 @@ def test_chain_status_pending(self): self.wait_for_channel_state( self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" ) + channels = self.fiber1.get_client().list_channels({}) + assert channels["channels"][0]["tlc_fee_proportional_millionths"] == hex(2000) # node2 add channel with node3 + self.fiber1.get_client().update_channel( + { + "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ], + "tlc_fee_proportional_millionths": hex(3000), + } + ) + channels = self.fiber1.get_client().list_channels({}) + assert channels["channels"][0]["tlc_fee_proportional_millionths"] == hex(3000) + + # shutdown + self.fiber1.get_client().shutdown_channel( + { + "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ], + "close_script": { + "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + "hash_type": "type", + "args": self.account1["lock_arg"], + }, + "fee_rate": "0x3FC", + } + ) + self.fiber1.get_client().update_channel( + { + "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ], + "tlc_fee_proportional_millionths": hex(4000), + } + ) + channels = self.fiber1.get_client().list_channels({"include_closed": True}) + assert channels["channels"][0]["tlc_fee_proportional_millionths"] == hex(4000) diff --git a/test_cases/fiber/devnet/update_channel/test_enabled.py b/test_cases/fiber/devnet/update_channel/test_enabled.py index e1534258..e2dfe25e 100644 --- a/test_cases/fiber/devnet/update_channel/test_enabled.py +++ b/test_cases/fiber/devnet/update_channel/test_enabled.py @@ -49,9 +49,10 @@ def test_true(self): self.fibers[1].get_client().update_channel( {"channel_id": channel["channels"][0]["channel_id"], "enabled": False} ) - time.sleep(1) + time.sleep(3) channels = self.fibers[1].get_client().graph_channels({}) - assert len(channels["channels"]) == 1 + assert len(channels["channels"]) == 2 + channel = ( self.fibers[1] .get_client() diff --git a/test_cases/fiber/devnet/update_channel/test_tlc_expiry_delta.py b/test_cases/fiber/devnet/update_channel/test_tlc_expiry_delta.py index a3669231..b8495e44 100644 --- a/test_cases/fiber/devnet/update_channel/test_tlc_expiry_delta.py +++ b/test_cases/fiber/devnet/update_channel/test_tlc_expiry_delta.py @@ -1,13 +1,76 @@ +import pytest + from framework.basic_fiber import FiberTest class TestTlcExpiryDelta(FiberTest): - def test_none(self): - pass + @pytest.mark.skip("tlc_expiry_delta = 0xffffffffffffffff, 预期:成功") + def test_01(self): + """ + + Returns: + + """ + self.open_channel(self.fiber1, self.fiber2, 1000 * 100000000, 1) + channel = ( + self.fibers[0] + .get_client() + .list_channels({"peer_id": self.fibers[1].get_peer_id()}) + ) + + # 0x0 + with pytest.raises(Exception) as exc_info: + self.fibers[1].get_client().update_channel( + { + "channel_id": channel["channels"][0]["channel_id"], + "tlc_expiry_delta": "0x0", + } + ) + expected_error_message = ( + "TLC expiry delta is too small, expect larger than 900000" + ) + assert expected_error_message in exc_info.value.args[0], ( + f"Expected substring '{expected_error_message}' " + f"not found in actual string '{exc_info.value.args[0]}'" + ) + + with pytest.raises(Exception) as exc_info: + self.fibers[1].get_client().update_channel( + { + "channel_id": channel["channels"][0]["channel_id"], + "tlc_expiry_delta": hex(900000 - 1), + } + ) + expected_error_message = ( + "TLC expiry delta is too small, expect larger than 900000" + ) + assert expected_error_message in exc_info.value.args[0], ( + f"Expected substring '{expected_error_message}' " + f"not found in actual string '{exc_info.value.args[0]}'" + ) - def test_overflow(self): - pass + with pytest.raises(Exception) as exc_info: + self.fibers[1].get_client().update_channel( + { + "channel_id": channel["channels"][0]["channel_id"], + "tlc_expiry_delta": "0xffffffffffffffffff", + } + ) + expected_error_message = ( + "TLC expiry delta is too small, expect larger than 900000" + ) + assert expected_error_message in exc_info.value.args[0], ( + f"Expected substring '{expected_error_message}' " + f"not found in actual string '{exc_info.value.args[0]}'" + ) - def test_0x0(self): - pass + self.fibers[1].get_client().update_channel( + { + "channel_id": channel["channels"][0]["channel_id"], + "tlc_expiry_delta": hex(1000000), + } + ) + channel = self.fibers[1].get_client().list_channels({}) + print(channel) + assert channel["tlc_expiry_delta"] == hex(1000000) diff --git a/test_cases/fiber/devnet/update_channel/test_tlc_fee_proportional_millionths.py b/test_cases/fiber/devnet/update_channel/test_tlc_fee_proportional_millionths.py index 676d930c..79f4ffa4 100644 --- a/test_cases/fiber/devnet/update_channel/test_tlc_fee_proportional_millionths.py +++ b/test_cases/fiber/devnet/update_channel/test_tlc_fee_proportional_millionths.py @@ -181,10 +181,10 @@ def test_max(self): ) time.sleep(3) graph_channels = self.fiber1.get_client().graph_channels() - assert graph_channels["channels"][0]["fee_rate_of_node2"] == hex( + assert graph_channels["channels"][0]["update_info_of_node2"]["fee_rate"] == hex( 340282366920938463 ) - assert graph_channels["channels"][0]["fee_rate_of_node1"] == hex( + assert graph_channels["channels"][0]["update_info_of_node1"]["fee_rate"] == hex( 340282366920938463 ) diff --git a/test_cases/fiber/devnet/update_channel/test_tlc_maximum_value.py b/test_cases/fiber/devnet/update_channel/test_tlc_maximum_value.py deleted file mode 100644 index 8aa52ee5..00000000 --- a/test_cases/fiber/devnet/update_channel/test_tlc_maximum_value.py +++ /dev/null @@ -1,13 +0,0 @@ -from framework.basic_fiber import FiberTest - - -class TestTlcMaximumValue(FiberTest): - - def test_0x0(self): - pass - - def test_normal(self): - pass - - def test_none(self): - pass diff --git a/test_cases/fiber/devnet/update_channel/test_tlc_minimum_value.py b/test_cases/fiber/devnet/update_channel/test_tlc_minimum_value.py index 07df0247..8f26cd8f 100644 --- a/test_cases/fiber/devnet/update_channel/test_tlc_minimum_value.py +++ b/test_cases/fiber/devnet/update_channel/test_tlc_minimum_value.py @@ -1,16 +1,74 @@ +import time + +import pytest + from framework.basic_fiber import FiberTest class TestTlcMinimumValue(FiberTest): + def test_01(self): + """ + 0x0 succ + 1ckb succ + int.max succ + int.max+1=> falied + Returns: - def test_0x0(self): - pass - - def test_overflow(self): - pass + """ + self.open_channel(self.fiber1, self.fiber2, 1000 * 100000000, 1000 * 100000000) + self.send_payment(self.fiber1, self.fiber2, 1 * 100000000) + self.send_payment(self.fiber2, self.fiber1, 1 * 100000000) + self.fiber1.get_client().update_channel( + { + "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ], + "tlc_minimum_value": hex(1 * 100000000), + } + ) + time.sleep(1) + self.send_payment(self.fiber2, self.fiber1, 1 * 100000000 - 1) + print("failed ") + with pytest.raises(Exception) as exc_info: + self.send_payment(self.fiber1, self.fiber2, 1 * 100000000 - 1) + expected_error_message = "no path found" + assert expected_error_message in exc_info.value.args[0], ( + f"Expected substring '{expected_error_message}' " + f"not found in actual string '{exc_info.value.args[0]}'" + ) - def test_max(self): - pass + # 0xffffffff + self.fiber1.get_client().update_channel( + { + "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ], + "tlc_minimum_value": "0xffffffffffffffffffffffffffffffff", + } + ) + time.sleep(1) + graph_channels = self.fiber1.get_client().graph_channels({}) + assert ( + graph_channels["channels"][0]["update_info_of_node2"]["tlc_minimum_value"] + == "0xffffffffffffffffffffffffffffffff" + or graph_channels["channels"][0]["update_info_of_node1"][ + "tlc_minimum_value" + ] + == "0xffffffffffffffffffffffffffffffff" + ) - def test_normal(self): - pass + # overflow + with pytest.raises(Exception) as exc_info: + self.fiber1.get_client().update_channel( + { + "channel_id": self.fiber1.get_client().list_channels({})[ + "channels" + ][0]["channel_id"], + "tlc_minimum_value": "0xfffffffffffffffffffffffffffffffff", + } + ) + expected_error_message = "Invalid params" + assert expected_error_message in exc_info.value.args[0], ( + f"Expected substring '{expected_error_message}' " + f"not found in actual string '{exc_info.value.args[0]}'" + ) diff --git a/test_cases/fiber/devnet/update_channel/test_update_channel.py b/test_cases/fiber/devnet/update_channel/test_update_channel.py index 515d52df..ec68a8c0 100644 --- a/test_cases/fiber/devnet/update_channel/test_update_channel.py +++ b/test_cases/fiber/devnet/update_channel/test_update_channel.py @@ -24,11 +24,6 @@ class TestUpdateChannel(FiberTest): 2. 修改为正常值 3. overflow 4. 0 - - tlc_maximum_value - 1. 不填 - 2. 修改为正常值 - 3. overflow - 4. 0 - tlc_fee_proportional_millionths 1. 不填 2. 修改为正常值 diff --git a/test_cases/fiber/devnet/watch_tower/test_htlc_expired.py b/test_cases/fiber/devnet/watch_tower/test_htlc_expired.py new file mode 100644 index 00000000..d84871e3 --- /dev/null +++ b/test_cases/fiber/devnet/watch_tower/test_htlc_expired.py @@ -0,0 +1,151 @@ +import time + +import pytest + +from framework.basic_fiber import FiberTest + + +class TestHtlcExpired(FiberTest): + start_fiber_config = {"fiber_watchtower_check_interval_seconds": 5} + + def test_ready_status(self): + """ + 处于ready 状态 + 过期tlc 会自动发送强制shutdown + Returns: + + """ + self.start_new_fiber(self.generate_account(1000)) + self.open_channel(self.fiber1, self.fiber2, 1000 * 100000000, 1) + self.open_channel(self.fiber2, self.fibers[2], 1000 * 100000000, 1) + before_balance1 = self.Ckb_cli.wallet_get_capacity( + self.account1["address"]["testnet"] + ) + before_balance2 = self.Ckb_cli.wallet_get_capacity( + self.account2["address"]["testnet"] + ) + + # add tlc node1 + CHANNEL_ID = self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ] + self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(300 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "expiry": hex((int(time.time()) + 15) * 1000), + } + ) + CHANNEL_ID = ( + self.fibers[2].get_client().list_channels({})["channels"][0]["channel_id"] + ) + # add tlc node2 + self.fiber2.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(300 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "expiry": hex((int(time.time()) + 10) * 1000), + } + ) + time.sleep(30) + msg = self.get_fibers_balance_message() + print(msg) + channels = self.fiber1.get_client().list_channels({}) + assert len(channels["channels"]) == 0 + channels = self.fiber2.get_client().list_channels( + {"peer_id": self.fibers[2].get_peer_id()} + ) + assert len(channels["channels"]) == 0 + for i in range(6): + self.node.getClient().generate_epochs("0x2") + time.sleep(6) + after_balance1 = self.Ckb_cli.wallet_get_capacity( + self.account1["address"]["testnet"] + ) + after_balance2 = self.Ckb_cli.wallet_get_capacity( + self.account2["address"]["testnet"] + ) + print("before_balance1:", int(before_balance1)) + print("after_balance1:", int(after_balance1)) + assert (int(after_balance1) - int(before_balance1)) == (1062) + print("before_balance2:", int(before_balance2)) + print("after_balance2:", int(after_balance2)) + assert (int(after_balance2) - int(before_balance2)) == (1124) + + @pytest.mark.skip("skip") + def test_shutdown_status(self): + """ + 处于shutdown状态 + 过期tlc 会自动发送强制shutdown + Returns: + """ + self.start_new_fiber(self.generate_account(1000)) + self.open_channel(self.fiber1, self.fiber2, 1000 * 100000000, 1) + self.open_channel(self.fiber2, self.fibers[2], 1000 * 100000000, 1) + before_balance1 = self.Ckb_cli.wallet_get_capacity( + self.account1["address"]["testnet"] + ) + before_balance2 = self.Ckb_cli.wallet_get_capacity( + self.account2["address"]["testnet"] + ) + + # add tlc node1 + CHANNEL_ID1 = self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ] + self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID1, + "amount": hex(300 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "expiry": hex((int(time.time()) + 15) * 1000), + } + ) + CHANNEL_ID = ( + self.fibers[2].get_client().list_channels({})["channels"][0]["channel_id"] + ) + # add tlc node2 + self.fiber2.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(300 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "expiry": hex((int(time.time()) + 10) * 1000), + } + ) + + # 因为有待处理的tlc 会导致正常 shutdown_channel 卡主 + self.fiber1.get_client().shutdown_channel( + { + "channel_id": CHANNEL_ID1, + "close_script": self.get_account_script(self.fiber1.account_private), + "fee_rate": "0x3FC", + } + ) + + time.sleep(30) + msg = self.get_fibers_balance_message() + print(msg) + channels = self.fiber1.get_client().list_channels({}) + assert len(channels["channels"]) == 0 + channels = self.fiber2.get_client().list_channels( + {"peer_id": self.fibers[2].get_peer_id()} + ) + assert len(channels["channels"]) == 0 + for i in range(6): + self.node.getClient().generate_epochs("0x2") + time.sleep(6) + after_balance1 = self.Ckb_cli.wallet_get_capacity( + self.account1["address"]["testnet"] + ) + after_balance2 = self.Ckb_cli.wallet_get_capacity( + self.account2["address"]["testnet"] + ) + print("before_balance1:", int(before_balance1)) + print("after_balance1:", int(after_balance1)) + assert (int(after_balance1) - int(before_balance1)) == (1062) + print("before_balance2:", int(before_balance2)) + print("after_balance2:", int(after_balance2)) + assert (int(after_balance2) - int(before_balance2)) == (1124) diff --git a/test_cases/fiber/devnet/watch_tower/test_htlc_mutil.py b/test_cases/fiber/devnet/watch_tower/test_htlc_mutil.py new file mode 100644 index 00000000..e69de29b diff --git a/test_cases/fiber/devnet/watch_tower/test_mutil_shutdown.py b/test_cases/fiber/devnet/watch_tower/test_mutil_shutdown.py index 032f39c7..63fb0f9c 100644 --- a/test_cases/fiber/devnet/watch_tower/test_mutil_shutdown.py +++ b/test_cases/fiber/devnet/watch_tower/test_mutil_shutdown.py @@ -79,12 +79,6 @@ def test_mutil_shutdown(self): self.fiber1.get_client().shutdown_channel( { "channel_id": channel["channel_id"], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -93,12 +87,6 @@ def test_mutil_shutdown(self): self.fiber2.get_client().shutdown_channel( { "channel_id": channel["channel_id"], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account2["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) diff --git a/test_cases/fiber/devnet/watch_tower/test_revert_tx.py b/test_cases/fiber/devnet/watch_tower/test_revert_tx.py index 1961b03e..e671b030 100644 --- a/test_cases/fiber/devnet/watch_tower/test_revert_tx.py +++ b/test_cases/fiber/devnet/watch_tower/test_revert_tx.py @@ -36,12 +36,6 @@ def test_01(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -86,12 +80,6 @@ def test_02(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) diff --git a/test_cases/fiber/devnet/watch_tower/test_watch_tower.py b/test_cases/fiber/devnet/watch_tower/test_watch_tower.py index 6f74ff68..ff939ca3 100644 --- a/test_cases/fiber/devnet/watch_tower/test_watch_tower.py +++ b/test_cases/fiber/devnet/watch_tower/test_watch_tower.py @@ -46,12 +46,6 @@ def test_node1_shutdown_when_open_and_node2_split_tx(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -152,12 +146,6 @@ def test_node2_shutdown_when_open_and_node2_split_tx(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account2["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -249,12 +237,6 @@ def test_node2_shutdown_when_open_and_node1_split_tx(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account2["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -346,12 +328,6 @@ def test_node1_shutdown_when_open_and_node1_split_tx(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -450,12 +426,6 @@ def test_node1_shutdown_after_send_tx1_and_node1_split_tx(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -551,12 +521,6 @@ def test_node1_shutdown_after_send_tx1_and_node2_split_tx(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -654,12 +618,6 @@ def test_node2_shutdown_after_send_tx1_and_node1_split_tx(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account2["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -755,12 +713,6 @@ def test_node2_shutdown_after_send_tx1_and_node2_split_tx(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -865,12 +817,6 @@ def test_node1_shutdown_after_send_tx2_and_node1_split_tx(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -969,12 +915,6 @@ def test_node1_shutdown_after_send_tx2_and_node2_split_tx(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -1074,12 +1014,6 @@ def test_node2_shutdown_after_send_tx2_and_node1_split_tx(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account2["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -1177,12 +1111,6 @@ def test_node2_shutdown_after_send_tx2_and_node2_split_tx(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -1279,12 +1207,6 @@ def test_node1_shutdown_after_send_txN_and_node1_split_tx(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -1385,12 +1307,6 @@ def test_node2_shutdown_after_send_txN_and_node1_split_tx(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account2["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) diff --git a/test_cases/fiber/devnet/watch_tower/test_watch_tower_udt.py b/test_cases/fiber/devnet/watch_tower/test_watch_tower_udt.py index 7c8657d4..9b274977 100644 --- a/test_cases/fiber/devnet/watch_tower/test_watch_tower_udt.py +++ b/test_cases/fiber/devnet/watch_tower/test_watch_tower_udt.py @@ -49,12 +49,6 @@ def test_node1_shutdown_when_open_and_node2_split_tx(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -159,12 +153,6 @@ def test_node2_shutdown_when_open_and_node2_split_tx(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account2["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -262,12 +250,6 @@ def test_node2_shutdown_when_open_and_node1_split_tx(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account2["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -349,12 +331,6 @@ def test_node1_shutdown_when_open_and_node1_split_tx(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -451,12 +427,6 @@ def test_node1_shutdown_after_send_tx1_and_node1_split_tx(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -561,12 +531,6 @@ def test_node1_shutdown_after_send_tx1_and_node2_split_tx(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -675,12 +639,6 @@ def test_node2_shutdown_after_send_tx1_and_node1_split_tx(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account2["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -786,12 +744,6 @@ def test_node2_shutdown_after_send_tx1_and_node2_split_tx(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -899,12 +851,6 @@ def test_node1_shutdown_after_send_tx2_and_node1_split_tx(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -1013,12 +959,6 @@ def test_node1_shutdown_after_send_tx2_and_node2_split_tx(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -1128,12 +1068,6 @@ def test_node2_shutdown_after_send_tx2_and_node1_split_tx(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account2["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -1241,12 +1175,6 @@ def test_node2_shutdown_after_send_tx2_and_node2_split_tx(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -1354,12 +1282,6 @@ def test_node1_shutdown_after_send_txN_and_node1_split_tx(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -1470,12 +1392,6 @@ def test_node2_shutdown_after_send_txN_and_node1_split_tx(self): "channel_id": self.fiber1.get_client().list_channels({})["channels"][0][ "channel_id" ], - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account2["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) diff --git a/test_cases/fiber/devnet/watch_tower/test_with_tx.py b/test_cases/fiber/devnet/watch_tower/test_with_tx.py index df452148..d79ce178 100644 --- a/test_cases/fiber/devnet/watch_tower/test_with_tx.py +++ b/test_cases/fiber/devnet/watch_tower/test_with_tx.py @@ -58,12 +58,6 @@ def test_send_force_shutdown_with_tx(self): self.fiber1.get_client().shutdown_channel( { "channel_id": N1N2_CHANNEL_ID, - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account1["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) @@ -143,12 +137,6 @@ def test_receive_force_shutdown_with_tx(self): self.fiber2.get_client().shutdown_channel( { "channel_id": N1N2_CHANNEL_ID, - "close_script": { - "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", - "hash_type": "type", - "args": self.account2["lock_arg"], - }, - "fee_rate": "0x3FC", "force": True, } ) diff --git a/test_cases/fiber/devnet/watch_tower_wit_tlc/test_pending_tlc.py b/test_cases/fiber/devnet/watch_tower_wit_tlc/test_pending_tlc.py new file mode 100644 index 00000000..fca18793 --- /dev/null +++ b/test_cases/fiber/devnet/watch_tower_wit_tlc/test_pending_tlc.py @@ -0,0 +1,3015 @@ +import time + +import pytest + +from framework.basic_fiber import FiberTest + + +class TestPendingTlc(FiberTest): + """ + pending tlc + watch tower,node1 force shutdown + node1 和node2 都没有tlc + node1 有N个tlc + 在tlc过期前 + 时间过去 0~ 1/3 个 delay_epoch + node2 可以通过pre_image 解锁部分tlc + node2 无法解锁 + node1 无法解锁 + 时间过去 delay_epoch 1/3 -2/3 + node2 可以通过pre_image 解锁部分tlc + node1 无法解锁 + 2/3~1 + node1无法取回 + node2 可以通过pre_image 解锁部分tlc + 时间过去 delay_epoch + node2 可以通过pre_image 解锁部分tlc + node2 可以舍弃tlc + node1 无法解锁 + 在tlc 过期后 + 时间过去 0~ 1/3 个 delay_epoch + node2 可以通过pre_image 解锁部分tlc + node1 无法解锁 + 时间过去 delay_epoch 1/3 -2/3 + node2 可以通过pre_image 解锁部分tlc + node1 可以解锁 + 2/3~1 + node1无法取回 + node2 可以通过pre_image 解锁部分tlc + 时间过去 delay_epoch + node2 可以通过pre_image 解锁部分tlc + node2 可以舍弃 tlc + node1 可以解锁 + node2有N个tlc + 在tlc 过期前 + delay_epoch 过去0-1/3 + node1 可以通过pre_image解锁部分tlc + remove_tlc 会失败 可能测不了 + node2 无法解锁 + delay_epoch 1/3 -2/3 + node1 可以通过pre_image 解锁部分tlc + remove_tlc 会失败 可能测不了 + node2 无法解锁 + 2/3~1 + node2无法取回 + node1 可以通过pre_image 解锁部分tlc + >delay_epoch + node1 可以通过pre_image 解锁部分tlc + remove_tlc 会失败 可能测不了 + node2 无法解锁 + 在tlc 过期后 + delay_epoch 过去0-1/3 + node1 可以通过pre_image 解锁部分tlc + remove_tlc 会失败 可能测不了 + node2 无法解锁 + delay_epoch 1/3 -2/3 + node1 可以通过pre_image 解锁部分tlc + remove_tlc 会失败 可能测不了 + node2 可以解锁 + 2/3~1 + node2无法取回 + node1 可以通过pre_image 解锁部分tlc + >delay_epoch + node1 可以通过pre_image 解锁部分tlc + remove_tlc 会失败 可能测不了 + node1 可以舍弃tlc + node2 可以解锁 + node1和node2 都有n个tlc + 复杂的场景5,5个tlc,node2有一个能解锁的tlc + 测试N的上限 + """ + + start_fiber_config = {"fiber_watchtower_check_interval_seconds": 5} + + # debug = True + + # node1 有tlc 过期前 + + def test_node1_has_n_tlc_with_node2_opt(self): + """ + node1 有N个tlc + 在tlc过期前 + 时间过去 0~ 1/3 个 delay_epoch + node2 无法解锁 + 时间过去 delay_epoch 1/3 -2/3 + node2 可以通过pre_image 解锁部分tlc + + 时间过去 delay_epoch + node2 可以舍弃tlc + + 1. node1 4个 add_tlc + 2. node2 remove_tlc + 1个tlc + 3. node1 强制shutdown + 4. node1 关闭watch tower + 5. 时间过去 0~ 1/3 个 delay_epoch + node2 不会操作 + 6. 时间过去 1/3 个delay_epoch + node2 会发送commit+ tlc + 7. 时间过去 delay epoch + node2 会遗弃 tlc + Returns: + """ + fiber3 = self.start_new_fiber(self.generate_account(1000)) + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(10000 * 100000000), + "public": True, + # ), + } + ) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" + ) + # 创建N个tlc + tlcs = [] + payment_preimages = [] + CHANNEL_ID = self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ] + self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(300 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "expiry": hex((int(time.time()) + 8000) * 1000), + } + ) + time.sleep(2) + for i in range(3): + payment_preimage = self.generate_random_preimage() + invoice = fiber3.get_client().new_invoice( + { + "amount": hex(300 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_expiry_delta": "0xDFFA0", + "payment_preimage": payment_preimage, + # "udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + tlc = self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(300 * 100000000), + "payment_hash": invoice["invoice"]["data"]["payment_hash"], + "expiry": hex((int(time.time()) + 200) * 1000), + } + ) + payment_preimages.append(payment_preimage) + tlcs.append(tlc) + time.sleep(4) + # shutdown + self.fiber1.get_client().shutdown_channel( + { + "channel_id": CHANNEL_ID, + "force": True, + } + ) + # remove tlc + for i in range(1): + try: + self.fiber2.get_client().remove_tlc( + { + "channel_id": CHANNEL_ID, + "tlc_id": tlcs[i]["tlc_id"], + "reason": {"payment_preimage": payment_preimages[i]}, + } + ) + time.sleep(1) + except Exception: + pass + self.fiber1.get_client().remove_watch_channel( + { + "channel_id": CHANNEL_ID, + } + ) + shutdown_tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + self.Miner.miner_until_tx_committed(self.node, shutdown_tx) + # 5. 时间过去 0~ 1/3 个 delay_epoch + # node2 不会操作 + time.sleep(15) + status = self.node.getClient().get_live_cell(hex(0), shutdown_tx) + assert status["status"] == "live" + + # 6. 时间过去 delay_epoch 1/3 -2/3 + # node2 会 发送commit+ tlc + self.node.getClient().generate_epochs("0x2") + node2_remove_tlc_tx = self.wait_and_check_tx_pool_fee(1000, False, 300) + self.Miner.miner_until_tx_committed(self.node, node2_remove_tlc_tx) + node2_remove_tlc_message = self.get_tx_message(node2_remove_tlc_tx) + assert ( + node2_remove_tlc_message["input_cells"][0]["capacity"] + - node2_remove_tlc_message["output_cells"][0]["capacity"] + == 300 * 100000000 + ) + assert ( + node2_remove_tlc_message["output_cells"][1]["args"] + == self.account2["lock_arg"] + ) + + # 7. 时间过去 0~ 1/3 个 delay_epoch + # node2 不会操作 + time.sleep(15) + status = self.node.getClient().get_live_cell(hex(0), node2_remove_tlc_tx) + assert status["status"] == "live" + # 9. 时间过去 delay epoch + # node2 会遗弃 tlc + self.node.getClient().generate_epochs("0x6") + final_tx = self.wait_and_check_tx_pool_fee(1000, False, 300) + # self.Miner.miner_until_tx_committed(self.node, final_tx) + final_tx_message = self.get_tx_message(final_tx) + assert { + "args": self.account2["lock_arg"], + "capacity": 6199999545, + } in final_tx_message["output_cells"] + assert { + "args": self.account1["lock_arg"], + "capacity": 879999999545, + } in final_tx_message["output_cells"] + assert final_tx_message["fee"] > 90000000000 + + def test_node1_has_n_tlc_with_node2_2_3_epoch_opt(self): + """ + node1 有N个tlc + 在tlc过期前 + 时间过去 2/3 delay epoch + node2 可以通过pre_image 解锁部分tlc + Returns: + + """ + fiber3 = self.start_new_fiber(self.generate_account(1000)) + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(10000 * 100000000), + "public": True, + # ), + } + ) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" + ) + # 创建N个tlc + tlcs = [] + payment_preimages = [] + CHANNEL_ID = self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ] + self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(300 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "expiry": hex((int(time.time()) + 8000) * 1000), + } + ) + time.sleep(2) + for i in range(3): + payment_preimage = self.generate_random_preimage() + invoice = fiber3.get_client().new_invoice( + { + "amount": hex(300 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_expiry_delta": "0xDFFA0", + "payment_preimage": payment_preimage, + # "udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + tlc = self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(300 * 100000000), + "payment_hash": invoice["invoice"]["data"]["payment_hash"], + "expiry": hex((int(time.time()) + 200) * 1000), + } + ) + payment_preimages.append(payment_preimage) + tlcs.append(tlc) + time.sleep(4) + # shutdown + self.fiber1.get_client().shutdown_channel( + { + "channel_id": CHANNEL_ID, + "force": True, + } + ) + # remove tlc + for i in range(1): + try: + self.fiber2.get_client().remove_tlc( + { + "channel_id": CHANNEL_ID, + "tlc_id": tlcs[i]["tlc_id"], + "reason": {"payment_preimage": payment_preimages[i]}, + } + ) + time.sleep(1) + except Exception: + pass + self.fiber1.get_client().remove_watch_channel( + { + "channel_id": CHANNEL_ID, + } + ) + shutdown_tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + self.Miner.miner_until_tx_committed(self.node, shutdown_tx) + # 5. 时间过去 0~ 1/3 个 delay_epoch + # node2 不会操作 + time.sleep(15) + status = self.node.getClient().get_live_cell(hex(0), shutdown_tx) + assert status["status"] == "live" + self.fiber2.stop() + + # 6. 时间过去 delay_epoch 2/3 + # node2 会 发送commit+ tlc + self.node.getClient().generate_epochs("0x4") + self.fiber2.start() + node2_remove_tlc_tx = self.wait_and_check_tx_pool_fee(1000, False, 300) + self.Miner.miner_until_tx_committed(self.node, node2_remove_tlc_tx) + node2_remove_tlc_message = self.get_tx_message(node2_remove_tlc_tx) + assert ( + node2_remove_tlc_message["input_cells"][0]["capacity"] + - node2_remove_tlc_message["output_cells"][0]["capacity"] + == 300 * 100000000 + ) + assert ( + node2_remove_tlc_message["output_cells"][1]["args"] + == self.account2["lock_arg"] + ) + + # 7. 时间过去 0~ 1/3 个 delay_epoch + # node2 不会操作 + time.sleep(15) + status = self.node.getClient().get_live_cell(hex(0), node2_remove_tlc_tx) + assert status["status"] == "live" + # 9. 时间过去 delay epoch + # node2 会遗弃 tlc + self.node.getClient().generate_epochs("0x6") + final_tx = self.wait_and_check_tx_pool_fee(1000, False, 300) + # self.Miner.miner_until_tx_committed(self.node, final_tx) + final_tx_message = self.get_tx_message(final_tx) + assert { + "args": self.account2["lock_arg"], + "capacity": 6199999545, + } in final_tx_message["output_cells"] + assert { + "args": self.account1["lock_arg"], + "capacity": 879999999545, + } in final_tx_message["output_cells"] + assert final_tx_message["fee"] > 90000000000 + + def test_node1_has_n_tlc_with_node2_epoch_opt_2(self): + """ + 在tlc过期前 + 时间过去 delay_epoch + 有能解锁的tlc ,也有不能解锁的tlc + node2 可以通过pre_image 解锁部分tlc + node2 不能 解锁的tlc 直接抛弃 + Returns: + + """ + fiber3 = self.start_new_fiber(self.generate_account(1000)) + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(10000 * 100000000), + "public": True, + # ), + } + ) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" + ) + # 创建N个tlc + tlcs = [] + payment_preimages = [] + CHANNEL_ID = self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ] + self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(300 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "expiry": hex((int(time.time()) + 8000) * 1000), + } + ) + time.sleep(2) + for i in range(2): + payment_preimage = self.generate_random_preimage() + invoice = fiber3.get_client().new_invoice( + { + "amount": hex(300 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_expiry_delta": "0xDFFA0", + "payment_preimage": payment_preimage, + # "udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + tlc = self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(300 * 100000000), + "payment_hash": invoice["invoice"]["data"]["payment_hash"], + "expiry": hex((int(time.time()) + 200) * 1000), + } + ) + payment_preimages.append(payment_preimage) + tlcs.append(tlc) + time.sleep(2) + # shutdown + self.fiber1.get_client().shutdown_channel( + { + "channel_id": CHANNEL_ID, + "force": True, + } + ) + # remove tlc + for i in range(1): + self.fiber2.get_client().remove_tlc( + { + "channel_id": CHANNEL_ID, + "tlc_id": tlcs[i]["tlc_id"], + "reason": {"payment_preimage": payment_preimages[i]}, + } + ) + time.sleep(1) + self.fiber1.get_client().remove_watch_channel( + { + "channel_id": CHANNEL_ID, + } + ) + shutdown_tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + self.Miner.miner_until_tx_committed(self.node, shutdown_tx) + # 5. 时间过去 0~ 1/3 个 delay_epoch + # node2 不会操作 + time.sleep(15) + status = self.node.getClient().get_live_cell(hex(0), shutdown_tx) + assert status["status"] == "live" + + self.fiber2.stop() + # 6. 时间过去 delay_epoch 0x6 + # node2 会 发送commit+ tlc + self.node.getClient().generate_epochs("0x6") + self.fiber2.start() + node2_remove_tlc_tx = self.wait_and_check_tx_pool_fee(1000, False, 300) + message = self.get_tx_message(node2_remove_tlc_tx) + print(message) + assert ( + message["input_cells"][0]["capacity"] + - message["output_cells"][0]["capacity"] + == 300 * 100000000 + ) + self.node.getClient().generate_epochs("0x6") + node2_remove_tlc_tx = self.wait_and_check_tx_pool_fee(1000, False, 300) + message = self.get_tx_message(node2_remove_tlc_tx) + print(message) + assert message["fee"] - 600 * 100000000 < 1000000 + + def test_node1_has_n_tlc_un_expired(self): + """ + node1 有N个tlc + 过期前 + 时间过去 0~ 1/3 个 delay_epoch + node1 无法解锁 + 时间过去 delay_epoch 1/3 -2/3 + node1 无法解锁 + 时间过去 delay_epoch 2/3 -1 + node1 无法解锁 + 时间过去 delay_epoch + node1 遗弃tlc + """ + fiber3 = self.start_new_fiber(self.generate_account(1000)) + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(1000 * 100000000), + "public": True, + # "commitment_delay_epoch": hex(6), + # "funding_udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" + ) + # 创建N个tlc + tlcs = [] + payment_preimages = [] + CHANNEL_ID = self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ] + self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(300 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "expiry": hex((int(time.time()) + 300) * 1000), + } + ) + time.sleep(2) + for i in range(2): + payment_preimage = self.generate_random_preimage() + invoice = fiber3.get_client().new_invoice( + { + "amount": hex(300 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_expiry_delta": "0xDFFA0", + "payment_preimage": payment_preimage, + # "udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + tlc = self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(300 * 100000000), + "payment_hash": invoice["invoice"]["data"]["payment_hash"], + "expiry": hex((int(time.time()) + 300) * 1000), + } + ) + payment_preimages.append(payment_preimage) + tlcs.append(tlc) + time.sleep(2) + # shutdown + self.fiber1.get_client().shutdown_channel( + { + "channel_id": CHANNEL_ID, + "force": True, + } + ) + # remove tlc + + self.fiber2.get_client().remove_watch_channel( + { + "channel_id": CHANNEL_ID, + } + ) + shutdown_tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + self.Miner.miner_until_tx_committed(self.node, shutdown_tx) + # 1/3 epoch + self.node.getClient().generate_epochs("0x2") + time.sleep(10) + self.node.getClient().generate_epochs("0x2") + time.sleep(10) + status = self.node.getClient().get_live_cell(hex(0), shutdown_tx) + assert status["status"] == "live" + self.node.getClient().generate_epochs("0x2") + tx_hash = self.wait_and_check_tx_pool_fee(1000, False, 120) + message = self.get_tx_message(tx_hash) + assert message["fee"] - 90000000000 > 0 + assert message["fee"] - 90000000000 < 100000 + + # node1 有tlc 过期后 + @pytest.mark.skip("tlc 过期后, node2 不可以通过pre_image 解锁部分tlc") + def test_node1_has_n_tlc_expired_with_node2_1_3_opt(self): + """ + node1 有N个tlc + 在tlc过期后 + 时间过去 0~ 1/3 个 delay_epoch + node2 无法解锁 + 时间过去 delay_epoch 1/3 -2/3 + node2 可以通过pre_image 解锁部分tlc + 时间过去 delay_epoch + node2 可以舍弃tlc + + 1. node1 4个 add_tlc + 2. node2 remove_tlc + 1个tlc + 3. node1 强制shutdown + 4. node1 关闭watch tower + 5. 时间过去 0~ 1/3 个 delay_epoch + node2 不会操作 + 6. 时间过去 1/3 个delay_epoch + node2 会发送commit+ tlc + 7. 时间过去 delay epoch + node2 会遗弃 tlc + Returns: + """ + fiber3 = self.start_new_fiber(self.generate_account(1000)) + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(10000 * 100000000), + "public": True, + # ), + } + ) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" + ) + # 创建N个tlc + tlcs = [] + payment_preimages = [] + CHANNEL_ID = self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ] + self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(300 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "expiry": hex((int(time.time()) + 5) * 1000), + } + ) + time.sleep(2) + for i in range(3): + payment_preimage = self.generate_random_preimage() + invoice = fiber3.get_client().new_invoice( + { + "amount": hex(300 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_expiry_delta": "0xDFFA0", + "payment_preimage": payment_preimage, + # "udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + tlc = self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(300 * 100000000), + "payment_hash": invoice["invoice"]["data"]["payment_hash"], + "expiry": hex((int(time.time()) + 5) * 1000), + } + ) + payment_preimages.append(payment_preimage) + tlcs.append(tlc) + time.sleep(2) + # shutdown + self.fiber1.get_client().shutdown_channel( + { + "channel_id": CHANNEL_ID, + "force": True, + } + ) + # remove tlc + for i in range(1): + try: + self.fiber2.get_client().remove_tlc( + { + "channel_id": CHANNEL_ID, + "tlc_id": tlcs[i]["tlc_id"], + "reason": {"payment_preimage": payment_preimages[i]}, + } + ) + time.sleep(1) + except Exception: + pass + self.fiber1.get_client().remove_watch_channel( + { + "channel_id": CHANNEL_ID, + } + ) + shutdown_tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + self.Miner.miner_until_tx_committed(self.node, shutdown_tx) + # 5. 时间过去 0~ 1/3 个 delay_epoch + # node2 不会操作 + time.sleep(15) + status = self.node.getClient().get_live_cell(hex(0), shutdown_tx) + assert status["status"] == "live" + + # 6. 时间过去 delay_epoch 1/3 -2/3 + # node2 会 发送commit+ tlc + self.node.getClient().generate_epochs("0x2") + node2_remove_tlc_tx = self.wait_and_check_tx_pool_fee(1000, False, 300) + self.Miner.miner_until_tx_committed(self.node, node2_remove_tlc_tx) + node2_remove_tlc_message = self.get_tx_message(node2_remove_tlc_tx) + assert ( + node2_remove_tlc_message["input_cells"][0]["capacity"] + - node2_remove_tlc_message["output_cells"][0]["capacity"] + == 300 * 100000000 + ) + assert ( + node2_remove_tlc_message["output_cells"][1]["args"] + == self.account2["lock_arg"] + ) + + # 7. 时间过去 0~ 1/3 个 delay_epoch + # node2 不会操作 + time.sleep(15) + status = self.node.getClient().get_live_cell(hex(0), node2_remove_tlc_tx) + assert status["status"] == "live" + # 9. 时间过去 delay epoch + # node2 会遗弃 tlc + self.node.getClient().generate_epochs("0x6") + final_tx = self.wait_and_check_tx_pool_fee(1000, False, 300) + # self.Miner.miner_until_tx_committed(self.node, final_tx) + final_tx_message = self.get_tx_message(final_tx) + assert { + "args": self.account2["lock_arg"], + "capacity": 6199999545, + } in final_tx_message["output_cells"] + assert { + "args": self.account1["lock_arg"], + "capacity": 879999999545, + } in final_tx_message["output_cells"] + assert final_tx_message["fee"] > 90000000000 + + @pytest.mark.skip("tlc 过期后, node2 不可以通过pre_image 解锁部分tlc") + def test_node1_has_n_tlc_expired_with_node2_2_3_opt(self): + """ + node1 有N个tlc + 在tlc过期后 + 时间过去 delay_epoch 2/3~1 + node2 可以通过pre_image 解锁部分tlc + 时间过去 delay_epoch + node2 可以舍弃tlc + Returns: + """ + fiber3 = self.start_new_fiber(self.generate_account(1000)) + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(10000 * 100000000), + "public": True, + # ), + } + ) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" + ) + # 创建N个tlc + tlcs = [] + payment_preimages = [] + CHANNEL_ID = self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ] + self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(300 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "expiry": hex((int(time.time()) + 5) * 1000), + } + ) + time.sleep(2) + for i in range(3): + payment_preimage = self.generate_random_preimage() + invoice = fiber3.get_client().new_invoice( + { + "amount": hex(300 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_expiry_delta": "0xDFFA0", + "payment_preimage": payment_preimage, + # "udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + tlc = self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(300 * 100000000), + "payment_hash": invoice["invoice"]["data"]["payment_hash"], + "expiry": hex((int(time.time()) + 5) * 1000), + } + ) + payment_preimages.append(payment_preimage) + tlcs.append(tlc) + time.sleep(2) + # shutdown + self.fiber1.get_client().shutdown_channel( + { + "channel_id": CHANNEL_ID, + "force": True, + } + ) + # remove tlc + for i in range(1): + try: + self.fiber2.get_client().remove_tlc( + { + "channel_id": CHANNEL_ID, + "tlc_id": tlcs[i]["tlc_id"], + "reason": {"payment_preimage": payment_preimages[i]}, + } + ) + time.sleep(1) + except Exception: + pass + self.fiber1.get_client().remove_watch_channel( + { + "channel_id": CHANNEL_ID, + } + ) + shutdown_tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + self.Miner.miner_until_tx_committed(self.node, shutdown_tx) + # 5. 时间过去 0~ 1/3 个 delay_epoch + # node2 不会操作 + time.sleep(15) + status = self.node.getClient().get_live_cell(hex(0), shutdown_tx) + assert status["status"] == "live" + self.fiber2.stop() + # 6. 时间过去 delay_epoch 1/3 -2/3 + # node2 会 发送commit+ tlc + self.node.getClient().generate_epochs("0x4") + self.fiber2.start() + time.sleep(15) + node2_remove_tlc_tx = self.wait_and_check_tx_pool_fee(1000, False, 300) + self.Miner.miner_until_tx_committed(self.node, node2_remove_tlc_tx) + node2_remove_tlc_message = self.get_tx_message(node2_remove_tlc_tx) + assert ( + node2_remove_tlc_message["input_cells"][0]["capacity"] + - node2_remove_tlc_message["output_cells"][0]["capacity"] + == 300 * 100000000 + ) + assert ( + node2_remove_tlc_message["output_cells"][1]["args"] + == self.account2["lock_arg"] + ) + + def test_node1_tlc_discard_by_node2_after_delay(self): + """ + node1 有N个tlc + 在tlc过期后 + 时间过去 delay_epoch + node2 可以舍弃tlc + Returns: + """ + fiber3 = self.start_new_fiber(self.generate_account(1000)) + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(10000 * 100000000), + "public": True, + # ), + } + ) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" + ) + # 创建N个tlc + tlcs = [] + payment_preimages = [] + CHANNEL_ID = self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ] + self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(300 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "expiry": hex((int(time.time()) + 15) * 1000), + } + ) + time.sleep(2) + for i in range(3): + payment_preimage = self.generate_random_preimage() + invoice = fiber3.get_client().new_invoice( + { + "amount": hex(300 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_expiry_delta": "0xDFFA0", + "payment_preimage": payment_preimage, + # "udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + tlc = self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(300 * 100000000), + "payment_hash": invoice["invoice"]["data"]["payment_hash"], + "expiry": hex((int(time.time()) + 15) * 1000), + } + ) + payment_preimages.append(payment_preimage) + tlcs.append(tlc) + time.sleep(2) + # shutdown + self.fiber1.get_client().shutdown_channel( + { + "channel_id": CHANNEL_ID, + "force": True, + } + ) + # remove tlc + for i in range(1): + try: + self.fiber2.get_client().remove_tlc( + { + "channel_id": CHANNEL_ID, + "tlc_id": tlcs[i]["tlc_id"], + "reason": {"payment_preimage": payment_preimages[i]}, + } + ) + time.sleep(1) + except Exception: + pass + self.fiber1.get_client().remove_watch_channel( + { + "channel_id": CHANNEL_ID, + } + ) + shutdown_tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + time.sleep(15) + self.Miner.miner_until_tx_committed(self.node, shutdown_tx) + # 5. 时间过去 delay_epoch + # node2 舍弃 tlc + self.fiber2.stop() + self.node.getClient().generate_epochs("0x6") + self.fiber2.start() + final_tx = self.wait_and_check_tx_pool_fee(1000, False, 300) + self.Miner.miner_until_tx_committed(self.node, final_tx) + final_tx_tlc_message = self.get_tx_message(final_tx) + print("final_tx_tlc_message:", final_tx_tlc_message) + assert final_tx_tlc_message["fee"] - 120000000000 > 0 + assert final_tx_tlc_message["fee"] - 120000000000 < 100000 + + def test_node1_tlc_expired_node1_opt(self): + """ + node1 有N个tlc + 在tlc过期后 + 时间过去 0~ 1/3 个 delay_epoch + node1 无法解锁 + 时间过去 delay_epoch 1/3 -2/3 + node1 无法解锁 + 时间过去 delay_epoch 2/3~1 + node1 可以解锁 + 时间过去 delay_epoch + node1 可以解锁 + Returns: + """ + fiber3 = self.start_new_fiber(self.generate_account(1000)) + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(1000 * 100000000), + "public": True, + # "commitment_delay_epoch": hex(6), + # "funding_udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" + ) + # 创建N个tlc + tlcs = [] + payment_preimages = [] + CHANNEL_ID = self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ] + self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(300 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "expiry": hex((int(time.time()) + 5) * 1000), + } + ) + time.sleep(2) + for i in range(2): + payment_preimage = self.generate_random_preimage() + invoice = fiber3.get_client().new_invoice( + { + "amount": hex(300 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_expiry_delta": "0xDFFA0", + "payment_preimage": payment_preimage, + # "udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + tlc = self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(300 * 100000000), + "payment_hash": invoice["invoice"]["data"]["payment_hash"], + "expiry": hex((int(time.time()) + 5) * 1000), + } + ) + payment_preimages.append(payment_preimage) + tlcs.append(tlc) + time.sleep(2) + # shutdown + self.fiber1.get_client().shutdown_channel( + { + "channel_id": CHANNEL_ID, + "force": True, + } + ) + # remove tlc + for i in range(1): + self.fiber2.get_client().remove_tlc( + { + "channel_id": CHANNEL_ID, + "tlc_id": tlcs[i]["tlc_id"], + "reason": {"payment_preimage": payment_preimages[i]}, + } + ) + self.fiber2.get_client().remove_watch_channel( + { + "channel_id": CHANNEL_ID, + } + ) + shutdown_tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + self.Miner.miner_until_tx_committed(self.node, shutdown_tx) + # 时间过去 0~ 1/3 个 delay_epoch + # node1 无法解锁 + time.sleep(10) + # 时间过去 delay_epoch 1/3 -2/3 + # node1 无法解锁 + self.node.getClient().generate_epochs("0x2") + time.sleep(10) + status = self.node.getClient().get_live_cell(hex(0), shutdown_tx) + assert status["status"] == "live" + # 时间过去 delay_epoch 2/3~1 + # node1 可以解锁 + self.node.getClient().generate_epochs("0x2") + first_tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + self.Miner.miner_until_tx_committed(self.node, first_tx) + first_tx_message = self.get_tx_message(first_tx) + print("first_tx_message:", first_tx_message) + assert ( + first_tx_message["input_cells"][0]["capacity"] + - first_tx_message["output_cells"][0]["capacity"] + == 300 * 100000000 + ) + # 时间过去 delay_epoch + # node1 可以解锁 + self.fiber1.stop() + self.node.getClient().generate_epochs("0x6") + self.fiber1.start() + next_tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + next_message = self.get_tx_message(next_tx) + print("next_message :", next_message) + assert ( + next_message["input_cells"][0]["capacity"] + - next_message["output_cells"][0]["capacity"] + == 300 * 100000000 + ) + self.Miner.miner_until_tx_committed(self.node, next_tx) + + # @pytest.mark.skip("如果有一个过期,一个没过期,node1 无法解锁") + def test_node1_tlc_expired_with_un_expired_node1_opt(self): + """ + 包含过期和没过期的tlc + 时间过去2/3 epoch + node1 解锁 过期tlc + 时间过去delay epoch + node1 能够解锁过期的tlc + Returns: + + """ + fiber3 = self.start_new_fiber(self.generate_account(1000)) + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(10000 * 100000000), + "public": True, + # "commitment_delay_epoch": hex(6), + # "funding_udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" + ) + # 创建N个tlc + tlcs = [] + payment_preimages = [] + CHANNEL_ID = self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ] + self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(300 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "expiry": hex((int(time.time()) + 50000) * 1000), + } + ) + time.sleep(2) + for i in range(2): + payment_preimage = self.generate_random_preimage() + invoice = fiber3.get_client().new_invoice( + { + "amount": hex(300 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_expiry_delta": "0xDFFA0", + "payment_preimage": payment_preimage, + # "udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + tlc = self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(300 * 100000000), + "payment_hash": invoice["invoice"]["data"]["payment_hash"], + "expiry": hex((int(time.time()) + 5) * 1000), + } + ) + payment_preimages.append(payment_preimage) + tlcs.append(tlc) + time.sleep(2) + # shutdown + self.fiber1.get_client().shutdown_channel( + { + "channel_id": CHANNEL_ID, + "force": True, + } + ) + # remove tlc + for i in range(1): + self.fiber2.get_client().remove_tlc( + { + "channel_id": CHANNEL_ID, + "tlc_id": tlcs[i]["tlc_id"], + "reason": {"payment_preimage": payment_preimages[i]}, + } + ) + self.fiber2.get_client().remove_watch_channel( + { + "channel_id": CHANNEL_ID, + } + ) + shutdown_tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + self.Miner.miner_until_tx_committed(self.node, shutdown_tx) + # 时间过去 0~ 1/3 个 delay_epoch + # node1 无法解锁 + time.sleep(10) + # 时间过去 delay_epoch 1/3 -2/3 + # node1 无法解锁 + self.node.getClient().generate_epochs("0x2") + time.sleep(10) + status = self.node.getClient().get_live_cell(hex(0), shutdown_tx) + assert status["status"] == "live" + # 时间过去 delay_epoch 2/3~1 + # node1 可以解锁 + self.node.getClient().generate_epochs("0x2") + first_tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + self.Miner.miner_until_tx_committed(self.node, first_tx) + first_tx_message = self.get_tx_message(first_tx) + print("first_tx_message:", first_tx_message) + assert ( + first_tx_message["input_cells"][0]["capacity"] + - first_tx_message["output_cells"][0]["capacity"] + == 300 * 100000000 + ) + # 时间过去 delay_epoch + # node1 可以解锁 + self.fiber1.stop() + self.node.getClient().generate_epochs("0x6") + self.fiber1.start() + next_tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + next_message = self.get_tx_message(next_tx) + print("next_message :", next_message) + assert ( + next_message["input_cells"][0]["capacity"] + - next_message["output_cells"][0]["capacity"] + == 300 * 100000000 + ) + self.Miner.miner_until_tx_committed(self.node, next_tx) + + def test_node1_tlc_expired_with_un_expired_node2_opt(self): + """ + 包含过期和没过期的tlc + 时间过去1/3 epoch + + 时间过去2/3 epoch + node2 解锁 过期tlc + 时间过去delay epoch + node1 能够解锁过期的tlc + Returns: + + """ + fiber3 = self.start_new_fiber(self.generate_account(1000)) + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(10000 * 100000000), + "public": True, + # "commitment_delay_epoch": hex(6), + # "funding_udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" + ) + # 创建N个tlc + tlcs = [] + payment_preimages = [] + CHANNEL_ID = self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ] + self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(300 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "expiry": hex((int(time.time()) + 50000) * 1000), + } + ) + time.sleep(2) + for i in range(2): + payment_preimage = self.generate_random_preimage() + invoice = fiber3.get_client().new_invoice( + { + "amount": hex(300 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_expiry_delta": "0xDFFA0", + "payment_preimage": payment_preimage, + # "udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + tlc = self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(300 * 100000000), + "payment_hash": invoice["invoice"]["data"]["payment_hash"], + "expiry": hex((int(time.time()) + 5) * 1000), + } + ) + payment_preimages.append(payment_preimage) + tlcs.append(tlc) + time.sleep(2) + # shutdown + self.fiber1.get_client().shutdown_channel( + { + "channel_id": CHANNEL_ID, + "force": True, + } + ) + # remove tlc + for i in range(1): + self.fiber2.get_client().remove_tlc( + { + "channel_id": CHANNEL_ID, + "tlc_id": tlcs[i]["tlc_id"], + "reason": {"payment_preimage": payment_preimages[i]}, + } + ) + self.fiber1.get_client().remove_watch_channel( + { + "channel_id": CHANNEL_ID, + } + ) + shutdown_tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + self.Miner.miner_until_tx_committed(self.node, shutdown_tx) + # 时间过去 0~ 1/3 个 delay_epoch + # node1 无法解锁 + time.sleep(10) + # 时间过去 delay_epoch 1/3 -2/3 + # node1 无法解锁 + self.node.getClient().generate_epochs("0x2") + time.sleep(10) + + # 过期前 + def test_node2_has_n_tlc_un_expired_node2_opt(self): + """ + node2 有N个tlc + 过期前 + 0~1/3 delay epoch + node2 无法处理 + 1/3~ 2/3 delay epoch + node2 不可以处理 + 2/3~1 delay epoch + node2 不可以处理 + 1 delay epoch + node2 遗弃tlc + Returns: + """ + fiber3 = self.start_new_fiber(self.generate_account(1000)) + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(1000 * 100000000), + "public": True, + # "funding_udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" + ) + # 创建N个tlc + self.send_payment(self.fiber1, self.fiber2, 100 * 100000000) + tlcs = [] + payment_preimages = [] + CHANNEL_ID = self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ] + self.fiber2.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(30 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "expiry": hex((int(time.time()) + 10000) * 1000), + } + ) + time.sleep(1) + for i in range(1): + payment_preimage = self.generate_random_preimage() + invoice = fiber3.get_client().new_invoice( + { + "amount": hex(30 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_expiry_delta": "0xDFFA0", + "payment_preimage": payment_preimage, + # "udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + tlc = self.fiber2.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(30 * 100000000), + "payment_hash": invoice["invoice"]["data"]["payment_hash"], + "expiry": hex((int(time.time()) + 10000) * 1000), + } + ) + payment_preimages.append(payment_preimage) + tlcs.append(tlc) + time.sleep(2) + # shutdown + self.fiber1.get_client().shutdown_channel( + { + "channel_id": CHANNEL_ID, + "force": True, + } + ) + + # remove tlc + for i in range(1): + try: + self.fiber1.get_client().remove_tlc( + { + "channel_id": CHANNEL_ID, + "tlc_id": tlcs[i]["tlc_id"], + "reason": {"payment_preimage": payment_preimages[i]}, + } + ) + except Exception as e: + print(e) + self.fiber1.get_client().remove_watch_channel( + { + "channel_id": CHANNEL_ID, + } + ) + # 过期前 处理不了 + shutdown_tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + self.Miner.miner_until_tx_committed(self.node, shutdown_tx) + # 0~1/3 delay epoch + # node2 无法处理 + time.sleep(10) + # 1/3~ 2/3 delay epoch + # node2 不可以处理 + self.node.getClient().generate_epochs("0x2") + time.sleep(10) + # 2/3~1 delay epoch + # node2 不可以处理 + self.node.getClient().generate_epochs("0x2") + time.sleep(10) + status = self.node.getClient().get_live_cell(hex(0), shutdown_tx) + assert status["status"] == "live" + # 1 delay epoch + # node2 遗弃tlc + self.node.getClient().generate_epochs("0x2") + final_tx = self.wait_and_check_tx_pool_fee(1000, False, 300) + message = self.get_tx_message(final_tx) + assert message["fee"] - 6000000000 > 0 + assert message["fee"] - 6000000000 < 100000 + + def test_node2_has_n_tlc_un_expired_node1_opt(self): + """ + node2 有N个tlc + 过期前 + 0~1/3 delay epoch + node1 无法处理 + 1/3~ 2/3 delay epoch + node1 可以通过pre_image 解锁部分tlc + 2/3~1 delay epoch + node1 可以通过pre_image 解锁部分tlc + node1 不可以抛弃tlc + 1 delay epoch + node1 可以通过pre_image 解锁部分tlc + node1 不可以抛弃tlc + Returns: + """ + fiber3 = self.start_new_fiber(self.generate_account(1000)) + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(10000 * 100000000), + "public": True, + # "funding_udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" + ) + # 创建N个tlc + self.send_payment(self.fiber1, self.fiber2, 100 * 100000000) + tlcs = [] + payment_preimages = [] + CHANNEL_ID = self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ] + self.fiber2.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(3 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "expiry": hex((int(time.time()) + 10000) * 1000), + } + ) + time.sleep(1) + for i in range(4): + payment_preimage = self.generate_random_preimage() + invoice = fiber3.get_client().new_invoice( + { + "amount": hex(3 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_expiry_delta": "0xDFFA0", + "payment_preimage": payment_preimage, + # "udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + tlc = self.fiber2.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(3 * 100000000), + "payment_hash": invoice["invoice"]["data"]["payment_hash"], + "expiry": hex((int(time.time()) + 10000) * 1000), + } + ) + payment_preimages.append(payment_preimage) + tlcs.append(tlc) + time.sleep(5) + # shutdown + self.fiber1.get_client().shutdown_channel( + { + "channel_id": CHANNEL_ID, + "force": True, + } + ) + shutdown_tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + # remove tlc + for i in range(3): + try: + self.fiber1.get_client().remove_tlc( + { + "channel_id": CHANNEL_ID, + "tlc_id": tlcs[i]["tlc_id"], + "reason": {"payment_preimage": payment_preimages[i]}, + } + ) + except Exception as e: + print(e) + time.sleep(1) + self.fiber2.get_client().remove_watch_channel( + { + "channel_id": CHANNEL_ID, + } + ) + self.Miner.miner_until_tx_committed(self.node, shutdown_tx) + # 0~1/3 delay epoch + # node1 无法处理 + time.sleep(10) + status = self.node.getClient().get_live_cell(hex(0), shutdown_tx) + assert status["status"] == "live" + # 1/3~ 2/3 delay epoch + # node1 可以通过pre_image 解锁部分tlc + self.node.getClient().generate_epochs("0x2") + settle_tx1 = self.wait_and_check_tx_pool_fee(1000, False, 120) + settle_tx1_message = self.get_tx_message(settle_tx1) + print("settle_tx1_message:", settle_tx1_message) + # todo add assert + self.Miner.miner_until_tx_committed(self.node, settle_tx1) + + # 2/3~1 delay epoch + # node1 可以通过pre_image 解锁部分tlc + self.fiber1.stop() + self.node.getClient().generate_epochs("0x4") + self.fiber1.start() + settle_tx2 = self.wait_and_check_tx_pool_fee(1000, False, 120) + settle_tx2_message = self.get_tx_message(settle_tx2) + print("settle_tx2_message:", settle_tx2_message) + + # 1 delay epoch + # node1 可以通过pre_image 解锁部分tlc + + self.fiber1.stop() + self.Miner.miner_until_tx_committed(self.node, settle_tx2) + self.node.getClient().generate_epochs("0x6") + self.fiber1.start() + settle_tx3 = self.wait_and_check_tx_pool_fee(1000, False, 120) + settle_tx3_message = self.get_tx_message(settle_tx3) + print("settle_tx3_message:", settle_tx3_message) + + # 2/3~1 delay epoch + # node1 不可以抛弃tlc + self.node.getClient().generate_epochs("0x4") + time.sleep(10) + status = self.node.getClient().get_live_cell(hex(0), settle_tx3) + assert status["status"] == "live" + # 1 delay epoch + # node1 可以抛弃tlc + self.node.getClient().generate_epochs("0x2") + final_tx = self.wait_and_check_tx_pool_fee(1000, False, 300) + message = self.get_tx_message(final_tx) + print("message:", message) + assert message["fee"] - 6 * 100000000 > 0 + assert message["fee"] - 6 * 100000000 < 100000 + + def test_node2_has_n_tlc_expired_node2_opt(self): + """ + node2有N个tlc + 在tlc 过期后 + 0~1/3 delay epoch + node2 无法解锁 + 1/3~2/3 delay epoch + node2 无法解锁 + 2/3~1 delay epoch + node2 可以解锁 + 1 delay epoch + node2 可以解锁 + Returns: + """ + fiber3 = self.start_new_fiber(self.generate_account(1000)) + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(1000 * 100000000), + "public": True, + # "funding_udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" + ) + # 创建N个tlc + self.send_payment(self.fiber1, self.fiber2, 100 * 100000000) + tlcs = [] + payment_preimages = [] + CHANNEL_ID = self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ] + self.fiber2.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(30 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "expiry": hex((int(time.time()) + 10) * 1000), + } + ) + time.sleep(1) + for i in range(1): + payment_preimage = self.generate_random_preimage() + invoice = fiber3.get_client().new_invoice( + { + "amount": hex(30 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_expiry_delta": "0xDFFA0", + "payment_preimage": payment_preimage, + # "udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + tlc = self.fiber2.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(30 * 100000000), + "payment_hash": invoice["invoice"]["data"]["payment_hash"], + "expiry": hex((int(time.time()) + 10) * 1000), + } + ) + payment_preimages.append(payment_preimage) + tlcs.append(tlc) + time.sleep(2) + # shutdown + self.fiber1.get_client().shutdown_channel( + { + "channel_id": CHANNEL_ID, + "force": True, + } + ) + shutdown_tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + self.Miner.miner_until_tx_committed(self.node, shutdown_tx) + self.fiber1.stop() + # 过期后 + # 0~1/3 delay epoch + # node2 无法解锁 + time.sleep(10) + status = self.node.getClient().get_live_cell(hex(0), shutdown_tx) + assert status["status"] == "live" + # 1/3~2/3 delay epoch + # node2 无法解锁 + self.node.getClient().generate_epochs("0x2") + time.sleep(10) + status = self.node.getClient().get_live_cell(hex(0), shutdown_tx) + assert status["status"] == "live" + # 2/3~1 delay epoch + # node2 可以解锁 + self.node.getClient().generate_epochs("0x2") + first_tx = self.wait_and_check_tx_pool_fee(1000, False, 300) + self.Miner.miner_until_tx_committed(self.node, first_tx) + first_tx_message = self.get_tx_message(first_tx) + print("first_tx_message:", first_tx_message) + assert ( + first_tx_message["input_cells"][0]["capacity"] + - first_tx_message["output_cells"][0]["capacity"] + == 30 * 100000000 + ) + # 1 delay epoch + # node2 可以解锁 + self.fiber2.stop() + self.node.getClient().generate_epochs("0x6") + self.fiber2.start() + sed_tx = self.wait_and_check_tx_pool_fee(1000, False, 300) + self.Miner.miner_until_tx_committed(self.node, sed_tx) + sed_tx_message = self.get_tx_message(sed_tx) + print("sed_tx_message:", sed_tx_message) + assert ( + sed_tx_message["input_cells"][0]["capacity"] + - sed_tx_message["output_cells"][0]["capacity"] + == 30 * 100000000 + ) + + @pytest.mark.skip("如果过期,node1 解锁不了") + def test_node2_has_n_tlc_expired_node1_opt(self): + """ + node2有N个tlc + 在tlc 过期后 + 0~1/3 delay epoch + node1 无法解锁 + 1/3~2/3 delay epoch + node1 可以通过pre_image 解锁部分tlc + node1 无法解锁 没有pre_image 的tlc + 2/3~1 delay epoch + node1 可以通过pre_image 解锁部分tlc + node1 无法解锁 没有pre_image 的tlc + 1 delay epoch + node1 可以通过pre_image 解锁部分tlc + node1 可以遗弃 + Returns: + + """ + fiber3 = self.start_new_fiber(self.generate_account(1000)) + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(1000 * 100000000), + "public": True, + # "funding_udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" + ) + # 创建N个tlc + self.send_payment(self.fiber1, self.fiber2, 100 * 100000000) + tlcs = [] + payment_preimages = [] + CHANNEL_ID = self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ] + self.fiber2.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(3 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "expiry": hex((int(time.time()) + 10) * 1000), + } + ) + time.sleep(1) + for i in range(4): + payment_preimage = self.generate_random_preimage() + invoice = fiber3.get_client().new_invoice( + { + "amount": hex(3 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_expiry_delta": "0xDFFA0", + "payment_preimage": payment_preimage, + # "udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + tlc = self.fiber2.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(3 * 100000000), + "payment_hash": invoice["invoice"]["data"]["payment_hash"], + "expiry": hex((int(time.time()) + 10) * 1000), + } + ) + payment_preimages.append(payment_preimage) + tlcs.append(tlc) + time.sleep(2) + # shutdown + self.fiber1.get_client().shutdown_channel( + { + "channel_id": CHANNEL_ID, + "force": True, + } + ) + shutdown_tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + for i in range(3): + try: + self.fiber1.get_client().remove_tlc( + { + "channel_id": CHANNEL_ID, + "tlc_id": tlcs[i]["tlc_id"], + "reason": {"payment_preimage": payment_preimages[i]}, + } + ) + except Exception as e: + print(e) + time.sleep(1) + continue + self.Miner.miner_until_tx_committed(self.node, shutdown_tx) + self.fiber2.stop() + + # 0~1/3 delay epoch + # node1 无法解锁 + time.sleep(10) + status = self.node.getClient().get_live_cell(hex(0), shutdown_tx) + assert status["status"] == "live" + # 1/3~2/3 delay epoch + # node1 可以通过pre_image 解锁部分tlc + self.node.getClient().generate_epochs("0x2") + first_tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + self.Miner.miner_until_tx_committed(self.node, first_tx) + first_tx_message = self.get_tx_message(first_tx) + print("first_tx_message:", first_tx_message) + assert ( + first_tx_message["input_cells"][0]["capacity"] + - first_tx_message["output_cells"][0]["capacity"] + == 3 * 100000000 + ) + # 2/3~1 delay epoch + # node1 可以通过pre_image 解锁部分tlc + self.fiber1.stop() + self.Miner.miner_until_tx_committed(self.node, first_tx) + self.node.getClient().generate_epochs("0x4") + self.fiber1.start() + sed_tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + sed_tx_message = self.get_tx_message(sed_tx) + assert ( + sed_tx_message["input_cells"][0]["capacity"] + - sed_tx_message["output_cells"][0]["capacity"] + == 3 * 100000000 + ) + # 1 delay epoch + # node1 可以通过pre_image 解锁部分tlc + self.fiber1.stop() + self.Miner.miner_until_tx_committed(self.node, sed_tx) + self.node.getClient().generate_epochs("0x6") + self.fiber1.start() + thr_tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + thr_tx_message = self.get_tx_message(thr_tx) + assert ( + thr_tx_message["input_cells"][0]["capacity"] + - thr_tx_message["output_cells"][0]["capacity"] + == 3 * 100000000 + ) + # 1/3~2/3 delay epoch + # node1 无法解锁 没有pre_image 的tlc + self.node.getClient().generate_epochs("0x2") + time.sleep(10) + + # 2/3~1 delay epoch + # node1 无法解锁 没有pre_image 的tlc + self.node.getClient().generate_epochs("0x2") + time.sleep(10) + status = self.node.getClient().get_live_cell(hex(0), thr_tx) + assert status["status"] == "live" + # 1 delay epoch + # node1 可以遗弃 + self.node.getClient().generate_epochs("0x2") + final_tx = self.wait_and_check_tx_pool_fee(1000, False, 300) + message = self.get_tx_message(final_tx) + print("message:", message) + assert message["fee"] - 6 * 10000000 > 0 + assert message["fee"] - 6 * 10000000 < 100000 + + # 都有tlc + # 都没过期 + def test_node1_and_node2_has_n_tlc_node1_node2(self): + """ + node1和node2 都有n个tlc + 都没过期 + 过去1/3 delay epoch + node1 解锁 + node2 解锁 + Returns: + """ + fiber3 = self.start_new_fiber(self.generate_account(1000)) + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(1000 * 100000000), + "public": True, + } + ) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" + ) + # 创建N个tlc + self.send_payment(self.fiber1, self.fiber2, 100 * 100000000) + CHANNEL_ID = self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ] + self.fiber2.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(1 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "expiry": hex((int(time.time()) + 700000) * 1000), + } + ) + time.sleep(1) + self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(1 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "expiry": hex((int(time.time()) + 700000) * 1000), + } + ) + time.sleep(1) + node1_tlcs = [] + node1_payment_preimages = [] + for i in range(2): + payment_preimage = self.generate_random_preimage() + invoice = fiber3.get_client().new_invoice( + { + "amount": hex(30 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_expiry_delta": "0xDFFA0", + "payment_preimage": payment_preimage, + # "udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + tlc = self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(1 * 100000000), + "payment_hash": invoice["invoice"]["data"]["payment_hash"], + "expiry": hex((int(time.time()) + 700000) * 1000), + } + ) + node1_payment_preimages.append(payment_preimage) + node1_tlcs.append(tlc) + time.sleep(2) + + node2_tlcs = [] + node2_payment_preimages = [] + for i in range(2): + payment_preimage = self.generate_random_preimage() + invoice = fiber3.get_client().new_invoice( + { + "amount": hex(30 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_expiry_delta": "0xDFFA0", + "payment_preimage": payment_preimage, + # "udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + tlc = self.fiber2.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(1 * 100000000), + "payment_hash": invoice["invoice"]["data"]["payment_hash"], + "expiry": hex((int(time.time()) + 700000) * 1000), + } + ) + node2_payment_preimages.append(payment_preimage) + node2_tlcs.append(tlc) + time.sleep(2) + # shutdown + self.fiber1.get_client().shutdown_channel( + { + "channel_id": CHANNEL_ID, + "force": True, + } + ) + + # # remove tlc + for i in range(1): + try: + self.fiber2.get_client().remove_tlc( + { + "channel_id": CHANNEL_ID, + "tlc_id": node1_tlcs[i]["tlc_id"], + "reason": {"payment_preimage": node1_payment_preimages[i]}, + } + ) + except Exception as e: + print(e) + shutdown_tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + time.sleep(1) + for i in range(1): + try: + self.fiber1.get_client().remove_tlc( + { + "channel_id": CHANNEL_ID, + "tlc_id": node2_tlcs[i]["tlc_id"], + "reason": {"payment_preimage": node2_payment_preimages[i]}, + } + ) + except Exception as e: + print(e) + time.sleep(1) + + self.Miner.miner_until_tx_committed(self.node, shutdown_tx) + # 1/3 + time.sleep(10) + status = self.node.getClient().get_live_cell(hex(0), shutdown_tx) + assert status["status"] == "live" + self.node.getClient().generate_epochs("0x2") + tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + message = self.get_tx_message(tx) + assert ( + message["input_cells"][0]["capacity"] + - message["output_cells"][0]["capacity"] + == 1 * 100000000 + ) + self.Miner.miner_until_tx_committed(self.node, tx) + self.node.getClient().generate_epochs("0x2") + tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + message = self.get_tx_message(tx) + assert ( + message["input_cells"][0]["capacity"] + - message["output_cells"][0]["capacity"] + == 1 * 100000000 + ) + + def test_node1_and_node2_has_n_tlc_2_3_node1_node2(self): + """ + node1和node2 都有n个tlc + 都没过期 + 过去2/3 delay epoch + node1 解锁 + node2 解锁 + Returns: + """ + fiber3 = self.start_new_fiber(self.generate_account(1000)) + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(1000 * 100000000), + "public": True, + } + ) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" + ) + # 创建N个tlc + self.send_payment(self.fiber1, self.fiber2, 100 * 100000000) + CHANNEL_ID = self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ] + self.fiber2.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(1 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "expiry": hex((int(time.time()) + 700000) * 1000), + } + ) + time.sleep(1) + self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(1 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "expiry": hex((int(time.time()) + 700000) * 1000), + } + ) + time.sleep(1) + node1_tlcs = [] + node1_payment_preimages = [] + for i in range(2): + payment_preimage = self.generate_random_preimage() + invoice = fiber3.get_client().new_invoice( + { + "amount": hex(30 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_expiry_delta": "0xDFFA0", + "payment_preimage": payment_preimage, + # "udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + tlc = self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(1 * 100000000), + "payment_hash": invoice["invoice"]["data"]["payment_hash"], + "expiry": hex((int(time.time()) + 700000) * 1000), + } + ) + node1_payment_preimages.append(payment_preimage) + node1_tlcs.append(tlc) + time.sleep(2) + + node2_tlcs = [] + node2_payment_preimages = [] + for i in range(2): + payment_preimage = self.generate_random_preimage() + invoice = fiber3.get_client().new_invoice( + { + "amount": hex(30 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_expiry_delta": "0xDFFA0", + "payment_preimage": payment_preimage, + # "udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + tlc = self.fiber2.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(1 * 100000000), + "payment_hash": invoice["invoice"]["data"]["payment_hash"], + "expiry": hex((int(time.time()) + 700000) * 1000), + } + ) + node2_payment_preimages.append(payment_preimage) + node2_tlcs.append(tlc) + time.sleep(2) + # shutdown + self.fiber1.get_client().shutdown_channel( + { + "channel_id": CHANNEL_ID, + "force": True, + } + ) + shutdown_tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + # # remove tlc + for i in range(1): + try: + self.fiber2.get_client().remove_tlc( + { + "channel_id": CHANNEL_ID, + "tlc_id": node1_tlcs[i]["tlc_id"], + "reason": {"payment_preimage": node1_payment_preimages[i]}, + } + ) + except Exception as e: + print(e) + time.sleep(1) + + for i in range(1): + try: + self.fiber1.get_client().remove_tlc( + { + "channel_id": CHANNEL_ID, + "tlc_id": node2_tlcs[i]["tlc_id"], + "reason": {"payment_preimage": node2_payment_preimages[i]}, + } + ) + except Exception as e: + print(e) + time.sleep(1) + + self.Miner.miner_until_tx_committed(self.node, shutdown_tx) + # 2/3 + self.fiber1.stop() + self.fiber2.stop() + self.node.getClient().generate_epochs("0x4") + self.fiber1.start() + self.fiber2.start() + tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + message = self.get_tx_message(tx) + assert ( + message["input_cells"][0]["capacity"] + - message["output_cells"][0]["capacity"] + == 1 * 100000000 + ) + time.sleep(5) + self.Miner.miner_until_tx_committed(self.node, tx) + self.fiber1.stop() + self.fiber2.stop() + self.node.getClient().generate_epochs("0x4") + self.fiber1.start() + self.fiber2.start() + tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + message = self.get_tx_message(tx) + assert ( + message["input_cells"][0]["capacity"] + - message["output_cells"][0]["capacity"] + == 1 * 100000000 + ) + + def test_node1_and_node2_has_n_tlc_3_3_node1_node2(self): + """ + node1和node2 都有n个tlc + 都没过期 + 过去 delay epoch + node1 解锁 + node2 解锁 + Returns: + """ + fiber3 = self.start_new_fiber(self.generate_account(1000)) + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(1000 * 100000000), + "public": True, + } + ) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" + ) + # 创建N个tlc + self.send_payment(self.fiber1, self.fiber2, 100 * 100000000) + CHANNEL_ID = self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ] + self.fiber2.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(1 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "expiry": hex((int(time.time()) + 700000) * 1000), + } + ) + time.sleep(1) + self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(1 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "expiry": hex((int(time.time()) + 700000) * 1000), + } + ) + time.sleep(1) + node1_tlcs = [] + node1_payment_preimages = [] + for i in range(2): + payment_preimage = self.generate_random_preimage() + invoice = fiber3.get_client().new_invoice( + { + "amount": hex(1 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_expiry_delta": "0xDFFA0", + "payment_preimage": payment_preimage, + # "udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + tlc = self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(1 * 100000000), + "payment_hash": invoice["invoice"]["data"]["payment_hash"], + "expiry": hex((int(time.time()) + 700000) * 1000), + } + ) + node1_payment_preimages.append(payment_preimage) + node1_tlcs.append(tlc) + time.sleep(2) + + node2_tlcs = [] + node2_payment_preimages = [] + for i in range(2): + payment_preimage = self.generate_random_preimage() + invoice = fiber3.get_client().new_invoice( + { + "amount": hex(1 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_expiry_delta": "0xDFFA0", + "payment_preimage": payment_preimage, + # "udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + tlc = self.fiber2.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(1 * 100000000), + "payment_hash": invoice["invoice"]["data"]["payment_hash"], + "expiry": hex((int(time.time()) + 700000) * 1000), + } + ) + node2_payment_preimages.append(payment_preimage) + node2_tlcs.append(tlc) + time.sleep(2) + # shutdown + self.fiber1.get_client().shutdown_channel( + { + "channel_id": CHANNEL_ID, + "force": True, + } + ) + shutdown_tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + # # remove tlc + for i in range(1): + try: + self.fiber2.get_client().remove_tlc( + { + "channel_id": CHANNEL_ID, + "tlc_id": node1_tlcs[i]["tlc_id"], + "reason": {"payment_preimage": node1_payment_preimages[i]}, + } + ) + except Exception as e: + print(e) + time.sleep(1) + + for i in range(1): + try: + self.fiber1.get_client().remove_tlc( + { + "channel_id": CHANNEL_ID, + "tlc_id": node2_tlcs[i]["tlc_id"], + "reason": {"payment_preimage": node2_payment_preimages[i]}, + } + ) + except Exception as e: + print(e) + time.sleep(1) + + self.Miner.miner_until_tx_committed(self.node, shutdown_tx) + # 2/3 + self.fiber1.stop() + self.fiber2.stop() + self.node.getClient().generate_epochs("0x6") + self.fiber1.start() + tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + self.fiber2.start() + message = self.get_tx_message(tx) + assert ( + message["input_cells"][0]["capacity"] + - message["output_cells"][0]["capacity"] + == 1 * 100000000 + ) + time.sleep(5) + self.Miner.miner_until_tx_committed(self.node, tx) + self.fiber1.stop() + self.fiber2.stop() + self.node.getClient().generate_epochs("0x6") + self.fiber2.start() + tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + self.fiber1.start() + message = self.get_tx_message(tx) + print("message:", message) + assert ( + message["input_cells"][0]["capacity"] + - message["output_cells"][0]["capacity"] + == 1 * 100000000 + ) + + @pytest.mark.skip("如果2边tlc都没过期,node1和node2 都解锁不了") + def test_node1_and_node2_has_n_tlc_expired_n12(self): + """ + node1和node2 都有n个tlc + 都没过期 + 过去 delay epoch + 2个节点都解锁不了 + Returns: + """ + fiber3 = self.start_new_fiber(self.generate_account(1000)) + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(1000 * 100000000), + "public": True, + } + ) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" + ) + # 创建N个tlc + self.send_payment(self.fiber1, self.fiber2, 100 * 100000000) + CHANNEL_ID = self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ] + self.fiber2.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(1 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "expiry": hex((int(time.time()) + 700000) * 1000), + } + ) + time.sleep(1) + self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(1 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "expiry": hex((int(time.time()) + 700000) * 1000), + } + ) + time.sleep(1) + node1_tlcs = [] + node1_payment_preimages = [] + for i in range(2): + payment_preimage = self.generate_random_preimage() + invoice = fiber3.get_client().new_invoice( + { + "amount": hex(1 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_expiry_delta": "0xDFFA0", + "payment_preimage": payment_preimage, + # "udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + tlc = self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(1 * 100000000), + "payment_hash": invoice["invoice"]["data"]["payment_hash"], + "expiry": hex((int(time.time()) + 700000) * 1000), + } + ) + node1_payment_preimages.append(payment_preimage) + node1_tlcs.append(tlc) + time.sleep(2) + + node2_tlcs = [] + node2_payment_preimages = [] + for i in range(2): + payment_preimage = self.generate_random_preimage() + invoice = fiber3.get_client().new_invoice( + { + "amount": hex(1 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_expiry_delta": "0xDFFA0", + "payment_preimage": payment_preimage, + # "udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + tlc = self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(1 * 100000000), + "payment_hash": invoice["invoice"]["data"]["payment_hash"], + "expiry": hex((int(time.time()) + 700000) * 1000), + } + ) + node2_payment_preimages.append(payment_preimage) + node2_tlcs.append(tlc) + time.sleep(2) + # shutdown + self.fiber1.get_client().shutdown_channel( + { + "channel_id": CHANNEL_ID, + "force": True, + } + ) + + # remove tlc + for i in range(1): + try: + self.fiber2.get_client().remove_tlc( + { + "channel_id": CHANNEL_ID, + "tlc_id": node1_tlcs[i]["tlc_id"], + "reason": {"payment_preimage": node1_payment_preimages[i]}, + } + ) + except Exception as e: + print(e) + time.sleep(1) + + for i in range(1): + try: + self.fiber1.get_client().remove_tlc( + { + "channel_id": CHANNEL_ID, + "tlc_id": node2_tlcs[i]["tlc_id"], + "reason": {"payment_preimage": node2_payment_preimages[i]}, + } + ) + except Exception as e: + print(e) + time.sleep(1) + + shutdown_tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + self.Miner.miner_until_tx_committed(self.node, shutdown_tx) + # 1/3 + time.sleep(10) + status = self.node.getClient().get_live_cell(hex(0), shutdown_tx) + assert status["status"] == "live" + self.node.getClient().generate_epochs("0x6") + tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + message = self.get_tx_message(tx) + + @pytest.mark.skip("node1 重启会导致 2/3的时候无法解锁过期tlc") + def test_node1_and_node2_has_n_tlc_expired_node1_stop_restart_opt(self): + """ + node1和node2 都有n个tlc + 都过期了 + 过去2/3 delay epoch + node1 能解锁过期的tlc + Returns: + + """ + fiber3 = self.start_new_fiber(self.generate_account(1000)) + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(1000 * 100000000), + "public": True, + } + ) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" + ) + # 创建N个tlc + self.send_payment(self.fiber1, self.fiber2, 100 * 100000000) + CHANNEL_ID = self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ] + self.fiber2.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(1 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "expiry": hex((int(time.time()) + 5) * 1000), + } + ) + time.sleep(1) + self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(1 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "expiry": hex((int(time.time()) + 5) * 1000), + } + ) + time.sleep(1) + node1_tlcs = [] + node1_payment_preimages = [] + for i in range(2): + payment_preimage = self.generate_random_preimage() + invoice = fiber3.get_client().new_invoice( + { + "amount": hex(1 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_expiry_delta": "0xDFFA0", + "payment_preimage": payment_preimage, + # "udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + tlc = self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(1 * 100000000), + "payment_hash": invoice["invoice"]["data"]["payment_hash"], + "expiry": hex((int(time.time()) + 5) * 1000), + } + ) + node1_payment_preimages.append(payment_preimage) + node1_tlcs.append(tlc) + time.sleep(2) + + node2_tlcs = [] + node2_payment_preimages = [] + for i in range(2): + payment_preimage = self.generate_random_preimage() + invoice = fiber3.get_client().new_invoice( + { + "amount": hex(1 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_expiry_delta": "0xDFFA0", + "payment_preimage": payment_preimage, + # "udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + tlc = self.fiber2.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(1 * 100000000), + "payment_hash": invoice["invoice"]["data"]["payment_hash"], + "expiry": hex((int(time.time()) + 5) * 1000), + } + ) + node2_payment_preimages.append(payment_preimage) + node2_tlcs.append(tlc) + time.sleep(2) + # shutdown + self.fiber1.get_client().shutdown_channel( + { + "channel_id": CHANNEL_ID, + "force": True, + } + ) + shutdown_tx = self.wait_and_check_tx_pool_fee(1000, False, 220) + + # # remove tlc + # for i in range(1): + # try: + # self.fiber2.get_client().remove_tlc({ + # "channel_id": CHANNEL_ID, + # "tlc_id": node1_tlcs[i]["tlc_id"], + # "reason": { + # "payment_preimage": node1_payment_preimages[i] + # } + # }) + # except Exception as e: + # print(e) + # time.sleep(1) + + for i in range(1): + try: + self.fiber1.get_client().remove_tlc( + { + "channel_id": CHANNEL_ID, + "tlc_id": node2_tlcs[i]["tlc_id"], + "reason": {"payment_preimage": node2_payment_preimages[i]}, + } + ) + except Exception as e: + print(e) + time.sleep(1) + + self.Miner.miner_until_tx_committed(self.node, shutdown_tx) + # 1/3 + time.sleep(10) + status = self.node.getClient().get_live_cell(hex(0), shutdown_tx) + assert status["status"] == "live" + # self.node.getClient().generate_epochs("0x2") + # 过去2/3 delay epoch + # node1 能解锁过期的tlc + # node2 能解锁过期的tlc + self.fiber1.stop() + self.fiber2.stop() + self.node.getClient().generate_epochs("0x4") + self.fiber1.start() + tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + message = self.get_tx_message(tx) + print("message:", message) + + def test_node1_and_node2_has_n_tlc_expired_node1_stop_opt(self): + """ + node1和node2 都有n个tlc + 都过期了 + 过去2/3 delay epoch + node1 能解锁过期的tlc + Returns: + + """ + fiber3 = self.start_new_fiber(self.generate_account(1000)) + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(1000 * 100000000), + "public": True, + } + ) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" + ) + # 创建N个tlc + self.send_payment(self.fiber1, self.fiber2, 100 * 100000000) + CHANNEL_ID = self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ] + self.fiber2.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(1 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "expiry": hex((int(time.time()) + 15) * 1000), + } + ) + time.sleep(1) + self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(1 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "expiry": hex((int(time.time()) + 15) * 1000), + } + ) + time.sleep(1) + node1_tlcs = [] + node1_payment_preimages = [] + for i in range(2): + payment_preimage = self.generate_random_preimage() + invoice = fiber3.get_client().new_invoice( + { + "amount": hex(1 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_expiry_delta": "0xDFFA0", + "payment_preimage": payment_preimage, + # "udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + tlc = self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(1 * 100000000), + "payment_hash": invoice["invoice"]["data"]["payment_hash"], + "expiry": hex((int(time.time()) + 15) * 1000), + } + ) + node1_payment_preimages.append(payment_preimage) + node1_tlcs.append(tlc) + time.sleep(2) + + node2_tlcs = [] + node2_payment_preimages = [] + for i in range(2): + payment_preimage = self.generate_random_preimage() + invoice = fiber3.get_client().new_invoice( + { + "amount": hex(1 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_expiry_delta": "0xDFFA0", + "payment_preimage": payment_preimage, + # "udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + tlc = self.fiber2.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(1 * 100000000), + "payment_hash": invoice["invoice"]["data"]["payment_hash"], + "expiry": hex((int(time.time()) + 15) * 1000), + } + ) + node2_payment_preimages.append(payment_preimage) + node2_tlcs.append(tlc) + time.sleep(2) + # shutdown + self.fiber1.get_client().shutdown_channel( + { + "channel_id": CHANNEL_ID, + "force": True, + } + ) + shutdown_tx = self.wait_and_check_tx_pool_fee(1000, False, 220) + + # # remove tlc + # for i in range(1): + # try: + # self.fiber2.get_client().remove_tlc({ + # "channel_id": CHANNEL_ID, + # "tlc_id": node1_tlcs[i]["tlc_id"], + # "reason": { + # "payment_preimage": node1_payment_preimages[i] + # } + # }) + # except Exception as e: + # print(e) + # time.sleep(1) + + for i in range(1): + try: + self.fiber1.get_client().remove_tlc( + { + "channel_id": CHANNEL_ID, + "tlc_id": node2_tlcs[i]["tlc_id"], + "reason": {"payment_preimage": node2_payment_preimages[i]}, + } + ) + except Exception as e: + print(e) + time.sleep(1) + + self.Miner.miner_until_tx_committed(self.node, shutdown_tx) + # 1/3 + time.sleep(10) + status = self.node.getClient().get_live_cell(hex(0), shutdown_tx) + assert status["status"] == "live" + # self.node.getClient().generate_epochs("0x2") + # 过去2/3 delay epoch + # node1 能解锁过期的tlc + # node2 能解锁过期的tlc + self.fiber2.stop() + self.node.getClient().generate_epochs("0x4") + tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + message = self.get_tx_message(tx) + print("message:", message) + time.sleep(3) + self.Miner.miner_until_tx_committed(self.node, tx) + + def test_node1_and_node2_has_n_tlc_expired(self): + """ + node1和node2 都有n个tlc + 都过期了 + 过去2/3 delay epoch + node1 能解锁过期的tlc + node2 能解锁过期的tlc + 过去 delay epoch + 2个节点都能解锁 + 当自己tlc 清理完 node 会遗弃交易 + Returns: + """ + fiber3 = self.start_new_fiber(self.generate_account(1000)) + self.fiber1.get_client().open_channel( + { + "peer_id": self.fiber2.get_peer_id(), + "funding_amount": hex(1000 * 100000000), + "public": True, + } + ) + self.wait_for_channel_state( + self.fiber1.get_client(), self.fiber2.get_peer_id(), "CHANNEL_READY" + ) + # 创建N个tlc + self.send_payment(self.fiber1, self.fiber2, 100 * 100000000) + CHANNEL_ID = self.fiber1.get_client().list_channels({})["channels"][0][ + "channel_id" + ] + self.fiber2.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(92 * 100000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "expiry": hex((int(time.time()) + 15) * 1000), + } + ) + time.sleep(1) + self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(1 * 110000000), + "payment_hash": "0x0000000000000000000000000000000000000000000000000000000000000001", + "expiry": hex((int(time.time()) + 15) * 1000), + } + ) + time.sleep(1) + self.get_fibers_balance_message() + node1_tlcs = [] + node1_payment_preimages = [] + for i in range(2): + payment_preimage = self.generate_random_preimage() + invoice = fiber3.get_client().new_invoice( + { + "amount": hex(1 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_expiry_delta": "0xDFFA0", + "payment_preimage": payment_preimage, + # "udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + tlc = self.fiber1.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(1 * 101000000 + i * 100000), + "payment_hash": invoice["invoice"]["data"]["payment_hash"], + "expiry": hex((int(time.time()) + 15) * 1000), + } + ) + node1_payment_preimages.append(payment_preimage) + node1_tlcs.append(tlc) + time.sleep(2) + + node2_tlcs = [] + node2_payment_preimages = [] + for i in range(2): + payment_preimage = self.generate_random_preimage() + invoice = fiber3.get_client().new_invoice( + { + "amount": hex(1 * 100000000), + "currency": "Fibd", + "description": "test invoice generated by node3", + "expiry": "0xe10", + "final_expiry_delta": "0xDFFA0", + "payment_preimage": payment_preimage, + # "udt_type_script": self.get_account_udt_script( + # self.fiber1.account_private + # ), + } + ) + tlc = self.fiber2.get_client().add_tlc( + { + "channel_id": CHANNEL_ID, + "amount": hex(1 * 102000000 + i * 100000), + "payment_hash": invoice["invoice"]["data"]["payment_hash"], + "expiry": hex((int(time.time()) + 15) * 1000), + } + ) + node2_payment_preimages.append(payment_preimage) + node2_tlcs.append(tlc) + time.sleep(2) + # shutdown + self.get_fibers_balance_message() + self.fiber1.get_client().shutdown_channel( + { + "channel_id": CHANNEL_ID, + "force": True, + } + ) + shutdown_tx = self.wait_and_check_tx_pool_fee(1000, False, 220) + + # # remove tlc + for i in range(1): + try: + self.fiber2.get_client().remove_tlc( + { + "channel_id": CHANNEL_ID, + "tlc_id": node1_tlcs[i]["tlc_id"], + "reason": {"payment_preimage": node1_payment_preimages[i]}, + } + ) + except Exception as e: + print(e) + time.sleep(1) + + for i in range(1): + try: + self.fiber1.get_client().remove_tlc( + { + "channel_id": CHANNEL_ID, + "tlc_id": node2_tlcs[i]["tlc_id"], + "reason": {"payment_preimage": node2_payment_preimages[i]}, + } + ) + except Exception as e: + print(e) + time.sleep(1) + + self.Miner.miner_until_tx_committed(self.node, shutdown_tx) + # 1/3 + time.sleep(10) + status = self.node.getClient().get_live_cell(hex(0), shutdown_tx) + assert status["status"] == "live" + # self.node.getClient().generate_epochs("0x2") + # 过去2/3 delay epoch + # node1 能解锁过期的tlc + # node2 能解锁过期的tlc + self.fiber1.stop() + self.fiber2.stop() + self.node.getClient().generate_epochs("0x4") + self.fiber1.start() + self.fiber2.start() + tx = self.wait_and_check_tx_pool_fee(1000, False, 220) + message = self.get_tx_message(tx) + assert ( + message["input_cells"][0]["capacity"] + - message["output_cells"][0]["capacity"] + == 110000000 + ) + print("first message:", message) + self.fiber1.stop() + self.fiber2.stop() + self.Miner.miner_until_tx_committed(self.node, tx) + self.node.getClient().generate_epochs("0x4") + self.fiber2.start() + tx = self.wait_and_check_tx_pool_fee(1000, False, 220) + self.fiber1.start() + message = self.get_tx_message(tx) + assert ( + message["input_cells"][0]["capacity"] + - message["output_cells"][0]["capacity"] + == 101000000 + ) + print("2 message:", message) + + # 过去 delay epoch + # 2个节点都能解锁 + self.fiber1.stop() + self.fiber2.stop() + self.Miner.miner_until_tx_committed(self.node, tx) + self.node.getClient().generate_epochs("0x6") + self.fiber1.start() + tx = self.wait_and_check_tx_pool_fee(1000, False, 220) + self.fiber2.start() + message = self.get_tx_message(tx) + print("3:", message) + assert ( + message["input_cells"][0]["capacity"] + - message["output_cells"][0]["capacity"] + == 101100000 + ) + self.fiber1.stop() + self.fiber2.stop() + self.Miner.miner_until_tx_committed(self.node, tx) + self.node.getClient().generate_epochs("0x6") + self.fiber2.start() + tx = self.wait_and_check_tx_pool_fee(1000, False, 120) + self.fiber1.start() + message = self.get_tx_message(tx) + assert ( + message["input_cells"][0]["capacity"] + - message["output_cells"][0]["capacity"] + == 9200000000 + ) + self.fiber1.stop() + self.fiber2.stop() + self.Miner.miner_until_tx_committed(self.node, tx) + time.sleep(5) + self.node.getClient().generate_epochs("0x6") + self.fiber1.start() + tx = self.wait_and_check_tx_pool_fee(1000, False, 220) + message = self.get_tx_message(tx) + assert ( + message["input_cells"][0]["capacity"] + - message["output_cells"][0]["capacity"] + == 102000000 + ) + time.sleep(5) + # 当自己tlc 清理完 node 会遗弃交易 + self.Miner.miner_until_tx_committed(self.node, tx) + time.sleep(5) + self.node.getClient().generate_epochs("0x6") + tx = self.wait_and_check_tx_pool_fee(1000, False, 220) + message = self.get_tx_message(tx) + assert message["fee"] > 1000000 diff --git a/test_cases/fiber/testnet/test_fiber_local_node.py b/test_cases/fiber/testnet/test_fiber_local_node.py new file mode 100644 index 00000000..1809af4f --- /dev/null +++ b/test_cases/fiber/testnet/test_fiber_local_node.py @@ -0,0 +1,351 @@ +import time + +from framework.basic import CkbTest +from framework.fiber_rpc import FiberRPCClient +from framework.test_fiber import Fiber, FiberConfigPath +from framework.util import generate_random_preimage +import logging + +LOGGER = logging.getLogger(__name__) + + +# ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqt2yg5ctyv59wsrqk2d634rj6k7c8kdjycft39my +# ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsq0n4lwpc3k24hnt75pmgpmg2hgack50wdgnlsp6m + + +class TestFiber(CkbTest): + cryptapeFiber1 = FiberRPCClient("http://18.163.221.211:8227") + cryptapeFiber2 = FiberRPCClient("http://18.162.235.225:8227") + + ACCOUNT_PRIVATE_1 = ( + "0xaae4515b745efcd6f00c1b40aaeef3dd66c82d75f8f43d0f18e1a1eecb90ada4" + ) + ACCOUNT_PRIVATE_2 = ( + "0x518d76bbfe5ffe3a8ef3ad486e784ec333749575fb3c697126cdaa8084d42532" + ) + fiber1: Fiber + fiber2: Fiber + + @classmethod + def setup_class(cls): + print("\nSetup TestClass2") + cls.fiber1 = Fiber.init_by_port( + FiberConfigPath.CURRENT_TESTNET, + cls.ACCOUNT_PRIVATE_1, + "fiber/node1", + "8228", + "8227", + ) + + cls.fiber2 = Fiber.init_by_port( + FiberConfigPath.CURRENT_TESTNET, + cls.ACCOUNT_PRIVATE_2, + "fiber/node2", + "8229", + "8230", + ) + + cls.fiber1.prepare() + cls.fiber1.start() + + cls.fiber2.prepare() + cls.fiber2.start() + + cls.fiber1.get_client().connect_peer( + {"address": cls.cryptapeFiber1.node_info()["addresses"][0]} + ) + + cls.fiber2.get_client().connect_peer( + {"address": cls.cryptapeFiber2.node_info()["addresses"][0]} + ) + time.sleep(10) + + @classmethod + def teardown_class(cls): + channels = cls.fiber1.get_client().list_channels({}) + for i in range(len(channels["channels"])): + channel = channels["channels"][i] + if channel["state"]["state_name"] != "CHANNEL_READY": + continue + cls.fiber1.get_client().shutdown_channel( + { + "channel_id": channel["channel_id"], + "close_script": { + "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + "hash_type": "type", + "args": cls.fiber1.get_account()["lock_arg"], + }, + "fee_rate": "0x3FC", + } + ) + wait_for_channel_state( + cls.fiber1.get_client(), cls.cryptapeFiber1.get_peer_id(), "CLOSED", 120 + ) + + channels = cls.fiber2.get_client().list_channels({}) + for i in range(len(channels["channels"])): + channel = channels["channels"][i] + if channel["state"]["state_name"] != "CHANNEL_READY": + continue + cls.fiber2.get_client().shutdown_channel( + { + "channel_id": channel["channel_id"], + "close_script": { + "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + "hash_type": "type", + "args": cls.fiber2.get_account()["lock_arg"], + }, + "fee_rate": "0x3FC", + } + ) + wait_for_channel_state( + cls.fiber2.get_client(), cls.cryptapeFiber2.get_peer_id(), "CLOSED", 120 + ) + + cls.fiber1.stop() + cls.fiber1.clean() + cls.fiber2.stop() + cls.fiber2.clean() + + def test_ckb_01(self): + # open_channel + temporary_channel_id = self.fiber1.get_client().open_channel( + { + "peer_id": self.cryptapeFiber1.get_peer_id(), + "funding_amount": hex(500 * 100000000), + "public": True, + # "tlc_fee_proportional_millionths": "0x4B0", + } + ) + + temporary_channel_id = self.fiber2.get_client().open_channel( + { + "peer_id": self.cryptapeFiber2.get_peer_id(), + "funding_amount": hex(500 * 100000000), + "public": True, + # "tlc_fee_proportional_millionths": "0x4B0", + } + ) + wait_for_channel_state( + self.fiber1.get_client(), + self.cryptapeFiber1.get_peer_id(), + "CHANNEL_READY", + 120, + ) + + wait_for_channel_state( + self.fiber2.get_client(), + self.cryptapeFiber2.get_peer_id(), + "CHANNEL_READY", + 120, + ) + begin = time.time() + # wait dry_run success + send_payment( + self.fiber1.get_client(), self.fiber2.get_client(), 1000, None, 20 * 60 + ) + fiber1_to_fiber2_time = time.time() + send_payment( + self.fiber2.get_client(), self.fiber1.get_client(), 1000, None, 20 * 60 + ) + fiber2_to_fiber1_time = time.time() + LOGGER.info(f"fiber1_to_fiber2 cost time: {fiber1_to_fiber2_time - begin}") + LOGGER.info( + f"fiber2_to_fiber1 cost time: {fiber2_to_fiber1_time - fiber1_to_fiber2_time}" + ) + + def test_ckb_02(self): + self.fiber1.stop() + self.fiber2.stop() + self.fiber1.start() + self.fiber2.start() + begin = time.time() + send_payment( + self.fiber1.get_client(), self.fiber2.get_client(), 1000, None, 20 * 60 + ) + fiber1_to_fiber2_time = time.time() + send_payment( + self.fiber2.get_client(), self.fiber1.get_client(), 1000, None, 20 * 60 + ) + fiber2_to_fiber1_time = time.time() + LOGGER.info(f"fiber1_to_fiber2 cost time: {fiber1_to_fiber2_time - begin}") + LOGGER.info( + f"fiber2_to_fiber1 cost time: {fiber2_to_fiber1_time - fiber1_to_fiber2_time}" + ) + + def test_udt(self): + funding_udt_type_script = { + "code_hash": "0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a", + "hash_type": "type", + "args": "0x878fcc6f1f08d48e87bb1c3b3d5083f23f8a39c5d5c764f253b55b998526439b", + } + temporary_channel_id = self.fiber1.get_client().open_channel( + { + "peer_id": self.cryptapeFiber1.get_peer_id(), + "funding_amount": hex(20 * 100000000), + "public": True, + "funding_udt_type_script": funding_udt_type_script, + } + ) + + temporary_channel_id = self.fiber2.get_client().open_channel( + { + "peer_id": self.cryptapeFiber2.get_peer_id(), + "funding_amount": hex(20 * 100000000), + "public": True, + "funding_udt_type_script": funding_udt_type_script, + } + ) + wait_for_channel_state( + self.fiber1.get_client(), + self.cryptapeFiber1.get_peer_id(), + "CHANNEL_READY", + 120, + ) + + wait_for_channel_state( + self.fiber2.get_client(), + self.cryptapeFiber2.get_peer_id(), + "CHANNEL_READY", + 120, + ) + begin = time.time() + # wait dry_run success + send_payment( + self.fiber1.get_client(), + self.cryptapeFiber2, + 100000, + funding_udt_type_script, + 20 * 60, + ) + send_payment( + self.fiber2.get_client(), + self.cryptapeFiber1, + 100000, + funding_udt_type_script, + 20 * 60, + ) + + send_payment( + self.fiber1.get_client(), + self.fiber2.get_client(), + 1000, + funding_udt_type_script, + 20 * 60, + ) + + fiber1_to_fiber2_time = time.time() + send_payment( + self.fiber2.get_client(), + self.fiber1.get_client(), + 1000, + funding_udt_type_script, + 20 * 60, + ) + fiber2_to_fiber1_time = time.time() + LOGGER.info(f"fiber1_to_fiber2 cost time: {fiber1_to_fiber2_time - begin}") + LOGGER.info( + f"fiber2_to_fiber1 cost time: {fiber2_to_fiber1_time - fiber1_to_fiber2_time}" + ) + + def test_udt_02(self): + funding_udt_type_script = { + "code_hash": "0x1142755a044bf2ee358cba9f2da187ce928c91cd4dc8692ded0337efa677d21a", + "hash_type": "type", + "args": "0x878fcc6f1f08d48e87bb1c3b3d5083f23f8a39c5d5c764f253b55b998526439b", + } + self.fiber1.stop() + self.fiber2.stop() + self.fiber1.start() + self.fiber2.start() + begin = time.time() + # wait dry_run success + + send_payment( + self.fiber1.get_client(), + self.cryptapeFiber2, + 100000, + funding_udt_type_script, + 20 * 60, + ) + send_payment( + self.fiber2.get_client(), + self.cryptapeFiber1, + 100000, + funding_udt_type_script, + 20 * 60, + ) + + send_payment( + self.fiber1.get_client(), + self.fiber2.get_client(), + 1000, + funding_udt_type_script, + 20 * 60, + ) + fiber1_to_fiber2_time = time.time() + send_payment( + self.fiber2.get_client(), + self.fiber1.get_client(), + 1000, + funding_udt_type_script, + 20 * 60, + ) + fiber2_to_fiber1_time = time.time() + LOGGER.info(f"fiber1_to_fiber2 cost time: {fiber1_to_fiber2_time - begin}") + LOGGER.info( + f"fiber2_to_fiber1 cost time: {fiber2_to_fiber1_time - fiber1_to_fiber2_time}" + ) + + +def send_payment( + fiber1: FiberRPCClient, fiber2: FiberRPCClient, amount, udt=None, wait_times=300 +): + try_times = 0 + payment = None + for i in range(wait_times): + try: + payment = fiber1.send_payment( + { + "amount": hex(amount), + "target_pubkey": fiber2.node_info()["node_id"], + "keysend": True, + "udt_type_script": udt, + } + ) + break + except Exception as e: + print(e) + print(f"send try count: {i}") + time.sleep(1) + continue + for i in range(wait_times): + time.sleep(1) + try: + payment = fiber1.get_payment({"payment_hash": payment["payment_hash"]}) + if payment["status"] == "Failed": + return send_payment(fiber1, fiber2, amount, udt, wait_times - i) + if payment["status"] == "Success": + print("payment success") + return payment + except Exception as e: + print(e) + print(f"wait try count: {i}") + continue + raise TimeoutError("payment timeout") + + +def wait_for_channel_state(client, peer_id, expected_state, timeout=120): + """Wait for a channel to reach a specific state.""" + for _ in range(timeout): + channels = client.list_channels({"peer_id": peer_id, "include_closed": True}) + if channels["channels"][0]["state"]["state_name"] == expected_state: + print(f"Channel reached expected state: {expected_state}") + return channels["channels"][0]["channel_id"] + print( + f"Waiting for channel state: {expected_state}, current state: {channels['channels'][0]['state']['state_name']}" + ) + time.sleep(1) + raise TimeoutError( + f"Channel did not reach state {expected_state} within timeout period." + ) From 58a9ac5495965c205de7eae045003419c6095e29 Mon Sep 17 00:00:00 2001 From: gpBlockchain <32102187+gpBlockchain@users.noreply.github.com> Date: Fri, 25 Apr 2025 17:47:23 +0800 Subject: [PATCH 55/57] Update fiber-testnet.yml --- .github/workflows/fiber-testnet.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/fiber-testnet.yml b/.github/workflows/fiber-testnet.yml index b18e3306..6ec526d7 100644 --- a/.github/workflows/fiber-testnet.yml +++ b/.github/workflows/fiber-testnet.yml @@ -52,7 +52,10 @@ jobs: GitUrl: '${{ github.event.inputs.GitUrl }}' GitBranch: '${{ github.event.inputs.GitBranch }}' BuildFIBER: '${{ github.event.inputs.BuildFIBER }}' - + + - name: Setup upterm session + if: always() + uses: lhotari/action-upterm@v1 - name: Run tests run: make fiber_testnet_test From e0acc0903415f8436413175695d082f80a59fbfc Mon Sep 17 00:00:00 2001 From: gpBlockchain <32102187+gpBlockchain@users.noreply.github.com> Date: Fri, 25 Apr 2025 17:59:35 +0800 Subject: [PATCH 56/57] Update download_fiber.py --- download_fiber.py | 1 + 1 file changed, 1 insertion(+) diff --git a/download_fiber.py b/download_fiber.py index 84581865..f9262bde 100644 --- a/download_fiber.py +++ b/download_fiber.py @@ -18,6 +18,7 @@ "0.3.1", "0.4.0", "0.4.2", + "0.5.0", ] # Replace with your versions DOWNLOAD_DIR = "download/fiber" From 4dce12c8f0f1adbb5a37c754c6baf574030b1611 Mon Sep 17 00:00:00 2001 From: gpBlockchain <32102187+gpBlockchain@users.noreply.github.com> Date: Fri, 25 Apr 2025 18:00:04 +0800 Subject: [PATCH 57/57] Update fiber-testnet.yml --- .github/workflows/fiber-testnet.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/fiber-testnet.yml b/.github/workflows/fiber-testnet.yml index 6ec526d7..815bac71 100644 --- a/.github/workflows/fiber-testnet.yml +++ b/.github/workflows/fiber-testnet.yml @@ -52,10 +52,8 @@ jobs: GitUrl: '${{ github.event.inputs.GitUrl }}' GitBranch: '${{ github.event.inputs.GitBranch }}' BuildFIBER: '${{ github.event.inputs.BuildFIBER }}' + - - name: Setup upterm session - if: always() - uses: lhotari/action-upterm@v1 - name: Run tests run: make fiber_testnet_test