diff --git a/framework/helper/ckb_cli.py b/framework/helper/ckb_cli.py index bf5661a..0f6da3b 100644 --- a/framework/helper/ckb_cli.py +++ b/framework/helper/ckb_cli.py @@ -306,6 +306,28 @@ def util_key_info_by_private_key(account_private): return json.loads(run_command(cmd)) +def molecule_encode(json_map, type): + """ + cat demo.json + {"args": "0x8883a512ee2383c01574a328f60eeccbb4d78240", "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", "hash_type": "type"} + ckb-cli molecule encode --type Script --json-path demo.json --local-only + 0x490000001000000030000000310000009bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce801140000008883a512ee2383c01574a328f60eeccbb4d78240 + + Args: + json_map: + type: + + Returns: + + """ + json_map = json.dumps(json_map) + with open("/tmp/tmp.json", "w") as f: + f.write(json_map) + cmd = f"{cli_path} molecule encode --type {type} --json-path /tmp/tmp.json --local-only" + ret = run_command(cmd) + return ret.replace("\n", "").replace(" ", "") + + def tx_init(tx_file, api_url="http://127.0.0.1:8114"): """ ./ckb-cli tx init --tx-file tx.txt diff --git a/framework/rpc.py b/framework/rpc.py index de2c90e..ff59f6b 100644 --- a/framework/rpc.py +++ b/framework/rpc.py @@ -50,6 +50,9 @@ def local_node_info(self): def ping_peers(self): return self.call("ping_peers", []) + def ipc_call(self, ipc_script_locator, ipc_req, ipc_env=None): + return self.call("ipc_call", [ipc_script_locator, ipc_req, ipc_env]) + def remove_node(self, peer_id): return self.call("remove_node", [peer_id]) @@ -97,7 +100,9 @@ def get_fee_rate_statics(self, target=None): return self.call("get_fee_rate_statics", [target]) def generate_epochs(self, epoch): - return self.call("generate_epochs", [epoch]) + response = self.call("generate_epochs", [epoch]) + time.sleep(3) + return response def generate_block(self): return self.call("generate_block", []) diff --git a/framework/test_node.py b/framework/test_node.py index c1f3f08..52781f8 100644 --- a/framework/test_node.py +++ b/framework/test_node.py @@ -18,7 +18,7 @@ class CkbNodeConfigPath(Enum): "source/template/ckb/v120/ckb.toml.j2", "source/template/ckb/v120/ckb-miner.toml.j2", "source/template/ckb/v120/specs/dev.toml", - "download/0.120.0", + "download/0.202.0", ) TESTNET = ( "source/template/ckb/v120/ckb.toml.j2", @@ -262,6 +262,15 @@ def start(self): # //todo replace by rpc time.sleep(3) + def start_without_indexer(self): + self.ckb_pid = run_command( + "cd {ckb_dir} && ./ckb run --skip-spec-check > node.log 2>&1 &".format( + ckb_dir=self.ckb_dir + ) + ) + # //todo replace by rpc + time.sleep(3) + def startWithRichIndexer(self): """ support richIndexer @@ -320,7 +329,7 @@ def prepare( root_path=get_project_root(), spec_path=self.ckb_config_path.ckb_spec_path, ), - self.ckb_dir, + "{dir}/dev.toml".format(dir=self.ckb_dir), ) shutil.copy( diff --git a/source/contract/ipc_test/ipc_test b/source/contract/ipc_test/ipc_test new file mode 100755 index 0000000..9d7d861 Binary files /dev/null and b/source/contract/ipc_test/ipc_test differ diff --git a/source/contract/ipc_test/ipc_test.rs b/source/contract/ipc_test/ipc_test.rs new file mode 100644 index 0000000..540e9a3 --- /dev/null +++ b/source/contract/ipc_test/ipc_test.rs @@ -0,0 +1,263 @@ +#![no_main] +#![no_std] + +use ckb_std::ckb_constants::Source; +use alloc::string::{String, ToString}; +use alloc::vec; +use alloc::vec::Vec; +use ckb_std::ckb_types::prelude::Entity; +use ckb_std::high_level::{load_cell, load_cell_capacity, load_cell_data, load_input, load_script, load_script_hash, load_transaction, load_tx_hash}; +use ckb_std::syscalls::{current_cycles, exec, load_block_extension, vm_version}; +use serde::{Deserialize, Serialize}; + +ckb_std::entry!(main); +ckb_std::default_alloc!(); + +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +pub struct BoundaryStruct { + pub usize_data: usize, + pub u128_data: u128, + pub u64_data: u64, + pub u32_data: u32, + pub u16_data: u16, + pub u8_data: u8, + pub isize_data: isize, + pub i128_data: i128, + pub i64_data: i64, + pub i32_data: i32, + pub i16_data: i16, + pub i8_data: i8, + pub bool_data: bool, + pub char_data: char, + pub f32_data: f32, + pub f64_data: f64, + pub str_data: String, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +pub struct CkbOpcode { + pub vm_version: u64, + load_tx_hash: [u8; 32], + load_script_hash: [u8; 32], + load_cell: Vec, + load_input: Vec, + load_transaction: Vec, + load_cell_capacity: u64, + load_script: Vec, + load_cell_data: Vec, + load_block_extension: Vec, +} + +impl CkbOpcode { + fn new() -> Self { + CkbOpcode { + vm_version: 0, + load_tx_hash: [0; 32], + load_script_hash: [0; 32], + load_cell: vec![], + load_input: vec![], + load_transaction: vec![], + load_cell_capacity: 0, + load_script: vec![], + load_cell_data: vec![], + load_block_extension: vec![], + } + } +} + +impl BoundaryStruct { + fn new() -> Self { + BoundaryStruct { + usize_data: 0, + u128_data: 0, + u64_data: 0, + u32_data: 0, + u16_data: 0, + u8_data: 0, + isize_data: 0, + i128_data: 0, + i64_data: 0, + i32_data: 0, + i16_data: 0, + i8_data: 0, + bool_data: false, + char_data: 'a', + f32_data: 0.0, + f64_data: 0.0, + str_data: "".to_string(), + } + } + + pub fn min_value() -> Self { + BoundaryStruct { + usize_data: usize::MIN, + u128_data: u128::MIN, + u64_data: u64::MIN, + u32_data: u32::MIN, + u16_data: u16::MIN, + u8_data: u8::MIN, + isize_data: isize::MIN, + i128_data: i128::MIN, + i64_data: i64::MIN, + i32_data: i32::MIN, + i16_data: i16::MIN, + i8_data: i8::MIN, + bool_data: false, + char_data: ' ', + f32_data: f32::MIN, + f64_data: f64::MIN, + str_data: "".to_string(), + } + } + + pub fn max_value() -> Self { + BoundaryStruct { + usize_data: usize::MAX, + u128_data: u128::MAX, + u64_data: u64::MAX, + u32_data: u32::MAX, + u16_data: u16::MAX, + u8_data: u8::MAX, + isize_data: 0, + i128_data: i128::MAX, + i64_data: i64::MAX, + i32_data: i32::MAX, + i16_data: i16::MAX, + i8_data: i8::MAX, + bool_data: true, + char_data: '0', + f32_data: f32::MAX, + f64_data: f64::MAX, + str_data: "max".to_string(), + } + } +} + + +#[ckb_script_ipc::service] +pub trait IpcTest { + fn math_add(a: u64, b: u64) -> u64; + fn spawn(s: String) -> String; + fn syscall_load_script() -> Vec; + fn test_boundary_struct(vec: Vec) -> Vec; + fn test_vec(vec: Vec) -> Vec; + fn test_input_vec(vec: Vec) -> usize; + fn test_mem(byte_data: usize, kb_data: usize, mb_data: usize) -> Vec; + fn test_cycle(cycle_limit: u64) -> usize; + fn test_input_payload(s: String) -> usize; + fn test_ckb_call() -> CkbOpcode; + fn test_empty(); + fn test_current_cycle()->u64; +} + +struct IpcTestServer {} + +impl IpcTest for IpcTestServer { + fn math_add(&mut self, a: u64, b: u64) -> u64 { + a.checked_add(b).unwrap() + } + + fn spawn(&mut self, s: String) -> String { + let argc: u64 = 0; + let argv = []; + let mut std_fds: [u64; 2] = [0, 0]; + let mut son_fds: [u64; 3] = [0, 0, 0]; + let (r, w) = ckb_std::syscalls::pipe().unwrap(); + std_fds[0] = r; + son_fds[1] = w; + let (r, w) = ckb_std::syscalls::pipe().unwrap(); + std_fds[1] = w; + son_fds[0] = r; + let mut pid: u64 = 0; + let mut spgs = ckb_std::syscalls::SpawnArgs { + argc, + argv: argv.as_ptr() as *const *const i8, + process_id: &mut pid as *mut u64, + inherited_fds: son_fds.as_ptr(), + }; + ckb_std::syscalls::spawn(0, ckb_std::ckb_constants::Source::CellDep, 0, 0, &mut spgs) + .unwrap(); + ckb_std::syscalls::write(std_fds[1], s.as_bytes()).unwrap(); + ckb_std::syscalls::close(std_fds[1]).unwrap(); + let mut buf = [0; 256]; + let buf_len = ckb_std::syscalls::read(std_fds[0], &mut buf).unwrap(); + String::from_utf8_lossy(&buf[..buf_len]).to_string() + } + + fn syscall_load_script(&mut self) -> Vec { + ckb_std::high_level::load_script() + .unwrap() + .as_bytes() + .into() + } + + fn test_boundary_struct(&mut self, vec: Vec) -> Vec { + if vec.len() == 0 { + return vec![BoundaryStruct::max_value(), BoundaryStruct::min_value()]; + } + return vec; + } + fn test_vec(&mut self, vec: Vec) -> Vec { + return vec; + } + + fn test_input_vec(&mut self, vec: Vec) -> usize { + return vec.len(); + } + + fn test_cycle(&mut self, cycle_limit: u64) -> usize { + let mut sum = 0; + while current_cycles() < cycle_limit { + sum += 1; + } + return current_cycles() as usize; + } + + fn test_mem(&mut self, byte_data: usize, kb_data: usize, mb_data: usize) -> Vec { + let total_bytes = byte_data + (kb_data * 1024) + (mb_data * 1024 * 1024); + vec![0; total_bytes] + } + + fn test_input_payload(&mut self, s: String) -> usize { + return s.len(); + } + + fn test_ckb_call(&mut self) -> CkbOpcode { + return get_block_opcode(); + } + + fn test_empty(&mut self) -> () { + // Do nothing + } + + fn test_current_cycle(&mut self) -> u64 { + current_cycles() + } + +} + +fn get_block_opcode() -> CkbOpcode { + let mut ckbOpcode = CkbOpcode::new(); + ckbOpcode.vm_version = vm_version().unwrap(); + ckbOpcode.load_tx_hash = load_tx_hash().unwrap(); + ckbOpcode.load_script_hash = load_script_hash().unwrap(); + ckbOpcode.load_cell = load_cell(0, Source::Output).unwrap().as_slice().to_vec(); + ckbOpcode.load_input = load_input(0, Source::Input).unwrap().as_slice().to_vec(); + ckbOpcode.load_transaction = load_transaction().unwrap().as_slice().to_vec(); + ckbOpcode.load_cell_capacity = load_cell_capacity(0, Source::Input).unwrap(); + ckbOpcode.load_script = load_script().unwrap().as_slice().to_vec(); + ckbOpcode.load_cell_data = load_cell_data(0, Source::Input).unwrap(); + let mut data = [0u8; 100]; + let result = load_block_extension(&mut data, 0, 0, Source::CellDep).unwrap(); + ckbOpcode.load_block_extension = data.to_vec(); + + return ckbOpcode; +} + + +fn main() -> i8 { + ckb_script_ipc_common::spawn::run_server(IpcTestServer {}.server()).unwrap(); + return 0; +} + + diff --git a/source/contract/ipc_test/ipc_test_with_exec b/source/contract/ipc_test/ipc_test_with_exec new file mode 100755 index 0000000..c0c7bea Binary files /dev/null and b/source/contract/ipc_test/ipc_test_with_exec differ diff --git a/source/contract/ipc_test/ipc_test_with_exec.rs b/source/contract/ipc_test/ipc_test_with_exec.rs new file mode 100644 index 0000000..0a9af2b --- /dev/null +++ b/source/contract/ipc_test/ipc_test_with_exec.rs @@ -0,0 +1,10 @@ +#![no_main] +#![no_std] + +ckb_std::entry!(main); +ckb_std::default_alloc!(); + +fn main() -> i8 { + ckb_std::syscalls::exec(0, ckb_std::ckb_constants::Source::CellDep, 0, 0, &[]); + return 0; +} diff --git a/source/contract/ipc_test/ipc_test_with_spawn b/source/contract/ipc_test/ipc_test_with_spawn new file mode 100755 index 0000000..be774c1 Binary files /dev/null and b/source/contract/ipc_test/ipc_test_with_spawn differ diff --git a/source/contract/ipc_test/ipc_test_with_spawn.rs b/source/contract/ipc_test/ipc_test_with_spawn.rs new file mode 100644 index 0000000..ce5bfea --- /dev/null +++ b/source/contract/ipc_test/ipc_test_with_spawn.rs @@ -0,0 +1,15 @@ +#![no_main] +#![no_std] + +ckb_std::entry!(main); +ckb_std::default_alloc!(); + +fn main() -> i8 { + let mut std_fds: [u64; 2] = [0; 2]; + ckb_std::syscalls::inherited_fds(&mut std_fds); + let mut buf = [0; 256]; + let buf_len = ckb_std::syscalls::read(std_fds[0], &mut buf).unwrap(); + ckb_std::syscalls::write(std_fds[1], &buf[..buf_len]).unwrap(); + ckb_std::syscalls::close(std_fds[1]).unwrap(); + return 0; +} diff --git a/test_cases/ipc_call/test_ipc_call.py b/test_cases/ipc_call/test_ipc_call.py new file mode 100644 index 0000000..1894bd4 --- /dev/null +++ b/test_cases/ipc_call/test_ipc_call.py @@ -0,0 +1,1206 @@ +import hashlib +import json +import random +import string +import time + +import pytest + +from framework.basic import CkbTest +from framework.helper.udt_contract import UdtContract +from framework.util import get_project_root + + +class TestIpcCall(CkbTest): + + @classmethod + def setup_class(cls): + """ + 1. start node in issue/node1 + 2. generate 2 epoch + Returns: + + """ + + # 1. start node in issue/node1 + node1 = cls.CkbNode.init_dev_by_port( + cls.CkbNodeConfigPath.CURRENT_TEST, "ipc_call/node1", 8114, 8927 + ) + cls.node = node1 + node1.prepare( + other_ckb_config={ + "ckb_rpc_modules": [ + "Net", + "Pool", + "Miner", + "Chain", + "Stats", + "Subscription", + "Experiment", + "Debug", + "IntegrationTest", + "IPC", + ] + } + ) + node1.start() + + # 2. miner 400 block + # cls.Miner.make_tip_height_number(cls.node, 400) + cls.node.getClient().generate_epochs("0x2") + time.sleep(3) + cls.ipc_test_contract_tx_hash = cls.Contract.deploy_ckb_contract( + cls.Config.ACCOUNT_PRIVATE_1, + f"{get_project_root()}/source/contract/ipc_test/ipc_test", + ) + cls.Miner.miner_until_tx_committed(cls.node, cls.ipc_test_contract_tx_hash) + + cls.ipc_test_with_exec_contract_tx_hash = cls.Contract.deploy_ckb_contract( + cls.Config.ACCOUNT_PRIVATE_1, + f"{get_project_root()}/source/contract/ipc_test/ipc_test_with_exec", + ) + cls.Miner.miner_until_tx_committed( + cls.node, cls.ipc_test_with_exec_contract_tx_hash + ) + + cls.ipc_test_with_spawn_contract_tx_hash = cls.Contract.deploy_ckb_contract( + cls.Config.ACCOUNT_PRIVATE_1, + f"{get_project_root()}/source/contract/ipc_test/ipc_test_with_spawn", + ) + cls.Miner.miner_until_tx_committed( + cls.node, cls.ipc_test_with_spawn_contract_tx_hash + ) + + @classmethod + def teardown_class(cls): + print("\nTeardown TestClass1") + cls.node.stop() + cls.node.clean() + + def test_not_spawn_serve(self): + """ + 存在的out_point,但不是spawn serve 合约,预期: 执行失败 + 不存在的out_point,预期:失败 + Returns: + """ + + block = self.node.getClient().get_block_by_number("0x0") + ipc_script_locator = { + "out_point": {"index": "0x0", "tx_hash": block["transactions"][0]["hash"]} + } + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "json", + "payload": { + "MathAdd": {"a": 2, "b": 1}, + }, + } + + with pytest.raises(Exception) as exc_info: + ipc_ret = self.node.getClient().ipc_call(ipc_script_locator, ipc_req) + expected_error_message = "sending on a closed channel" + 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]}'" + + # 不存在的out_point + ipc_script_locator = { + "out_point": { + "index": "0x0", + "tx_hash": "0x000867a5e09eebdedecaa0437455e54d99f9c6752adad1fd299bd6ede303f461", + } + } + with pytest.raises(Exception) as exc_info: + ipc_ret = self.node.getClient().ipc_call(ipc_script_locator, ipc_req) + expected_error_message = "Get out point failed: OutPoint" + 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_type_script_args_not_spawn_contract(self): + """ + 存在的 type script args,但不是spawn serve 合约,预期: 执行失败 + + Returns: + """ + udtContract = UdtContract() + udtContract.deploy(self.Config.ACCOUNT_PRIVATE_1, self.node) + deploy_hash, deploy_index = udtContract.get_deploy_hash_and_index() + ipc_script_locator = { + "out_point": {"index": hex(deploy_index), "tx_hash": deploy_hash} + } + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "json", + "payload": { + "MathAdd": {"a": 2, "b": 1}, + }, + } + + with pytest.raises(Exception) as exc_info: + ipc_ret = self.node.getClient().ipc_call(ipc_script_locator, ipc_req) + expected_error_message = "sending on a closed channel" + 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_death_cell(self): + """ + 已经被消费的out_point,但是spawn serve 合约,调用serve方法,预期: 成功 + Returns: + """ + rand_account_private = ( + "0x000c06bfd800d27397002dca6fb0993d5ba6399b4238b2f29ee9deb97593d2bc" + ) + rand_account = self.Ckb_cli.util_key_info_by_private_key(rand_account_private) + ipc_test_data_contract_tx_hash = self.Contract.deploy_ckb_contract( + self.Config.ACCOUNT_PRIVATE_1, + f"{get_project_root()}/source/contract/ipc_test/ipc_test", + 2000, + False, + self.node.getClient().url, + rand_account["address"]["testnet"], + ) + self.Miner.miner_until_tx_committed(self.node, ipc_test_data_contract_tx_hash) + # deploy math contract + ipc_script_locator = { + "out_point": {"index": "0x0", "tx_hash": ipc_test_data_contract_tx_hash} + } + + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "json", + "payload": { + "MathAdd": {"a": 2, "b": 1}, + }, + } + print("first ipc") + live_cell = self.node.getClient().get_live_cell( + "0x0", ipc_test_data_contract_tx_hash + ) + print("live_cell:", live_cell) + ipc_ret = self.node.getClient().ipc_call(ipc_script_locator, ipc_req) + print(f"main1: call result json={ipc_ret}") + assert ipc_ret["payload"]["MathAdd"] == 3 + + # cost ipc_test_data_contract_tx_hash + tx_hash = self.Tx.send_transfer_self_tx_with_input( + [ipc_test_data_contract_tx_hash], + ["0x0"], + rand_account_private, + output_count=1, + fee=1090, + api_url=self.node.getClient().url, + ) + self.Miner.miner_until_tx_committed(self.node, tx_hash) + live_cell = self.node.getClient().get_live_cell( + "0x0", ipc_test_data_contract_tx_hash + ) + print("live_cell:", live_cell) + ipc_ret = self.node.getClient().ipc_call(ipc_script_locator, ipc_req) + print(f"main: call result json={ipc_ret}") + + def test_call_payload_empty(self): + """ + 测试空 payload,预期:失败,IPC: EOF while parsing a value at line 1 column 0 + + Returns: + + """ + # deploy math contract + ipc_script_locator = { + "out_point": {"index": "0x0", "tx_hash": self.ipc_test_contract_tx_hash} + } + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "json", + "payload": { + # "MathAdd": {"a": 2, "b": 1}, + }, + } + + with pytest.raises(Exception) as exc_info: + ipc_ret = self.node.getClient().ipc_call(ipc_script_locator, ipc_req) + expected_error_message = "IPC: EOF while parsing a value at line 1 column 0" + 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_call_math_add(self): + """ + 存在的out_point,并且是spawn serve 合约,调用serve方法,预期: 执行成功 + 测试默认值(0),预期: 执行成功 + "json" 模式下传递 JSON 对象 + Returns: + + """ + # deploy math contract + ipc_script_locator = { + "out_point": {"index": "0x0", "tx_hash": self.ipc_test_contract_tx_hash} + } + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "json", + "payload": { + "MathAdd": {"a": 2, "b": 1}, + }, + } + ipc_ret = self.node.getClient().ipc_call(ipc_script_locator, ipc_req) + print(f"main: call result json={ipc_ret}") + assert ipc_ret["payload"]["MathAdd"] == 3 + + def test_IpcRequest_version_and_method_id_not_eq_0x(self): + """ + version 测试不存在的版本号(如 999),预期报错或 fallback + method_id 填写任意值都可以,预期:成功 + Returns: + + """ + # deploy math contract + ipc_script_locator = { + "out_point": {"index": "0x0", "tx_hash": self.ipc_test_contract_tx_hash} + } + ipc_req = { + "version": "0xff", + "method_id": "0x1213", + "payload_format": "json", + "payload": { + "MathAdd": {"a": 2, "b": 1}, + }, + } + ipc_ret = self.node.getClient().ipc_call(ipc_script_locator, ipc_req) + print(f"main: call result json={ipc_ret}") + assert ipc_ret["payload"]["MathAdd"] == 3 + + def test_math_add_with_exec(self): + """ + exec 调用spawn server + lock 合约,预期:成功 + 存在匹配的script_hash,预期:成功 + 通过校验的tx,预期:成功 + 调用合约执行exec opcode,预期:成功 + Returns: + + """ + account = self.Ckb_cli.util_key_info_by_private_key( + self.Config.ACCOUNT_PRIVATE_1 + ) + father_tx_hash = self.Ckb_cli.wallet_transfer_by_private_key( + self.Config.ACCOUNT_PRIVATE_1, + account["address"]["testnet"], + 100000, + self.node.getClient().url, + ) + self.Miner.miner_until_tx_committed(self.node, father_tx_hash) + + tx = self.Tx.build_send_transfer_self_tx_with_input( + [father_tx_hash], + ["0x0"], + self.Config.ACCOUNT_PRIVATE_1, + output_count=15, + fee=15000, + api_url=self.node.getClient().url, + dep_cells=[ + {"tx_hash": self.ipc_test_contract_tx_hash, "index_hex": hex(0)}, + ], + ) + result = self.node.getClient().test_tx_pool_accept(tx, "passthrough") + print("result:", result) + # + ipc_script_locator = { + "out_point": { + "index": "0x0", + "tx_hash": self.ipc_test_with_exec_contract_tx_hash, + }, + } + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "json", + "payload": { + "MathAdd": {"a": 2, "b": 1}, + }, + } + ipc_env = { + "tx": tx, + "script_group_type": "lock", + "script_hash": account["lock_hash"], + } + ipc_call_result = self.node.getClient().ipc_call( + ipc_script_locator, ipc_req, ipc_env + ) + print("ipc_call_result:", ipc_call_result) + assert ipc_call_result["payload"]["MathAdd"] == 3 + + def test_math_add_with_spawn(self): + """ + 调用合约执行 spawn opcode + Returns: + + """ + account = self.Ckb_cli.util_key_info_by_private_key( + self.Config.ACCOUNT_PRIVATE_1 + ) + father_tx_hash = self.Ckb_cli.wallet_transfer_by_private_key( + self.Config.ACCOUNT_PRIVATE_1, + account["address"]["testnet"], + 100000, + self.node.getClient().url, + ) + self.Miner.miner_until_tx_committed(self.node, father_tx_hash) + + tx = self.Tx.build_send_transfer_self_tx_with_input( + [father_tx_hash], + ["0x0"], + self.Config.ACCOUNT_PRIVATE_1, + output_count=15, + fee=15000, + api_url=self.node.getClient().url, + dep_cells=[ + { + "tx_hash": self.ipc_test_with_spawn_contract_tx_hash, + "index_hex": hex(0), + }, + ], + ) + result = self.node.getClient().test_tx_pool_accept(tx, "passthrough") + print("result:", result) + # + ipc_script_locator = { + "out_point": {"index": "0x0", "tx_hash": self.ipc_test_contract_tx_hash}, + } + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "json", + "payload": { + "Spawn": {"s": "Hello"}, + }, + } + ipc_env = { + "tx": tx, + "script_group_type": "lock", + "script_hash": account["lock_hash"], + } + ipc_call_result = self.node.getClient().ipc_call( + ipc_script_locator, ipc_req, ipc_env + ) + print("ipc_call_result:", ipc_call_result) + assert ipc_call_result["payload"]["Spawn"] == "Hello" + + def test_math_add_with_hex(self): + """ + "hex" 模式下传递有效 hex 字符串 + Returns: + + """ + ipc_script_locator = { + "out_point": {"index": "0x0", "tx_hash": self.ipc_test_contract_tx_hash}, + } + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "hex", + "payload": "0x" + + bytearray( + json.dumps( + { + "MathAdd": {"a": 2, "b": 1}, + } + ).encode() + ).hex(), + } + ipc_ret = self.node.getClient().ipc_call(ipc_script_locator, ipc_req) + print(f"main: call result data={ipc_ret}") + assert ( + json.loads(bytearray.fromhex(ipc_ret["payload"][2:]).decode())["MathAdd"] + == 3 + ) + + def test_math_add_with_xml(self): + """ + 错误的 payload_format(如 "xml")应返回错误:unknown variant `xml` + Returns: + + """ + ipc_script_locator = { + "out_point": {"index": "0x0", "tx_hash": self.ipc_test_contract_tx_hash}, + } + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "xml", + "payload": "0x" + + bytearray( + json.dumps( + { + "MathAdd": {"a": 2, "b": 1}, + } + ).encode() + ).hex(), + } + + with pytest.raises(Exception) as exc_info: + ipc_ret = self.node.getClient().ipc_call(ipc_script_locator, ipc_req) + expected_error_message = "unknown variant `xml`" + 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_math_add_with_type_id_args(self): + """ + 存在的type script args,并且是spawn serve 合约,预期:执行成功 + Returns: + + """ + tx = self.node.getClient().get_transaction(self.ipc_test_contract_tx_hash) + ipc_script_locator = { + "type_id_args": tx["transaction"]["outputs"][0]["type"]["args"], + } + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "json", + "payload": { + "MathAdd": {"a": 2, "b": 1}, + }, + } + ipc_ret = self.node.getClient().ipc_call(ipc_script_locator, ipc_req) + print(f"main: call result json={ipc_ret}") + assert ipc_ret["payload"]["MathAdd"] == 3 + + def test_test_call_syscall_load_script(self): + """ + syscall_load_script + Returns: + + """ + tx = self.node.getClient().get_transaction(self.ipc_test_contract_tx_hash) + ipc_script_locator = { + "type_id_args": tx["transaction"]["outputs"][0]["type"]["args"], + } + + ipc_script_locator = { + "out_point": {"index": "0x0", "tx_hash": self.ipc_test_contract_tx_hash} + } + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "json", + "payload": { + "SyscallLoadScript": {}, + }, + } + ipc_ret = self.node.getClient().ipc_call(ipc_script_locator, ipc_req) + + # https://explorer.nervos.org/address/ckb1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqp29j5z9tay5gqgqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqj34x52 + assert ( + bytearray(ipc_ret["payload"]["SyscallLoadScript"]).hex() + == "35000000100000003000000031000000b95123c71a870e3f0f74a7ee1dab8268dbfbc1407b46733ebd1b41f854b4324a0100000000" + ) + + def test_test_call_syscall_load_script_with_env(self): + """ + syscall_load_script + Returns: + + """ + account = self.Ckb_cli.util_key_info_by_private_key( + self.Config.ACCOUNT_PRIVATE_1 + ) + father_tx_hash = self.Ckb_cli.wallet_transfer_by_private_key( + self.Config.ACCOUNT_PRIVATE_1, + account["address"]["testnet"], + 100000, + self.node.getClient().url, + ) + self.Miner.miner_until_tx_committed(self.node, father_tx_hash) + + tx = self.Tx.build_send_transfer_self_tx_with_input( + [father_tx_hash], + ["0x0"], + self.Config.ACCOUNT_PRIVATE_1, + output_count=15, + fee=15000, + api_url=self.node.getClient().url, + dep_cells=[ + { + "tx_hash": self.ipc_test_with_spawn_contract_tx_hash, + "index_hex": hex(0), + }, + ], + ) + result = self.node.getClient().test_tx_pool_accept(tx, "passthrough") + print("result:", result) + # + ipc_script_locator = { + "out_point": {"index": "0x0", "tx_hash": self.ipc_test_contract_tx_hash}, + } + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "json", + "payload": { + "SyscallLoadScript": {}, + }, + } + ipc_env = { + "tx": tx, + "script_group_type": "lock", + "script_hash": account["lock_hash"], + } + ipc_call_result = self.node.getClient().ipc_call( + ipc_script_locator, ipc_req, ipc_env + ) + assert ( + f"490000001000000030000000310000009bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce80114000000{account['lock_arg'][2:]}" + == bytearray(ipc_call_result["payload"]["SyscallLoadScript"]).hex() + ) + + def test_type_id_args_not_exist(self): + """ + 不存在的type script args,预期:执行失败 + Returns: + """ + ipc_script_locator = { + "type_id_args": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", + } + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "json", + "payload": { + "MathAdd": {"a": 2, "b": 1}, + }, + } + + with pytest.raises(Exception) as exc_info: + ipc_ret = self.node.getClient().ipc_call(ipc_script_locator, ipc_req) + # todo add expected_error_message + expected_error_message = "Get type id args failed" + 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_mix_payload(self): + """ + 复杂 payload,含多层嵌套的结构或大数据,预期:成功 + Returns: + + """ + # deploy math contract + test_vec = [ + { + "bool_data": True, + "char_data": "0", + "f32_data": 3.4028235, + "f64_data": 1.7976931348623157, + "i128_data": 17014118346046923, + "i16_data": 32767, + "i32_data": 2147483647, + "i64_data": 9223372036854775807, + "i8_data": 127, + "isize_data": 0, + "str_data": "max", + "u128_data": 3402823669209385, + "u16_data": 65535, + "u32_data": 4294967295, + "u64_data": 18446744073709551615, + "u8_data": 255, + "usize_data": 18446744073709551615, + }, + { + "bool_data": False, + "char_data": " ", + "f32_data": -3.4028235, + "f64_data": -1.7976931348623157, + "i128_data": -17014118346046923, + "i16_data": -32768, + "i32_data": -2147483648, + "i64_data": -9223372036854775808, + "i8_data": -128, + "isize_data": -9223372036854775808, + "str_data": "", + "u128_data": 0, + "u16_data": 0, + "u32_data": 0, + "u64_data": 0, + "u8_data": 0, + "usize_data": 0, + }, + ] + ipc_script_locator = { + "out_point": {"index": "0x0", "tx_hash": self.ipc_test_contract_tx_hash} + } + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "json", + "payload": { + "TestBoundaryStruct": {"vec": test_vec}, + }, + } + ipc_ret = self.node.getClient().ipc_call(ipc_script_locator, ipc_req) + print(f"main: call result json={ipc_ret}") + assert ( + json.dumps(ipc_ret["payload"]) + == '{"TestBoundaryStruct": [{"bool_data": true, "char_data": "0", "f32_data": 3.4028234, "f64_data": 1.7976931348623155, "i128_data": 17014118346046923, "i16_data": 32767, "i32_data": 2147483647, "i64_data": 9223372036854775807, "i8_data": 127, "isize_data": 0, "str_data": "max", "u128_data": 3402823669209385, "u16_data": 65535, "u32_data": 4294967295, "u64_data": 18446744073709551615, "u8_data": 255, "usize_data": 18446744073709551615}, {"bool_data": false, "char_data": " ", "f32_data": -3.4028234, "f64_data": -1.7976931348623155, "i128_data": -17014118346046923, "i16_data": -32768, "i32_data": -2147483648, "i64_data": -9223372036854775808, "i8_data": -128, "isize_data": -9223372036854775808, "str_data": "", "u128_data": 0, "u16_data": 0, "u32_data": 0, "u64_data": 0, "u8_data": 0, "usize_data": 0}]}' + ) + + def test_script_group_type_type(self): + """ + type合约 ,预期:成功 + 不存在匹配的script_hash,预期:报错 IPC: ScriptNotFound + 已经上过链的tx,预期:报错:TransactionFailedToResolve + Returns: + + """ + account = self.Ckb_cli.util_key_info_by_private_key( + self.Config.ACCOUNT_PRIVATE_1 + ) + account1 = self.Ckb_cli.util_key_info_by_private_key( + self.Config.ACCOUNT_PRIVATE_2 + ) + account2 = self.Ckb_cli.util_key_info_by_private_key( + self.Config.MINER_PRIVATE_1 + ) + + # deploy + udtContract = UdtContract() + udtContract.deploy(self.Config.ACCOUNT_PRIVATE_1, self.node) + deploy_hash, deploy_index = udtContract.get_deploy_hash_and_index() + # issue + invoke_arg, invoke_data = udtContract.issue(account1["lock_arg"], 100000) + tx_hash = self.Contract.invoke_ckb_contract( + account_private=self.Config.ACCOUNT_PRIVATE_2, + contract_out_point_tx_hash=deploy_hash, + contract_out_point_tx_index=deploy_index, + type_script_arg=invoke_arg, + hash_type="type", + data=invoke_data, + fee=1000, + api_url=self.node.getClient().url, + cell_deps=[], + input_cells=[], + output_lock_arg=account2["lock_arg"], + ) + tx = self.node.getClient().get_transaction(tx_hash) + + print("tx:", tx) + molecule_hex = self.Ckb_cli.molecule_encode( + tx["transaction"]["outputs"][0]["type"], "Script" + ) + data = bytes.fromhex(molecule_hex.replace("0x", "")) + personalization = "ckb-default-hash".encode("utf-8") + # cal blake 2b hash + hash_object = hashlib.blake2b(digest_size=32, person=personalization) + hash_object.update(data) + script_hash = "0x" + hash_object.hexdigest() + print("hex:", script_hash) + tx = tx["transaction"] + del tx["hash"] + ipc_script_locator = { + "out_point": {"index": "0x0", "tx_hash": self.ipc_test_contract_tx_hash}, + } + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "json", + "payload": { + "SyscallLoadScript": {}, + }, + } + ipc_env = { + "tx": tx, + "script_group_type": "type", + "script_hash": script_hash, + } + ipc_call_result = self.node.getClient().ipc_call( + ipc_script_locator, ipc_req, ipc_env + ) + print("ipc_call_result:", ipc_call_result) + + # 不匹配hash + ipc_env["script_hash"] = ( + "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8" + ) + + # ScriptNotFound + with pytest.raises(Exception) as exc_info: + ipc_call_result = self.node.getClient().ipc_call( + ipc_script_locator, ipc_req, ipc_env + ) + expected_error_message = "ScriptNotFound" + 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]}'" + + self.Miner.miner_until_tx_committed(self.node, tx_hash) + + with pytest.raises(Exception) as exc_info: + self.node.getClient().ipc_call(ipc_script_locator, ipc_req, ipc_env) + expected_error_message = "TransactionFailedToResolve: Unknown(OutPoint" + 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_script_is_data_data1_data2(self): + """ + tx的script 是data + tx的script 是data1 + tx的script 是data2 + Returns: + + """ + + account1 = self.Ckb_cli.util_key_info_by_private_key( + self.Config.MINER_PRIVATE_1 + ) + + # deploy + udtContract = UdtContract() + udtContract.deploy(self.Config.ACCOUNT_PRIVATE_1, self.node) + deploy_hash, deploy_index = udtContract.get_deploy_hash_and_index() + # data + invoke_arg, invoke_data = udtContract.issue(account1["lock_arg"], 100000) + tx = self.Contract.build_invoke_ckb_contract( + account_private=self.Config.MINER_PRIVATE_1, + contract_out_point_tx_hash=deploy_hash, + contract_out_point_tx_index=deploy_index, + type_script_arg=invoke_arg, + hash_type="data", + data=invoke_data, + fee=1000, + api_url=self.node.getClient().url, + ) + + print("tx:", tx) + molecule_hex = self.Ckb_cli.molecule_encode(tx["outputs"][0]["type"], "Script") + data = bytes.fromhex(molecule_hex.replace("0x", "")) + personalization = "ckb-default-hash".encode("utf-8") + # cal blake 2b hash + hash_object = hashlib.blake2b(digest_size=32, person=personalization) + hash_object.update(data) + script_hash = "0x" + hash_object.hexdigest() + print("hex:", script_hash) + # tx = tx["transaction"] + # del tx["hash"] + ipc_script_locator = { + "out_point": {"index": "0x0", "tx_hash": self.ipc_test_contract_tx_hash}, + } + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "json", + "payload": { + "SyscallLoadScript": {}, + }, + } + ipc_env = { + "tx": tx, + "script_group_type": "type", + "script_hash": script_hash, + } + + with pytest.raises(Exception) as exc_info: + ipc_call_result = self.node.getClient().ipc_call( + ipc_script_locator, ipc_req, ipc_env + ) + expected_error_message = "IPC: sending on a closed channel" + 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]}'" + + # data1 + invoke_arg, invoke_data = udtContract.issue(account1["lock_arg"], 100000) + tx = self.Contract.build_invoke_ckb_contract( + account_private=self.Config.MINER_PRIVATE_1, + contract_out_point_tx_hash=deploy_hash, + contract_out_point_tx_index=deploy_index, + type_script_arg=invoke_arg, + hash_type="data1", + data=invoke_data, + fee=1000, + api_url=self.node.getClient().url, + ) + + molecule_hex = self.Ckb_cli.molecule_encode(tx["outputs"][0]["type"], "Script") + data = bytes.fromhex(molecule_hex.replace("0x", "")) + personalization = "ckb-default-hash".encode("utf-8") + # cal blake 2b hash + hash_object = hashlib.blake2b(digest_size=32, person=personalization) + hash_object.update(data) + script_hash = "0x" + hash_object.hexdigest() + print("hex:", script_hash) + # tx = tx["transaction"] + # del tx["hash"] + ipc_script_locator = { + "out_point": {"index": "0x0", "tx_hash": self.ipc_test_contract_tx_hash}, + } + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "json", + "payload": { + "SyscallLoadScript": {}, + }, + } + ipc_env = { + "tx": tx, + "script_group_type": "type", + "script_hash": script_hash, + } + ipc_call_result = self.node.getClient().ipc_call( + ipc_script_locator, ipc_req, ipc_env + ) + + # data2 + invoke_arg, invoke_data = udtContract.issue(account1["lock_arg"], 100000) + tx = self.Contract.build_invoke_ckb_contract( + account_private=self.Config.MINER_PRIVATE_1, + contract_out_point_tx_hash=deploy_hash, + contract_out_point_tx_index=deploy_index, + type_script_arg=invoke_arg, + hash_type="data2", + data=invoke_data, + fee=1000, + api_url=self.node.getClient().url, + ) + + print("tx:", tx) + molecule_hex = self.Ckb_cli.molecule_encode(tx["outputs"][0]["type"], "Script") + data = bytes.fromhex(molecule_hex.replace("0x", "")) + personalization = "ckb-default-hash".encode("utf-8") + # cal blake 2b hash + hash_object = hashlib.blake2b(digest_size=32, person=personalization) + hash_object.update(data) + script_hash = "0x" + hash_object.hexdigest() + print("hex:", script_hash) + # tx = tx["transaction"] + # del tx["hash"] + ipc_script_locator = { + "out_point": {"index": "0x0", "tx_hash": self.ipc_test_contract_tx_hash}, + } + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "json", + "payload": { + "SyscallLoadScript": {}, + }, + } + ipc_env = { + "tx": tx, + "script_group_type": "type", + "script_hash": script_hash, + } + ipc_call_result = self.node.getClient().ipc_call( + ipc_script_locator, ipc_req, ipc_env + ) + + def test_cycle_limit(self): + """ + cycle执行特别大,测试cycle 请求最大值100亿,超过100亿,报错:IPC: ReadVlqError + Returns: + """ + # deploy math contract + ipc_script_locator = { + "out_point": {"index": "0x0", "tx_hash": self.ipc_test_contract_tx_hash} + } + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "json", + "payload": { + "TestCycle": {"cycle_limit": 9999994700}, + }, + } + ipc_ret = self.node.getClient().ipc_call(ipc_script_locator, ipc_req) + print(f"main: call result json={ipc_ret}") + assert ipc_ret["payload"]["TestCycle"] > 9999994700 + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "json", + "payload": { + "TestCycle": {"cycle_limit": 10000000000}, + }, + } + + with pytest.raises(Exception) as exc_info: + ipc_ret = self.node.getClient().ipc_call(ipc_script_locator, ipc_req) + expected_error_message = "IPC: ReadVlqError" + 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_payload_limit(self): + """ + 测试返回payload 最大值,请求小于64k,成功,超过64k ,报错::IPC: ReadVlqError + Returns: + + """ + # deploy math contract + ipc_script_locator = { + "out_point": {"index": "0x0", "tx_hash": self.ipc_test_contract_tx_hash} + } + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "json", + "payload": { + "TestMem": {"byte_data": 1000, "kb_data": 63, "mb_data": 0}, + }, + } + ipc_ret = self.node.getClient().ipc_call(ipc_script_locator, ipc_req) + print(f"main: call result json={ipc_ret}") + assert len(ipc_ret["payload"]["TestMem"]) == 1000 + 63 * 1024 + + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "json", + "payload": { + "TestMem": {"byte_data": 0, "kb_data": 64, "mb_data": 0}, + }, + } + + with pytest.raises(Exception) as exc_info: + ipc_ret = self.node.getClient().ipc_call(ipc_script_locator, ipc_req) + expected_error_message = "IPC: ReadVlqError" + 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_input_payload(self): + """ + payload 请求特别大,测试payload 请求最大值,如果是str 128*1024 ,vec 1024 * 64 就会报错:IPC: ReadVlqError~~ + + Returns: + + """ + ipc_script_locator = { + "out_point": {"index": "0x0", "tx_hash": self.ipc_test_contract_tx_hash} + } + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "json", + "payload": { + "TestInputPayload": {"s": rand_str(1024 * 128)}, + }, + } + ipc_ret = self.node.getClient().ipc_call(ipc_script_locator, ipc_req) + print(f"main: call result json={ipc_ret}") + + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "json", + "payload": { + "TestInputPayload": {"s": rand_str(1024 * 129)}, + }, + } + with pytest.raises(Exception) as exc_info: + ipc_ret = self.node.getClient().ipc_call(ipc_script_locator, ipc_req) + expected_error_message = "IPC: ReadVlqError" + 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]}'" + + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "json", + "payload": { + "TestInputVec": {"vec": [1] * 1024 * 64}, + }, + } + ipc_ret = self.node.getClient().ipc_call(ipc_script_locator, ipc_req) + assert ipc_ret["payload"]["TestInputVec"] == 1024 * 64 + + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "json", + "payload": { + "TestInputVec": {"vec": [1] * 1024 * 65}, + }, + } + with pytest.raises(Exception) as exc_info: + ipc_ret = self.node.getClient().ipc_call(ipc_script_locator, ipc_req) + expected_error_message = "IPC: ReadVlqError" + 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_ckb_opcode(self): + """ + 有IpcEnv 测试所有的block 操作是否能正常使用 + 调用查询 opcode + + 没有IpcEnv 测试所有的block 操作是否能正常使用 + 调用查询opcode, current_cycle,load 一些查询会失败,报错:返回 + Returns: + + """ + account1 = self.Ckb_cli.util_key_info_by_private_key( + self.Config.MINER_PRIVATE_1 + ) + # deploy + udtContract = UdtContract() + udtContract.deploy(self.Config.ACCOUNT_PRIVATE_1, self.node) + deploy_hash, deploy_index = udtContract.get_deploy_hash_and_index() + # data + invoke_arg, invoke_data = udtContract.issue(account1["lock_arg"], 100000) + tx = self.Contract.build_invoke_ckb_contract( + account_private=self.Config.MINER_PRIVATE_1, + contract_out_point_tx_hash=deploy_hash, + contract_out_point_tx_index=deploy_index, + type_script_arg=invoke_arg, + hash_type="type", + data=invoke_data, + fee=1000, + api_url=self.node.getClient().url, + ) + + print("tx:", tx) + molecule_hex = self.Ckb_cli.molecule_encode(tx["outputs"][0]["type"], "Script") + data = bytes.fromhex(molecule_hex.replace("0x", "")) + personalization = "ckb-default-hash".encode("utf-8") + # cal blake 2b hash + hash_object = hashlib.blake2b(digest_size=32, person=personalization) + hash_object.update(data) + script_hash = "0x" + hash_object.hexdigest() + print("hex:", script_hash) + # tx = tx["transaction"] + # del tx["hash"] + ipc_script_locator = { + "out_point": {"index": "0x0", "tx_hash": self.ipc_test_contract_tx_hash}, + } + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "json", + "payload": { + "TestCkbCall": {}, + }, + } + ipc_env = { + "tx": tx, + "script_group_type": "type", + "script_hash": script_hash, + } + + ipc_call_result = self.node.getClient().ipc_call( + ipc_script_locator, ipc_req, ipc_env + ) + + with pytest.raises(Exception) as exc_info: + ipc_call_result = self.node.getClient().ipc_call( + ipc_script_locator, ipc_req, None + ) + expected_error_message = "IPC: ReadVlqError" + 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_empty(self): + """ + + Returns: + + """ + tx = self.node.getClient().get_transaction(self.ipc_test_contract_tx_hash) + ipc_script_locator = { + "type_id_args": tx["transaction"]["outputs"][0]["type"]["args"], + } + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "json", + "payload": { + "TestEmpty": {}, + }, + } + ipc_ret = self.node.getClient().ipc_call(ipc_script_locator, ipc_req) + print(f"main: call result json={ipc_ret}") + assert ipc_ret["payload"]["TestEmpty"] == None + + def test_ipc_time_with_exec(self): + """ + 死循环,执行时间超过8s,预期:rpc返回sending on a closed channel,vm 继续执行 + Returns: + + """ + account = self.Ckb_cli.util_key_info_by_private_key( + self.Config.ACCOUNT_PRIVATE_1 + ) + father_tx_hash = self.Ckb_cli.wallet_transfer_by_private_key( + self.Config.ACCOUNT_PRIVATE_1, + account["address"]["testnet"], + 100000, + self.node.getClient().url, + ) + self.Miner.miner_until_tx_committed(self.node, father_tx_hash) + + tx = self.Tx.build_send_transfer_self_tx_with_input( + [father_tx_hash], + ["0x0"], + self.Config.ACCOUNT_PRIVATE_1, + output_count=15, + fee=15000, + api_url=self.node.getClient().url, + dep_cells=[ + { + "tx_hash": self.ipc_test_with_exec_contract_tx_hash, + "index_hex": hex(0), + }, + ], + ) + result = self.node.getClient().test_tx_pool_accept(tx, "passthrough") + print("result:", result) + # + ipc_script_locator = { + "out_point": { + "index": "0x0", + "tx_hash": self.ipc_test_with_exec_contract_tx_hash, + }, + } + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "json", + "payload": { + "TestCurrentCycle": {}, + }, + } + ipc_env = { + "tx": tx, + "script_group_type": "lock", + "script_hash": account["lock_hash"], + } + + # IPC: sending on a closed channel + with pytest.raises(Exception) as exc_info: + ipc_call_result = self.node.getClient().ipc_call( + ipc_script_locator, ipc_req, ipc_env + ) + expected_error_message = "sending on a closed channel" + 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 rand_str(size): + """ + 生成随机字符串 + :param size: 字符串长度 + :return: 随机字符串 + """ + return "".join(random.choice(string.ascii_letters) for _ in range(size)) diff --git a/test_cases/ipc_call/test_ipc_call_without_indexer.py b/test_cases/ipc_call/test_ipc_call_without_indexer.py new file mode 100644 index 0000000..9b4a444 --- /dev/null +++ b/test_cases/ipc_call/test_ipc_call_without_indexer.py @@ -0,0 +1,88 @@ +import time + +import pytest + +from framework.basic import CkbTest +from framework.util import get_project_root + + +class TestIpcWithOutIndexer(CkbTest): + + @classmethod + def setup_class(cls): + """ + 1. start node in issue/node1 + 2. generate 2 epoch + Returns: + + """ + + # 1. start node in issue/node1 + node1 = cls.CkbNode.init_dev_by_port( + cls.CkbNodeConfigPath.CURRENT_TEST, "ipc_call/node1", 8114, 8927 + ) + cls.node = node1 + node1.prepare( + other_ckb_config={ + "ckb_rpc_modules": [ + "Net", + "Pool", + "Miner", + "Chain", + "Stats", + "Subscription", + "Experiment", + "Debug", + "IntegrationTest", + "IPC", + ] + } + ) + node1.start() + + # 2. miner 400 block + # cls.Miner.make_tip_height_number(cls.node, 400) + cls.node.getClient().generate_epochs("0x2") + time.sleep(3) + cls.ipc_test_contract_tx_hash = cls.Contract.deploy_ckb_contract( + cls.Config.ACCOUNT_PRIVATE_1, + f"{get_project_root()}/source/contract/ipc_test/ipc_test", + ) + cls.Miner.miner_until_tx_committed(cls.node, cls.ipc_test_contract_tx_hash) + + @classmethod + def teardown_class(cls): + print("\nTeardown TestClass1") + cls.node.stop() + cls.node.clean() + + def test_type_id_args_without_indexer(self): + """ + 没开启index模块,IpcScriptLocator 使用type_id_args 模式,报错:Query by type id requires enabling Indexer + Returns: + + """ + tx = self.node.getClient().get_transaction(self.ipc_test_contract_tx_hash) + ipc_script_locator = { + "type_id_args": tx["transaction"]["outputs"][0]["type"]["args"], + } + ipc_req = { + "version": "0x0", + "method_id": "0x0", + "payload_format": "json", + "payload": { + "MathAdd": {"a": 2, "b": 1}, + }, + } + ipc_ret = self.node.getClient().ipc_call(ipc_script_locator, ipc_req) + print(f"main: call result json={ipc_ret}") + assert ipc_ret["payload"]["MathAdd"] == 3 + self.node.stop() + self.node.start_without_indexer() + + with pytest.raises(Exception) as exc_info: + ipc_ret = self.node.getClient().ipc_call(ipc_script_locator, ipc_req) + expected_error_message = "Query by type id requires enabling Indexer" + 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]}'"