diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 8101807..84c74df 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -35,6 +35,10 @@ jobs: - name: Run tests run: make test +# - name: Setup upterm session +# if: always() +# uses: lhotari/action-upterm@v1 + - name: Publish reports if: failure() uses: actions/upload-artifact@v2 diff --git a/Makefile b/Makefile index 4881ec9..34bd856 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,7 @@ check_failed_html: exit 1; \ fi 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 diff --git a/download.py b/download.py index 7528255..b3af8f9 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'] # Replace with your versions +versions = ['0.109.0', '0.110.2', '0.111.0', '0.112.1', '0.113.1'] # Replace with your versions DOWNLOAD_DIR = "download" SYSTEMS = { 'Windows': { diff --git a/download_ckb_light_client.py b/download_ckb_light_client.py index abba7d4..d21889f 100644 --- a/download_ckb_light_client.py +++ b/download_ckb_light_client.py @@ -11,7 +11,7 @@ import requests from tqdm import tqdm -versions = ['0.2.4', '0.3.0', '0.3.1'] # Replace with your versions +versions = ['0.2.4', '0.3.0', '0.3.1', '0.3.2', '0.3.3', '0.3.4', '0.3.5'] # Replace with your versions DOWNLOAD_DIR = "download" SYSTEMS = { 'Windows': { diff --git a/framework/basic.py b/framework/basic.py index 8231652..af7c495 100644 --- a/framework/basic.py +++ b/framework/basic.py @@ -1,4 +1,4 @@ -from abc import ABC, abstractmethod +from abc import ABC import unittest import framework.helper.miner @@ -10,7 +10,6 @@ import framework.test_node import framework.test_light_client import framework.test_cluster -import framework.helper import framework.config import shutil from framework.util import get_project_root diff --git a/framework/ckb_light_client_rpc.py b/framework/ckb_light_client_rpc.py index da08bf4..57a0d9c 100644 --- a/framework/ckb_light_client_rpc.py +++ b/framework/ckb_light_client_rpc.py @@ -19,9 +19,14 @@ def get_scripts(self): def get_cells_capacity(self, script): return self.call("get_cells_capacity", [script]) + def get_cells(self, search_key, order, limit, after): + return self.call2("get_cells", [search_key, order, limit, after]) + def fetch_transaction(self, tx_hash): return self.call("fetch_transaction", [tx_hash]) + def get_transactions(self, search_key, order, limit, after): + return self.call("get_transactions", [search_key, order, limit, after]) def send_transaction(self,tx): return self.call("send_transaction",[tx]) @@ -54,3 +59,29 @@ def call(self, method, params): time.sleep(2) continue raise Exception("request time out") + + def call2(self, method, params): + + headers = {'content-type': 'application/json'} + data = { + "id": 42, + "jsonrpc": "2.0", + "method": method, + "params": params + } + print("request:url:{url},data:\n{data}".format(url=self.url, data=json.dumps(data))) + for i in range(100): + try: + response = requests.post(self.url, data=json.dumps(data), headers=headers).json() + # print("response:\n{response}".format(response=json.dumps(response))) + if 'error' in response.keys(): + error_message = response['error'].get('message', 'Unknown error') + raise Exception(f"Error: {error_message}") + + return response.get('result', None) + except requests.exceptions.ConnectionError as e: + print(e) + print("request too quickly, wait 2s") + time.sleep(2) + continue + raise Exception("request time out") diff --git a/framework/helper/contract.py b/framework/helper/contract.py index d73e823..a7af1c0 100644 --- a/framework/helper/contract.py +++ b/framework/helper/contract.py @@ -172,3 +172,106 @@ def invoke_ckb_contract(account_private, contract_out_point_tx_hash, contract_ou tx_info(tmp_tx_file, api_url) # send tx return hash return tx_send(tmp_tx_file, api_url).strip() + + +@exception_use_old_ckb() +def build_invoke_ckb_contract(account_private, contract_out_point_tx_hash, contract_out_point_tx_index, type_script_arg, + hash_type="type", + data="0x", fee=1000, + api_url="http://127.0.0.1:8114"): + """ + + Args: + account_private: + contract_out_point_tx_hash: + contract_out_point_tx_index: + type_script_arg: + hash_type: data ,data1,type data2 + data: + fee: + api_url: + + Returns: + + """ + if hash_type == "type": + contract_code_hash = get_ckb_contract_codehash(contract_out_point_tx_hash, contract_out_point_tx_index, + enable_type_id=True, + api_url=api_url) + else: + contract_code_hash = get_ckb_contract_codehash(contract_out_point_tx_hash, contract_out_point_tx_index, + enable_type_id=False, + api_url=api_url) + # get input_cell + account = util_key_info_by_private_key(account_private) + account_address = account["address"]["testnet"] + account_live_cells = wallet_get_live_cells(account_address, api_url=api_url) + assert len(account_live_cells['live_cells']) > 0 + input_cell_out_points = [] + input_cell_cap = 0 + for i in range(len(account_live_cells['live_cells'])): + input_cell_out_point = { + 'tx_hash': account_live_cells['live_cells'][i]['tx_hash'], + 'index': account_live_cells['live_cells'][i]['output_index'] + } + input_cell_cap += float(account_live_cells['live_cells'][i]['capacity'].replace('(CKB)', "").strip()) * 100000000 + input_cell_out_points.append(input_cell_out_point) + if input_cell_cap> 10000000000: + break + + + # get output_cells.cap = input_cell.cap - fee + # "capacity": "21685.0 (CKB)", + output_cell_capacity = input_cell_cap - fee + + output_cell = { + "capacity": hex(int(output_cell_capacity)), + # rand ckb address for pass ckb-cli check lock address + "lock": { + "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + "hash_type": "type", + "args": "0x470dcdc5e44064909650113a274b3b36aecb6dc7" + }, + "type": { + "code_hash": contract_code_hash, + "hash_type": hash_type, + "args": type_script_arg + } + } + # add dep + cell_dep = { + 'tx_hash': contract_out_point_tx_hash, + 'index': hex(contract_out_point_tx_index) + } + # tx file init + tmp_tx_file = "/tmp/demo.json" + tx_init(tmp_tx_file, api_url) + tx_add_multisig_config(account_address, tmp_tx_file, api_url) + # add input + for i in range(len(input_cell_out_points)): + input_cell_out_point = input_cell_out_points[i] + tx_add_input(input_cell_out_point['tx_hash'], input_cell_out_point['index'], tmp_tx_file, api_url) + transaction = RPCClient(api_url).get_transaction(contract_out_point_tx_hash) + tx_add_header_dep(transaction['tx_status']['block_hash'], tmp_tx_file) + # add output + tx_add_type_out_put(output_cell["type"]["code_hash"], output_cell["type"]["hash_type"], output_cell["type"]["args"], + output_cell["capacity"], data, tmp_tx_file) + # add dep + tx_add_cell_dep(cell_dep['tx_hash'], cell_dep['index'], tmp_tx_file) + # sign + sign_data = tx_sign_inputs(account_private, tmp_tx_file, api_url) + tx_add_signature(sign_data[0]['lock-arg'], sign_data[0]['signature'], tmp_tx_file, api_url) + tx_info(tmp_tx_file, api_url) + # send tx return hash + return build_tx_info(tmp_tx_file) + + +def build_tx_info(tmp_tx_file): + with open(tmp_tx_file, "r") as file: + tx_info_str = file.read() + tx = json.loads(tx_info_str) + sign_keys = list(tx['signatures'].keys())[0] + witness = "0x5500000010000000550000005500000041000000" + tx['signatures'][sign_keys][0][2:] + tx_msg = tx['transaction'] + tx_msg['witnesses'] = [witness] + return tx_msg \ No newline at end of file diff --git a/framework/helper/miner.py b/framework/helper/miner.py index 35941a5..a654c4c 100644 --- a/framework/helper/miner.py +++ b/framework/helper/miner.py @@ -38,7 +38,7 @@ def miner_until_tx_committed(node, tx_hash, with_unknown=False): if tx_response['tx_status']['status'] == "rejected" or tx_response['tx_status']['status'] == "unknown": raise Exception(f"status:{tx_response['tx_status']['status']},reason:{tx_response['tx_status']['reason']}") - raise Exception(f"miner 100 block ,but tx_response always pending:{tx_hash}") + raise Exception(f"miner 100 block ,but tx_response always pending:{tx_hash},tx_response:{tx_response}") # https://github.com/nervosnetwork/rfcs/pull/416 diff --git a/framework/helper/node.py b/framework/helper/node.py index 54af817..25547b0 100644 --- a/framework/helper/node.py +++ b/framework/helper/node.py @@ -23,6 +23,10 @@ def wrapper(*args, **kwargs): def wait_get_transaction(node, tx_hash, status): return node.getClient().get_transaction(tx_hash)['tx_status']['status'] == status +@wait_until_timeout(wait_times=60) +def wait_fetch_transaction(node, tx_hash, status): + return node.getClient().fetch_transaction(tx_hash)['status'] == status + @wait_until_timeout(wait_times=60) def wait_tx_pool(node, pool_key, gt_size): diff --git a/framework/helper/tx.py b/framework/helper/tx.py index d0783eb..7e5b98b 100644 --- a/framework/helper/tx.py +++ b/framework/helper/tx.py @@ -43,3 +43,56 @@ def send_transfer_self_tx_with_input(input_tx_hash_list, input_tx_index_list, si tx_info(tmp_tx_file, api_url) # send tx return hash return tx_send(tmp_tx_file, api_url).strip() + + +def build_send_transfer_self_tx_with_input(input_tx_hash_list, input_tx_index_list, sign_private, data="0x", + fee=5000, output_count=1, + api_url="http://127.0.0.1:8114", dep_cells=[]): + # tx file init + + tmp_tx_file = f"/tmp/demo{time.time()}-{random.randint(0, 100000000)}.json" + tx_init(tmp_tx_file, api_url) + account = util_key_info_by_private_key(sign_private) + account_address = account["address"]["testnet"] + tx_add_multisig_config(account_address, tmp_tx_file, api_url) + # add input + output_cell_capacity_total = 0 + input_cell_template: any + for i in range(len(input_tx_hash_list)): + input_tx_index = input_tx_index_list[i] + input_tx_hash = input_tx_hash_list[i] + print(f"input_tx_index:{input_tx_index}") + tx_add_input(input_tx_hash, int(input_tx_index, 16), tmp_tx_file, api_url) + # add output + input_cell = RPCClient(api_url).get_transaction(input_tx_hash)["transaction"]["outputs"][ + int(input_tx_index, 16)] + output_cell_capacity = int(int(input_cell["capacity"], 16) - fee) + output_cell_capacity_total += output_cell_capacity + input_cell_template = input_cell + min_output_count = min(int(output_cell_capacity_total / (100 * 100000000)), output_count) + min_output_count = max(min_output_count, 1) + output_cell_capacity = int(output_cell_capacity_total / min_output_count) + for i in range(min_output_count): + tx_add_type_out_put(input_cell_template["lock"]["code_hash"], input_cell_template["lock"]["hash_type"], + input_cell_template["lock"]["args"], + hex(output_cell_capacity), data, tmp_tx_file, False) + for i in range(len(dep_cells)): + tx_add_cell_dep(dep_cells[i]['tx_hash'],dep_cells[i]['index_hex'],tmp_tx_file) + + # sign + sign_data = tx_sign_inputs(sign_private, tmp_tx_file, api_url) + tx_add_signature(sign_data[0]['lock-arg'], sign_data[0]['signature'], tmp_tx_file, api_url) + tx_info(tmp_tx_file, api_url) + # send tx return hash + return build_tx_info(tmp_tx_file) + + +def build_tx_info(tmp_tx_file): + with open(tmp_tx_file, "r") as file: + tx_info_str = file.read() + tx = json.loads(tx_info_str) + sign_keys = list(tx['signatures'].keys())[0] + witness = "0x5500000010000000550000005500000041000000" + tx['signatures'][sign_keys][0][2:] + tx_msg = tx['transaction'] + tx_msg['witnesses'] = [witness] + return tx_msg \ No newline at end of file diff --git a/framework/rpc.py b/framework/rpc.py index 27c01de..304b9b9 100644 --- a/framework/rpc.py +++ b/framework/rpc.py @@ -99,7 +99,7 @@ def generate_epochs(self, epoch): return self.call("generate_epochs", [epoch]) def generate_block(self): - return self.call("generate_block",[]) + return self.call("generate_block", []) def get_deployments_info(self): return self.call("get_deployments_info", []) @@ -145,7 +145,7 @@ def get_transaction(self, tx_hash, verbosity=None, only_committed=None): return self.call("get_transaction", [tx_hash, verbosity, only_committed]) def get_transactions(self, search_key, order, limit, after): - return self.call("get_transactions", [search_key, order, limit,after]) + return self.call("get_transactions", [search_key, order, limit, after]) def dry_run_transaction(self, tx): return self.call("dry_run_transaction", [tx]) @@ -183,8 +183,8 @@ def get_live_cell(self, index, tx_hash, with_data=True): def submit_block(self, work_id, block): return self.call("submit_block", [work_id, block]) - def subscribe(self,topic): - return self.call("subscribe",[topic]) + def subscribe(self, topic): + return self.call("subscribe", [topic]) def get_cells_capacity(self, script): return self.call("get_cells_capacity", [script]) @@ -192,7 +192,7 @@ def get_cells_capacity(self, script): def get_current_epoch(self): return self.call("get_current_epoch", []) - def call(self, method, params): + def call(self, method, params, try_count=15): headers = {'content-type': 'application/json'} data = { @@ -202,7 +202,7 @@ def call(self, method, params): "params": params } print(f"request:url:{self.url},data:\n{json.dumps(data)}") - for i in range(15): + for i in range(try_count): try: response = requests.post(self.url, data=json.dumps(data), headers=headers).json() print(f"response:\n{json.dumps(response)}") diff --git a/framework/test_light_client.py b/framework/test_light_client.py index 384faeb..e53f41f 100644 --- a/framework/test_light_client.py +++ b/framework/test_light_client.py @@ -9,7 +9,11 @@ class CkbLightClientConfigPath(Enum): V0_2_4 = ("source/template/ckb_light_client/0.2.4/testnet.toml.j2", "download/0.2.4/ckb-light-client") V0_3_0 = ("source/template/ckb_light_client/0.3.0/testnet.toml.j2", "download/0.3.0/ckb-light-client") V0_3_1 = ("source/template/ckb_light_client/0.3.0/testnet.toml.j2", "download/0.3.1/ckb-light-client") - CURRENT_TEST = ("source/template/ckb_light_client/0.3.0/testnet.toml.j2", "download/0.3.1/ckb-light-client") + V0_3_2 = ("source/template/ckb_light_client/0.3.0/testnet.toml.j2", "download/0.3.2/ckb-light-client") + V0_3_3 = ("source/template/ckb_light_client/0.3.0/testnet.toml.j2", "download/0.3.3/ckb-light-client") + V0_3_4 = ("source/template/ckb_light_client/0.3.0/testnet.toml.j2", "download/0.3.4/ckb-light-client") + V0_3_5 = ("source/template/ckb_light_client/0.3.0/testnet.toml.j2", "download/0.3.5/ckb-light-client") + CURRENT_TEST = ("source/template/ckb_light_client/0.3.0/testnet.toml.j2", "download/0.3.5/ckb-light-client") def __init__(self, ckb_light_client_config_path, ckb_light_bin_path): self.ckb_light_client_config_path = ckb_light_client_config_path @@ -31,7 +35,8 @@ def __init__(self, ckb_light_client_config_path: CkbLightClientConfigPath, ckb_p self.ckb_light_config = { "ckb_light_client_chain": ckb_spec_path, "ckb_light_client_network_bootnodes": ckb_p2p_infos, - "ckb_light_client_rpc_listen_address": f"127.0.0.1:{rpc_port}" + "ckb_light_client_rpc_listen_address": f"127.0.0.1:{rpc_port}", + "ckb_light_client_network_listen_addresses": [f"/ip4/0.0.0.0/tcp/1{rpc_port}"] } self.ckb_light_config_path = f"{self.tmp_path}/testnet.toml" self.client = CKBLightRPCClient(f"http://127.0.0.1:{rpc_port}") diff --git a/framework/test_node.py b/framework/test_node.py index e685a18..90a2c97 100644 --- a/framework/test_node.py +++ b/framework/test_node.py @@ -4,14 +4,27 @@ from framework.config import get_tmp_path, CKB_DEFAULT_CONFIG, CKB_MINER_CONFIG from framework.rpc import RPCClient import shutil +import telnetlib +from websocket import create_connection, WebSocket class CkbNodeConfigPath(Enum): CURRENT_TEST = ( - "source/template/ckb/v112/ckb.toml.j2", + "source/template/ckb/v113/ckb.toml.j2", "source/template/ckb/v112/ckb-miner.toml.j2", "source/template/ckb/v112/specs/dev.toml", - "download/0.112.1" + "download/0.113.1" + ) + CURRENT_MAIN = ("source/template/ckb/v112/ckb.toml.j2", + "source/template/ckb/v112/ckb-miner.toml.j2", + "source/template/specs/mainnet.toml.j2", + "download/0.113.1") + + V113 = ( + "source/template/ckb/v113/ckb.toml.j2", + "source/template/ckb/v113/ckb-miner.toml.j2", + "source/template/ckb/v113/specs/dev.toml", + "download/0.113.1" ) V112 = ( @@ -21,10 +34,14 @@ class CkbNodeConfigPath(Enum): "download/0.112.1" ) - CURRENT_MAIN = ("source/template/ckb/v112/ckb.toml.j2", - "source/template/ckb/v112/ckb-miner.toml.j2", - "source/template/specs/mainnet.toml.j2", - "download/0.112.1") + V112_MAIN = ( + "source/template/ckb/v112/ckb.toml.j2", + "source/template/ckb/v112/ckb-miner.toml.j2", + "source/template/specs/mainnet.toml.j2", + "download/0.112.1" + ) + + V111 = ( "source/template/ckb/v111/ckb.toml.j2", "source/template/ckb/v111/ckb-miner.toml.j2", @@ -198,3 +215,45 @@ def stop_miner(self): def version(self): pass + + def subscribe_telnet(self, topic, other_url=None) -> telnetlib.Telnet: + # new_tip_header | new_tip_block | new_transaction | proposed_transaction | rejected_transaction + if "ckb_tcp_listen_address" not in self.ckb_config.keys(): + raise Exception("not set ckb_ws_listen_address") + ckb_tcp_listen_address = self.ckb_config['ckb_tcp_listen_address'] + if other_url is not None: + ckb_tcp_listen_address = other_url + # get host + host = ckb_tcp_listen_address.split(":")[0] + # get port + port = ckb_tcp_listen_address.split(":")[1] + # new telnet + tn = telnetlib.Telnet(host, int(port)) + print("----") + topic_str = '{"id": 2, "jsonrpc": "2.0", "method": "subscribe", "params": ["' + topic + '"]}' + tn.write(topic_str.encode('utf-8') + b"\n") + data = tn.read_until(b'}\n') + if data: + output = data.decode('utf-8') + print("telnet read:", output) + return tn + + + def subscribe_websocket(self, topic, other_url=None) -> WebSocket: + if other_url is None and "ckb_ws_listen_address" not in self.ckb_config.keys(): + raise Exception("not set ckb_ws_listen_address") + print("subscribe_websocket") + if other_url is not None: + ckb_ws_listen_address = other_url + else: + ckb_ws_listen_address = self.ckb_config['ckb_ws_listen_address'] + print(ckb_ws_listen_address) + ws = create_connection(f"ws://{ckb_ws_listen_address}") + topic_str = '{"id": 2, "jsonrpc": "2.0", "method": "subscribe", "params": ["' + topic + '"]}' + ws.send(topic_str) + print("Sent") + print("Receiving...") + result = ws.recv() + print(result) + # ws.settimeout(1) + return ws diff --git a/framework/util.py b/framework/util.py index 3bfa0fe..8a76c85 100644 --- a/framework/util.py +++ b/framework/util.py @@ -22,7 +22,7 @@ def get_ckb_configs(p2p_port, rpc_port, spec='{ file = "dev.toml" }'): 'ckb_network_listen_addresses': ["/ip4/0.0.0.0/tcp/{p2p_port}".format(p2p_port=p2p_port)], 'ckb_rpc_listen_address': '127.0.0.1:{rpc_port}'.format(rpc_port=rpc_port), 'ckb_rpc_modules': ["Net", "Pool", "Miner", "Chain", "Stats", "Subscription", "Experiment", "Debug", - "IntegrationTest","Indexer"], + "IntegrationTest", "Indexer"], 'ckb_block_assembler_code_hash': '0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8', 'ckb_block_assembler_args': '0x8883a512ee2383c01574a328f60eeccbb4d78240', 'ckb_block_assembler_hash_type': 'type', @@ -57,7 +57,7 @@ def create_config_file(config_values, template_path, output_file): f.write(output) -def run_command(cmd,check_exit_code=True): +def run_command(cmd, check_exit_code=True): if cmd[-1] == "&": cmd1 = "{cmd} echo $! > pid.txt".format(cmd=cmd) print("cmd:{cmd}".format(cmd=cmd1)) @@ -82,12 +82,13 @@ def run_command(cmd,check_exit_code=True): process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) stdout, stderr = process.communicate() exit_code = process.returncode - if not check_exit_code: - return exit_code + if exit_code != 0: print("Command failed with exit code:", exit_code) if stderr: print("Error:", stderr.decode('utf-8')) + if not check_exit_code: + return exit_code raise Exception(stderr.decode('utf-8')) if stderr.decode('utf-8') != "" and stdout.decode('utf-8') != "": print("wain:{result}".format(result=stderr.decode('utf-8'))) @@ -97,7 +98,6 @@ def run_command(cmd,check_exit_code=True): return stdout.decode('utf-8') - def get_project_root(): current_path = os.path.dirname(os.path.abspath(__file__)) pattern = r"(.*ckb-py-integration-test)" diff --git a/prepare.sh b/prepare.sh index d60090b..97d2b90 100644 --- a/prepare.sh +++ b/prepare.sh @@ -6,9 +6,10 @@ make prod cp target/release/ckb-cli ../source/ckb-cli cd ../ cp download/0.110.2/ckb-cli ./source/ckb-cli-old -git clone https://github.com/nervosnetwork/ckb-light-client.git -cd ckb-light-client -cargo build --release -cd ../ -mkdir -p download/0.2.5 -cp ckb-light-client/target/release/ckb-light-client download/0.2.5 +#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 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 03db359..2c10de7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,5 @@ pytest-html==3.2.0 PyYAML==6.0 pytest-docs==0.1.0 parameterized==0.9.0 -toml==0.10.2 \ No newline at end of file +toml==0.10.2 +websocket-client==1.6.4 \ No newline at end of file diff --git a/source/template/ckb/v112/ckb.toml.j2 b/source/template/ckb/v112/ckb.toml.j2 index ee0951e..7ac0464 100644 --- a/source/template/ckb/v112/ckb.toml.j2 +++ b/source/template/ckb/v112/ckb.toml.j2 @@ -94,8 +94,14 @@ max_request_body_size = {{ ckb_rpc_max_request_body_size | default(10485760) }} modules = {{ ckb_rpc_modules | to_json }} # By default RPC only binds to HTTP service, you can bind it to TCP and WebSocket. -# tcp_listen_address = "127.0.0.1:18114" -# ws_listen_address = "127.0.0.1:28114" +#{% if ckb_tcp_listen_address is defined %} +tcp_listen_address = "{{ ckb_tcp_listen_address }}" +#{% endif %} + +#{% if ckb_ws_listen_address is defined %} +ws_listen_address = "{{ ckb_ws_listen_address }}" +#{% endif %} + reject_ill_transactions = {{ ckb_rpc_reject_ill_transactions | default("true") }} # By default deprecated rpc methods are disabled. diff --git a/source/template/ckb/v113/ckb-miner.toml.j2 b/source/template/ckb/v113/ckb-miner.toml.j2 new file mode 100644 index 0000000..6c29b12 --- /dev/null +++ b/source/template/ckb/v113/ckb-miner.toml.j2 @@ -0,0 +1,45 @@ +# Config generated by `ckb init --chain dev` + +data_dir = "{{ ckb_miner_data_dir | default(ckb_data_dir) }}" + +[chain] +{# Choose the kind of chains to run, possible values: #} +{# - { file = "specs/dev.toml" } #} +{# - { bundled = "specs/testnet.toml" } #} +{# - { bundled = "specs/mainnet.toml" } #} +spec = {{ ckb_chain_spec }} + + +[logger] +filter = "{{ ckb_miner_logger_filter | default("info") }}" +color = {{ ckb_miner_logger_color | default("true") }} +log_to_file = {{ ckb_miner_logger_log_to_file | default("true") }} +log_to_stdout = {{ ckb_miner_logger_log_to_stdout | default("true") }} + +[sentry] +# set to blank to disable sentry error collection +dsn = "{{ ckb_miner_sentry_dsn | default("") }}" +# if you are willing to help us to improve, +# please leave a way to contact you when we have troubles to reproduce the errors. +# org_contact = "{{ ckb_miner_sentry_org_contact | default() }}" + +[miner.client] +rpc_url = "http://{{ ckb_miner_rpc_url | default("127.0.0.1:8114") }}" +block_on_submit = {{ ckb_miner_block_on_submit | default("true") }} + +# block template polling interval in milliseconds +poll_interval = {{ ckb_miner_poll_interval | default("1000") }} + +#{% if ckb_miner_workers is defined %} +# {% for worker in ckb_miner_workers %} +# [[miner.workers]] +# worker_type = "{{ worker.worker_type }}" +# delay_type = "{{ worker.delay_type }}" +# value = {{ worker.value }} +# {% endfor %} +#{% else %} +[[miner.workers]] +worker_type = "Dummy" +delay_type = "Constant" +value = 1000 +#{% endif %} diff --git a/source/template/ckb/v113/ckb.toml.j2 b/source/template/ckb/v113/ckb.toml.j2 new file mode 100644 index 0000000..7ac0464 --- /dev/null +++ b/source/template/ckb/v113/ckb.toml.j2 @@ -0,0 +1,187 @@ +# Config generated by `ckb init --chain dev` + +data_dir = "{{ ckb_data_dir | default("data") }}" + + +[chain] +# Choose the kind of chains to run, possible values: +# - { file = "specs/dev.toml" } +# - { bundled = "specs/testnet.toml" } +# - { bundled = "specs/mainnet.toml" } +spec = {{ ckb_chain_spec }} + + +[logger] +filter = "{{ ckb_logger_filter | default("info") }}" +color = {{ ckb_logger_color | default("true") }} +log_to_file = {{ ckb_logger_log_to_file | default("true") }} +log_to_stdout = {{ ckb_logger_log_to_stdout | default("true") }} + + +[sentry] +# set to blank to disable sentry error collection +dsn = "{{ ckb_sentry_dsn | default("") }}" +# if you are willing to help us to improve, +# please leave a way to contact you when we have troubles to reproduce the errors. +org_contact = "{{ ckb_sentry_org_contact | default("") }}" + + +# # **Experimental** Monitor memory changes. +# [memory_tracker] +# # Seconds between checking the process, 0 is disable, default is 0. +# interval = 600 + +[db] +# The capacity of RocksDB cache, which caches uncompressed data blocks, indexes and filters, default is 128MB. +# Rocksdb will automatically create and use an 8MB internal cache if you set this value to 0. +# To turning off cache, you need to set this value to 0 and set `no_block_cache = true` in the options_file, +# however, we strongly discourage this setting, it may lead to severe performance degradation. +cache_size = {{ ckb_db_cache_size | default("134217728") }} + +# Provide an options file to tune RocksDB for your workload and your system configuration. +# More details can be found in [the official tuning guide](https://github.com/facebook/rocksdb/wiki/RocksDB-Tuning-Guide). +options_file = "{{ ckb_db_options_file | default("default.db-options") }}" + +[network] +listen_addresses = {{ ckb_network_listen_addresses | default(["/ip4/0.0.0.0/tcp/8115"]) | to_json }} +### Specify the public and routable network addresses +public_addresses = {{ ckb_network_public_addresses | default([]) | to_json }} + +# Node connects to nodes listed here to discovery other peers when there's no local stored peers. +# When chain.spec is changed, this usually should also be changed to the bootnodes in the new chain. +bootnodes = {{ ckb_network_bootnodes | default([]) | to_json }} + +### Whitelist-only mode +whitelist_only = {{ ckb_network_whitelist_only | default("false") }} +### Whitelist peers connecting from the given IP addresses +whitelist_peers = {{ ckb_network_whitelist_peers | default([]) | to_json }} +### Enable `SO_REUSEPORT` feature to reuse port on Linux, not supported on other OS yet +# reuse_port_on_linux = true + +max_peers = {{ ckb_network_max_peers | default(125) }} +max_outbound_peers = {{ ckb_network_max_outbound_peers | default(8) }} +# 2 minutes +ping_interval_secs = {{ ckb_network_ping_interval_secs | default(120) }} +# 20 minutes +ping_timeout_secs = {{ ckb_network_ping_timeout_secs | default(1200) }} +connect_outbound_interval_secs = 15 +# If set to true, try to register upnp +upnp = {{ ckb_network_upnp | default("false") }} +# If set to true, network service will add discovered local address to peer store, it's helpful for private net development +discovery_local_address = {{ ckb_network_discovery_local_address | default("true") }} +# If set to true, random cleanup when there are too many inbound nodes +# Ensure that itself can continue to serve as a bootnode node +bootnode_mode = {{ ckb_network_bootnode_mode | default("false") }} + +# Supported protocols list, only "Sync" and "Identify" are mandatory, others are optional +support_protocols = ["Ping", "Discovery", "Identify", "Feeler", "DisconnectMessage", "Sync", "Relay", "Time", "Alert", "LightClient", "Filter"] + +# [network.sync.header_map] +# memory_limit = "600MB" + +[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. +listen_address = "{{ ckb_rpc_listen_address | default("127.0.0.1:8114") }}" + +# Default is 10MiB = 10 * 1024 * 1024 +max_request_body_size = {{ ckb_rpc_max_request_body_size | default(10485760) }} + +# List of API modules: ["Net", "Pool", "Miner", "Chain", "Stats", "Subscription", "Experiment", "Debug", "Indexer"] +#modules = ["Net", "Pool", "Miner", "Chain", "Stats", "Subscription", "Experiment", "Debug"] +modules = {{ ckb_rpc_modules | to_json }} + +# By default RPC only binds to HTTP service, you can bind it to TCP and WebSocket. +#{% if ckb_tcp_listen_address is defined %} +tcp_listen_address = "{{ ckb_tcp_listen_address }}" +#{% endif %} + +#{% if ckb_ws_listen_address is defined %} +ws_listen_address = "{{ ckb_ws_listen_address }}" +#{% endif %} + +reject_ill_transactions = {{ ckb_rpc_reject_ill_transactions | default("true") }} + +# By default deprecated rpc methods are disabled. +enable_deprecated_rpc = {{ ckb_rpc_enable_deprecated_rpc | default("false") }} + + +[tx_pool] +max_tx_pool_size = {{ ckb_tx_pool_max_tx_pool_size | default("180_000_000") }} +min_fee_rate = {{ ckb_tx_pool_min_fee_rate | default("1_000") }} +max_tx_verify_cycles = {{ ckb_tx_pool_max_tx_verify_cycles | default("70_000_000") }} +max_ancestors_count = {{ ckb_tx_pool_max_ancestors_count | default("25") }} +min_rbf_rate = {{ ckb_tx_pool_min_rbf_rate | default("1_500") }} + + +[store] +header_cache_size = {{ ckb_store_header_cache_size | default("4096")}} +cell_data_cache_size = {{ ckb_store_cell_data_cache_size | default("128")}} +block_proposals_cache_size = {{ ckb_store_block_proposals_cache_size | default("30")}} +block_tx_hashes_cache_size = {{ ckb_store_block_tx_hashes_cache_size | default("30")}} +block_uncles_cache_size = {{ ckb_store_block_uncles_cache_size | default("30")}} + +# [notifier] +# # Execute command when the new tip block changes, first arg is block hash. +# new_block_notify_script = "your_new_block_notify_script.sh" +# # Execute command when node received an network alert, first arg is alert message string. +# network_alert_notify_script = "your_network_alert_notify_script.sh" + +# Set the lock script to protect mined CKB. +# +# CKB uses CS architecture for miner. Miner process (ckb miner) gets block +# template from the Node process (ckb run) via RPC. Thus the lock script is +# configured in ckb.toml instead of ckb-miner.toml, and the config takes effect +# after restarting Node process. +# +# The `code_hash` identifies different cryptography algorithm. Read the manual +# of the lock script provider about how to generate this config. +# +# CKB provides an secp256k1 implementation, it requires a hash on the +# compressed public key. The hash algorithm is blake2b, with personal +# "ckb-default-hash". The first 160 bits (20 bytes) are used as the only arg. +# +# You can use any tool you trust to generate a Bitcoin private key and public +# key pair, which can be used in CKB as well. CKB CLI provides the function for +# you to convert the public key into block assembler configuration parameters. +# +# Here is an example using ckb-cli to generate an account, this command will +# print the block assembler args(lock_arg) to screen: +# +# ckb-cli account new +# +# If you already have a raw secp256k1 private key, you can get the lock_arg by: +# +# ckb-cli util key-info --privkey-path +# +# The command `ckb init` also accepts options to generate the block assembler +# directly. See `ckb init --help` for details. +# +# ckb init +# +# secp256k1_blake160_sighash_all example: +# [block_assembler] +# code_hash = "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8" +# args = "ckb-cli util blake2b --prefix-160 " +# hash_type = "type" +# message = "A 0x-prefixed hex string" +# # +# # CKB will prepend the binary version to message, to identify the block miner client. (default true, false to disable it) +# use_binary_version_as_message_prefix = true +# # +# # Block assembler will notify new block template through http post to specified endpoints when update +# notify = ["http://127.0.0.1:8888"] +# # Or you may want use more flexible scripts, block template as arg. +# notify_scripts = ["{cmd} {blocktemplate}"] +# +# [indexer_v2] +# # Indexing the pending txs in the ckb tx-pool +# index_tx_pool = false + +[block_assembler] +code_hash = "{{ ckb_block_assembler_code_hash }}" +args = "{{ ckb_block_assembler_args }}" +hash_type = "{{ ckb_block_assembler_hash_type }}" +message = "{{ ckb_block_assembler_message }}" diff --git a/source/template/ckb/v113/default.db-options b/source/template/ckb/v113/default.db-options new file mode 100644 index 0000000..bffbdc0 --- /dev/null +++ b/source/template/ckb/v113/default.db-options @@ -0,0 +1,22 @@ +# This is a RocksDB option file. +# +# For detailed file format spec, please refer to the official documents +# in https://rocksdb.org/docs/ +# + +[DBOptions] +bytes_per_sync=1048576 +max_background_jobs=6 +max_total_wal_size=134217728 +keep_log_file_num=32 + +[CFOptions "default"] +level_compaction_dynamic_level_bytes=true +write_buffer_size=8388608 +min_write_buffer_number_to_merge=1 +max_write_buffer_number=2 +max_write_buffer_size_to_maintain=-1 + +[TableOptions/BlockBasedTable "default"] +cache_index_and_filter_blocks=true +pin_l0_filter_and_index_blocks_in_cache=true diff --git a/test_cases/bug/__init__.py b/source/template/ckb/v113/specs/benchmark-spec.toml similarity index 100% rename from test_cases/bug/__init__.py rename to source/template/ckb/v113/specs/benchmark-spec.toml diff --git a/source/template/ckb/v113/specs/dev.toml b/source/template/ckb/v113/specs/dev.toml new file mode 100644 index 0000000..a238e87 --- /dev/null +++ b/source/template/ckb/v113/specs/dev.toml @@ -0,0 +1,100 @@ +name = "ckb_dev" + +[genesis] +version = 0 +parent_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +timestamp = 0 +compact_target = 0x20010000 +uncles_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +nonce = "0x0" + +[genesis.genesis_cell] +message = "1688032132025" + +[genesis.genesis_cell.lock] +code_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +args = "0x" +hash_type = "data" + +# An array list paths to system cell files, which is absolute or relative to +# the directory containing this config file. +[[genesis.system_cells]] +file = { bundled = "specs/cells/secp256k1_blake160_sighash_all" } +create_type_id = true +capacity = 100_000_0000_0000 +[[genesis.system_cells]] +file = { bundled = "specs/cells/dao" } +create_type_id = true +capacity = 16_000_0000_0000 +[[genesis.system_cells]] +file = { bundled = "specs/cells/secp256k1_data" } +create_type_id = false +capacity = 1_048_617_0000_0000 +[[genesis.system_cells]] +file = { bundled = "specs/cells/secp256k1_blake160_multisig_all" } +create_type_id = true +capacity = 100_000_0000_0000 + +[genesis.system_cells_lock] +code_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +args = "0x" +hash_type = "data" + +# Dep group cells +[[genesis.dep_groups]] +name = "secp256k1_blake160_sighash_all" +files = [ + { bundled = "specs/cells/secp256k1_data" }, + { bundled = "specs/cells/secp256k1_blake160_sighash_all" }, +] +[[genesis.dep_groups]] +name = "secp256k1_blake160_multisig_all" +files = [ + { bundled = "specs/cells/secp256k1_data" }, + { bundled = "specs/cells/secp256k1_blake160_multisig_all" }, +] + +# For first 11 block +[genesis.bootstrap_lock] +code_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +args = "0x" +hash_type = "type" + +# Burn +[[genesis.issued_cells]] +capacity = 8_400_000_000_00000000 +lock.code_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +lock.args = "0x62e907b15cbf27d5425399ebf6f0fb50ebb88f18" +lock.hash_type = "data" + +# issue for random generated private key: d00c06bfd800d27397002dca6fb0993d5ba6399b4238b2f29ee9deb97593d2bc +[[genesis.issued_cells]] +capacity = 20_000_000_000_00000000 +lock.code_hash = "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8" +lock.args = "0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7" +lock.hash_type = "type" + +# issue for random generated private key: 63d86723e08f0f813a36ce6aa123bb2289d90680ae1e99d4de8cdb334553f24d +[[genesis.issued_cells]] +capacity = 5_198_735_037_00000000 +lock.code_hash = "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8" +lock.args = "0x470dcdc5e44064909650113a274b3b36aecb6dc7" +lock.hash_type = "type" + +[params] +initial_primary_epoch_reward = 1_917_808_21917808 +secondary_epoch_reward = 613_698_63013698 +max_block_cycles = 10_000_000_000 +cellbase_maturity = 0 +primary_epoch_reward_halving_interval = 8760 +epoch_duration_target = 14400 +genesis_epoch_length = 1000 +# For development and testing purposes only. +# Keep difficulty be permanent if the pow is Dummy. (default: false) +permanent_difficulty_in_dummy = true + +[params.hardfork] +ckb2023 = 1000000 + +[pow] +func = "Dummy" diff --git a/source/template/ckb/v113/specs/mainnet.toml b/source/template/ckb/v113/specs/mainnet.toml new file mode 100644 index 0000000..daf7edf --- /dev/null +++ b/source/template/ckb/v113/specs/mainnet.toml @@ -0,0 +1,69 @@ +name = "ckb" + +[genesis] +version = 0 +parent_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +timestamp = 1573852190812 +compact_target = 0x1a08a97e +uncles_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +nonce = "0x0" + +[genesis.genesis_cell] +message = "lina 0x18e020f6b1237a3d06b75121f25a7efa0550e4b3f44f974822f471902424c104" + +[genesis.genesis_cell.lock] +code_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +args = "0x" +hash_type = "data" + +# An array list paths to system cell files, which is absolute or relative to +# the directory containing this config file. +[[genesis.system_cells]] +file = { bundled = "specs/cells/secp256k1_blake160_sighash_all" } +create_type_id = true +capacity = 100_000_0000_0000 +[[genesis.system_cells]] +file = { bundled = "specs/cells/dao" } +create_type_id = true +capacity = 16_000_0000_0000 +[[genesis.system_cells]] +file = { bundled = "specs/cells/secp256k1_data" } +create_type_id = false +capacity = 1_048_617_0000_0000 +[[genesis.system_cells]] +file = { bundled = "specs/cells/secp256k1_blake160_multisig_all" } +create_type_id = true +capacity = 100_000_0000_0000 + +[genesis.system_cells_lock] +code_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +args = "0x" +hash_type = "data" + +# Dep group cells +[[genesis.dep_groups]] +name = "secp256k1_blake160_sighash_all" +files = [ + { bundled = "specs/cells/secp256k1_data" }, + { bundled = "specs/cells/secp256k1_blake160_sighash_all" }, +] +[[genesis.dep_groups]] +name = "secp256k1_blake160_multisig_all" +files = [ + { bundled = "specs/cells/secp256k1_data" }, + { bundled = "specs/cells/secp256k1_blake160_multisig_all" }, +] + +# For first 11 block +[genesis.bootstrap_lock] +code_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +args = "0x" +hash_type = "data" + + + +[params] +genesis_epoch_length = 1743 + +[pow] +func = "Eaglesong" \ No newline at end of file diff --git a/source/template/ckb/v113/specs/testnet.toml b/source/template/ckb/v113/specs/testnet.toml new file mode 100644 index 0000000..2461e53 --- /dev/null +++ b/source/template/ckb/v113/specs/testnet.toml @@ -0,0 +1,90 @@ +name = "ckb_testnet" + +[genesis] +version = 0 +parent_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +timestamp = 1589276230000 +compact_target = 0x1e015555 +uncles_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +nonce = "0x0" +# run `cargo run list-hashes -b` to get the genesis hash +hash = "0x10639e0895502b5688a6be8cf69460d76541bfa4821629d86d62ba0aae3f9606" + +[genesis.genesis_cell] +message = "aggron-v4" + +[genesis.genesis_cell.lock] +code_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +args = "0x" +hash_type = "data" + +# An array list paths to system cell files, which is absolute or relative to +# the directory containing this config file. +[[genesis.system_cells]] +file = { bundled = "specs/cells/secp256k1_blake160_sighash_all" } +create_type_id = true +capacity = 100_000_0000_0000 +[[genesis.system_cells]] +file = { bundled = "specs/cells/dao" } +create_type_id = true +capacity = 16_000_0000_0000 +[[genesis.system_cells]] +file = { bundled = "specs/cells/secp256k1_data" } +create_type_id = false +capacity = 1_048_617_0000_0000 +[[genesis.system_cells]] +file = { bundled = "specs/cells/secp256k1_blake160_multisig_all" } +create_type_id = true +capacity = 100_000_0000_0000 + +[genesis.system_cells_lock] +code_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +args = "0x" +hash_type = "data" + +# Dep group cells +[[genesis.dep_groups]] +name = "secp256k1_blake160_sighash_all" +files = [ + { bundled = "specs/cells/secp256k1_data" }, + { bundled = "specs/cells/secp256k1_blake160_sighash_all" }, +] +[[genesis.dep_groups]] +name = "secp256k1_blake160_multisig_all" +files = [ + { bundled = "specs/cells/secp256k1_data" }, + { bundled = "specs/cells/secp256k1_blake160_multisig_all" }, +] + +# For first 11 block +[genesis.bootstrap_lock] +code_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +args = "0x" +hash_type = "type" + +# Burn +[[genesis.issued_cells]] +capacity = 8_400_000_000_00000000 +lock.code_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +lock.args = "0x62e907b15cbf27d5425399ebf6f0fb50ebb88f18" +lock.hash_type = "data" + +# Locks for developers to run tests +[[genesis.issued_cells]] +capacity = 8_399_578_345_00000000 +lock.code_hash = "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8" +lock.args = "0x64257f00b6b63e987609fa9be2d0c86d351020fb" +lock.hash_type = "type" +[[genesis.issued_cells]] +capacity = 8_399_578_345_00000000 +lock.code_hash = "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8" +lock.args = "0x3f1573b44218d4c12a91919a58a863be415a2bc3" +lock.hash_type = "type" +[[genesis.issued_cells]] +capacity = 8_399_578_347_00000000 +lock.code_hash = "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8" +lock.args = "0x57ccb07be6875f61d93636b0ee11b675494627d2" +lock.hash_type = "type" + +[pow] +func = "EaglesongBlake2b" \ No newline at end of file diff --git a/test_cases/ckb2023/test_01_before_hardfork.py b/test_cases/ckb2023/test_01_before_hardfork.py index 1000111..0ccdc12 100644 --- a/test_cases/ckb2023/test_01_before_hardfork.py +++ b/test_cases/ckb2023/test_01_before_hardfork.py @@ -132,8 +132,7 @@ def test_05_0049_transfer_to_data2_address(self): 140, self.cluster.ckb_nodes[0].client.url) print(exc_info) - expected_error_message = "the feature \"VM Version 2\" is used in current transaction " \ - "but not enabled in current chain" + expected_error_message = "the feature \"VM Version 2\" is used in current transaction, but not enabled in current chain" 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/ckb2023/test_03_ckb_light_client_after_hardfork.py b/test_cases/ckb2023/test_03_ckb_light_client_after_hardfork.py index ce00d43..0730282 100644 --- a/test_cases/ckb2023/test_03_ckb_light_client_after_hardfork.py +++ b/test_cases/ckb2023/test_03_ckb_light_client_after_hardfork.py @@ -1,7 +1,7 @@ import os import time - +import pytest from parameterized import parameterized from framework.basic import CkbTest @@ -28,7 +28,6 @@ def get_all_files(directory): # "spawn_fib", # "spawn_times" # ] -# [] # return [s for s in files if not any(s.endswith(suffix) for suffix in files_list)] def get_successful_files(): @@ -36,7 +35,7 @@ def get_successful_files(): f"{get_project_root()}/source/contract/test_cases/ckb_get_memory_limit", f"{get_project_root()}/source/contract/test_cases/atomic_i32", f"{get_project_root()}/source/contract/test_cases/spawn_current_cycles", - # f"{get_project_root()}/source/contract/test_cases/load_block_extension", //TODO wait https://github.com/nervosnetwork/ckb-light-client/pull/156/files + f"{get_project_root()}/source/contract/test_cases/load_block_extension", ] @@ -62,31 +61,48 @@ def setup_class(cls): cls.Miner.make_tip_height_number(cls.cluster.ckb_nodes[0], 2000) cls.Node.wait_cluster_height(cls.cluster, 2000, 100) - cls.ckb_light_node_0_2_5 = cls.CkbLightClientNode.init_by_nodes(cls.CkbLightClientConfigPath.CURRENT_TEST, - cls.cluster.ckb_nodes, - "tx_pool_light/node1", 8001) + cls.ckb_light_node_current = cls.CkbLightClientNode.init_by_nodes(cls.CkbLightClientConfigPath.CURRENT_TEST, + cls.cluster.ckb_nodes, + "tx_pool_light/node1", 8001) - cls.ckb_light_node_0_2_5.prepare() - cls.ckb_light_node_0_2_5.start() + cls.ckb_light_node_current.prepare() + cls.ckb_light_node_current.start() account = cls.Ckb_cli.util_key_info_by_private_key(cls.Config.MINER_PRIVATE_1) - cls.ckb_light_node_0_2_5.getClient().set_scripts([{"script": { + cls.ckb_light_node_current.getClient().set_scripts([{"script": { "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", "hash_type": "type", "args": account['lock_arg']}, "script_type": "lock", "block_number": "0x0"}]) - cls.Node.wait_light_sync_height(cls.ckb_light_node_0_2_5, 2000, 200) - + cls.cluster.ckb_nodes[0].start_miner() + cls.Node.wait_light_sync_height(cls.ckb_light_node_current, 2000, 200) @classmethod def teardown_class(cls): print("\nTeardown TestClass1") cls.cluster.stop_all_nodes() cls.cluster.clean_all_nodes() - cls.ckb_light_node_0_2_5.stop() - cls.ckb_light_node_0_2_5.clean() + cls.ckb_light_node_current.stop() + cls.ckb_light_node_current.clean() - def test_01_ckb_light_client_0_2_4_link_node(self): - pass + def test_01_ckb_light_client_0_3_1_link_node(self): - def test_02_ckb_light_client_0_2_5_link_node(self): + version = self.CkbLightClientConfigPath.V0_3_1 + ckb_light_node = self.CkbLightClientNode.init_by_nodes(version, + self.cluster.ckb_nodes, + "tx_pool_light/node2", 8002) + ckb_light_node.prepare() + ckb_light_node.start() + account = self.Ckb_cli.util_key_info_by_private_key(self.Config.MINER_PRIVATE_1) + ckb_light_node.getClient().set_scripts([{"script": { + "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", "hash_type": "type", + "args": account['lock_arg']}, "script_type": "lock", "block_number": "0x0"}]) + with pytest.raises(Exception) as exc_info: + self.Node.wait_light_sync_height(ckb_light_node, 2000, 200) + expected_error_message = "time out" + assert expected_error_message in exc_info.value.args[0], \ + f"Expected substring '{expected_error_message}' not found in actual string '{exc_info.value.args[0]}'" + ckb_light_node.stop() + ckb_light_node.clean() + + def test_02_ckb_light_client_current_link_node(self): """ 1. setScript miner account set successful @@ -96,12 +112,12 @@ def test_02_ckb_light_client_0_2_5_link_node(self): """ account = self.Ckb_cli.util_key_info_by_private_key(self.Config.MINER_PRIVATE_1) - self.ckb_light_node_0_2_5.getClient().set_scripts([{"script": { + self.ckb_light_node_current.getClient().set_scripts([{"script": { "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", "hash_type": "type", "args": account['lock_arg']}, "script_type": "lock", "block_number": "0x0"}]) - self.Node.wait_light_sync_height(self.ckb_light_node_0_2_5, 2000, 200) + self.Node.wait_light_sync_height(self.ckb_light_node_current, 2000, 200) - def test_03_ckb_light_client_0_2_5_set_script_data2(self): + def test_03_ckb_light_client_current_set_script_data2(self): """ 1. set data2 account sync successful @@ -109,25 +125,23 @@ def test_03_ckb_light_client_0_2_5_set_script_data2(self): """ account = self.Ckb_cli.util_key_info_by_private_key(self.Config.MINER_PRIVATE_1) - self.ckb_light_node_0_2_5.getClient().set_scripts([{"script": { + self.ckb_light_node_current.getClient().set_scripts([{"script": { "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", "hash_type": "data2", "args": account['lock_arg']}, "script_type": "lock", "block_number": "0x0"}]) - self.Node.wait_light_sync_height(self.ckb_light_node_0_2_5, 2000, 200) + self.Node.wait_light_sync_height(self.ckb_light_node_current, 2000, 200) - def test_04_ckb_light_client_0_2_5_transfer_data2_tx(self): + def test_04_ckb_light_client_current_transfer_data2_tx(self): """ 1. send data2 tx on the ckb light client send successful ,return tx_hash Returns: """ - self.cluster.ckb_nodes[0].start_miner() account = self.Ckb_cli.util_key_info_by_private_key(self.Config.MINER_PRIVATE_1) - self.ckb_light_node_0_2_5.getClient().set_scripts([{"script": { + self.ckb_light_node_current.getClient().set_scripts([{"script": { "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", "hash_type": "type", "args": account['lock_arg']}, "script_type": "lock", "block_number": "0x0"}]) - self.Node.wait_light_sync_height(self.ckb_light_node_0_2_5, 2000, 200) - self.cluster.ckb_nodes[0].stop_miner() + self.Node.wait_light_sync_height(self.ckb_light_node_current, 2000, 200) tx_hash = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.MINER_PRIVATE_1, "ckt1qp5usrt2syzfjj7acyetk45vj57kp7hq4jfg4ky8e9k7ss6v52neqpqh7xtq0", @@ -137,11 +151,11 @@ def test_04_ckb_light_client_0_2_5_transfer_data2_tx(self): transaction = tx['transaction'] tx_hash = transaction['hash'] del transaction['hash'] - light_tx_hash = self.ckb_light_node_0_2_5.getClient().send_transaction(transaction) + light_tx_hash = self.ckb_light_node_current.getClient().send_transaction(transaction) assert tx_hash == light_tx_hash - def test_05_ckb_light_client_0_2_5_spawn_contract_use_data2(self): + def test_05_ckb_light_client_current_spawn_contract_use_data2(self): """ 1. send spawn tx ( hash type : data2), on the ckb light client send successful ,return tx_hash @@ -150,10 +164,10 @@ def test_05_ckb_light_client_0_2_5_spawn_contract_use_data2(self): # send rfc50 tx self.cluster.ckb_nodes[0].start_miner() account = self.Ckb_cli.util_key_info_by_private_key(self.Config.MINER_PRIVATE_1) - self.ckb_light_node_0_2_5.getClient().set_scripts([{"script": { + self.ckb_light_node_current.getClient().set_scripts([{"script": { "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", "hash_type": "type", "args": account['lock_arg']}, "script_type": "lock", "block_number": "0x0"}]) - self.Node.wait_light_sync_height(self.ckb_light_node_0_2_5, 2000, 200) + self.Node.wait_light_sync_height(self.ckb_light_node_current, 2000, 200) self.cluster.ckb_nodes[0].stop_miner() code_tx_hash, code_tx_index = self.spawn_contract.get_deploy_hash_and_index() @@ -162,19 +176,19 @@ def test_05_ckb_light_client_0_2_5_spawn_contract_use_data2(self): invoke_arg, "data2", invoke_data, api_url=self.cluster.ckb_nodes[0].getClient().url) - self.ckb_light_node_0_2_5.getClient().fetch_transaction(code_tx_hash) + self.ckb_light_node_current.getClient().fetch_transaction(code_tx_hash) # TODO wait fetch tx succ time.sleep(5) - self.ckb_light_node_0_2_5.getClient().fetch_transaction(code_tx_hash) + self.ckb_light_node_current.getClient().fetch_transaction(code_tx_hash) tx = self.cluster.ckb_nodes[0].getClient().get_transaction(tx_hash) transaction = tx['transaction'] tx_hash = transaction['hash'] del transaction['hash'] - light_tx_hash = self.ckb_light_node_0_2_5.getClient().send_transaction(transaction) + light_tx_hash = self.ckb_light_node_current.getClient().send_transaction(transaction) assert tx_hash == light_tx_hash - def test_05_ckb_light_client_0_2_5_spawn_contract_use_type(self): + def test_05_ckb_light_client_current_spawn_contract_use_type(self): """ 1. send spawn tx ( hash type : type), on the ckb light client send successful ,return tx_hash @@ -183,10 +197,10 @@ def test_05_ckb_light_client_0_2_5_spawn_contract_use_type(self): # send rfc50 tx self.cluster.ckb_nodes[0].start_miner() account = self.Ckb_cli.util_key_info_by_private_key(self.Config.MINER_PRIVATE_1) - self.ckb_light_node_0_2_5.getClient().set_scripts([{"script": { + self.ckb_light_node_current.getClient().set_scripts([{"script": { "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", "hash_type": "type", "args": account['lock_arg']}, "script_type": "lock", "block_number": "0x0"}]) - self.Node.wait_light_sync_height(self.ckb_light_node_0_2_5, 2000, 200) + self.Node.wait_light_sync_height(self.ckb_light_node_current, 2000, 200) self.cluster.ckb_nodes[0].stop_miner() code_tx_hash, code_tx_index = self.spawn_contract.get_deploy_hash_and_index() @@ -195,24 +209,28 @@ def test_05_ckb_light_client_0_2_5_spawn_contract_use_type(self): invoke_arg, "type", invoke_data, api_url=self.cluster.ckb_nodes[0].getClient().url) - self.ckb_light_node_0_2_5.getClient().fetch_transaction(code_tx_hash) + self.ckb_light_node_current.getClient().fetch_transaction(code_tx_hash) time.sleep(5) - self.ckb_light_node_0_2_5.getClient().fetch_transaction(code_tx_hash) + self.ckb_light_node_current.getClient().fetch_transaction(code_tx_hash) tx = self.cluster.ckb_nodes[0].getClient().get_transaction(tx_hash) transaction = tx['transaction'] tx_hash = transaction['hash'] del transaction['hash'] - light_tx_hash = self.ckb_light_node_0_2_5.getClient().send_transaction(transaction) - + light_tx_hash = self.ckb_light_node_current.getClient().send_transaction(transaction) assert tx_hash == light_tx_hash + # @pytest.mark.skip @parameterized.expand(success_files) def test_06_ckb_light_client_deploy_and_invoke_contract(self, path): - self.cluster.ckb_nodes[0].stop_miner() + self.cluster.ckb_nodes[0].start_miner() self.deploy_and_invoke(self.Config.MINER_PRIVATE_1, path, self.cluster.ckb_nodes[0]) + self.cluster.ckb_nodes[0].start_miner() # def test_07_ckb_light_client_deploy_and_invoke_contract(self): - # self.deploy_and_invoke(self.Config.MINER_PRIVATE_1, f"{get_project_root()}/source/contract/test_cases/spawn_demo", self.cluster.ckb_nodes[0]) + # self.cluster.ckb_nodes[0].start_miner() + # self.deploy_and_invoke(self.Config.MINER_PRIVATE_1, + # f"{get_project_root()}/source/contract/test_cases/spawn_demo", self.cluster.ckb_nodes[0]) + # self.cluster.ckb_nodes[0].stop_miner() def deploy_and_invoke(self, account, path, node, try_count=5): if try_count < 0: @@ -223,23 +241,29 @@ def deploy_and_invoke(self, account, path, node, try_count=5): enable_type_id=True, api_url=node.getClient().url) self.Miner.miner_until_tx_committed(node, deploy_hash) - time.sleep(1) - self.ckb_light_node_0_2_5.getClient().fetch_transaction(deploy_hash) - self.Node.wait_light_sync_height(self.ckb_light_node_0_2_5, node.getClient().get_tip_block_number(), 100) - time.sleep(10) - invoke_hash = self.Contract.invoke_ckb_contract(account_private=account, - contract_out_point_tx_hash=deploy_hash, - contract_out_point_tx_index=0, - type_script_arg="0x02", data="0x1234", - hash_type="type", - api_url=node.getClient().url) - tx = node.getClient().get_transaction(invoke_hash) - transaction = tx['transaction'] - tx_hash = transaction['hash'] - del transaction['hash'] - light_tx_hash = self.ckb_light_node_0_2_5.getClient().send_transaction(transaction) - assert tx_hash == light_tx_hash - return invoke_hash + self.Node.wait_light_sync_height(self.ckb_light_node_current, node.getClient().get_tip_block_number(), 200) + self.Node.wait_fetch_transaction(self.ckb_light_node_current, deploy_hash, "fetched") + tx_msg = self.Contract.build_invoke_ckb_contract(account_private=account, + contract_out_point_tx_hash=deploy_hash, + contract_out_point_tx_index=0, + type_script_arg="0x02", data="0x1234", + hash_type="type", + api_url=node.getClient().url) + self.Node.wait_light_sync_height(self.ckb_light_node_current, node.getClient().get_tip_block_number(), 200) + for i in range(100): + light_tx_hash = self.ckb_light_node_current.getClient().send_transaction(tx_msg) + light_ret = node.getClient().get_transaction(light_tx_hash) + time.sleep(1) + print("light ret status:", light_ret['tx_status']['status']) + if light_ret['tx_status']['status'] != 'pending': + continue + if light_ret['tx_status']['status'] == 'pending': + print("status is pending i:", i) + break + if i == 99: + raise Exception("status is failed ") + self.Miner.miner_until_tx_committed(node, light_tx_hash, with_unknown=True) + return light_tx_hash except Exception as e: print(e) if "Resolve failed Dead" in str(e): diff --git a/test_cases/ckb2023/test_04_ckb_light_client_before_hardfork.py b/test_cases/ckb2023/test_04_ckb_light_client_before_hardfork.py new file mode 100644 index 0000000..bfcd68e --- /dev/null +++ b/test_cases/ckb2023/test_04_ckb_light_client_before_hardfork.py @@ -0,0 +1,102 @@ +import time + + +from framework.basic import CkbTest +from framework.util import get_project_root + + +class TestCkbLightClientAfterHardFork(CkbTest): + + @classmethod + def setup_class(cls): + nodes = [ + cls.CkbNode.init_dev_by_port(cls.CkbNodeConfigPath.CURRENT_TEST, "cluster/hardfork/node{i}".format(i=i), + 8124 + i, + 8225 + i) + for + i in range(1, 5) + ] + cls.cluster = cls.Cluster(nodes) + cls.cluster.prepare_all_nodes() + cls.cluster.start_all_nodes() + cls.cluster.connected_all_nodes() + contracts = cls.Contract_util.deploy_contracts(cls.Config.ACCOUNT_PRIVATE_1, cls.cluster.ckb_nodes[0]) + cls.spawn_contract = contracts["SpawnContract"] + + cls.Miner.make_tip_height_number(cls.cluster.ckb_nodes[0], 20) + cls.Node.wait_cluster_height(cls.cluster, 20, 100) + + cls.ckb_light_node_current = cls.CkbLightClientNode.init_by_nodes(cls.CkbLightClientConfigPath.CURRENT_TEST, + cls.cluster.ckb_nodes, + "tx_pool_light/node1", 8001) + + cls.ckb_light_node_current.prepare() + cls.ckb_light_node_current.start() + account = cls.Ckb_cli.util_key_info_by_private_key(cls.Config.MINER_PRIVATE_1) + cls.ckb_light_node_current.getClient().set_scripts([{"script": { + "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", "hash_type": "type", + "args": account['lock_arg']}, "script_type": "lock", "block_number": "0x0"}]) + cls.Node.wait_light_sync_height(cls.ckb_light_node_current, 20, 200) + + @classmethod + def teardown_class(cls): + print("\nTeardown TestClass1") + cls.cluster.stop_all_nodes() + cls.cluster.clean_all_nodes() + cls.ckb_light_node_current.stop() + cls.ckb_light_node_current.clean() + + def test_01_ckb_light_client_deploy_and_invoke_contract(self): + self.cluster.ckb_nodes[0].start_miner() + self.deploy_and_invoke(self.Config.MINER_PRIVATE_1, + f"{get_project_root()}/source/contract/test_cases/always_success", + self.cluster.ckb_nodes[0]) + self.cluster.ckb_nodes[0].stop_miner() + + def deploy_and_invoke(self, account, path, node, try_count=5): + if try_count < 0: + raise Exception("try out of times") + try: + deploy_hash = self.Contract.deploy_ckb_contract(account, + path, + enable_type_id=True, + api_url=node.getClient().url) + self.Miner.miner_until_tx_committed(node, deploy_hash) + self.Node.wait_light_sync_height(self.ckb_light_node_current, node.getClient().get_tip_block_number(), 200) + self.Node.wait_fetch_transaction(self.ckb_light_node_current, deploy_hash, "fetched") + tx_msg = self.Contract.build_invoke_ckb_contract(account_private=account, + contract_out_point_tx_hash=deploy_hash, + contract_out_point_tx_index=0, + type_script_arg="0x02", data="0x1234", + hash_type="type", + api_url=node.getClient().url) + self.Node.wait_light_sync_height(self.ckb_light_node_current, node.getClient().get_tip_block_number(), 200) + for i in range(100): + light_tx_hash = self.ckb_light_node_current.getClient().send_transaction(tx_msg) + light_ret = node.getClient().get_transaction(light_tx_hash) + time.sleep(1) + print("light ret status:", light_ret['tx_status']['status']) + if light_ret['tx_status']['status'] != 'pending': + continue + if light_ret['tx_status']['status'] == 'pending': + print("status is pending i:", i) + break + if i == 99: + raise Exception("status is failed ") + self.Miner.miner_until_tx_committed(node, light_tx_hash, with_unknown=True) + return light_tx_hash + except Exception as e: + print(e) + if "Resolve failed Dead" in str(e): + try_count -= 1 + for i in range(2): + self.Miner.miner_with_version(node, "0x0") + time.sleep(3) + return self.deploy_and_invoke(account, path, node, try_count) + if "PoolRejectedRBF" in str(e): + try_count -= 1 + for i in range(2): + self.Miner.miner_with_version(node, "0x0") + time.sleep(3) + return self.deploy_and_invoke(account, path, node, try_count) + raise e diff --git a/test_cases/contracts/test_01_contract.py b/test_cases/contracts/test_01_contract.py index cfcf869..0e651ce 100644 --- a/test_cases/contracts/test_01_contract.py +++ b/test_cases/contracts/test_01_contract.py @@ -153,4 +153,10 @@ def deploy_and_invoke(self, account, path, node, try_count=5): self.Miner.miner_with_version(node, "0x0") time.sleep(3) return self.deploy_and_invoke(account, path, node, try_count) + if "PoolRejectedRBF" in str(e): + try_count -= 1 + for i in range(2): + self.Miner.miner_with_version(node, "0x0") + time.sleep(3) + return self.deploy_and_invoke(account, path, node, try_count) raise e diff --git a/test_cases/light_client/test_ckb_disconnect_fetch_transaction_solo.py b/test_cases/light_client/test_ckb_disconnect_fetch_transaction_solo.py index c2abb89..d152224 100644 --- a/test_cases/light_client/test_ckb_disconnect_fetch_transaction_solo.py +++ b/test_cases/light_client/test_ckb_disconnect_fetch_transaction_solo.py @@ -69,7 +69,7 @@ def test_fetch_transaction_when_solo_ckb_restart(self): self.ckb_light_node.getClient().get_scripts() state = self.ckb_light_node.getClient().fetch_transaction(tx_hash) print(state['status']) - time.sleep(1) + time.sleep(2) if state['status'] != "fetching": end_time = datetime.now() time_difference = end_time - current_time diff --git a/test_cases/light_client/test_light_sync.py b/test_cases/light_client/test_light_sync.py new file mode 100644 index 0000000..c343f20 --- /dev/null +++ b/test_cases/light_client/test_light_sync.py @@ -0,0 +1,113 @@ +import pytest + +from framework.basic import CkbTest + + +class TestLightSync(CkbTest): + node: CkbTest.CkbNode + cluster: CkbTest.Cluster + ckb_light_node: CkbTest.CkbLightClientNode + + @classmethod + def setup_class(cls): + node1 = cls.CkbNode.init_dev_by_port(cls.CkbNodeConfigPath.V113, "tx_pool_main/node1", 8115, + 8227) + node2 = cls.CkbNode.init_dev_by_port(cls.CkbNodeConfigPath.V113, "tx_pool_main/node2", 8116, + 8228) + + cls.node = node1 + cls.node2 = node2 + cls.cluster = cls.Cluster([node1, node2]) + node1.prepare(other_ckb_spec_config={"ckb_params_genesis_epoch_length": "1", "ckb_name": "ckb_dev", + "ckb_params_genesis_compact_target": "0x2020000"}) + node2.prepare(other_ckb_spec_config={"ckb_params_genesis_epoch_length": "1", "ckb_name": "ckb_dev", + "ckb_params_genesis_compact_target": "0x2020000"}) + + cls.cluster.start_all_nodes() + cls.cluster.connected_all_nodes() + cls.Miner.make_tip_height_number(cls.node, 13000) + cls.Node.wait_cluster_height(cls.cluster, 5, 13000) + node1.start_miner() + + @classmethod + def teardown_class(cls): + print("\nTeardown TestClass1") + cls.cluster.stop_all_nodes() + cls.cluster.clean_all_nodes() + + def test_sync(self): + for i in range(10): + print("current idx", i) + self.ckb_light_node = self.CkbLightClientNode.init_by_nodes(self.CkbLightClientConfigPath.CURRENT_TEST, + [self.node, self.node2], + "tx_pool_light/node1", 8001) + self.ckb_light_node.prepare() + self.ckb_light_node.start() + + old_sync_account = [ + self.Config.ACCOUNT_PRIVATE_1, + self.Config.ACCOUNT_PRIVATE_2 + ] + new_sync_account = [ + self.Config.MINER_PRIVATE_1 + ] + old_sync_block_number = 6000 + new_sync_block_number = 3000 + add_sync_new_account_block_number = 8000 + new_sync_until_number = 12000 + + print("sync old account") + setScripts = [] + for account_private in old_sync_account: + acc = self.Ckb_cli.util_key_info_by_private_key(account_private) + setScripts.append({"script": { + "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + "hash_type": "type", + "args": acc['lock_arg']}, "script_type": "lock", "block_number": hex(old_sync_block_number)}) + self.ckb_light_node.getClient().set_scripts(setScripts) + + print(f"until sync:{add_sync_new_account_block_number}") + self.Node.wait_light_sync_height(self.ckb_light_node, add_sync_new_account_block_number, 1000000) + print("sync new account ") + scripts = self.ckb_light_node.getClient().get_scripts() + newSetScripts = [] + for i in scripts: + newSetScripts.append(i) + for account_private in new_sync_account: + acc = self.Ckb_cli.util_key_info_by_private_key(account_private) + newSetScripts.append({"script": { + "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + "hash_type": "type", + "args": acc['lock_arg']}, "script_type": "lock", "block_number": hex(new_sync_block_number)}) + self.ckb_light_node.getClient().set_scripts(newSetScripts) + print("until sync ") + self.Node.wait_light_sync_height(self.ckb_light_node, new_sync_until_number, 100000) + print("compare new account data") + for acc in new_sync_account: + print("------------~~~----------------------") + acc = self.Ckb_cli.util_key_info_by_private_key(acc) + light_cells = self.ckb_light_node.getClient().get_cells({ + "script": { + "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + "hash_type": "type", + "args": acc['lock_arg'] + }, + "script_type": "lock", + "filter": { + "block_range": [hex(new_sync_block_number), hex(new_sync_until_number)] + }}, "asc", "0xffffff", None) + full_cells = self.node.getClient().get_cells({ + "script": { + "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + "hash_type": "type", + "args": acc['lock_arg'] + }, + "script_type": "lock", + "filter": { + "block_range": [hex(new_sync_block_number + 1), hex(new_sync_until_number)] + }}, "asc", "0xffffff", None) + assert len(light_cells['objects']) == len(full_cells['objects']) + print(len(light_cells['objects'])) + print(len(full_cells['objects'])) + self.ckb_light_node.stop() + self.ckb_light_node.clean() diff --git a/test_cases/node_compatible/test_after_ckb_2023_hardfork.py b/test_cases/node_compatible/test_after_ckb_2023_hardfork.py index 1324a6b..8c13d34 100644 --- a/test_cases/node_compatible/test_after_ckb_2023_hardfork.py +++ b/test_cases/node_compatible/test_after_ckb_2023_hardfork.py @@ -4,70 +4,84 @@ from framework.basic import CkbTest -class TestAfterCkb2023(CkbTest): - node_current = CkbTest.CkbNode.init_dev_by_port(CkbTest.CkbNodeConfigPath.CURRENT_TEST, - "node_compatible/current/node1", 8115, - 8225) - node_111 = CkbTest.CkbNode.init_dev_by_port(CkbTest.CkbNodeConfigPath.V111, - "node_compatible/current/node2", - 8116, - 8226) - node_110 = CkbTest.CkbNode.init_dev_by_port(CkbTest.CkbNodeConfigPath.V110, "node_compatible/current/node3", 8117, - 8227) - nodes = [node_current, node_111, node_110] - lt_111_nodes = [node_110] - ge_111_nodes = [node_current, node_111] - cluster: CkbTest.Cluster = CkbTest.Cluster(nodes) - - @classmethod - def setup_class(cls): - cls.cluster.prepare_all_nodes( - other_ckb_config={'ckb_logger_filter': 'debug'} - ) - cls.cluster.start_all_nodes() - cls.node_current.connected(cls.node_111) - cls.node_current.connected(cls.node_110) - contracts = cls.Contract_util.deploy_contracts(cls.Config.ACCOUNT_PRIVATE_1, cls.cluster.ckb_nodes[0]) - cls.spawn_contract = contracts["SpawnContract"] - cls.Miner.make_tip_height_number(cls.node_current, 900) - cls.Node.wait_cluster_sync_with_miner(cls.cluster, 300, 900) - heights = cls.cluster.get_all_nodes_height() - print(f"heights:{heights}") - cls.Miner.make_tip_height_number(cls.node_current, 1100) - - @classmethod - def teardown_class(cls): - print("\nTeardown TestClass1") - heights = cls.cluster.get_all_nodes_height() - print(heights) - cls.cluster.stop_all_nodes() - cls.cluster.clean_all_nodes() - - def test_01_lt_111_sync_hard_fork(self): - self.Node.wait_node_height(self.node_110, 990, 100) - time.sleep(10) - tip_number = self.node_110.getClient().get_tip_block_number() - assert tip_number <= 999 - - def test_02_lt_111_sync_failed(self): - node = self.CkbNode.init_dev_by_port(self.CkbNodeConfigPath.V110, "node_compatible/current/node5", 8229, - 8339) - node.prepare() - node.start() - node.connected(self.node_current) - self.cluster.ckb_nodes.append(node) - with pytest.raises(Exception) as exc_info: - self.Node.wait_node_height(node, 1, 30) - expected_error_message = "time out" - assert expected_error_message in exc_info.value.args[0], \ - f"Expected substring '{expected_error_message}' not found in actual string '{exc_info.value.args[0]}'" - - def test_03_sync_successful_ge_111(self): - node = self.CkbNode.init_dev_by_port(self.CkbNodeConfigPath.V111, "node_compatible/current/node6", 8129, - 8239) - node.prepare() - node.start() - node.connected(self.node_current) - self.Node.wait_node_height(node, 1001, 1000) - self.cluster.ckb_nodes.append(node) +# https: // github.com / nervosnetwork / ckb / pull / 4227 +# 这是 0.112.1 和 0.113.0-rc1 的一个差异。 +# 112 版本之前的dev dao 计算是错误的,所以113之后去掉兼容性测试 +# +# class TestAfterCkb2023(CkbTest): +# node_current = CkbTest.CkbNode.init_dev_by_port(CkbTest.CkbNodeConfigPath.CURRENT_TEST, +# "node_compatible/current/node1", 8115, +# 8225) +# node_111 = CkbTest.CkbNode.init_dev_by_port(CkbTest.CkbNodeConfigPath.V111, +# "node_compatible/current/node2", +# 8116, +# 8226) +# node_110 = CkbTest.CkbNode.init_dev_by_port(CkbTest.CkbNodeConfigPath.V110, "node_compatible/current/node3", 8117, +# 8227) +# nodes = [node_current, node_111, node_110] +# lt_111_nodes = [node_110] +# ge_111_nodes = [node_current, node_111] +# cluster: CkbTest.Cluster = CkbTest.Cluster(nodes) +# +# @classmethod +# def setup_class(cls): +# cls.cluster.prepare_all_nodes( +# other_ckb_config={'ckb_logger_filter': 'debug'} +# ) +# cls.cluster.start_all_nodes() +# cls.node_current.connected(cls.node_111) +# cls.node_current.connected(cls.node_110) +# contracts = cls.Contract_util.deploy_contracts(cls.Config.ACCOUNT_PRIVATE_1, cls.cluster.ckb_nodes[0]) +# cls.spawn_contract = contracts["SpawnContract"] +# cls.Miner.make_tip_height_number(cls.node_current, 900) +# cls.Node.wait_cluster_sync_with_miner(cls.cluster, 300, 900) +# heights = cls.cluster.get_all_nodes_height() +# print(f"heights:{heights}") +# cls.Miner.make_tip_height_number(cls.node_current, 1100) +# +# @classmethod +# def teardown_class(cls): +# pass +# # print("\nTeardown TestClass1") +# # heights = cls.cluster.get_all_nodes_height() +# # print(heights) +# # cls.cluster.stop_all_nodes() +# # cls.cluster.clean_all_nodes() +# +# def test_01_lt_111_sync_hard_fork(self): +# self.Node.wait_node_height(self.node_110, 990, 100) +# time.sleep(10) +# tip_number = self.node_110.getClient().get_tip_block_number() +# assert tip_number <= 999 +# +# def test_02_lt_111_sync_failed(self): +# node = self.CkbNode.init_dev_by_port(self.CkbNodeConfigPath.V110, "node_compatible/current/node5", 8229, +# 8339) +# node.prepare() +# node.start() +# node.connected(self.node_current) +# self.cluster.ckb_nodes.append(node) +# with pytest.raises(Exception) as exc_info: +# self.Node.wait_node_height(node, 1, 30) +# expected_error_message = "time out" +# assert expected_error_message in exc_info.value.args[0], \ +# f"Expected substring '{expected_error_message}' not found in actual string '{exc_info.value.args[0]}'" +# +# def test_03_sync_successful_ge_111(self): +# node = self.CkbNode.init_dev_by_port(self.CkbNodeConfigPath.V111, "node_compatible/current/node6", 8129, +# 8239) +# node.prepare() +# node.start() +# node.connected(self.node_current) +# self.Node.wait_node_height(node, 1001, 1000) +# self.cluster.ckb_nodes.append(node) +# +# def test_04_sync_successful_ge_112(self): +# node = self.CkbNode.init_dev_by_port(self.CkbNodeConfigPath.V112, "node_compatible/current/node7", 8130, +# 8330) +# node.prepare() +# node.start() +# node.connected(self.node_current) +# self.Node.wait_node_height(node, 1001, 1000) +# self.cluster.ckb_nodes.append(node) diff --git a/test_cases/replace_rpc/test_4309.py b/test_cases/replace_rpc/test_4309.py new file mode 100644 index 0000000..722c0e1 --- /dev/null +++ b/test_cases/replace_rpc/test_4309.py @@ -0,0 +1,26 @@ +from framework.basic import CkbTest +from framework.util import run_command + + +class Test4309(CkbTest): + + @classmethod + def setup_class(cls): + cls.node113 = cls.CkbNode.init_dev_by_port(cls.CkbNodeConfigPath.CURRENT_TEST, "telnet/node", 8114, 8115) + cls.node113.prepare() + cls.node113.start() + + @classmethod + def teardown_class(cls): + print("teardown_class") + cls.node113.stop() + cls.node113.clean() + + def test_4309(self): + """ + https://github.com/nervosnetwork/ckb/issues/4309 + """ + ret = run_command( + """curl -s -X GET http://127.0.0.1:8114 -H 'Content-Type: application/json' -d '{ "id": 42, "jsonrpc": + "2.0", "method": "sync_state", "params": [ ] }'""") + assert "Used HTTP Method is not allowed. POST or OPTIONS is required" in ret diff --git a/test_cases/replace_rpc/test_rpc.py b/test_cases/replace_rpc/test_rpc.py new file mode 100644 index 0000000..18b84df --- /dev/null +++ b/test_cases/replace_rpc/test_rpc.py @@ -0,0 +1,191 @@ +import pytest + +from framework.basic import CkbTest +from framework.util import run_command + + +class TestRpc(CkbTest): + + @classmethod + def setup_class(cls): + cls.node113 = cls.CkbNode.init_dev_by_port(cls.CkbNodeConfigPath.CURRENT_TEST, "telnet/node", 8114, 8115) + cls.node113.prepare(other_ckb_config={"ckb_tcp_listen_address": "127.0.0.1:18114", + "ckb_ws_listen_address": "127.0.0.1:18124"}) + + cls.node112 = cls.CkbNode.init_dev_by_port(cls.CkbNodeConfigPath.V112, "telnet/node2", 8116, 8117) + cls.node112.prepare(other_ckb_config={"ckb_tcp_listen_address": "127.0.0.1:18115", + "ckb_ws_listen_address": "127.0.0.1:18125"}) + cls.node112.start() + cls.node113.start() + cls.node112.connected(cls.node113) + cls.Miner.make_tip_height_number(cls.node113, 100) + + @classmethod + def teardown_class(cls): + print("teardown_class") + cls.node112.stop() + cls.node112.clean() + cls.node113.stop() + cls.node113.clean() + + def test_without_header(self): + """ + 112: need application/json + 113: not need + """ + ret113 = run_command( + """curl -X POST -d '[{"jsonrpc": "2.0", "method": "ping_peers", "params": [], "id": "1"}]' """ + f"""{self.node113.rpcUrl}/ """) + assert "null" in ret113 + ret = run_command( + """curl -X POST -d '[{"jsonrpc": "2.0", "method": "ping_peers", "params": [], "id": "1"}]' """ + f"""{self.node112.rpcUrl}/ """) + assert "Content-Type: application/json is required" in ret + + def test_01_with_error(self): + """ + {"id": 42, "jsonrpc": "2.0", "method": "get_block_by_number", "params": ["0", null, null]} + 112:{"jsonrpc": "2.0", "error": {"code": -32602, "message": "Invalid params: Invalid Uint64 0: without `0x` prefix."}, "id": 42} + 113:{"jsonrpc": "2.0", "error": {"code": -32602, "message": "Invalid parameter for `block_number`: Invalid Uint64 0: without `0x` prefix"}, "id": 42} + """ + with pytest.raises(Exception) as exc_info: + ret = self.node112.getClient().get_block_by_number("0") + expected_error_message = "Invalid params: Invalid Uint64 0: without `0x` prefix" + assert expected_error_message in exc_info.value.args[0], \ + f"Expected substring '{expected_error_message}' not found in actual string '{exc_info.value.args[0]}'" + + with pytest.raises(Exception) as exc_info: + ret = self.node113.getClient().get_block_by_number("0") + expected_error_message = "Invalid parameter for `block_number`: Invalid Uint64 0: without `0x` prefix" + assert expected_error_message in exc_info.value.args[0], \ + f"Expected substring '{expected_error_message}' not found in actual string '{exc_info.value.args[0]}'" + + pass + + def test_01_with_error_2(self): + """ + {"jsonrpc": "2.0", "method": "dry_run_transaction", "params": [{}], "id": "1"} + 112:{"jsonrpc": "2.0", "error": {"code": -32602, "message": "message":"Invalid params: missing field `version`."}, "id": 42} + 113:{"jsonrpc": "2.0", "error": {"code": -32602, "message": "message":"Invalid params: missing field `version`."}, "id": 42} + """ + with pytest.raises(Exception) as exc_info: + ret = self.node112.getClient().get_block_by_number("0") + expected_error_message = "Invalid params: Invalid Uint64 0: without `0x` prefix" + assert expected_error_message in exc_info.value.args[0], \ + f"Expected substring '{expected_error_message}' not found in actual string '{exc_info.value.args[0]}'" + + with pytest.raises(Exception) as exc_info: + ret = self.node113.getClient().get_block_by_number("0") + expected_error_message = "" + assert expected_error_message in exc_info.value.args[0], \ + f"Expected substring '{expected_error_message}' not found in actual string '{exc_info.value.args[0]}'" + + + @pytest.mark.skip + def test_max_response(self): + """ + batch get_block_by_number count:922 + 112: successful + 113: successful + batch get_block_by_number count:923 + 112:failed + 113:failed + """ + # 测试返回数据上限 + rpcUrls = [self.node112.rpcUrl, self.node113.rpcUrl] + for rpcUrl in rpcUrls: + requestBody = "" + for i in range(922): + requestBody = requestBody + """{"jsonrpc": "2.0", "method": "get_block_by_number", "params": ["0x0"], + "id": "1"},""" + requestBody = requestBody[:-1] + requests = """curl -vvv -X POST -H "Content-Type: application/json" -d '[""" + str( + requestBody) + f"""]' {rpcUrl} > /tmp/null""" + run_command(requests) + run_command("rm -rf /tmp/null") + + for rpcUrl in rpcUrls: + requestBody = "" + for i in range(923): + requestBody = requestBody + """{"jsonrpc": "2.0", "method": "get_block_by_number", "params": ["0x0"], + "id": "1"},""" + requestBody = requestBody[:-1] + requests = """curl -vvv -X POST -H "Content-Type: application/json" -d '[""" + str( + requestBody) + f"""]' {rpcUrl} > /tmp/null""" + with pytest.raises(Exception) as exc_info: + run_command(requests) + expected_error_message = "Empty reply from server" + assert expected_error_message in exc_info.value.args[0], \ + f"Expected substring '{expected_error_message}' not found in actual string '{exc_info.value.args[0]}'" + + @pytest.mark.skip + def test_max_batch_count(self): + """ + batch send count :15376 + 112: successful + 113: successful + batch send count :15377 + 112: failed + 113: failed + """ + rpcUrls = [ + self.node112.rpcUrl, + self.node113.rpcUrl + ] + for rpcUrl in rpcUrls: + requestBody = "" + for i in range(15376): + requestBody = requestBody + """{"jsonrpc": "2.0", "method": "ping_peers", "params": [], "id": "1"},""" + requestBody = requestBody[:-1] + requests = """curl -vvv -X POST -H "Content-Type: application/json" -d '[""" + str( + requestBody) + f"""]' {rpcUrl}""" + run_command(requests) + for i in range(15378): + requestBody = requestBody + """{"jsonrpc": "2.0", "method": "ping_peers", "params": [], "id": "1"},""" + requestBody = requestBody[:-1] + requests = """curl -vvv -X POST -H "Content-Type: application/json" -d '[""" + str( + requestBody) + f"""]' {rpcUrl}""" + + with pytest.raises(Exception) as exc_info: + run_command(requests) + expected_error_message = "Argument list too long" + assert expected_error_message in exc_info.value.args[1], \ + f"Expected substring '{expected_error_message}' not found in actual string '{exc_info.value.args[0]}'" + + def test_websocket(self): + """ + not support websocket + 112: not support,Handshake status 405 Method Not Allowed + 113: not support,not allowed. POST or OPTIONS is required + """ + # 112: not support,Handshake status 405 Method Not Allowed + with pytest.raises(Exception) as exc_info: + socket = self.node112.subscribe_websocket("new_tip_header", self.node112.rpcUrl.replace("http://", "")) + expected_error_message = "Handshake status 405 Method Not Allowed" + assert expected_error_message in exc_info.value.args[0], \ + f"Expected substring '{expected_error_message}' not found in actual string '{exc_info.value.args[0]}'" + + # 113 support + with pytest.raises(Exception) as exc_info: + socket = self.node113.subscribe_websocket("new_tip_header", self.node113.rpcUrl.replace("http://", "")) + expected_error_message = "not allowed. POST or OPTIONS is required" + assert expected_error_message in exc_info.value.args[0], \ + f"Expected substring '{expected_error_message}' not found in actual string '{exc_info.value.args[0]}'" + + def test_telnet(self): + """ + support telnet + 112: not + 113: not + """ + socket = self.node112.subscribe_telnet("new_tip_header", self.node112.rpcUrl.replace("http://", "")) + with pytest.raises(Exception) as exc_info: + socket.read_very_eager() + expected_error_message = "telnet connection closed" + assert expected_error_message in exc_info.value.args[0], \ + f"Expected substring '{expected_error_message}' not found in actual string '{exc_info.value.args[0]}'" + + socket = self.node113.subscribe_telnet("new_tip_header", self.node113.rpcUrl.replace("http://", "")) + with pytest.raises(Exception) as exc_info: + socket.read_very_eager() + expected_error_message = "telnet connection closed" + assert expected_error_message in exc_info.value.args[0], \ + f"Expected substring '{expected_error_message}' not found in actual string '{exc_info.value.args[0]}'" \ No newline at end of file diff --git a/test_cases/replace_rpc/test_sub_telnet_with_websocket.py b/test_cases/replace_rpc/test_sub_telnet_with_websocket.py new file mode 100644 index 0000000..b6e87b3 --- /dev/null +++ b/test_cases/replace_rpc/test_sub_telnet_with_websocket.py @@ -0,0 +1,215 @@ +import json +import time + +from framework.basic import CkbTest + + +class TestTelnetAndWebsocket(CkbTest): + + @classmethod + def setup_class(cls): + cls.node113 = cls.CkbNode.init_dev_by_port(cls.CkbNodeConfigPath.CURRENT_TEST, "telnet/node", 8119, 8129) + cls.node113.prepare( + other_ckb_config={ + "ckb_logger_filter": "debug", + "ckb_tcp_listen_address": "127.0.0.1:18116", + "ckb_ws_listen_address": "127.0.0.1:18124"}) + cls.node112 = cls.CkbNode.init_dev_by_port(cls.CkbNodeConfigPath.V112, "telnet/node2", 8126, 8127) + cls.node112.prepare(other_ckb_config={ + "ckb_logger_filter": "debug", + "ckb_tcp_listen_address": "127.0.0.1:18115", + "ckb_ws_listen_address": "127.0.0.1:18125"}) + cls.node112.start() + cls.node113.start() + cls.node112.connected(cls.node113) + cls.Miner.make_tip_height_number(cls.node113, 100) + cls.Node.wait_node_height(cls.node112, 100, 1000) + + @classmethod + def teardown_class(cls): + cls.node112.stop() + cls.node112.clean() + + cls.node113.stop() + cls.node113.clean() + + def test_01_sub_tip_block_number(self): + telnet_new_tip_header_112 = self.node112.subscribe_telnet("new_tip_header") + telnet_new_tip_header_113 = self.node113.subscribe_telnet("new_tip_header") + ws_new_tip_header_112 = self.node112.subscribe_websocket("new_tip_header") + ws_new_tip_header_113 = self.node113.subscribe_websocket("new_tip_header") + for i in range(10): + self.Miner.miner_with_version(self.node112, "0x0") + telnet_new_tip_header_112_ret = telnet_new_tip_header_112.read_very_eager() + ws_new_tip_header_112_ret = ws_new_tip_header_112.recv() + telnet_new_tip_header_113_ret = telnet_new_tip_header_113.read_very_eager() + ws_new_tip_header_113_ret = ws_new_tip_header_113.recv() + print("telnet_new_tip_header_113_ret:", + json.loads(telnet_new_tip_header_113_ret.decode())['params']['result']) + print("ws_new_tip_header_113_ret:", json.loads(ws_new_tip_header_113_ret)['params']['result']) + assert json.loads(telnet_new_tip_header_113_ret.decode())['params']['result'] == \ + json.loads(ws_new_tip_header_113_ret)['params']['result'] + # print("telnet_new_tip_header_112_ret:", + # json.loads(telnet_new_tip_header_112_ret.decode())['params']['result']) + # assert json.loads(telnet_new_tip_header_112_ret.decode())['params']['result'] == \ + # json.loads(telnet_new_tip_header_113_ret.decode())['params']['result'] + print("ws_new_tip_header_112_ret:", json.loads(ws_new_tip_header_112_ret)['params']['result']) + assert json.loads(ws_new_tip_header_112_ret)['params']['result'] == \ + json.loads(ws_new_tip_header_113_ret)['params']['result'] + telnet_new_tip_header_112.close() + telnet_new_tip_header_113.close() + ws_new_tip_header_112.close() + ws_new_tip_header_113.close() + + def test_02_sub_new_tip_block(self): + telnet_new_tip_block_112 = self.node112.subscribe_telnet("new_tip_block") + telnet_new_tip_block_113 = self.node113.subscribe_telnet("new_tip_block") + ws_new_tip_block_112 = self.node112.subscribe_websocket("new_tip_block") + ws_new_tip_block_113 = self.node113.subscribe_websocket("new_tip_block") + for i in range(10): + self.Miner.miner_with_version(self.node112, "0x0") + telnet_new_tip_block_112_ret = telnet_new_tip_block_112.read_very_eager() + ws_new_tip_block_112_ret = ws_new_tip_block_112.recv() + telnet_new_tip_block_113_ret = telnet_new_tip_block_113.read_very_eager() + ws_new_tip_block_113_ret = ws_new_tip_block_113.recv() + # print("telnet_new_tip_block_112_ret:", json.loads(telnet_new_tip_block_112_ret)['params']['result']) + print("ws_new_tip_block_112_ret:", json.loads(ws_new_tip_block_112_ret)['params']['result']) + print("telnet_new_tip_block_113_ret:", json.loads(telnet_new_tip_block_113_ret)['params']['result']) + print("ws_new_tip_block_113_ret:", json.loads(ws_new_tip_block_113_ret)['params']['result']) + assert json.loads(ws_new_tip_block_112_ret)['params']['result'] == \ + json.loads(telnet_new_tip_block_113_ret)['params']['result'] + assert json.loads(ws_new_tip_block_112_ret)['params']['result'] == \ + json.loads(ws_new_tip_block_113_ret)['params']['result'] + telnet_new_tip_block_112.close() + telnet_new_tip_block_113.close() + ws_new_tip_block_112.close() + ws_new_tip_block_113.close() + + def test_03_sub_new_tx(self): + telnet_new_tx_112 = self.node112.subscribe_telnet("new_transaction") + ws_new_tx_112 = self.node112.subscribe_websocket("new_transaction") + telnet_new_tx_113 = self.node113.subscribe_telnet("new_transaction") + ws_new_tx_113 = self.node113.subscribe_websocket("new_transaction") + account1 = self.Ckb_cli.util_key_info_by_private_key(self.Config.ACCOUNT_PRIVATE_1) + + for i in range(1): + tx_hash = self.Ckb_cli.wallet_transfer_by_private_key( + self.Config.MINER_PRIVATE_1, + account1['address']['testnet'], + 140, + self.node113.client.url) + self.Miner.miner_until_tx_committed(self.node113, tx_hash) + telnet_new_tx_113_ret = telnet_new_tx_113.read_very_eager() + telnet_new_tx_112_ret = telnet_new_tx_112.read_very_eager() + ws_new_tx_113_ret = ws_new_tx_113.recv() + ws_new_tx_112_ret = ws_new_tx_112.recv() + print("telnet_new_tx_113_ret:", telnet_new_tx_113_ret) + print("ws_new_tx_113_ret:", ws_new_tx_113_ret) + # print("telnet_new_tx_112_ret:", telnet_new_tx_112_ret) + print("ws_new_tx_112_ret:", ws_new_tx_112_ret) + assert len(json.loads(telnet_new_tx_113_ret.decode())['params']['result']) == \ + len(json.loads(ws_new_tx_113_ret)['params']['result']) + assert len(json.loads(telnet_new_tx_113_ret.decode())['params']['result']) == \ + len(json.loads(ws_new_tx_112_ret)['params']['result']) + telnet_new_tx_112.close() + ws_new_tx_112.close() + telnet_new_tx_113.close() + ws_new_tx_113.close() + + def test_04_sub_proposal_tx(self): + telnet_new_tx_112 = self.node112.subscribe_telnet("proposed_transaction") + ws_new_tx_112 = self.node112.subscribe_websocket("proposed_transaction") + telnet_new_tx_113 = self.node113.subscribe_telnet("proposed_transaction") + ws_new_tx_113 = self.node113.subscribe_websocket("proposed_transaction") + account1 = self.Ckb_cli.util_key_info_by_private_key(self.Config.ACCOUNT_PRIVATE_1) + for i in range(1): + tx_hash = self.Ckb_cli.wallet_transfer_by_private_key( + self.Config.MINER_PRIVATE_1, + account1['address']['testnet'], + 140, + self.node113.client.url) + + self.Miner.miner_until_tx_committed(self.node113, tx_hash) + telnet_new_tx_113_ret = telnet_new_tx_113.read_very_eager() + telnet_new_tx_112_ret = telnet_new_tx_112.read_very_eager() + ws_new_tx_113_ret = ws_new_tx_113.recv() + ws_new_tx_112_ret = ws_new_tx_112.recv() + print("telnet_new_tx_113_ret:", telnet_new_tx_113_ret) + print("ws_new_tx_113_ret:", ws_new_tx_113_ret) + # print("telnet_new_tx_112_ret:", telnet_new_tx_112_ret) + print("ws_new_tx_112_ret:", ws_new_tx_112_ret) + print("json:", json.loads(telnet_new_tx_113_ret.decode())['params']['result']) + assert len(json.loads(telnet_new_tx_113_ret.decode())['params']['result']) == \ + len(json.loads(ws_new_tx_113_ret)['params']['result']) + assert len(json.loads(telnet_new_tx_113_ret.decode())['params']['result']) == \ + len(json.loads(ws_new_tx_112_ret)['params']['result']) + telnet_new_tx_112.close() + ws_new_tx_112.close() + telnet_new_tx_113.close() + ws_new_tx_113.close() + + def test_05_reject_tx(self): + telnet_new_tx_112 = self.node112.subscribe_telnet("rejected_transaction") + ws_new_tx_112 = self.node112.subscribe_websocket("rejected_transaction") + telnet_new_tx_113 = self.node113.subscribe_telnet("rejected_transaction") + ws_new_tx_113 = self.node113.subscribe_websocket("rejected_transaction") + account1 = self.Ckb_cli.util_key_info_by_private_key(self.Config.ACCOUNT_PRIVATE_1) + for i in range(1): + tx_hash = self.Ckb_cli.wallet_transfer_by_private_key( + self.Config.MINER_PRIVATE_1, + account1['address']['testnet'], + 151, + self.node113.client.url) + time.sleep(3) + tx_hash = self.Ckb_cli.wallet_transfer_by_private_key( + self.Config.MINER_PRIVATE_1, + account1['address']['testnet'], + 151000, + self.node113.client.url, "10000") + + tx_hash = self.Ckb_cli.wallet_transfer_by_private_key( + self.Config.MINER_PRIVATE_1, + account1['address']['testnet'], + 151000, + self.node113.client.url, "100000") + + ws_new_tx_113_ret = ws_new_tx_113.recv() + print("ws_new_tx_113_ret:", ws_new_tx_113_ret) + + telnet_new_tx_113_ret = telnet_new_tx_113.read_very_eager() + print("telnet_new_tx_113_ret:", telnet_new_tx_113_ret) + + # telnet_new_tx_112_ret = telnet_new_tx_112.read_very_eager() + # print("telnet_new_tx_112_ret:", telnet_new_tx_112_ret) + + ws_new_tx_112_ret = ws_new_tx_112.recv() + print("ws_new_tx_112_ret:",ws_new_tx_112_ret) + + # assert len(json.loads(telnet_new_tx_113_ret.decode())['params']['result']) == \ + # len(json.loads(ws_new_tx_113_ret)['params']['result']) + # assert len(json.loads(telnet_new_tx_113_ret.decode())['params']['result']) == \ + # len(json.loads(ws_new_tx_112_ret)['params']['result']) + telnet_new_tx_112.close() + ws_new_tx_112.close() + telnet_new_tx_113.close() + ws_new_tx_113.close() + pass + + # //{"id": 2, "jsonrpc": "2.0", "method": "subscribe", "params": ["new_tip_block"]} + # def test_sub_max_11(self): + # topic = "new_tip_block" + # + # topic_str = '{"id": 2, "jsonrpc": "2.0", "method": "subscribe", "params": ["' + topic + '"]}' + # # topic_str = '' + # # for i in range(1): + # # topic_str = topic_str+topic_str1 + "\n" + # tns = [] + # for i in range(1000): + # tn = self.node113.subscribe_telnet("new_tip_block") + # tns.append(tn) + # for i in range(10000000): + # for tn in tns: + # tn.write(topic_str.encode('utf-8') + b"\n") + # # data = tn.read_until(b'}\n') + # # print(str(i) + ":", data) + # print("i:", i) diff --git a/test_cases/replace_rpc/test_telnet.py b/test_cases/replace_rpc/test_telnet.py new file mode 100644 index 0000000..22c4aba --- /dev/null +++ b/test_cases/replace_rpc/test_telnet.py @@ -0,0 +1,189 @@ +import json +import time + +import pytest + +from framework.basic import CkbTest +from framework.util import run_command + + +class TestRpc(CkbTest): + + @classmethod + def setup_class(cls): + cls.node113 = cls.CkbNode.init_dev_by_port(cls.CkbNodeConfigPath.CURRENT_TEST, "telnet/node", 8114, 8115) + cls.node113.prepare(other_ckb_config={ + "ckb_logger_filter": "debug", + "ckb_tcp_listen_address": "127.0.0.1:18115", + "ckb_ws_listen_address": "127.0.0.1:18124" + }) + cls.node112 = cls.CkbNode.init_dev_by_port(cls.CkbNodeConfigPath.V112, "telnet/node2", 8116, 8117) + cls.node112.prepare(other_ckb_config={ + "ckb_tcp_listen_address": "127.0.0.1:18114" + }) + cls.node112.start() + cls.node113.start() + cls.node112.connected(cls.node113) + cls.Miner.make_tip_height_number(cls.node113, 100) + cls.Node.wait_node_height(cls.node112, 90, 1000) + + @classmethod + def teardown_class(cls): + cls.node112.stop() + cls.node112.clean() + + cls.node113.stop() + cls.node113.clean() + + def test_link_count_max(self): + """ + link tcp + 112: 1022 + 113: > 10234 + """ + telnets = [] + for i in range(1000): + print(i) + telnet = self.node112.subscribe_telnet("new_tip_header") + telnets.append(telnet) + + self.Miner.miner_with_version(self.node112, "0x0") + time.sleep(1) + for i in range(len(telnets)): + telnet = telnets[i] + ret = telnet.read_very_eager() + print(ret) + print(i, ':', len(ret)) + assert len(ret) > 700 + telnet.close() + + # test 113 max link count + telnets = [] + for i in range(10000): + print(i) + telnet = self.node113.subscribe_telnet("new_tip_header") + telnets.append(telnet) + + self.Miner.miner_with_version(self.node113, "0x0") + for i in range(len(telnets)): + telnet = telnets[i] + ret = telnet.read_very_eager() + print(i, ':', len(ret)) + assert len(ret) > 700 + telnet.close() + + def test_link_time_max(self): + """ + link time + 112: keep link + 113: keep link + """ + telnet113 = self.node113.subscribe_telnet("new_tip_header") + telnet112 = self.node112.subscribe_telnet("new_tip_header") + + for i in range(300): + self.Miner.miner_with_version(self.node113, "0x0") + print("current idx:", i) + ret113 = telnet113.read_very_eager() + ret112 = telnet112.read_very_eager() + print(ret113) + print(ret112) + time.sleep(1) + telnet113.close() + telnet112.close() + + def test_link_websocket(self): + """ + support websocket + 112: not support + 113: not support + """ + with pytest.raises(Exception) as exc_info: + socket = self.node112.subscribe_websocket("new_tip_header", + self.node112.ckb_config['ckb_tcp_listen_address']) + expected_error_message = "invalid literal for int() with base 10" + assert expected_error_message in exc_info.value.args[0], \ + f"Expected substring '{expected_error_message}' not found in actual string '{exc_info.value.args[0]}'" + + with pytest.raises(Exception) as exc_info: + socket = self.node113.subscribe_websocket("new_tip_header", + self.node113.ckb_config['ckb_tcp_listen_address']) + expected_error_message = "invalid literal for int() with base 10" + assert expected_error_message in exc_info.value.args[0], \ + f"Expected substring '{expected_error_message}' not found in actual string '{exc_info.value.args[0]}'" + + def test_rpc(self): + """ + support rpc + 112: not support + 113: not support + """ + client = self.node112.getClient() + client.url = f"http://{self.node112.ckb_config['ckb_tcp_listen_address']}" + + with pytest.raises(Exception) as exc_info: + response = client.call("get_tip_block_number", [], 1) + expected_error_message = "request time out" + assert expected_error_message in exc_info.value.args[0], \ + f"Expected substring '{expected_error_message}' not found in actual string '{exc_info.value.args[0]}'" + + client = self.node113.getClient() + client.url = f"http://{self.node113.ckb_config['ckb_tcp_listen_address']}" + + with pytest.raises(Exception) as exc_info: + response = client.call("get_tip_block_number", [], 1) + expected_error_message = "request time out" + assert expected_error_message in exc_info.value.args[0], \ + f"Expected substring '{expected_error_message}' not found in actual string '{exc_info.value.args[0]}'" + + def test_stop_node_when_link_telnet(self): + """ + stop ckb when socker is keep live + 112: stop successful + 113: stop successful + """ + self.node112.restart() + socket = self.node112.subscribe_telnet("new_tip_header") + self.node112.stop() + port = self.node112.ckb_config['ckb_tcp_listen_address'].split(":")[-1] + ret = run_command(f"lsof -i:{port} | grep ckb", check_exit_code=False) + assert "ckb" not in str(ret) + socket.close() + ret = run_command(f"lsof -i:{port} | grep ckb", check_exit_code=False) + assert "ckb" not in str(ret) + + self.node113.restart() + socket = self.node113.subscribe_telnet("new_tip_header") + self.node113.stop() + port = self.node113.ckb_config['ckb_tcp_listen_address'].split(":")[-1] + ret = run_command(f"lsof -i:{port} | grep ckb", check_exit_code=False) + assert "ckb" not in str(ret) + socket.close() + ret = run_command(f"lsof -i:{port} | grep ckb", check_exit_code=False) + assert "ckb" not in str(ret) + self.node112.restart() + self.node113.restart() + + def test_unsubscribe(self): + """ + subscribe topic 1 + unsubscribe topic 1 + unsubscribe successful + """ + client = self.node113.getClient() + client.url = f"http://{self.node113.ckb_config['ckb_rpc_listen_address']}" + + socket = self.node113.subscribe_telnet("new_tip_header") + self.Miner.miner_with_version(self.node113, "0x0") + ret = socket.read_very_eager() + ret = json.loads(ret) + print(ret['params']['subscription']) + subscribe_str = '{"id": 2, "jsonrpc": "2.0", "method": "unsubscribe", "params": ["' + ret['params'][ + 'subscription'] + '"]}' + print("subscribe_str:", subscribe_str) + socket.write(subscribe_str.encode('utf-8') + b"\n") + data = socket.read_until(b'}\n') + assert "true" in data.decode('utf-8') + self.Miner.miner_with_version(self.node113, "0x0") + ret = socket.read_very_eager() + assert ret.decode('utf-8') == "" diff --git a/test_cases/replace_rpc/test_websocket.py b/test_cases/replace_rpc/test_websocket.py new file mode 100644 index 0000000..6b9a1a3 --- /dev/null +++ b/test_cases/replace_rpc/test_websocket.py @@ -0,0 +1,162 @@ +import time + +import pytest + +from framework.basic import CkbTest +from framework.util import run_command + + +class TestWebsocket(CkbTest): + + @classmethod + def setup_class(cls): + cls.node113 = cls.CkbNode.init_dev_by_port(cls.CkbNodeConfigPath.CURRENT_TEST, "telnet/node", 8114, 8115) + cls.node113.prepare( + other_ckb_config={ + "ckb_logger_filter": "debug", + "ckb_tcp_listen_address": "127.0.0.1:18114", + "ckb_ws_listen_address": "127.0.0.1:18124"}) + cls.node112 = cls.CkbNode.init_dev_by_port(cls.CkbNodeConfigPath.V112, "telnet/node2", 8116, 8117) + cls.node112.prepare(other_ckb_config={ + "ckb_logger_filter": "debug", + "ckb_tcp_listen_address": "127.0.0.1:18115", + "ckb_ws_listen_address": "127.0.0.1:18125"}) + cls.node112.start() + cls.node113.start() + cls.node112.connected(cls.node113) + cls.Miner.make_tip_height_number(cls.node113, 100) + cls.Node.wait_node_height(cls.node112, 100, 1000) + + @classmethod + def teardown_class(cls): + cls.node112.stop() + cls.node112.clean() + + cls.node113.stop() + cls.node113.clean() + + def test_link_count_max(self): + + """ + link tcp + 112: 1022 + 113: > 10234 + """ + # test 112 max link count + websockets = [] + for i in range(100): + print(i) + websocket = self.node112.subscribe_websocket("new_tip_header") + websockets.append(websocket) + + self.Miner.miner_with_version(self.node112, "0x0") + for i in range(len(websockets)): + websocket = websockets[i] + ret = websocket.recv() + print(i, ':', len(ret)) + websocket.close() + + # test 113 max link count + websockets = [] + for i in range(10000): + print(i) + websocket = self.node113.subscribe_websocket("new_tip_header") + websockets.append(websocket) + + self.Miner.miner_with_version(self.node113, "0x0") + for i in range(len(websockets)): + websocket = websockets[i] + ret = websocket.recv() + print(i, ':', len(ret)) + websocket.close() + + def test_link_time_max(self): + """ + link time + 112: keep link + 113: keep link + """ + websocket112 = self.node112.subscribe_websocket("new_tip_header") + websocket113 = self.node113.subscribe_websocket("new_tip_header") + + for i in range(300): + self.Miner.miner_with_version(self.node113, "0x0") + print("current idx:", i) + ret112 = websocket112.recv() + ret113 = websocket113.recv() + print(ret112) + assert len(ret112) > 0 + assert len(ret113) > 0 + print(ret113) + time.sleep(1) + websocket113.close() + websocket112.close() + def test_rpc(self): + """ + support rpc + 112: not support + 113: support + """ + client = self.node112.getClient() + client.url = f"http://{self.node112.ckb_config['ckb_ws_listen_address']}" + + with pytest.raises(Exception) as exc_info: + response = client.call("get_tip_block_number", [], 1) + expected_error_message = "Expecting value: line 1 column 1" + assert expected_error_message in exc_info.value.args[0], \ + f"Expected substring '{expected_error_message}' not found in actual string '{exc_info.value.args[0]}'" + + client = self.node113.getClient() + client.url = f"http://{self.node113.ckb_config['ckb_ws_listen_address']}" + + response = client.call("get_tip_block_number", [], 1) + + # with pytest.raises(Exception) as exc_info: + # response = client.call("get_tip_block_number", [], 1) + # expected_error_message = "request time out" + # assert expected_error_message in exc_info.value.args[0], \ + # f"Expected substring '{expected_error_message}' not found in actual string '{exc_info.value.args[0]}'" + + def test_telnet(self): + socket = self.node112.subscribe_telnet("new_tip_header", self.node112.ckb_config['ckb_ws_listen_address']) + with pytest.raises(Exception) as exc_info: + socket.read_very_eager() + expected_error_message = "telnet connection closed" + assert expected_error_message in exc_info.value.args[0], \ + f"Expected substring '{expected_error_message}' not found in actual string '{exc_info.value.args[0]}'" + + socket = self.node113.subscribe_telnet("new_tip_header", self.node112.ckb_config['ckb_ws_listen_address']) + with pytest.raises(Exception) as exc_info: + socket.read_very_eager() + expected_error_message = "telnet connection closed" + assert expected_error_message in exc_info.value.args[0], \ + f"Expected substring '{expected_error_message}' not found in actual string '{exc_info.value.args[0]}'" + + + def test_stop_node_when_link_websocket(self): + """ + stop ckb when socker is keep live + 112: stop successful + 113: stop successful + """ + self.node112.restart() + socket = self.node112.subscribe_websocket("new_tip_header") + self.node112.stop() + port = self.node112.ckb_config['ckb_ws_listen_address'].split(":")[-1] + ret = run_command(f"lsof -i:{port} | grep ckb", check_exit_code=False) + assert "ckb" not in str(ret) + socket.close() + ret = run_command(f"lsof -i:{port} | grep ckb", check_exit_code=False) + assert "ckb" not in str(ret) + + self.node113.restart() + socket = self.node113.subscribe_websocket("new_tip_header") + self.node113.stop() + port = self.node113.ckb_config['ckb_ws_listen_address'].split(":")[-1] + ret = run_command(f"lsof -i:{port} | grep ckb", check_exit_code=False) + assert "ckb" not in str(ret) + socket.close() + ret = run_command(f"lsof -i:{port} | grep ckb", check_exit_code=False) + assert "ckb" not in str(ret) + self.node112.restart() + self.node113.restart() diff --git a/test_cases/rpc/test_get_pool_tx_detail_info.py b/test_cases/rpc/test_get_pool_tx_detail_info.py new file mode 100644 index 0000000..e69de29 diff --git a/test_cases/tx_pool_refactor/test_01_tx_replace_rule.py b/test_cases/tx_pool_refactor/test_01_tx_replace_rule.py index 032c03b..fba67fa 100644 --- a/test_cases/tx_pool_refactor/test_01_tx_replace_rule.py +++ b/test_cases/tx_pool_refactor/test_01_tx_replace_rule.py @@ -234,16 +234,18 @@ def test_tx_conflict_too_many_txs(self): api_url=self.node.getClient().url) tx_list.append(tx_hash) self.Node.wait_get_transaction(self.node, tx_hash, "pending") + first_tx = self.node.getClient().get_transaction(first_hash) with pytest.raises(Exception) as exc_info: self.Ckb_cli.wallet_transfer_by_private_key(self.Config.ACCOUNT_PRIVATE_1, account["address"]["testnet"], 360000, - self.node.getClient().url, "8800") - expected_error_message = "Server error: PoolRejectedRBF: RBF rejected: Tx conflict too many txs, conflict txs count: 101" + self.node.getClient().url, int(first_tx['min_replace_fee'], 16)) + expected_error_message = "RBF rejected: Tx conflict with too many txs, conflict txs count: 101, expect <= 100" 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.Tx.send_transfer_self_tx_with_input([first_hash], ["0x0"], self.Config.ACCOUNT_PRIVATE_1, fee=5000, + second_tx = self.node.getClient().get_transaction(tx_list[0]) + self.Tx.send_transfer_self_tx_with_input([first_hash], ["0x0"], self.Config.ACCOUNT_PRIVATE_1, + fee=int(second_tx['min_replace_fee'], 16), api_url=self.node.getClient().url) tx_pool = self.node.getClient().get_raw_tx_pool(True) assert len(tx_pool['pending'].keys()) == 2 @@ -280,7 +282,7 @@ def test_replace_pending_transaction_successful(self): assert tx_a_response['tx_status']['status'] == 'rejected' assert "RBFRejected" in tx_a_response['tx_status']['reason'] - def test_replace_proposal_transaction_failure(self): + def test_replace_proposal_transaction_successful(self): """ Replacing the transaction for the proposal, replacement failed. 1. Send a transaction and submit it to the proposal. @@ -288,7 +290,7 @@ def test_replace_proposal_transaction_failure(self): 2. Replace the transaction for that proposal. successful 3. get_block_template - contains proposal that is removed + contains proposal that is not removed 4. generate empty block successful 5. get_block_template @@ -311,6 +313,7 @@ def test_replace_proposal_transaction_failure(self): output_count=1, fee=1000, api_url=self.node.getClient().url) tx_list.append(tx_hash) + self.Miner.miner_with_version(self.node, "0x0") self.Miner.miner_with_version(self.node, "0x0") time.sleep(5) @@ -323,18 +326,11 @@ def test_replace_proposal_transaction_failure(self): tx_response = self.node.getClient().get_transaction(tx) assert tx_response['tx_status']['status'] == 'proposed' tx_response = self.node.getClient().get_transaction(proposal_txs[0]) - # with pytest.raises(Exception) as exc_info: - - # expected_error_message = "RBF rejected: all conflict Txs should be in Pending status" - # 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]}'" - replace_proposal_hash = self.Tx.send_transfer_self_tx_with_input( [tx_response['transaction']['inputs'][0]['previous_output']['tx_hash']], ['0x0'], self.Config.ACCOUNT_PRIVATE_2, output_count=1, - fee=2000, + fee=1000000, api_url=self.node.getClient().url) time.sleep(5) @@ -346,18 +342,6 @@ def test_replace_proposal_transaction_failure(self): block_template = self.node.getClient().get_block_template() assert not proposal_txs[0] in json.dumps(block_template) - # proposal_tx_response = self.node.getClient().get_transaction(proposal_txs[0]) - # tx_response = self.node.getClient().get_transaction(tx_hash) - # assert tx_response['tx_status']['status'] == 'pending' - # assert proposal_tx_response['tx_status']['status'] == 'rejected' - # tx_pool = self.node.getClient().get_raw_tx_pool(True) - # assert proposal_txs[0] not in list(tx_pool['proposed'].keys()) - # - # for i in range(5): - # miner_with_version(self.node, "0x0") - # # miner_until_tx_committed(self.node, proposal_txs[0]) - # self.node.getClient().get_transaction(proposal_txs[0]) - def test_send_transaction_duplicate_input_with_son_tx(self): """ Replacing the transaction will also remove the child transactions. @@ -377,31 +361,35 @@ def test_send_transaction_duplicate_input_with_son_tx(self): api_url=self.node.getClient().url, fee_rate="1000") first_tx_hash = tx_hash tx_list = [first_tx_hash] + self.Miner.miner_until_tx_committed(self.node, first_tx_hash) tx_hash = self.Tx.send_transfer_self_tx_with_input([tx_hash], ["0x0"], self.Config.ACCOUNT_PRIVATE_1, output_count=1, fee=1000, api_url=self.node.getClient().url) tx_list.append(tx_hash) self.Node.wait_get_transaction(self.node, tx_hash, 'pending') - for i in range(5): + for i in range(10): tx_hash = self.Tx.send_transfer_self_tx_with_input([tx_hash], ['0x0'], self.Config.ACCOUNT_PRIVATE_1, output_count=1, fee=1000, api_url=self.node.getClient().url) tx_list.append(tx_hash) - replace_tx_hash = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.ACCOUNT_PRIVATE_1, - account["address"]["testnet"], 360000, - api_url=self.node.getClient().url, - fee_rate="10000") + + replace_tx = self.node.getClient().get_transaction(tx_list[1]) + replace_tx_hash = self.Tx.send_transfer_self_tx_with_input([first_tx_hash], ["0x0"], + self.Config.ACCOUNT_PRIVATE_1, + output_count=1, + fee=int(replace_tx['min_replace_fee'], 16), + api_url=self.node.getClient().url) tx_pool = self.node.getClient().get_raw_tx_pool(True) assert len(tx_pool['pending']) == 1 assert replace_tx_hash in list(tx_pool['pending']) - for tx in tx_list: + for tx in tx_list[1:]: tx_response = self.node.getClient().get_transaction(tx) assert tx_response['tx_status']['status'] == "rejected" assert "RBFRejected" in tx_response['tx_status']['reason'] - def test_min_replace_fee_unchanged_with_child_tx(self): + def test_min_replace_fee_changed_with_child_tx(self): """ based on transaction A, send a child transaction. The 'min_replace_fee' of transaction A will not change, @@ -414,7 +402,7 @@ def test_min_replace_fee_unchanged_with_child_tx(self): 3. Send a child transaction of transaction A. successful 4. Query the updated 'min_replace_fee' of transaction A. - min_replace_fee unchanged + min_replace_fee changed 5.Send B to replace A. replace successful :return: @@ -432,9 +420,9 @@ def test_min_replace_fee_unchanged_with_child_tx(self): self.Tx.send_transfer_self_tx_with_input([tx_hash1], ["0x0"], self.Config.ACCOUNT_PRIVATE_1, fee=1000, api_url=self.node.getClient().url) after_transaction1 = self.node.getClient().get_transaction(tx_hash1) - assert after_transaction1['min_replace_fee'] == transaction1['min_replace_fee'] + assert after_transaction1['min_replace_fee'] != transaction1['min_replace_fee'] replace_tx_hash = self.Tx.send_transfer_self_tx_with_input([tx_hash], ["0x0"], self.Config.ACCOUNT_PRIVATE_1, - fee=int(transaction1['min_replace_fee'], 16), + fee=int(after_transaction1['min_replace_fee'], 16), api_url=self.node.getClient().url) transaction = self.node.getClient().get_transaction(replace_tx_hash) assert transaction['tx_status']['status'] == 'pending' diff --git a/test_cases/tx_pool_refactor/test_02_txs_lifecycle_chain.py b/test_cases/tx_pool_refactor/test_02_txs_lifecycle_chain.py index 84bc84e..7977873 100644 --- a/test_cases/tx_pool_refactor/test_02_txs_lifecycle_chain.py +++ b/test_cases/tx_pool_refactor/test_02_txs_lifecycle_chain.py @@ -3,16 +3,6 @@ import pytest from framework.basic import CkbTest -# from framework.config import ACCOUNT_PRIVATE_2 -# from framework.helper.ckb_cli import util_key_info_by_private_key, wallet_transfer_by_private_key -# from framework.helper.miner import make_tip_height_number, block_template_transfer_to_submit_block, miner_with_version -# from framework.helper.node import wait_get_transaction -# from framework.helper.tx import send_transfer_self_tx_with_input -# from framework.test_node import CkbNode, CkbNodeConfigPath - - -# import concurrent.futures - class TestTxsLifeCycleChain(CkbTest): @classmethod diff --git a/test_cases/tx_pool_refactor/test_07_replace_dep_father_tx.py b/test_cases/tx_pool_refactor/test_07_replace_dep_father_tx.py index bfc5970..3d4682a 100644 --- a/test_cases/tx_pool_refactor/test_07_replace_dep_father_tx.py +++ b/test_cases/tx_pool_refactor/test_07_replace_dep_father_tx.py @@ -190,7 +190,10 @@ def test_replacing_sub_tx_rejects_dependent_txs(self): fee=1090, api_url=self.node1.getClient().url) + tx11_hash = tx_hash # Send a transaction: tx21 (dep = tx11.output) + tx11_response = self.node1.getClient().get_transaction(tx11_hash) + print("before tx11 min fee:", int(tx11_response['min_replace_fee'], 16)) dep_is_tx_hash = self.Tx.send_transfer_self_tx_with_input([tx2_hash], ["0x0"], account_private2, output_count=2, fee=20900, @@ -201,9 +204,11 @@ def test_replacing_sub_tx_rejects_dependent_txs(self): ) # Send a transaction: will replace the previous tx11 + tx11_response = self.node1.getClient().get_transaction(tx11_hash) + print("after tx11 min fee:", int(tx11_response['min_replace_fee'], 16)) replace_hash = self.Tx.send_transfer_self_tx_with_input([tx1_hash], ["0x0"], account_private, output_count=2, - fee=20900, + fee=22900, api_url=self.node1.getClient().url, dep_cells=[{ "tx_hash": tx1_hash, "index_hex": "0x0" @@ -211,7 +216,6 @@ def test_replacing_sub_tx_rejects_dependent_txs(self): ) # Query pool: the previous tx11 and tx21 will be rejected - result = self.node1.getClient().get_transaction(tx_hash) assert result['tx_status']['status'] == "rejected" result = self.node1.getClient().get_transaction(dep_is_tx_hash) diff --git a/test_cases/tx_pool_refactor/test_08_many_tx.py b/test_cases/tx_pool_refactor/test_08_many_tx.py new file mode 100644 index 0000000..7b4687c --- /dev/null +++ b/test_cases/tx_pool_refactor/test_08_many_tx.py @@ -0,0 +1,206 @@ +import time + +from framework.basic import CkbTest +import concurrent.futures + + +class ManyTx(CkbTest): + + @classmethod + def setup_class(cls): + cls.current_node = cls.CkbNode.init_dev_by_port(cls.CkbNodeConfigPath.CURRENT_TEST, "tx_pool/node1", 8120, + 8225) + cls.node_111 = cls.CkbNode.init_dev_by_port(cls.CkbNodeConfigPath.V111, "tx_pool/node2", 8121, + 8226) + cls.cluster = cls.Cluster([cls.current_node, cls.node_111]) + cls.cluster.prepare_all_nodes() + cls.cluster.start_all_nodes() + cls.cluster.connected_all_nodes() + cls.Miner.make_tip_height_number(cls.current_node, 200) + cls.Node.wait_cluster_height(cls.cluster, 150, 50) + + @classmethod + def teardown_class(cls): + cls.cluster.stop_all_nodes() + cls.cluster.clean_all_nodes() + + def test_may_tx(self): + """ + 1. 给节点1 并发发送冲突子交易 + 2. 查询节点的pool池 + pending = 1 + """ + TEST_PRIVATE_1 = self.Config.MINER_PRIVATE_1 + + account = self.Ckb_cli.util_key_info_by_private_key(TEST_PRIVATE_1) + tx1 = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.MINER_PRIVATE_1, + account["address"]["testnet"], 100, + self.current_node.getClient().url, "1500") + self.Miner.miner_until_tx_committed(self.current_node, tx1) + tip_number = self.current_node.getClient().get_tip_block_number() + self.Node.wait_node_height(self.node_111, tip_number, 1000) + self.node_111.getClient().set_network_active(False) + + txs = [] + # 给节点1 并发发送 + for i in range(10): + tx = self.Tx.send_transfer_self_tx_with_input([tx1], ["0x0"], TEST_PRIVATE_1, + output_count=1, + fee=1090 + i, + api_url=self.node_111.getClient().url) + transaction = self.node_111.getClient().get_transaction(tx) + transaction = transaction['transaction'] + del transaction['hash'] + txs.insert(0, transaction) + + successfulSize = 0 + successfulTxHash = "" + with concurrent.futures.ThreadPoolExecutor() as executor: + futures = {executor.submit(self.current_node.getClient().send_transaction, tx): tx for tx in txs} + for future in concurrent.futures.as_completed(futures): + tx = futures[future] + try: + result = future.result() + # 处理返回结果 + print(f"Transaction sent successfully: {result}") + successfulSize += 1 + successfulTxHash = result + except Exception as e: + print(f"Error sending transaction: {e}") + before_pool = self.current_node.getClient().tx_pool_info() + assert successfulSize == 1 + assert before_pool['pending'] == '0x1' + assert before_pool['proposed'] == '0x0' + self.node_111.getClient().set_network_active(True) + self.node_111.connected(self.current_node) + for i in range(10): + self.Miner.miner_with_version(self.node_111, "0x0") + tip_number = self.node_111.getClient().get_tip_block_number() + self.Node.wait_node_height(self.current_node, tip_number, 1000) + pool = self.current_node.getClient().tx_pool_info() + assert pool['pending'] == '0x0' + assert pool['proposed'] == '0x0' + ret = self.current_node.getClient().get_transaction(successfulTxHash) + assert ret['tx_status']['status'] == 'rejected' + + def test_orphan_turn_pending(self): + """ + 1. 发送tx1 + 2. 发送 子交易 tx(1,10) : input = tx1.input + 3. 先转发子交易到另外一个节点 + 4. 查询另外一个节点pool池的orphan交易>0 ,pending = 0 + 5. 发送tx1到另外一个节点 + 6. 在另外一个节点查询pool 只有2笔交易 + 7. 上链 + """ + TEST_PRIVATE_1 = self.Config.MINER_PRIVATE_1 + + account = self.Ckb_cli.util_key_info_by_private_key(TEST_PRIVATE_1) + self.node_111.getClient().set_network_active(False) + tx1 = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.MINER_PRIVATE_1, + account["address"]["testnet"], 100, + self.node_111.getClient().url, "1500") + transaction1 = self.node_111.getClient().get_transaction(tx1) + transaction1 = transaction1['transaction'] + del transaction1['hash'] + txs = [] + # 给节点1 并发发送 + for i in range(10): + tx = self.Tx.send_transfer_self_tx_with_input([tx1], ["0x0"], TEST_PRIVATE_1, + output_count=1, + fee=1090 + i, + api_url=self.node_111.getClient().url) + transaction = self.node_111.getClient().get_transaction(tx) + transaction = transaction['transaction'] + del transaction['hash'] + txs.append(transaction) + self.node_111.getClient().remove_transaction(tx1) + self.node_111.getClient().set_network_active(True) + successfulSize = 0 + with concurrent.futures.ThreadPoolExecutor() as executor: + futures = {executor.submit(self.node_111.getClient().send_transaction, tx): tx for tx in txs} + for future in concurrent.futures.as_completed(futures): + tx = futures[future] + try: + result = future.result() + # 处理返回结果 + print(f"Transaction sent successfully: {result}") + successfulSize += 1 + except Exception as e: + print(f"Error sending transaction: {e}") + before_pool = self.current_node.getClient().tx_pool_info() + print(before_pool) + time.sleep(3) + before_pool = self.current_node.getClient().tx_pool_info() + assert before_pool['pending'] == '0x0' + assert before_pool['orphan'] != '0x0' + self.current_node.getClient().send_transaction(transaction1) + try: + self.node_111.getClient().send_transaction(transaction1) + except: + pass + before_pool = self.current_node.getClient().tx_pool_info() + assert before_pool['pending'] == '0x2' + + for i in range(11): + self.Miner.miner_with_version(self.node_111, "0x0") + self.Miner.miner_until_tx_committed(self.node_111, tx1, 1000) + tip_number = self.node_111.getClient().get_tip_block_number() + self.Node.wait_node_height(self.current_node, tip_number, 1000) + node111_pool = self.node_111.getClient().tx_pool_info() + assert node111_pool['pending'] == '0x0' + assert node111_pool['orphan'] == '0x0' + after_pool = self.current_node.getClient().tx_pool_info() + assert after_pool['pending'] == '0x0' + assert after_pool['orphan'] == '0x0' + + def test_dep_tx_clean(self): + """ + 1. 发送tx1 + 2. 发送tx2 + 3. 发送tx21 : input = tx2.input,dep = tx1.input + 4. 发送tx11 : input = tx1.input,dep + 5. tx11 先上链 + 6. 查询tx21的状态 + rejected + """ + TEST_PRIVATE_1 = self.Config.MINER_PRIVATE_1 + TEST_PRIVATE_2 = self.Config.ACCOUNT_PRIVATE_1 + account = self.Ckb_cli.util_key_info_by_private_key(TEST_PRIVATE_1) + account2 = self.Ckb_cli.util_key_info_by_private_key(TEST_PRIVATE_2) + # 1. 发送tx1 + tx1 = self.Ckb_cli.wallet_transfer_by_private_key(TEST_PRIVATE_1, + account["address"]["testnet"], 100, + self.current_node.getClient().url, "1500") + # 2. 发送tx2 + tx2 = self.Ckb_cli.wallet_transfer_by_private_key(TEST_PRIVATE_2, + account2["address"]["testnet"], 100, + self.current_node.getClient().url, "1500") + self.Miner.miner_until_tx_committed(self.current_node, tx1) + self.Miner.miner_until_tx_committed(self.current_node, tx2) + tip_number = self.current_node.getClient().get_tip_block_number() + self.Node.wait_node_height(self.node_111, tip_number, 1000) + # 3. 发送tx21 : input = tx2.input,dep = tx1.input + tx21_dep1 = self.Tx.send_transfer_self_tx_with_input([tx2], ["0x0"], TEST_PRIVATE_2, + output_count=1, + fee=1090, + api_url=self.current_node.getClient().url, + dep_cells=[{ + "tx_hash": tx1, "index_hex": "0x0" + }]) + + # 4. 发送tx11 : input = tx1.input,dep + tx11 = self.Tx.send_transfer_self_tx_with_input([tx1], ["0x0"], TEST_PRIVATE_1, + output_count=1, + fee=1090, + api_url=self.current_node.getClient().url) + # 5. tx11 先上链 + self.Node.wait_get_transaction(self.node_111, tx21_dep1, 'pending') + self.Node.wait_get_transaction(self.node_111, tx11, 'pending') + self.node_111.getClient().remove_transaction(tx21_dep1) + for i in range(10): + self.Miner.miner_with_version(self.node_111, "0x0") + pool = self.current_node.getClient().tx_pool_info() + # 6. 查询tx21的状态 + ret = self.current_node.getClient().get_transaction(tx21_dep1) + assert ret['tx_status']['status'] == 'rejected' diff --git a/test_cases/tx_pool_refactor/test_09_child_pay_for_parent.py b/test_cases/tx_pool_refactor/test_09_child_pay_for_parent.py new file mode 100644 index 0000000..95614d4 --- /dev/null +++ b/test_cases/tx_pool_refactor/test_09_child_pay_for_parent.py @@ -0,0 +1,55 @@ +import pytest + +from framework.basic import CkbTest + + +class TestRbfChildPayForParent(CkbTest): + + @classmethod + def setup_class(cls): + cls.node = cls.CkbNode.init_dev_by_port(cls.CkbNodeConfigPath.CURRENT_TEST, "tx_pool/node1", 8120, + 8225) + cls.node.prepare() + cls.node.start() + cls.Miner.make_tip_height_number(cls.node, 30) + + @classmethod + def teardown_class(cls): + cls.node.stop() + cls.node.clean() + + def test_child_big_fee(self): + """ + 1. send tx1-1(fee:1000) + 2. send tx1-1-1(fee:10000) + 3. send tx1-2(fee:2000) + return error + """ + # 1. send tx1 + account = self.Ckb_cli.util_key_info_by_private_key(self.Config.ACCOUNT_PRIVATE_1) + tx1_hash = self.Ckb_cli.wallet_transfer_by_private_key(self.Config.ACCOUNT_PRIVATE_1, + account["address"]["testnet"], 100, + self.node.getClient().url, "1500") + print("send tx1_hash:", tx1_hash) + # 1. send tx11(fee:1000) + tx11_hash = self.Tx.send_transfer_self_tx_with_input([tx1_hash], ["0x0"], self.Config.ACCOUNT_PRIVATE_1, + fee=1000, output_count=1, + api_url=self.node.getClient().url) + print("send tx11_hash:", tx11_hash) + + # 2. send tx 111(fee:10000) + tx111_hash = self.Tx.send_transfer_self_tx_with_input([tx11_hash], ["0x0"], self.Config.ACCOUNT_PRIVATE_1, + fee=10000000, + api_url=self.node.getClient().url) + print("send tx111_hash:", tx111_hash) + + # 3. send tx 12(fee:2000) + with pytest.raises(Exception) as exc_info: + tx12_hash = self.Tx.send_transfer_self_tx_with_input([tx1_hash], ["0x0"], self.Config.ACCOUNT_PRIVATE_1, + fee=2000, + api_url=self.node.getClient().url) + expected_error_message = "Server error: PoolRejectedRBF: RBF rejected: Tx's current fee is 2000, expect it to " \ + ">= 10001532 to replace old txs" + 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/tx_pool_refactor/test_10_issues.py b/test_cases/tx_pool_refactor/test_10_issues.py new file mode 100644 index 0000000..21cb15f --- /dev/null +++ b/test_cases/tx_pool_refactor/test_10_issues.py @@ -0,0 +1,119 @@ +import pytest + +from framework.basic import CkbTest + + +class TestIssues(CkbTest): + + @classmethod + def setup_class(cls): + cls.node1 = cls.CkbNode.init_dev_by_port(cls.CkbNodeConfigPath.CURRENT_TEST, "tx_pool/node1", 8120, + 8225) + cls.node1.prepare() + cls.node1.start() + cls.Miner.make_tip_height_number(cls.node1, 30) + + @classmethod + def teardown_class(cls): + cls.node1.stop() + cls.node1.clean() + + @pytest.mark.skip + def test_4315(self): + """ + 1. 发送 tx1 + 2. 发送 tx(1000): dep(tx1.output) + 3. 发送消费 tx1.output + 4. 发送 依赖tx1.output + """ + # 1. 发送 tx1 + + account = self.Ckb_cli.util_key_info_by_private_key(self.Config.ACCOUNT_PRIVATE_2) + account_private = self.Config.ACCOUNT_PRIVATE_2 + tx1_hash = self.Ckb_cli.wallet_transfer_by_private_key(account_private, + account["address"]["testnet"], 10000000, + self.node1.getClient().url, "1500000") + tx11_hash = self.Tx.send_transfer_self_tx_with_input([tx1_hash], ["0x0"], account_private, + output_count=20, + fee=10090, + api_url=self.node1.getClient().url, dep_cells={ + + }) + self.Miner.miner_until_tx_committed(self.node1, tx11_hash) + # 2. 发送 tx(1000): dep(tx1.output) + for j in range(10): + tx_hash = self.Tx.send_transfer_self_tx_with_input([tx11_hash], [hex(j)], account_private, + output_count=1, + fee=1090, + api_url=self.node1.getClient().url, + dep_cells=[ + {"tx_hash": tx11_hash, "index_hex": hex(19)}]) + for i in range(20): + print("curent i:", i, "curent j:", j) + tx_hash = self.Tx.send_transfer_self_tx_with_input([tx_hash], ["0x0"], account_private, + output_count=1, + fee=1090, + api_url=self.node1.getClient().url, + dep_cells=[ + {"tx_hash": tx11_hash, "index_hex": hex(19)}]) + + # 3. 发送消费 tx1.output + tx_hash = self.Tx.send_transfer_self_tx_with_input([tx11_hash], [hex(19)], account_private, + output_count=1, + fee=1090000, + api_url=self.node1.getClient().url, dep_cells=[]) + # 发送 依赖tx1.output + with pytest.raises(Exception) as exc_info: + tx_hash = self.Tx.send_transfer_self_tx_with_input([tx11_hash], [hex(11)], account_private, + output_count=1, + fee=1090, + api_url=self.node1.getClient().url, + dep_cells=[ + {"tx_hash": tx11_hash, "index_hex": hex(19)}]) + expected_error_message = "Resolve failed Dead" + 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_4306(self): + """ + 老的交易串 A -> B -> C ,新发一个交易 D,但是交易 D cell dep C,那么 RBF 不会成功 + 之前的代码逻辑只是检查了 D 是否 cell dep A,应该检查所有的 desendants + 1. 发送 tx1 + 2. 发送 tx11 + 3. 发送 tx111 + 4. 发送 tx12(cellDep = tx111.output ) + """ + # 1. 发送 tx1 + 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, "1500000") + + # 2. Send a sub transaction: tx11 + tx11_hash = self.Tx.send_transfer_self_tx_with_input([tx1_hash], ["0x0"], account_private, + output_count=2, + fee=1090, + api_url=self.node1.getClient().url) + + # 3. 发送 tx111 + tx111_hash = self.Tx.send_transfer_self_tx_with_input([tx11_hash], ["0x0"], account_private, + output_count=2, + fee=1090, + api_url=self.node1.getClient().url) + # 4. 发送 tx12(cellDep = tx111.output ) + with pytest.raises(Exception) as exc_info: + + replace_hash = self.Tx.send_transfer_self_tx_with_input([tx1_hash], ["0x0"], account_private, + output_count=2, + fee=20900, + api_url=self.node1.getClient().url, + dep_cells=[{ + "tx_hash": tx111_hash, "index_hex": "0x1" + }] + ) + expected_error_message = "RBF rejected: new Tx contains cell deps from conflicts" + 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]}'" \ No newline at end of file