diff --git a/Makefile b/Makefile index dec0c35..5c71619 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ +.PHONY: prepare test clean docs + prepare: python3 -m venv venv . venv/bin/activate @@ -10,31 +12,37 @@ prepare: echo "install ckb cli" sh prepare.sh -check_failed_html: - @if test -n "$$(ls report/*failed.html 2>/dev/null)"; then \ - echo "Error: Failed HTML files found in the 'report' directory"; \ - exit 1; \ - fi +test_cases := \ + test_cases/replace_rpc \ + test_cases/ckb_cli \ + test_cases/ckb2023 \ + test_cases/contracts \ + test_cases/example \ + test_cases/framework \ + test_cases/light_client \ + test_cases/mocking \ + test_cases/node_compatible \ + test_cases/rpc \ + test_cases/soft_fork \ + test_cases/issue \ + test_cases/tx_pool_refactor \ + test_cases/feature + test: - bash test.sh test_cases/replace_rpc - bash test.sh test_cases/ckb2023 - bash test.sh test_cases/ckb_cli - bash test.sh test_cases/contracts - bash test.sh test_cases/example - bash test.sh test_cases/framework - bash test.sh test_cases/light_client - bash test.sh test_cases/mocking - bash test.sh test_cases/node_compatible - bash test.sh test_cases/rpc - bash test.sh test_cases/soft_fork - bash test.sh test_cases/issue - bash test.sh test_cases/tx_pool_refactor - bash test.sh test_cases/feature - @if test -n "$$(ls report/*failed.html 2>/dev/null)"; then \ - echo "Error: Failed HTML files found in the 'report' directory"; \ + @failed_cases=; \ + for test_case in $(test_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 + clean: pkill ckb rm -rf tmp @@ -44,4 +52,4 @@ clean: rm -rf ckb-* docs: - python -m pytest --docs=docs/soft.md --doc-type=md test_cases/soft_fork \ No newline at end of file + python -m pytest --docs=docs/soft.md --doc-type=md test_cases/soft_fork diff --git a/download.py b/download.py index 332c7f6..b95cfac 100644 --- a/download.py +++ b/download.py @@ -11,7 +11,7 @@ import requests from tqdm import tqdm -versions = ['0.109.0', '0.110.2', '0.111.0', '0.112.1', '0.113.1', "0.114.0", "0.115.0-rc1"] # Replace with your versions +versions = ['0.109.0', '0.110.2', '0.111.0', '0.112.1', '0.113.1', "0.114.0", "0.115.0"] # Replace with your versions DOWNLOAD_DIR = "download" diff --git a/framework/test_node.py b/framework/test_node.py index 6b53c0d..71cbc4c 100644 --- a/framework/test_node.py +++ b/framework/test_node.py @@ -22,7 +22,7 @@ class CkbNodeConfigPath(Enum): "source/template/specs/mainnet.toml.j2", "download/0.115.0") - v115_rc1 = ( + v115 = ( "source/template/ckb/v115/ckb.toml.j2", "source/template/ckb/v115/ckb-miner.toml.j2", "source/template/ckb/v115/specs/dev.toml", diff --git a/pytest.ini b/pytest.ini index 8c13aea..2255416 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,5 +1,5 @@ [pytest] ;ci -;addopts = --html=report/report.html +addopts = --html=report/report.html ;debug -addopts = -s +;addopts = -s diff --git a/source/template/ckb/v112/ckb.toml.j2 b/source/template/ckb/v112/ckb.toml.j2 index 5e00e7a..6aa00b2 100644 --- a/source/template/ckb/v112/ckb.toml.j2 +++ b/source/template/ckb/v112/ckb.toml.j2 @@ -179,25 +179,6 @@ block_uncles_cache_size = {{ ckb_store_block_uncles_cache_size | default(" # [indexer_v2] # # Indexing the pending txs in the ckb tx-pool # index_tx_pool = false - -# # Customize block filtering rules to index only retained blocks -block_filter = "block.header.number.to_uint() >= \"0x0\".to_uint()" -# # Customize cell filtering rules to index only retained cells -cell_filter = "let script = output.type;script!=() && script.code_hash == \"0x00000000000000000000000000000000000000000000000000545950455f4944\"" -# # The initial tip can be set higher than the current indexer tip as the starting height for indexing. -init_tip_hash = "0x8fbd0ec887159d2814cee475911600e3589849670f5ee1ed9798b38fdeef4e44" -# -# # CKB rich-indexer has its unique configuration. -[indexer_v2.rich_indexer] -# # By default, it uses an embedded SQLite database. -# # Alternatively, you can set up a PostgreSQL database service and provide the connection parameters. -# db_type = "postgres" -# db_name = "ckb-rich-indexer" -# db_host = "127.0.0.1" -# db_port = 5432 -# db_user = "postgres" -# db_password = "123456" - [block_assembler] code_hash = "{{ ckb_block_assembler_code_hash }}" args = "{{ ckb_block_assembler_args }}" diff --git a/test.sh b/test.sh index e2abae1..cce318f 100644 --- a/test.sh +++ b/test.sh @@ -1,14 +1,45 @@ -python -m pytest $1 -if [ $? == 1 ];then - pkill ckb - sleep 3 - rm -rf tmp - echo "run failed " - mv report/report.html report/${1////_}_failed.html - exit 0 -fi -pkill ckb -sleep 3 -rm -rf tmp -rm -rf report/report.html -echo "run cusscessful" +#!/bin/bash + +# Initialize variables to store passed and failed test cases +passed_cases="" +failed_cases="None" + +# Function to run pytest and process the output +# Function to run pytest and process the output +run_test() { + # Run pytest with verbose and no capture, redirect stderr to stdout + pytest_output=$(python3 -m pytest -vv "$1" 2>&1) + + # Print pytest output + echo "$pytest_output" + + # Check if pytest output contains "skipped" + if grep -q "skipped" <<< "$pytest_output"; then + echo "Test case $1 was skipped" + return 0 # Exit with success code + fi + + # Check if pytest output contains "failed" + if grep -q "failed" <<< "$pytest_output"; then + # Handle failed test case + echo "Test case $1 failed" + failed_cases+=" $1" + return 1 + fi + + # Handle successful test case + echo "Test case $1 passed" + passed_cases+=" $1" + return 0 +} + + +# Main loop to run tests for each test case +for test_case in "$@"; do + run_test "$test_case" +done + +# Display summary of test results +echo "Summary:" +echo "Passed test cases:${passed_cases}" +echo "Failed test cases:${failed_cases}" diff --git a/test_cases/feature/test_generate_epochs.py b/test_cases/feature/test_generate_epochs.py index 35f64fd..6b68531 100644 --- a/test_cases/feature/test_generate_epochs.py +++ b/test_cases/feature/test_generate_epochs.py @@ -7,7 +7,7 @@ class TestGenerateEpochs(CkbTest): @classmethod def setup_class(cls): nodes = [ - cls.CkbNode.init_dev_by_port(cls.CkbNodeConfigPath.v114_rc3, + cls.CkbNode.init_dev_by_port(cls.CkbNodeConfigPath.CURRENT_TEST, "feature/gene_rate_epochs/node{i}".format(i=i), 8114 + i, 8225 + i) for @@ -17,9 +17,9 @@ def setup_class(cls): cls.ckb_spec_config_dict = read_toml_file(get_project_root() + "/" + cls.cluster.ckb_nodes[0].ckb_config_path.ckb_spec_path) cls.cluster.ckb_nodes[0].startWithRichIndexer() cls.cluster.ckb_nodes[1].start() - cls.Miner.make_tip_height_number(nodes[0], 1100) + cls.Miner.make_tip_height_number(nodes[0], 100) cls.cluster.connected_all_nodes() - cls.Node.wait_cluster_height(cls.cluster, 1100, 100) + cls.Node.wait_cluster_height(cls.cluster, 100, 1000) @classmethod def teardown_class(cls): @@ -27,20 +27,6 @@ def teardown_class(cls): cls.cluster.stop_all_nodes() cls.cluster.clean_all_nodes() - def test_demo(self): - """ - test - Returns: - - """ - miner_ACCOUNT= self.Ckb_cli.util_key_info_by_private_key(self.Config.MINER_PRIVATE_1) - self.Ckb_cli.wallet_get_capacity(miner_ACCOUNT["address"]["testnet"],self.cluster.ckb_nodes[0].getClient().url) - self.Ckb_cli.wallet_get_live_cells(miner_ACCOUNT["address"]["testnet"],self.cluster.ckb_nodes[0].getClient().url) - self.Ckb_cli.wallet_get_capacity(miner_ACCOUNT["address"]["testnet"], self.cluster.ckb_nodes[1].getClient().url) - self.Ckb_cli.wallet_get_live_cells(miner_ACCOUNT["address"]["testnet"], - self.cluster.ckb_nodes[1].getClient().url) - - def test_01_generate_epochs_0x2(self): """ 调用generate_epochs 生成2个epoch 的number; @@ -94,5 +80,3 @@ def parse_hex_string(hex_string): return None value = int(hex_string, 16) return EpochNumberWithFraction(value) - - diff --git a/test_cases/rpc/node_fixture.py b/test_cases/rpc/node_fixture.py index ce0cde8..71aed10 100644 --- a/test_cases/rpc/node_fixture.py +++ b/test_cases/rpc/node_fixture.py @@ -34,7 +34,7 @@ def get_cluster(): @pytest.fixture(scope='module') def get_cluster_indexer(): nodes = [ - CkbNode.init_dev_by_port(CkbNodeConfigPath.v115_rc1, "cluster_indexer/node{i}".format(i=i), 8114 + i, + CkbNode.init_dev_by_port(CkbNodeConfigPath.v115, "cluster_indexer/node{i}".format(i=i), 8114 + i, 8225 + i) for i in range(0, 2) diff --git a/test_cases/soft_fork/test_sync_failed.py b/test_cases/soft_fork/test_sync_failed.py new file mode 100644 index 0000000..af804dc --- /dev/null +++ b/test_cases/soft_fork/test_sync_failed.py @@ -0,0 +1,85 @@ +import time + +from framework.basic import CkbTest +from framework.util import run_command, get_project_root + +# use ckb0.110.1-rc1: generate DaoLockSizeMismatch tx in softfork before and after +DATA_ERROR_TAT = f"{get_project_root()}/source/data/data.err.tar.gz" + + +class TestSyncFailed(CkbTest): + + def teardown_method(self, method): + super().teardown_method(method) + print("\nTearing down method", method.__name__) + self.node1.stop() + self.node1.clean() + + self.node2.stop() + self.node2.clean() + + self.node3.stop() + self.node3.clean() + + def test_sync_other_node_again_after_failed(self): + """ + can't sync DaoLockSizeMismatch tx + - after softFork active + - starting_block_limiting_dao_withdrawing_lock <= dao deposit tx block number + 6000 block contains DaoLockSizeMismatch tx + 8669 block contains DaoLockSizeMismatch tx + 1. can sync 6000 block + tip block num > 6000 + 2. node2 and node3 can't sync 8669 block + tip block == 8668 + 3. node2 miner + 4. node2 restart and miner + 5. node1 stop + 6. link node2 and node3 + 7. node3 sync node2 successful + + Returns: + """ + node1 = self.CkbNode.init_dev_by_port(self.CkbNodeConfigPath.V110_MAIN, "tx_pool_test/node1", 8114, 8227) + node2 = self.CkbNode.init_dev_by_port(self.CkbNodeConfigPath.CURRENT_MAIN, "tx_pool_test/node2", 8112, 8228) + node3 = self.CkbNode.init_dev_by_port(self.CkbNodeConfigPath.CURRENT_MAIN, "tx_pool_test/node3", 8113, 8229) + self.node1 = node1 + self.node2 = node2 + self.node3 = node3 + node1.prepare(other_ckb_spec_config={"starting_block_limiting_dao_withdrawing_lock": "5494"}) + node2.prepare(other_ckb_spec_config={"starting_block_limiting_dao_withdrawing_lock": "5494"}) + node3.prepare(other_ckb_spec_config={"starting_block_limiting_dao_withdrawing_lock": "5494"}) + tar_file(DATA_ERROR_TAT, node1.ckb_dir) + node1.start() + node2.start() + node3.start() + self.Miner.make_tip_height_number(node1, 15000) + node1.start_miner() + node1.connected(node2) + node1.connected(node3) + self.Node.wait_node_height(self.node2, 8668, 120) + self.Node.wait_node_height(self.node3, 8668, 120) + block_num = self.node2.getClient().get_tip_block_number() + assert block_num == 8668 + time.sleep(10) + block_num = self.node2.getClient().get_tip_block_number() + assert block_num == 8668 + node2_banned_result = node2.getClient().get_banned_addresses() + node3_banned_result = node3.getClient().get_banned_addresses() + assert "BlockIsInvalid" in node2_banned_result[0]['ban_reason'] + assert "BlockIsInvalid" in node3_banned_result[0]['ban_reason'] + node1.stop() + node2.getClient().clear_banned_addresses() + node3.getClient().clear_banned_addresses() + self.Miner.make_tip_height_number(node2, 10000) + node2.restart() + self.node2.start_miner() + node2.connected(node3) + node3.connected(node2) + + self.Node.wait_node_height(self.node2, 10001, 120) + self.Node.wait_node_height(self.node3, 10001, 120) + + +def tar_file(src_tar, dec_data): + run_command(f"tar -xvf {src_tar} -C {dec_data}") diff --git a/test_cases/tx_pool_refactor/test_11_issue4363.py b/test_cases/tx_pool_refactor/test_11_issue4363.py new file mode 100644 index 0000000..21cbd0e --- /dev/null +++ b/test_cases/tx_pool_refactor/test_11_issue4363.py @@ -0,0 +1,148 @@ +import time + +import pytest + +from framework.basic import CkbTest + + +class TestIssue4363(CkbTest): + + @classmethod + def setup_class(cls): + cls.node1 = cls.CkbNode.init_dev_by_port(cls.CkbNodeConfigPath.CURRENT_TEST, "node/node1", 8114, 8115) + cls.node2 = cls.CkbNode.init_dev_by_port(cls.CkbNodeConfigPath.CURRENT_TEST, "node/node2", 8116, 8117) + + cls.node1.prepare() + cls.node1.start() + cls.node2.prepare() + cls.node2.start() + cls.node2.connected(cls.node1) + cls.Miner.make_tip_height_number(cls.node1, 300) + cls.Node.wait_node_height(cls.node2, 300, 300) + + @classmethod + def teardown_class(cls): + cls.node1.stop() + cls.node1.clean() + cls.node2.stop() + cls.node2.clean() + + def test_01_4363(self): + """ + https://github.com/nervosnetwork/ckb/pull/4363/files + 插入消费cellDep时,如果链条太长,就会将多余的链条交易删除 + 0. 生成250个live cell 和 cell=a + 1. 发送200笔 tx1(cellDep=a) + 2. 发送 tx2(input = 低手续费的tx1.putput(1 || 2 || 3 || 4 || 5)) + 3. 发送 tx3(cellDep = 低手续费的tx1.output(1 || 2 || 3 || 4 || 5)) + 4. 发送 tx4(消费 a) + 5. 查询低手续费的tx1 报 PoolRejectedInvalidated + 6. 查询下tx2 报 PoolRejectedInvalidated + 7. 查询 tx3 报 PoolRejectedInvalidated + """ + # 0. 生成250个live cell + account = self.Ckb_cli.util_key_info_by_private_key(self.Config.ACCOUNT_PRIVATE_1) + account_private = self.Config.ACCOUNT_PRIVATE_1 + tx1_hash = self.Ckb_cli.wallet_transfer_by_private_key(account_private, + account["address"]["testnet"], 1000000, + self.node1.getClient().url, "15000000") + tx_live_cell_hash = self.Tx.send_transfer_self_tx_with_input([tx1_hash], ["0x0"], account_private, + output_count=250, + fee=1000090, + api_url=self.node1.getClient().url) + self.Miner.miner_until_tx_committed(self.node1, tx_live_cell_hash) + tx2_hash = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.ACCOUNT_PRIVATE_2, + account["address"]["testnet"], 1000000, + self.node1.getClient().url, "15000000") + tx_3_father_hash = self.Tx.send_transfer_self_tx_with_input([tx2_hash], ["0x0"], account_private, + output_count=5, + fee=1000090, + api_url=self.node1.getClient().url) + self.Miner.miner_until_tx_committed(self.node1, tx_3_father_hash) + + # 0 生成cell a + tx_a_hash = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.ACCOUNT_PRIVATE_2, + account["address"]["testnet"], 1000000, + self.node1.getClient().url, "1500000") + self.Miner.miner_until_tx_committed(self.node1, tx_a_hash) + # 1. 发送200笔 tx1(cellDep=a) + tx1_list = [] + for i in range(200): + print("current i:", i) + tx_hash = self.Tx.send_transfer_self_tx_with_input([tx_live_cell_hash], [hex(i)], account_private, + output_count=3, + fee=100090 + i * 1000, + api_url=self.node1.getClient().url, + dep_cells=[{"tx_hash": tx_a_hash, "index_hex": "0x0"}]) + tx1_list.append(tx_hash) + + # 2. 发送 tx2(input = 低手续费的tx1.putput(1 || 2 || 3 || 4 || 5)) + tx2_list = [] + tx22_list = [] + for i in range(3): + tx_hash = self.Tx.send_transfer_self_tx_with_input([tx1_list[0]], [hex(i)], account_private, + output_count=2, + fee=100090 + i * 1000, + api_url=self.node1.getClient().url) + tx2_list.append(tx_hash) + tx_hash = self.Tx.send_transfer_self_tx_with_input([tx_hash], [hex(1)], account_private, + output_count=1, + fee=100090 + i * 1000, + api_url=self.node1.getClient().url) + tx22_list.append(tx_hash) + + # 3. 发送 tx3(cellDep = 低手续费的tx1.output(1 || 2 || 3 || 4 || 5)) + tx3_list = [] + for i in range(3): + tx_hash = self.Tx.send_transfer_self_tx_with_input([tx_3_father_hash], [hex(i)], account_private, + output_count=2, + fee=100090 + i * 1000, + api_url=self.node1.getClient().url, + dep_cells=[{"tx_hash": tx1_list[1], "index_hex": "0x0"}]) + tx3_list.append(tx_hash) + + # 4. 发送 tx4(消费 a) + tx_a_cost_hash = self.Tx.send_transfer_self_tx_with_input([tx_a_hash], ["0x0"], account_private, + output_count=1, + fee=100090, + api_url=self.node1.getClient().url) + # TODO remove sleep + time.sleep(10) + # 5. 查询低手续费的tx1 报 PoolRejectedInvalidated + print("---- tx1_list------") + pending_status = 0 + rejected_status = 0 + for tx_hash in tx1_list: + response = self.node1.getClient().get_transaction(tx_hash) + response2 = self.node2.getClient().get_transaction(tx_hash) + assert response['tx_status']['status'] == response2['tx_status']['status'] + if response['tx_status']['status'] == 'pending': + pending_status += 1 + if response['tx_status']['status'] == 'rejected': + rejected_status += 1 + assert pending_status == 124 + assert rejected_status == 76 + # 6. 查询下tx2 报 PoolRejectedInvalidated + print("---- tx2_hash------") + for tx_hash in tx2_list: + response = self.node1.getClient().get_transaction(tx_hash) + response2 = self.node2.getClient().get_transaction(tx_hash) + assert response['tx_status']['status'] == response2['tx_status']['status'] + assert response['tx_status']['status'] == 'rejected' + + print("---- tx22_list------") + for tx_hash in tx22_list: + response = self.node1.getClient().get_transaction(tx_hash) + response2 = self.node2.getClient().get_transaction(tx_hash) + # unknown: the transaction (tx) did not broadcast to node 2. + assert response2['tx_status']['status'] == 'rejected' or response2['tx_status']['status'] == 'unknown' + assert response['tx_status']['status'] == 'rejected' + + # 7. 查询 tx3 报 PoolRejectedInvalidated + print("---- tx3_list------") + for tx_hash in tx3_list: + response = self.node1.getClient().get_transaction(tx_hash) + response2 = self.node2.getClient().get_transaction(tx_hash) + # unknown: the transaction (tx) did not broadcast to node 2. + assert response2['tx_status']['status'] == 'rejected' or response2['tx_status']['status'] == 'unknown' + assert response['tx_status']['status'] == 'rejected'