diff --git a/Cargo.lock b/Cargo.lock index 3897cead3e..03a410d8ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1525,9 +1525,9 @@ dependencies = [ [[package]] name = "ckb-vm" -version = "0.23.2" +version = "0.24.0-beta" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fee7eb8d6525a0d3a4f113414c1b51dd3aaf75ef7b54fe7ce306e545c87cdd" +checksum = "0c0cdbbf781e743153bfdae8690ba5246a5a026426f22fe865336ee75b18b1c2" dependencies = [ "byteorder", "bytes 1.4.0", @@ -1543,9 +1543,9 @@ dependencies = [ [[package]] name = "ckb-vm-definitions" -version = "0.23.2" +version = "0.24.0-beta" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9403cda7180a8203facf05e96537f0038d188b2efdcef13c84538bfbdfcbcbf" +checksum = "19f7db57ea5cffd9d310ecdb5bc96512a1f0e420ba1f3244d7a2f3baaf0b8dac" [[package]] name = "clang-sys" diff --git a/rpc/README.md b/rpc/README.md index 2a99fb7f50..fe976772fd 100644 --- a/rpc/README.md +++ b/rpc/README.md @@ -6581,11 +6581,12 @@ Allowed kinds: “data”, “type” and “data1”. Refer to the section [Code Locating](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0022-transaction-structure/0022-transaction-structure.md#code-locating) and [Upgradable Script](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0022-transaction-structure/0022-transaction-structure.md#upgradable-script) in the RFC *CKB Transaction Structure*. -`ScriptHashType` is equivalent to `"data" | "type" | "data1"`. +`ScriptHashType` is equivalent to `"data" | "type" | "data1" | "data2"`. * Type “data” matches script code via cell data hash, and run the script code in v0 CKB VM. * Type “type” matches script code via cell type script hash. * Type “data1” matches script code via cell data hash, and run the script code in v1 CKB VM. +* Type “data2” matches script code via cell data hash, and run the script code in v2 CKB VM. ### Type `SerializedBlock` diff --git a/script/Cargo.toml b/script/Cargo.toml index 3c77668bd8..4e7e2fc35b 100644 --- a/script/Cargo.toml +++ b/script/Cargo.toml @@ -22,7 +22,7 @@ ckb-traits = { path = "../traits", version = "= 0.109.0-pre" } byteorder = "1.3.1" ckb-types = { path = "../util/types", version = "= 0.109.0-pre" } ckb-hash = { path = "../util/hash", version = "= 0.109.0-pre" } -ckb-vm = { version = "=0.23.2", default-features = false } +ckb-vm = { version = "=0.24.0-beta", default-features = false } faster-hex = "0.6" ckb-logger = { path = "../util/logger", version = "= 0.109.0-pre", optional = true } serde = { version = "1.0", features = ["derive"] } diff --git a/script/src/cost_model.rs b/script/src/cost_model.rs index 60ae1f2010..41869c22d4 100644 --- a/script/src/cost_model.rs +++ b/script/src/cost_model.rs @@ -1,10 +1,6 @@ //! CKB VM cost model. //! //! The cost model assign cycles to instructions. -use ckb_vm::{ - instructions::{extract_opcode, insts}, - Instruction, -}; /// How many bytes can transfer when VM costs one cycle. // 0.25 cycles per byte @@ -15,53 +11,3 @@ pub fn transferred_byte_cycles(bytes: u64) -> u64 { // Compiler will optimize the divisin here to shifts. (bytes + BYTES_PER_CYCLE - 1) / BYTES_PER_CYCLE } - -/// Returns the spent cycles to execute the secific instruction. -pub fn instruction_cycles(i: Instruction) -> u64 { - match extract_opcode(i) { - // IMC - insts::OP_JALR => 3, - insts::OP_LD => 2, - insts::OP_LW => 3, - insts::OP_LH => 3, - insts::OP_LB => 3, - insts::OP_LWU => 3, - insts::OP_LHU => 3, - insts::OP_LBU => 3, - insts::OP_SB => 3, - insts::OP_SH => 3, - insts::OP_SW => 3, - insts::OP_SD => 2, - insts::OP_BEQ => 3, - insts::OP_BGE => 3, - insts::OP_BGEU => 3, - insts::OP_BLT => 3, - insts::OP_BLTU => 3, - insts::OP_BNE => 3, - insts::OP_EBREAK => 500, - insts::OP_ECALL => 500, - insts::OP_JAL => 3, - insts::OP_MUL => 5, - insts::OP_MULW => 5, - insts::OP_MULH => 5, - insts::OP_MULHU => 5, - insts::OP_MULHSU => 5, - insts::OP_DIV => 32, - insts::OP_DIVW => 32, - insts::OP_DIVU => 32, - insts::OP_DIVUW => 32, - insts::OP_REM => 32, - insts::OP_REMW => 32, - insts::OP_REMU => 32, - insts::OP_REMUW => 32, - // MOP - insts::OP_WIDE_MUL => 5, - insts::OP_WIDE_MULU => 5, - insts::OP_WIDE_MULSU => 5, - insts::OP_WIDE_DIV => 32, - insts::OP_WIDE_DIVU => 32, - insts::OP_FAR_JUMP_REL => 3, - insts::OP_FAR_JUMP_ABS => 3, - _ => 1, - } -} diff --git a/script/src/error.rs b/script/src/error.rs index 6c42e37393..74d91014a4 100644 --- a/script/src/error.rs +++ b/script/src/error.rs @@ -99,7 +99,7 @@ impl ScriptError { /// Creates a script error originated the script and its exit code. pub fn validation_failure(script: &Script, exit_code: i8) -> ScriptError { let url_path = match ScriptHashType::try_from(script.hash_type()).expect("checked data") { - ScriptHashType::Data | ScriptHashType::Data1 => { + ScriptHashType::Data | ScriptHashType::Data1 | ScriptHashType::Data2 => { format!("by-data-hash/{:x}", script.code_hash()) } ScriptHashType::Type => { diff --git a/script/src/lib.rs b/script/src/lib.rs index b8c2d84a40..5adfd1fc9c 100644 --- a/script/src/lib.rs +++ b/script/src/lib.rs @@ -12,5 +12,5 @@ pub use crate::types::{ CoreMachine, ScriptGroup, ScriptGroupType, ScriptVersion, TransactionSnapshot, TransactionState, VerifyResult, VmIsa, VmVersion, }; -pub use crate::verify::TransactionScriptsVerifier; +pub use crate::verify::{TransactionScriptsSyscallsGenerator, TransactionScriptsVerifier}; pub use crate::verify_env::TxVerifyEnv; diff --git a/script/src/syscalls/current_cycles.rs b/script/src/syscalls/current_cycles.rs index 4f80c73d3f..15528ce111 100644 --- a/script/src/syscalls/current_cycles.rs +++ b/script/src/syscalls/current_cycles.rs @@ -4,7 +4,7 @@ use ckb_vm::{ Error as VMError, Register, SupportMachine, Syscalls, }; -#[derive(Debug)] +#[derive(Debug, Default)] pub struct CurrentCycles {} impl CurrentCycles { diff --git a/script/src/syscalls/exec.rs b/script/src/syscalls/exec.rs index 292f15266f..2c33a52165 100644 --- a/script/src/syscalls/exec.rs +++ b/script/src/syscalls/exec.rs @@ -1,4 +1,5 @@ use crate::cost_model::transferred_byte_cycles; +use crate::syscalls::utils::load_c_string; use crate::syscalls::{ Place, Source, SourceEntry, EXEC, INDEX_OUT_OF_BOUND, SLICE_OUT_OF_BOUND, WRONG_FORMAT, }; @@ -9,7 +10,7 @@ use ckb_types::packed::{Bytes as PackedBytes, BytesVec}; use ckb_vm::Memory; use ckb_vm::{ registers::{A0, A1, A2, A3, A4, A5, A7}, - Bytes, Error as VMError, Register, SupportMachine, Syscalls, + Error as VMError, Register, SupportMachine, Syscalls, }; use ckb_vm::{DEFAULT_STACK_SIZE, RISCV_MAX_MEMORY}; use std::sync::Arc; @@ -99,25 +100,6 @@ impl Exec
{ } } -fn load_c_string(machine: &mut Mac, addr: u64) -> Result { - let mut buffer = Vec::new(); - let mut addr = addr; - - loop { - let byte = machine - .memory_mut() - .load8(&Mac::REG::from_u64(addr))? - .to_u8(); - if byte == 0 { - break; - } - buffer.push(byte); - addr += 1; - } - - Ok(Bytes::from(buffer)) -} - impl Syscalls for Exec
{ fn initialize(&mut self, _machine: &mut Mac) -> Result<(), VMError> { Ok(()) diff --git a/script/src/syscalls/get_memory_limit.rs b/script/src/syscalls/get_memory_limit.rs new file mode 100644 index 0000000000..7b28887ce1 --- /dev/null +++ b/script/src/syscalls/get_memory_limit.rs @@ -0,0 +1,30 @@ +use crate::syscalls::GET_MEMORY_LIMIT; +use ckb_vm::{ + registers::{A0, A7}, + Error as VMError, Register, SupportMachine, Syscalls, +}; + +#[derive(Debug)] +pub struct GetMemoryLimit { + memory_limit: u64, +} + +impl GetMemoryLimit { + pub fn new(memory_limit: u64) -> Self { + Self { memory_limit } + } +} + +impl Syscalls for GetMemoryLimit { + fn initialize(&mut self, _machine: &mut Mac) -> Result<(), VMError> { + Ok(()) + } + + fn ecall(&mut self, machine: &mut Mac) -> Result { + if machine.registers()[A7].to_u64() != GET_MEMORY_LIMIT { + return Ok(false); + } + machine.set_register(A0, Mac::REG::from_u64(self.memory_limit)); + Ok(true) + } +} diff --git a/script/src/syscalls/mod.rs b/script/src/syscalls/mod.rs index a546b64000..495d937702 100644 --- a/script/src/syscalls/mod.rs +++ b/script/src/syscalls/mod.rs @@ -1,6 +1,7 @@ mod current_cycles; mod debugger; mod exec; +mod get_memory_limit; mod load_cell; mod load_cell_data; mod load_header; @@ -9,6 +10,8 @@ mod load_script; mod load_script_hash; mod load_tx; mod load_witness; +mod set_content; +mod spawn; mod utils; mod vm_version; @@ -21,6 +24,7 @@ mod tests; pub use self::current_cycles::CurrentCycles; pub use self::debugger::Debugger; pub use self::exec::Exec; +pub use self::get_memory_limit::GetMemoryLimit; pub use self::load_cell::LoadCell; pub use self::load_cell_data::LoadCellData; pub use self::load_header::LoadHeader; @@ -29,6 +33,8 @@ pub use self::load_script::LoadScript; pub use self::load_script_hash::LoadScriptHash; pub use self::load_tx::LoadTx; pub use self::load_witness::LoadWitness; +pub use self::set_content::SetContent; +pub use self::spawn::Spawn; pub use self::vm_version::VMVersion; #[cfg(test)] @@ -45,6 +51,9 @@ pub const INDEX_OUT_OF_BOUND: u8 = 1; pub const ITEM_MISSING: u8 = 2; pub const SLICE_OUT_OF_BOUND: u8 = 3; pub const WRONG_FORMAT: u8 = 4; +pub const SPAWN_EXCEEDED_MAX_CONTENT_LENGTH: u8 = 5; +pub const SPAWN_WRONG_MEMORY_LIMIT: u8 = 6; +pub const SPAWN_EXCEEDED_MAX_PEAK_MEMORY: u8 = 7; pub const VM_VERSION: u64 = 2041; pub const CURRENT_CYCLES: u64 = 2042; @@ -62,10 +71,18 @@ pub const LOAD_HEADER_BY_FIELD_SYSCALL_NUMBER: u64 = 2082; pub const LOAD_INPUT_BY_FIELD_SYSCALL_NUMBER: u64 = 2083; pub const LOAD_CELL_DATA_AS_CODE_SYSCALL_NUMBER: u64 = 2091; pub const LOAD_CELL_DATA_SYSCALL_NUMBER: u64 = 2092; +pub const SPAWN: u64 = 2101; +pub const GET_MEMORY_LIMIT: u64 = 2102; +pub const SET_CONTENT: u64 = 2103; pub const DEBUG_PRINT_SYSCALL_NUMBER: u64 = 2177; #[cfg(test)] pub const DEBUG_PAUSE: u64 = 2178; +pub const SPAWN_MAX_MEMORY: u64 = 8; +pub const SPAWN_MAX_PEAK_MEMORY: u64 = 64; // 64 * 0.5M = 32M +pub const SPAWN_MEMORY_PAGE_SIZE: u64 = 512 * 1024; // 0.5M +pub const SPAWN_MAX_CONTENT_LENGTH: u64 = 256 * 1024; // 256K + #[derive(Debug, PartialEq, Clone, Copy, Eq)] enum CellField { Capacity = 0, diff --git a/script/src/syscalls/set_content.rs b/script/src/syscalls/set_content.rs new file mode 100644 index 0000000000..c1442c30c9 --- /dev/null +++ b/script/src/syscalls/set_content.rs @@ -0,0 +1,51 @@ +use crate::cost_model::transferred_byte_cycles; +use crate::syscalls::utils::load_bytes; +use crate::syscalls::SET_CONTENT; +use ckb_vm::{ + registers::{A0, A1, A7}, + Error as VMError, Memory, Register, SupportMachine, Syscalls, +}; +use std::sync::{Arc, Mutex}; + +#[derive(Debug)] +pub struct SetContent { + content: Arc>>, + content_size: u64, +} + +impl SetContent { + pub fn new(content: Arc>>, content_size: u64) -> Self { + Self { + content, + content_size, + } + } +} + +impl Syscalls for SetContent { + fn initialize(&mut self, _machine: &mut Mac) -> Result<(), VMError> { + Ok(()) + } + + fn ecall(&mut self, machine: &mut Mac) -> Result { + if machine.registers()[A7].to_u64() != SET_CONTENT { + return Ok(false); + } + let content_addr = machine.registers()[A0].to_u64(); + let request_size_addr = machine.registers()[A1].to_u64(); + let request_size = machine + .memory_mut() + .load64(&Mac::REG::from_u64(request_size_addr))?; + let size = std::cmp::min(self.content_size, request_size.to_u64()); + self.content.lock().unwrap().resize(size as usize, 0); + let content = load_bytes(machine, content_addr, size)?; + self.content.lock().unwrap().copy_from_slice(&content); + machine.memory_mut().store64( + &Mac::REG::from_u64(request_size_addr), + &Mac::REG::from_u64(size), + )?; + machine.add_cycles_no_checking(transferred_byte_cycles(size))?; + machine.set_register(A0, Mac::REG::from_u64(0)); + Ok(true) + } +} diff --git a/script/src/syscalls/spawn.rs b/script/src/syscalls/spawn.rs new file mode 100644 index 0000000000..23f9ecddc3 --- /dev/null +++ b/script/src/syscalls/spawn.rs @@ -0,0 +1,267 @@ +use crate::cost_model::transferred_byte_cycles; +use crate::syscalls::utils::load_c_string; +use crate::syscalls::{ + Source, SourceEntry, INDEX_OUT_OF_BOUND, SLICE_OUT_OF_BOUND, SPAWN, + SPAWN_EXCEEDED_MAX_CONTENT_LENGTH, SPAWN_EXCEEDED_MAX_PEAK_MEMORY, SPAWN_MAX_CONTENT_LENGTH, + SPAWN_MAX_MEMORY, SPAWN_MAX_PEAK_MEMORY, SPAWN_MEMORY_PAGE_SIZE, SPAWN_WRONG_MEMORY_LIMIT, + WRONG_FORMAT, +}; +use crate::types::Indices; +use crate::types::{set_vm_max_cycles, CoreMachineType, Machine}; +use crate::TransactionScriptsSyscallsGenerator; +use crate::{ScriptGroup, ScriptVersion}; +use ckb_traits::{CellDataProvider, HeaderProvider}; +use ckb_types::core::cell::{CellMeta, ResolvedTransaction}; +use ckb_vm::{ + cost_model::estimate_cycles, + registers::{A0, A1, A2, A3, A4, A5, A7}, + DefaultMachineBuilder, Error as VMError, Memory, Register, SupportMachine, Syscalls, +}; +use std::sync::{Arc, Mutex}; + +pub struct Spawn
{ + data_loader: DL, + group_inputs: Indices, + group_outputs: Indices, + rtx: Arc, + script_group: ScriptGroup, + script_version: ScriptVersion, + syscalls_generator: TransactionScriptsSyscallsGenerator
, + outputs: Arc>, + peak_memory: u64, +} + +impl Spawn
{ + #[allow(clippy::too_many_arguments)] + pub fn new( + data_loader: DL, + group_inputs: Indices, + group_outputs: Indices, + rtx: Arc, + script_group: ScriptGroup, + script_version: ScriptVersion, + syscalls_generator: TransactionScriptsSyscallsGenerator
, + outputs: Arc>, + peak_memory: u64, + ) -> Self { + Self { + data_loader, + group_inputs, + group_outputs, + rtx, + script_group, + script_version, + syscalls_generator, + outputs, + peak_memory, + } + } + + #[inline] + fn resolved_inputs(&self) -> &Vec { + &self.rtx.resolved_inputs + } + + #[inline] + fn resolved_cell_deps(&self) -> &Vec { + &self.rtx.resolved_cell_deps + } + + fn fetch_cell(&self, source: Source, index: usize) -> Result<&CellMeta, u8> { + let cell_opt = match source { + Source::Transaction(SourceEntry::Input) => self.resolved_inputs().get(index), + Source::Transaction(SourceEntry::Output) => self.outputs.get(index), + Source::Transaction(SourceEntry::CellDep) => self.resolved_cell_deps().get(index), + Source::Group(SourceEntry::Input) => self + .group_inputs + .get(index) + .and_then(|actual_index| self.resolved_inputs().get(*actual_index)), + Source::Group(SourceEntry::Output) => self + .group_outputs + .get(index) + .and_then(|actual_index| self.outputs.get(*actual_index)), + Source::Transaction(SourceEntry::HeaderDep) + | Source::Group(SourceEntry::CellDep) + | Source::Group(SourceEntry::HeaderDep) => { + return Err(INDEX_OUT_OF_BOUND); + } + }; + + cell_opt.ok_or(INDEX_OUT_OF_BOUND) + } +} + +impl Syscalls for Spawn
+where + Mac: SupportMachine, + DL: CellDataProvider + HeaderProvider + Send + Sync + Clone + 'static, +{ + fn initialize(&mut self, _machine: &mut Mac) -> Result<(), VMError> { + Ok(()) + } + + fn ecall(&mut self, machine: &mut Mac) -> Result { + if machine.registers()[A7].to_u64() != SPAWN { + return Ok(false); + } + // Arguments for positioning child programs. + let index = machine.registers()[A0].to_u64(); + let source = Source::parse_from_u64(machine.registers()[A1].to_u64())?; + let bounds = machine.registers()[A2].to_u64(); + let offset = (bounds >> 32) as usize; + let length = bounds as u32 as usize; + // Arguments for childs. + let argc = machine.registers()[A3].to_u64(); + let argv_addr = machine.registers()[A4].to_u64(); + let spgs_addr = machine.registers()[A5].to_u64(); + let memory_limit_addr = spgs_addr; + let exit_code_addr_addr = spgs_addr.wrapping_add(8); + let content_addr_addr = spgs_addr.wrapping_add(16); + let content_length_addr_addr = spgs_addr.wrapping_add(24); + // Arguments for limiting. + let memory_limit = machine + .memory_mut() + .load64(&Mac::REG::from_u64(memory_limit_addr))? + .to_u64(); + let cycles_limit = machine.max_cycles() - machine.cycles(); + // Arguments for saving outputs from child programs. + let exit_code_addr = machine + .memory_mut() + .load64(&Mac::REG::from_u64(exit_code_addr_addr))?; + let content_addr = machine + .memory_mut() + .load64(&Mac::REG::from_u64(content_addr_addr))?; + let content_length_addr = machine + .memory_mut() + .load64(&Mac::REG::from_u64(content_length_addr_addr))?; + let content_length = machine.memory_mut().load64(&content_length_addr)?.to_u64(); + // Arguments check. + if content_length > SPAWN_MAX_CONTENT_LENGTH { + machine.set_register(A0, Mac::REG::from_u8(SPAWN_EXCEEDED_MAX_CONTENT_LENGTH)); + return Ok(true); + } + if memory_limit > SPAWN_MAX_MEMORY || memory_limit == 0 { + machine.set_register(A0, Mac::REG::from_u8(SPAWN_WRONG_MEMORY_LIMIT)); + return Ok(true); + } + if self.peak_memory + memory_limit > SPAWN_MAX_PEAK_MEMORY { + machine.set_register(A0, Mac::REG::from_u8(SPAWN_EXCEEDED_MAX_PEAK_MEMORY)); + return Ok(true); + } + // Build child machine. + let machine_content = Arc::new(Mutex::new(Vec::::new())); + let mut machine_child = { + let machine_isa = self.script_version.vm_isa(); + let machine_version = self.script_version.vm_version(); + let machine_core = CoreMachineType::new_with_memory( + machine_isa, + machine_version, + cycles_limit, + (memory_limit * SPAWN_MEMORY_PAGE_SIZE) as usize, + ); + let machine_builder = DefaultMachineBuilder::new(machine_core) + .instruction_cycle_func(Box::new(estimate_cycles)); + let machine_syscalls = self + .syscalls_generator + .generate_same_syscalls(self.script_version, &self.script_group); + let machine_builder = machine_syscalls + .into_iter() + .fold(machine_builder, |builder, syscall| builder.syscall(syscall)); + let machine_builder = machine_builder.syscall(Box::new( + self.syscalls_generator.build_get_memory_limit(memory_limit), + )); + let machine_builder = + machine_builder.syscall(Box::new(self.syscalls_generator.build_set_content( + Arc::>>::clone(&machine_content), + content_length.to_u64(), + ))); + let machine_builder = machine_builder.syscall(Box::new(Spawn::new( + self.data_loader.clone(), + Arc::clone(&self.group_inputs), + Arc::clone(&self.group_outputs), + Arc::clone(&self.rtx), + self.script_group.clone(), + self.script_version, + self.syscalls_generator.clone(), + Arc::clone(&self.outputs), + self.peak_memory + memory_limit, + ))); + let mut machine_child = Machine::new(machine_builder.build()); + set_vm_max_cycles(&mut machine_child, cycles_limit); + machine_child + }; + // Get binary. + let program = { + let cell = self.fetch_cell(source, index as usize); + if let Err(err) = cell { + machine.set_register(A0, Mac::REG::from_u8(err)); + return Ok(true); + } + let cell = cell.unwrap(); + let data = self.data_loader.load_cell_data(cell).ok_or_else(|| { + VMError::Unexpected(format!( + "Unexpected load_cell_data failed {}", + cell.out_point, + )) + })?; + let size = data.len(); + if offset >= size { + machine.set_register(A0, Mac::REG::from_u8(SLICE_OUT_OF_BOUND)); + return Ok(true); + }; + if length == 0 { + data.slice(offset..size) + } else { + let end = offset.checked_add(length).ok_or(VMError::MemOutOfBound)?; + if end > size { + machine.set_register(A0, Mac::REG::from_u8(SLICE_OUT_OF_BOUND)); + return Ok(true); + } + data.slice(offset..end) + } + }; + // Build arguments. + let mut addr = argv_addr.to_u64(); + let mut argv_vec = Vec::new(); + for _ in 0..argc { + let target_addr = machine + .memory_mut() + .load64(&Mac::REG::from_u64(addr))? + .to_u64(); + let cstr = load_c_string(machine, target_addr)?; + argv_vec.push(cstr); + addr += 8; + } + // Run child machine. + match machine_child.load_program(&program, &argv_vec) { + Ok(size) => { + machine_child + .machine + .add_cycles_no_checking(transferred_byte_cycles(size))?; + } + Err(_) => { + machine.set_register(A0, Mac::REG::from_u8(WRONG_FORMAT)); + return Ok(true); + } + } + // Check result. + match machine_child.run() { + Ok(data) => { + machine.set_register(A0, Mac::REG::from_u32(0)); + machine + .memory_mut() + .store8(&exit_code_addr, &Mac::REG::from_i8(data))?; + machine + .memory_mut() + .store_bytes(content_addr.to_u64(), &machine_content.lock().unwrap())?; + machine.memory_mut().store64( + &content_length_addr, + &Mac::REG::from_u64(machine_content.lock().unwrap().len() as u64), + )?; + machine.add_cycles_no_checking(machine_child.machine.cycles())?; + Ok(true) + } + Err(err) => Err(err), + } + } +} diff --git a/script/src/syscalls/tests/vm_latest/syscalls_2.rs b/script/src/syscalls/tests/vm_latest/syscalls_2.rs index feb65a3dbc..2dd25c375a 100644 --- a/script/src/syscalls/tests/vm_latest/syscalls_2.rs +++ b/script/src/syscalls/tests/vm_latest/syscalls_2.rs @@ -1,7 +1,8 @@ use ckb_vm::{ registers::{A0, A1, A2, A3, A4, A5, A7}, - CoreMachine, SupportMachine, Syscalls, + CoreMachine, Memory, SupportMachine, Syscalls, }; +use std::sync::{Arc, Mutex}; use super::SCRIPT_VERSION; use crate::syscalls::*; @@ -45,3 +46,46 @@ fn test_current_cycles() { assert!(result.unwrap()); assert_eq!(machine.registers()[A0], cycles); } + +#[test] +fn test_get_memory_limit() { + let mut machine = SCRIPT_VERSION.init_core_machine_without_limit(); + + machine.set_register(A0, 0); + machine.set_register(A1, 0); + machine.set_register(A2, 0); + machine.set_register(A3, 0); + machine.set_register(A4, 0); + machine.set_register(A5, 0); + machine.set_register(A7, GET_MEMORY_LIMIT); + + let result = GetMemoryLimit::new(8).ecall(&mut machine); + + assert!(result.unwrap()); + assert_eq!(machine.registers()[A0], 8); +} + +#[test] +fn test_set_content() { + let mut machine = SCRIPT_VERSION.init_core_machine_without_limit(); + machine.memory_mut().store64(&20000, &10).unwrap(); + machine + .memory_mut() + .store_bytes(30000, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + .unwrap(); + + machine.set_register(A0, 30000); + machine.set_register(A1, 20000); + machine.set_register(A2, 0); + machine.set_register(A3, 0); + machine.set_register(A4, 0); + machine.set_register(A5, 0); + machine.set_register(A7, SET_CONTENT); + + let content_data = Arc::new(Mutex::new(vec![])); + let result = + SetContent::new(Arc::>>::clone(&content_data), 5).ecall(&mut machine); + + assert!(result.unwrap()); + assert_eq!(machine.memory_mut().load64(&20000).unwrap(), 5); +} diff --git a/script/src/syscalls/utils.rs b/script/src/syscalls/utils.rs index 8729e82759..cacfdfa0fb 100644 --- a/script/src/syscalls/utils.rs +++ b/script/src/syscalls/utils.rs @@ -1,7 +1,7 @@ use byteorder::{ByteOrder, LittleEndian}; use ckb_vm::{ registers::{A0, A1, A2}, - Error as VMError, Memory, Register, SupportMachine, + Bytes, Error as VMError, Memory, Register, SupportMachine, }; use std::cmp; @@ -28,3 +28,40 @@ pub fn store_u64(machine: &mut Mac, v: u64) -> Result(machine: &mut Mac, addr: u64) -> Result { + let mut buffer = Vec::new(); + let mut addr = addr; + + loop { + let byte = machine + .memory_mut() + .load8(&Mac::REG::from_u64(addr))? + .to_u8(); + if byte == 0 { + break; + } + buffer.push(byte); + addr += 1; + } + + Ok(Bytes::from(buffer)) +} + +pub fn load_bytes( + machine: &mut Mac, + addr: u64, + size: u64, +) -> Result { + let mut buffer = Vec::new(); + let mut addr = addr; + for _ in 0..size { + let byte = machine + .memory_mut() + .load8(&Mac::REG::from_u64(addr))? + .to_u8(); + buffer.push(byte); + addr += 1; + } + Ok(Bytes::from(buffer)) +} diff --git a/script/src/syscalls/vm_version.rs b/script/src/syscalls/vm_version.rs index ad2283dfcb..1884c0fa69 100644 --- a/script/src/syscalls/vm_version.rs +++ b/script/src/syscalls/vm_version.rs @@ -4,7 +4,7 @@ use ckb_vm::{ Error as VMError, Register, SupportMachine, Syscalls, }; -#[derive(Debug)] +#[derive(Debug, Default)] pub struct VMVersion {} impl VMVersion { diff --git a/script/src/types.rs b/script/src/types.rs index a4ccd3cf75..6a5525dc2d 100644 --- a/script/src/types.rs +++ b/script/src/types.rs @@ -5,9 +5,9 @@ use ckb_types::{ packed::{Byte32, Script}, }; use ckb_vm::{ - machine::{VERSION0, VERSION1}, + machine::{VERSION0, VERSION1, VERSION2}, snapshot::{make_snapshot, Snapshot}, - Error as VMInternalError, SupportMachine, ISA_B, ISA_IMC, ISA_MOP, + Error as VMInternalError, SupportMachine, ISA_A, ISA_B, ISA_IMC, ISA_MOP, }; use serde::{Deserialize, Serialize}; use std::fmt; @@ -51,12 +51,14 @@ pub enum ScriptVersion { V0 = 0, /// CKB VM 1 with Syscall version 1 and version 2. V1 = 1, + /// /// CKB VM 1 with Syscall version 1, version 2 and version 3. + V2 = 2, } impl ScriptVersion { /// Returns the latest version. pub const fn latest() -> Self { - Self::V1 + Self::V2 } /// Returns the ISA set of CKB VM in current script version. @@ -64,6 +66,7 @@ impl ScriptVersion { match self { Self::V0 => ISA_IMC, Self::V1 => ISA_IMC | ISA_B | ISA_MOP, + Self::V2 => ISA_IMC | ISA_A | ISA_B | ISA_MOP, } } @@ -72,6 +75,7 @@ impl ScriptVersion { match self { Self::V0 => VERSION0, Self::V1 => VERSION1, + Self::V2 => VERSION2, } } @@ -84,6 +88,7 @@ impl ScriptVersion { match self { Self::V0 => ScriptHashType::Data, Self::V1 => ScriptHashType::Data1, + Self::V2 => ScriptHashType::Data2, } } @@ -160,6 +165,7 @@ pub(crate) fn set_vm_max_cycles(vm: &mut Machine, cycles: Cycle) { /// A script group will only be executed once per transaction, the /// script itself should check against all inputs/outputs in its group /// if needed. +#[derive(Clone)] pub struct ScriptGroup { /// The script. /// diff --git a/script/src/verify.rs b/script/src/verify.rs index 2dddb0ff01..c0e82240b4 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -1,11 +1,11 @@ #[cfg(test)] use crate::syscalls::Pause; use crate::{ - cost_model::{instruction_cycles, transferred_byte_cycles}, + cost_model::transferred_byte_cycles, error::{ScriptError, TransactionScriptError}, syscalls::{ - CurrentCycles, Debugger, Exec, LoadCell, LoadCellData, LoadHeader, LoadInput, LoadScript, - LoadScriptHash, LoadTx, LoadWitness, VMVersion, + CurrentCycles, Debugger, Exec, GetMemoryLimit, LoadCell, LoadCellData, LoadHeader, + LoadInput, LoadScript, LoadScriptHash, LoadTx, LoadWitness, SetContent, Spawn, VMVersion, }, type_id::TypeIdSystemScript, types::{ @@ -28,12 +28,13 @@ use ckb_types::{ prelude::*, }; use ckb_vm::{ + cost_model::estimate_cycles, snapshot::{resume, Snapshot}, DefaultMachineBuilder, Error as VMInternalError, SupportMachine, Syscalls, }; use std::cell::RefCell; use std::collections::{BTreeMap, HashMap}; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; #[cfg(test)] use core::sync::atomic::{AtomicBool, Ordering}; @@ -115,6 +116,194 @@ impl Binaries { } } +/// Syscalls can be generated individually by TransactionScriptsSyscallsGenerator. +/// +/// TransactionScriptsSyscallsGenerator can be cloned. +#[derive(Clone)] +pub struct TransactionScriptsSyscallsGenerator
{ + data_loader: DL, + debug_printer: DebugPrinter, + outputs: Arc>, + rtx: Arc, + #[cfg(test)] + skip_pause: Arc, +} + +impl + TransactionScriptsSyscallsGenerator
+{ + /// Build syscall: current_cycles + pub fn build_current_cycles(&self) -> CurrentCycles { + CurrentCycles::new() + } + + /// Build syscall: vm_version + pub fn build_vm_version(&self) -> VMVersion { + VMVersion::new() + } + + /// Build syscall: exec + pub fn build_exec(&self, group_inputs: Indices, group_outputs: Indices) -> Exec
{ + Exec::new( + self.data_loader.clone(), + Arc::clone(&self.rtx), + Arc::clone(&self.outputs), + group_inputs, + group_outputs, + ) + } + + /// Build syscall: load_tx + pub fn build_load_tx(&self) -> LoadTx { + LoadTx::new(Arc::clone(&self.rtx)) + } + + /// Build syscall: load_cell + pub fn build_load_cell(&self, group_inputs: Indices, group_outputs: Indices) -> LoadCell
{ + LoadCell::new( + self.data_loader.clone(), + Arc::clone(&self.rtx), + Arc::clone(&self.outputs), + group_inputs, + group_outputs, + ) + } + + /// Build syscall: load_cell_data + pub fn build_load_cell_data( + &self, + group_inputs: Indices, + group_outputs: Indices, + ) -> LoadCellData
{ + LoadCellData::new( + self.data_loader.clone(), + Arc::clone(&self.rtx), + Arc::clone(&self.outputs), + group_inputs, + group_outputs, + ) + } + + ///Build syscall: load_input + pub fn build_load_input(&self, group_inputs: Indices) -> LoadInput { + LoadInput::new(Arc::clone(&self.rtx), group_inputs) + } + + /// Build syscall: load_script_hash + pub fn build_load_script_hash(&self, hash: Byte32) -> LoadScriptHash { + LoadScriptHash::new(hash) + } + + /// Build syscall: load_header + pub fn build_load_header(&self, group_inputs: Indices) -> LoadHeader
{ + LoadHeader::new( + self.data_loader.clone(), + Arc::clone(&self.rtx), + group_inputs, + ) + } + + /// Build syscall: load_witness + pub fn build_load_witness(&self, group_inputs: Indices, group_outputs: Indices) -> LoadWitness { + LoadWitness::new(Arc::clone(&self.rtx), group_inputs, group_outputs) + } + + /// Build syscall: load_script + pub fn build_load_script(&self, script: Script) -> LoadScript { + LoadScript::new(script) + } + + /// Build syscall: get_memory_limit + pub fn build_get_memory_limit(&self, memory_limit: u64) -> GetMemoryLimit { + GetMemoryLimit::new(memory_limit) + } + + /// Build syscall: set_content + pub fn build_set_content( + &self, + content: Arc>>, + content_length: u64, + ) -> SetContent { + SetContent::new(content, content_length) + } + + /// Generate same syscalls. The result does not contain spawn syscalls. + pub fn generate_same_syscalls( + &self, + script_version: ScriptVersion, + script_group: &ScriptGroup, + ) -> Vec)>> { + let current_script_hash = script_group.script.calc_script_hash(); + let script_group_input_indices = Arc::new(script_group.input_indices.clone()); + let script_group_output_indices = Arc::new(script_group.output_indices.clone()); + let mut syscalls: Vec)>> = vec![ + Box::new(self.build_load_script_hash(current_script_hash.clone())), + Box::new(self.build_load_tx()), + Box::new(self.build_load_cell( + Arc::clone(&script_group_input_indices), + Arc::clone(&script_group_output_indices), + )), + Box::new(self.build_load_input(Arc::clone(&script_group_input_indices))), + Box::new(self.build_load_header(Arc::clone(&script_group_input_indices))), + Box::new(self.build_load_witness( + Arc::clone(&script_group_input_indices), + Arc::clone(&script_group_output_indices), + )), + Box::new(self.build_load_script(script_group.script.clone())), + Box::new(self.build_load_cell_data( + Arc::clone(&script_group_input_indices), + Arc::clone(&script_group_output_indices), + )), + Box::new(Debugger::new( + current_script_hash, + Arc::clone(&self.debug_printer), + )), + ]; + #[cfg(test)] + syscalls.push(Box::new(Pause::new(Arc::clone(&self.skip_pause)))); + if script_version >= ScriptVersion::V1 { + syscalls.append(&mut vec![ + Box::new(self.build_vm_version()), + Box::new(self.build_current_cycles()), + Box::new(self.build_exec( + Arc::clone(&script_group_input_indices), + Arc::clone(&script_group_output_indices), + )), + ]); + } + syscalls + } + + /// Generate root syscalls. + pub fn generate_root_syscalls( + &self, + script_version: ScriptVersion, + script_group: &ScriptGroup, + ) -> Vec)>> { + let script_group_input_indices = Arc::new(script_group.input_indices.clone()); + let script_group_output_indices = Arc::new(script_group.output_indices.clone()); + let mut syscalls = self.generate_same_syscalls(script_version, script_group); + if script_version >= ScriptVersion::V2 { + syscalls.append(&mut vec![ + Box::new(self.build_get_memory_limit(8)), + Box::new(self.build_set_content(Arc::new(Mutex::new(vec![])), 0)), + Box::new(Spawn::new( + self.data_loader.clone(), + Arc::clone(&script_group_input_indices), + Arc::clone(&script_group_output_indices), + Arc::clone(&self.rtx), + script_group.clone(), + script_version, + self.clone(), + Arc::clone(&self.outputs), + 8, + )), + ]) + } + syscalls + } +} + /// This struct leverages CKB VM to verify transaction inputs. /// /// FlatBufferBuilder owned `Vec` that grows as needed, in the @@ -259,82 +448,12 @@ impl self.rtx.transaction.hash() } - fn build_current_cycles(&self) -> CurrentCycles { - CurrentCycles::new() - } - - fn build_vm_version(&self) -> VMVersion { - VMVersion::new() - } - - fn build_exec(&self, group_inputs: Indices, group_outputs: Indices) -> Exec
{ - Exec::new( - self.data_loader.clone(), - Arc::clone(&self.rtx), - Arc::clone(&self.outputs), - group_inputs, - group_outputs, - ) - } - - fn build_load_tx(&self) -> LoadTx { - LoadTx::new(Arc::clone(&self.rtx)) - } - - fn build_load_cell(&self, group_inputs: Indices, group_outputs: Indices) -> LoadCell
{ - LoadCell::new( - self.data_loader.clone(), - Arc::clone(&self.rtx), - Arc::clone(&self.outputs), - group_inputs, - group_outputs, - ) - } - - fn build_load_cell_data( - &self, - group_inputs: Indices, - group_outputs: Indices, - ) -> LoadCellData
{ - LoadCellData::new( - self.data_loader.clone(), - Arc::clone(&self.rtx), - Arc::clone(&self.outputs), - group_inputs, - group_outputs, - ) - } - - fn build_load_input(&self, group_inputs: Indices) -> LoadInput { - LoadInput::new(Arc::clone(&self.rtx), group_inputs) - } - - fn build_load_script_hash(&self, hash: Byte32) -> LoadScriptHash { - LoadScriptHash::new(hash) - } - - fn build_load_header(&self, group_inputs: Indices) -> LoadHeader
{ - LoadHeader::new( - self.data_loader.clone(), - Arc::clone(&self.rtx), - group_inputs, - ) - } - - fn build_load_witness(&self, group_inputs: Indices, group_outputs: Indices) -> LoadWitness { - LoadWitness::new(Arc::clone(&self.rtx), group_inputs, group_outputs) - } - - fn build_load_script(&self, script: Script) -> LoadScript { - LoadScript::new(script) - } - /// Extracts actual script binary either in dep cells. pub fn extract_script(&self, script: &Script) -> Result { let script_hash_type = ScriptHashType::try_from(script.hash_type()) .map_err(|err| ScriptError::InvalidScriptHashType(err.to_string()))?; match script_hash_type { - ScriptHashType::Data | ScriptHashType::Data1 => { + ScriptHashType::Data | ScriptHashType::Data1 | ScriptHashType::Data2 => { if let Some(lazy) = self.binaries_by_data_hash.get(&script.code_hash()) { Ok(lazy.access(&self.data_loader)) } else { @@ -362,6 +481,7 @@ impl match script_hash_type { ScriptHashType::Data => Ok(ScriptVersion::V0), ScriptHashType::Data1 => Ok(ScriptVersion::V1), + ScriptHashType::Data2 => Ok(ScriptVersion::V2), ScriptHashType::Type => Ok(ScriptVersion::V1), } } @@ -803,45 +923,17 @@ impl script_version: ScriptVersion, script_group: &ScriptGroup, ) -> Vec)>> { - let current_script_hash = script_group.script.calc_script_hash(); - let script_group_input_indices = Arc::new(script_group.input_indices.clone()); - let script_group_output_indices = Arc::new(script_group.output_indices.clone()); - let mut syscalls: Vec)>> = vec![ - Box::new(self.build_load_script_hash(current_script_hash.clone())), - Box::new(self.build_load_tx()), - Box::new(self.build_load_cell( - Arc::clone(&script_group_input_indices), - Arc::clone(&script_group_output_indices), - )), - Box::new(self.build_load_input(Arc::clone(&script_group_input_indices))), - Box::new(self.build_load_header(Arc::clone(&script_group_input_indices))), - Box::new(self.build_load_witness( - Arc::clone(&script_group_input_indices), - Arc::clone(&script_group_output_indices), - )), - Box::new(self.build_load_script(script_group.script.clone())), - Box::new(self.build_load_cell_data( - Arc::clone(&script_group_input_indices), - Arc::clone(&script_group_output_indices), - )), - Box::new(Debugger::new( - current_script_hash, - Arc::clone(&self.debug_printer), - )), - ]; - #[cfg(test)] - syscalls.push(Box::new(Pause::new(Arc::clone(&self.skip_pause)))); - if script_version >= ScriptVersion::V1 { - syscalls.append(&mut vec![ - Box::new(self.build_vm_version()), - Box::new(self.build_current_cycles()), - Box::new(self.build_exec( - Arc::clone(&script_group_input_indices), - Arc::clone(&script_group_output_indices), - )), - ]) - } - syscalls + let generator = TransactionScriptsSyscallsGenerator { + data_loader: self.data_loader.clone(), + debug_printer: Arc:: Fn(&'a Byte32, &'b str) + Send + Sync>::clone( + &self.debug_printer, + ), + outputs: Arc::>::clone(&self.outputs), + rtx: Arc::::clone(&self.rtx), + #[cfg(test)] + skip_pause: Arc::::clone(&self.skip_pause), + }; + generator.generate_root_syscalls(script_version, script_group) } fn build_machine( @@ -852,7 +944,7 @@ impl let script_version = self.select_version(&script_group.script)?; let core_machine = script_version.init_core_machine(max_cycles); let machine_builder = DefaultMachineBuilder::::new(core_machine) - .instruction_cycle_func(Box::new(instruction_cycles)); + .instruction_cycle_func(Box::new(estimate_cycles)); let machine_builder = self .generate_syscalls(script_version, script_group) .into_iter() diff --git a/script/src/verify/tests/ckb_2019.rs b/script/src/verify/tests/ckb_2019.rs index ab2029d3ac..3b4d058bf4 100644 --- a/script/src/verify/tests/ckb_2019.rs +++ b/script/src/verify/tests/ckb_2019.rs @@ -4,3 +4,5 @@ const SCRIPT_VERSION: crate::ScriptVersion = crate::ScriptVersion::V0; mod features_since_v2019; #[path = "ckb_latest/features_since_v2021.rs"] mod features_since_v2021; +#[path = "ckb_latest/features_since_v2023.rs"] +mod features_since_v2023; diff --git a/script/src/verify/tests/ckb_2021.rs b/script/src/verify/tests/ckb_2021.rs new file mode 100644 index 0000000000..2ea92325d7 --- /dev/null +++ b/script/src/verify/tests/ckb_2021.rs @@ -0,0 +1,8 @@ +const SCRIPT_VERSION: crate::ScriptVersion = crate::ScriptVersion::V1; + +#[path = "ckb_latest/features_since_v2019.rs"] +mod features_since_v2019; +#[path = "ckb_latest/features_since_v2021.rs"] +mod features_since_v2021; +#[path = "ckb_latest/features_since_v2023.rs"] +mod features_since_v2023; diff --git a/script/src/verify/tests/ckb_latest/features_since_v2021.rs b/script/src/verify/tests/ckb_latest/features_since_v2021.rs index 40e8d087f6..bd368402e4 100644 --- a/script/src/verify/tests/ckb_latest/features_since_v2021.rs +++ b/script/src/verify/tests/ckb_latest/features_since_v2021.rs @@ -17,6 +17,7 @@ use crate::syscalls::SOURCE_GROUP_FLAG; use crate::{ type_id::TYPE_ID_CYCLES, verify::{tests::utils::*, *}, + ScriptError, }; #[test] @@ -254,7 +255,7 @@ fn check_vm_version() { let verifier = TransactionScriptsVerifierWithEnv::new(); let result = verifier.verify_without_limit(script_version, &rtx); - assert_eq!(result.is_ok(), script_version >= ScriptVersion::V1); + assert_eq!(result.is_ok(), script_version == ScriptVersion::V1); } #[test] @@ -289,9 +290,9 @@ fn check_vm_version_with_snapshot() { let max_cycles = Cycle::MAX; let result = verifier.verify_without_pause(script_version, &rtx, max_cycles); - assert_eq!(result.is_ok(), script_version >= ScriptVersion::V1); + assert_eq!(result.is_ok(), script_version == ScriptVersion::V1); - if script_version < ScriptVersion::V1 { + if script_version != ScriptVersion::V1 { return; } diff --git a/script/src/verify/tests/ckb_latest/features_since_v2023.rs b/script/src/verify/tests/ckb_latest/features_since_v2023.rs new file mode 100644 index 0000000000..c39924e4c9 --- /dev/null +++ b/script/src/verify/tests/ckb_latest/features_since_v2023.rs @@ -0,0 +1,569 @@ +use ckb_types::{ + core::{capacity_bytes, Capacity, TransactionBuilder}, + packed::{CellInput, CellOutputBuilder, OutPoint, Script}, +}; + +use super::SCRIPT_VERSION; +use crate::verify::{tests::utils::*, *}; + +// check_vm_version: vm_version() returns 2. +// check_get_memory_limit: get_memory_limit() returns 8 in prime script. +// check_set_content: set_content() succeed in prime script but write length is 0. +// check_spawn_strcat: a smoking test for spawn(). +// check_spawn_strcat_data_hash: position child script by data hash. +// check_spawn_get_memory_limit: call get_memory_limit() in child script. +// check_spawn_set_content: set_content() with content < lenght, = length and > length. +// check_spawn_out_of_cycles: child script out-of-cycles. +// check_spawn_exec: A exec B spawn C. +// check_spawn_strcat_wrap: A spawn B spwan C. +// check_spawn_out_of_cycles_wrap: A spawn B spwan C, but C out-of-cycles. +// check_spawn_recursive: A spawn A spawn A ... ... spawn A +// check_spawn_big_memory_size: fails when memory_limit > 8. +// check_spawn_big_content_length: fails when content_length > 256K. +// check_peak_memory_4m_to_32m: spawn should success when peak memory <= 32M +// check_peak_memory_2m_to_32m: spawn should success when peak memory <= 32M + +#[test] +fn check_vm_version() { + let script_version = SCRIPT_VERSION; + + let (vm_version_cell, vm_version_data_hash) = load_cell_from_path("testdata/vm_version_2"); + + let vm_version_script = Script::new_builder() + .hash_type(script_version.data_hash_type().into()) + .code_hash(vm_version_data_hash) + .build(); + let output = CellOutputBuilder::default() + .capacity(capacity_bytes!(100).pack()) + .lock(vm_version_script) + .build(); + let input = CellInput::new(OutPoint::null(), 0); + + let transaction = TransactionBuilder::default().input(input).build(); + let dummy_cell = create_dummy_cell(output); + + let rtx = ResolvedTransaction { + transaction, + resolved_cell_deps: vec![vm_version_cell], + resolved_inputs: vec![dummy_cell], + resolved_dep_groups: vec![], + }; + + let verifier = TransactionScriptsVerifierWithEnv::new(); + let result = verifier.verify_without_limit(script_version, &rtx); + assert_eq!(result.is_ok(), script_version == ScriptVersion::V2); +} + +#[test] +fn check_get_memory_limit() { + let script_version = SCRIPT_VERSION; + + let (memory_limit_cell, memory_limit_data_hash) = + load_cell_from_path("testdata/get_memory_limit"); + + let memory_limit_script = Script::new_builder() + .hash_type(script_version.data_hash_type().into()) + .code_hash(memory_limit_data_hash) + .build(); + let output = CellOutputBuilder::default() + .capacity(capacity_bytes!(100).pack()) + .lock(memory_limit_script) + .build(); + + let input = CellInput::new(OutPoint::null(), 0); + + let transaction = TransactionBuilder::default().input(input).build(); + let dummy_cell = create_dummy_cell(output); + + let rtx = ResolvedTransaction { + transaction, + resolved_cell_deps: vec![memory_limit_cell], + resolved_inputs: vec![dummy_cell], + resolved_dep_groups: vec![], + }; + + let verifier = TransactionScriptsVerifierWithEnv::new(); + let result = verifier.verify_without_limit(script_version, &rtx); + assert_eq!(result.is_ok(), script_version >= ScriptVersion::V2); +} + +#[test] +fn check_set_content() { + let script_version = SCRIPT_VERSION; + + let (set_content_cell, set_content_data_hash) = load_cell_from_path("testdata/set_content"); + + let memory_limit_script = Script::new_builder() + .hash_type(script_version.data_hash_type().into()) + .code_hash(set_content_data_hash) + .build(); + let output = CellOutputBuilder::default() + .capacity(capacity_bytes!(100).pack()) + .lock(memory_limit_script) + .build(); + + let input = CellInput::new(OutPoint::null(), 0); + + let transaction = TransactionBuilder::default().input(input).build(); + let dummy_cell = create_dummy_cell(output); + + let rtx = ResolvedTransaction { + transaction, + resolved_cell_deps: vec![set_content_cell], + resolved_inputs: vec![dummy_cell], + resolved_dep_groups: vec![], + }; + + let verifier = TransactionScriptsVerifierWithEnv::new(); + let result = verifier.verify_without_limit(script_version, &rtx); + assert_eq!(result.is_ok(), script_version >= ScriptVersion::V2); +} + +#[test] +fn check_spawn_strcat() { + let script_version = SCRIPT_VERSION; + + let (spawn_caller_cell, spawn_caller_data_hash) = + load_cell_from_path("testdata/spawn_caller_strcat"); + let (spawn_callee_cell, _spawn_callee_data_hash) = + load_cell_from_path("testdata/spawn_callee_strcat"); + + let spawn_caller_script = Script::new_builder() + .hash_type(script_version.data_hash_type().into()) + .code_hash(spawn_caller_data_hash) + .build(); + let output = CellOutputBuilder::default() + .capacity(capacity_bytes!(100).pack()) + .lock(spawn_caller_script) + .build(); + let input = CellInput::new(OutPoint::null(), 0); + + let transaction = TransactionBuilder::default().input(input).build(); + let dummy_cell = create_dummy_cell(output); + + let rtx = ResolvedTransaction { + transaction, + resolved_cell_deps: vec![spawn_caller_cell, spawn_callee_cell], + resolved_inputs: vec![dummy_cell], + resolved_dep_groups: vec![], + }; + let verifier = TransactionScriptsVerifierWithEnv::new(); + let result = verifier.verify_without_limit(script_version, &rtx); + assert_eq!(result.is_ok(), script_version >= ScriptVersion::V2); +} + +#[test] +fn check_spawn_strcat_data_hash() { + let script_version = SCRIPT_VERSION; + + let (spawn_caller_cell, spawn_caller_data_hash) = + load_cell_from_path("testdata/spawn_caller_strcat_data_hash"); + let (spawn_callee_cell, _spawn_callee_data_hash) = + load_cell_from_path("testdata/spawn_callee_strcat"); + + let spawn_caller_script = Script::new_builder() + .hash_type(script_version.data_hash_type().into()) + .code_hash(spawn_caller_data_hash) + .build(); + let output = CellOutputBuilder::default() + .capacity(capacity_bytes!(100).pack()) + .lock(spawn_caller_script) + .build(); + let input = CellInput::new(OutPoint::null(), 0); + + let transaction = TransactionBuilder::default().input(input).build(); + let dummy_cell = create_dummy_cell(output); + + let rtx = ResolvedTransaction { + transaction, + resolved_cell_deps: vec![spawn_caller_cell, spawn_callee_cell], + resolved_inputs: vec![dummy_cell], + resolved_dep_groups: vec![], + }; + let verifier = TransactionScriptsVerifierWithEnv::new(); + let result = verifier.verify_without_limit(script_version, &rtx); + assert_eq!(result.is_ok(), script_version >= ScriptVersion::V2); +} + +#[test] +fn check_spawn_get_memory_limit() { + let script_version = SCRIPT_VERSION; + + let (spawn_caller_cell, spawn_caller_data_hash) = + load_cell_from_path("testdata/spawn_caller_get_memory_limit"); + let (spawn_callee_cell, _spawn_callee_data_hash) = + load_cell_from_path("testdata/spawn_callee_get_memory_limit"); + + let spawn_caller_script = Script::new_builder() + .hash_type(script_version.data_hash_type().into()) + .code_hash(spawn_caller_data_hash) + .build(); + let output = CellOutputBuilder::default() + .capacity(capacity_bytes!(100).pack()) + .lock(spawn_caller_script) + .build(); + let input = CellInput::new(OutPoint::null(), 0); + + let transaction = TransactionBuilder::default().input(input).build(); + let dummy_cell = create_dummy_cell(output); + + let rtx = ResolvedTransaction { + transaction, + resolved_cell_deps: vec![spawn_caller_cell, spawn_callee_cell], + resolved_inputs: vec![dummy_cell], + resolved_dep_groups: vec![], + }; + let verifier = TransactionScriptsVerifierWithEnv::new(); + let result = verifier.verify_without_limit(script_version, &rtx); + assert_eq!(result.is_ok(), script_version >= ScriptVersion::V2); +} + +#[test] +fn check_spawn_set_content() { + let script_version = SCRIPT_VERSION; + + let (spawn_caller_cell, spawn_caller_data_hash) = + load_cell_from_path("testdata/spawn_caller_set_content"); + let (spawn_callee_cell, _spawn_callee_data_hash) = + load_cell_from_path("testdata/spawn_callee_set_content"); + + let spawn_caller_script = Script::new_builder() + .hash_type(script_version.data_hash_type().into()) + .code_hash(spawn_caller_data_hash) + .build(); + let output = CellOutputBuilder::default() + .capacity(capacity_bytes!(100).pack()) + .lock(spawn_caller_script) + .build(); + let input = CellInput::new(OutPoint::null(), 0); + + let transaction = TransactionBuilder::default().input(input).build(); + let dummy_cell = create_dummy_cell(output); + + let rtx = ResolvedTransaction { + transaction, + resolved_cell_deps: vec![spawn_caller_cell, spawn_callee_cell], + resolved_inputs: vec![dummy_cell], + resolved_dep_groups: vec![], + }; + let verifier = TransactionScriptsVerifierWithEnv::new(); + let result = verifier.verify_without_limit(script_version, &rtx); + assert_eq!(result.is_ok(), script_version >= ScriptVersion::V2); +} + +#[test] +fn check_spawn_out_of_cycles() { + let script_version = SCRIPT_VERSION; + + let (spawn_caller_cell, spawn_caller_data_hash) = + load_cell_from_path("testdata/spawn_caller_out_of_cycles"); + let (spawn_callee_cell, _spawn_callee_data_hash) = + load_cell_from_path("testdata/spawn_callee_out_of_cycles"); + + let spawn_caller_script = Script::new_builder() + .hash_type(script_version.data_hash_type().into()) + .code_hash(spawn_caller_data_hash) + .build(); + let output = CellOutputBuilder::default() + .capacity(capacity_bytes!(100).pack()) + .lock(spawn_caller_script) + .build(); + let input = CellInput::new(OutPoint::null(), 0); + + let transaction = TransactionBuilder::default().input(input).build(); + let dummy_cell = create_dummy_cell(output); + + let rtx = ResolvedTransaction { + transaction, + resolved_cell_deps: vec![spawn_caller_cell, spawn_callee_cell], + resolved_inputs: vec![dummy_cell], + resolved_dep_groups: vec![], + }; + let verifier = TransactionScriptsVerifierWithEnv::new(); + let result = verifier.verify(script_version, &rtx, 0xffffff); + if script_version >= ScriptVersion::V2 { + assert!(result + .unwrap_err() + .to_string() + .contains("ExceededMaximumCycles")) + } else { + assert!(result.is_err()) + } +} + +#[test] +fn check_spawn_exec() { + let script_version = SCRIPT_VERSION; + + let (spawn_caller_cell, spawn_caller_data_hash) = + load_cell_from_path("testdata/spawn_caller_exec"); + let (spawn_callee_caller_cell, _) = load_cell_from_path("testdata/spawn_callee_exec_caller"); + let (spawn_callee_callee_cell, _) = load_cell_from_path("testdata/spawn_callee_exec_callee"); + + let spawn_caller_script = Script::new_builder() + .hash_type(script_version.data_hash_type().into()) + .code_hash(spawn_caller_data_hash) + .build(); + let output = CellOutputBuilder::default() + .capacity(capacity_bytes!(100).pack()) + .lock(spawn_caller_script) + .build(); + let input = CellInput::new(OutPoint::null(), 0); + + let transaction = TransactionBuilder::default().input(input).build(); + let dummy_cell = create_dummy_cell(output); + + let rtx = ResolvedTransaction { + transaction, + resolved_cell_deps: vec![ + spawn_caller_cell, + spawn_callee_caller_cell, + spawn_callee_callee_cell, + ], + resolved_inputs: vec![dummy_cell], + resolved_dep_groups: vec![], + }; + let verifier = TransactionScriptsVerifierWithEnv::new(); + let result = verifier.verify(script_version, &rtx, 0xffffff); + assert_eq!(result.is_ok(), script_version >= ScriptVersion::V2); +} + +#[test] +fn check_spawn_strcat_wrap() { + let script_version = SCRIPT_VERSION; + + let (spawn_caller_cell, spawn_caller_data_hash) = + load_cell_from_path("testdata/spawn_caller_strcat_wrap"); + let (spawn_callee_caller_cell, _) = load_cell_from_path("testdata/spawn_caller_strcat"); + let (spawn_callee_callee_cell, _) = load_cell_from_path("testdata/spawn_callee_strcat"); + + let spawn_caller_script = Script::new_builder() + .hash_type(script_version.data_hash_type().into()) + .code_hash(spawn_caller_data_hash) + .build(); + let output = CellOutputBuilder::default() + .capacity(capacity_bytes!(100).pack()) + .lock(spawn_caller_script) + .build(); + let input = CellInput::new(OutPoint::null(), 0); + + let transaction = TransactionBuilder::default().input(input).build(); + let dummy_cell = create_dummy_cell(output); + + let rtx = ResolvedTransaction { + transaction, + resolved_cell_deps: vec![ + spawn_caller_cell, + spawn_callee_callee_cell, + spawn_callee_caller_cell, + ], + resolved_inputs: vec![dummy_cell], + resolved_dep_groups: vec![], + }; + let verifier = TransactionScriptsVerifierWithEnv::new(); + let result = verifier.verify(script_version, &rtx, 0xffffff); + assert_eq!(result.is_ok(), script_version >= ScriptVersion::V2); +} + +#[test] +fn check_spawn_out_of_cycles_wrap() { + let script_version = SCRIPT_VERSION; + + let (spawn_caller_cell, spawn_caller_data_hash) = + load_cell_from_path("testdata/spawn_caller_out_of_cycles_wrap"); + let (spawn_callee_caller_cell, _) = load_cell_from_path("testdata/spawn_caller_out_of_cycles"); + let (spawn_callee_callee_cell, _) = load_cell_from_path("testdata/spawn_callee_out_of_cycles"); + + let spawn_caller_script = Script::new_builder() + .hash_type(script_version.data_hash_type().into()) + .code_hash(spawn_caller_data_hash) + .build(); + let output = CellOutputBuilder::default() + .capacity(capacity_bytes!(100).pack()) + .lock(spawn_caller_script) + .build(); + let input = CellInput::new(OutPoint::null(), 0); + + let transaction = TransactionBuilder::default().input(input).build(); + let dummy_cell = create_dummy_cell(output); + + let rtx = ResolvedTransaction { + transaction, + resolved_cell_deps: vec![ + spawn_caller_cell, + spawn_callee_callee_cell, + spawn_callee_caller_cell, + ], + resolved_inputs: vec![dummy_cell], + resolved_dep_groups: vec![], + }; + let verifier = TransactionScriptsVerifierWithEnv::new(); + let result = verifier.verify(script_version, &rtx, 0xffffff); + if script_version >= ScriptVersion::V2 { + assert!(result + .unwrap_err() + .to_string() + .contains("ExceededMaximumCycles")) + } else { + assert!(result.is_err()) + } +} + +#[test] +fn check_spawn_recursive() { + let script_version = SCRIPT_VERSION; + + let (spawn_caller_cell, spawn_caller_data_hash) = + load_cell_from_path("testdata/spawn_recursive"); + + let spawn_caller_script = Script::new_builder() + .hash_type(script_version.data_hash_type().into()) + .code_hash(spawn_caller_data_hash) + .build(); + let output = CellOutputBuilder::default() + .capacity(capacity_bytes!(100).pack()) + .lock(spawn_caller_script) + .build(); + let input = CellInput::new(OutPoint::null(), 0); + + let transaction = TransactionBuilder::default().input(input).build(); + let dummy_cell = create_dummy_cell(output); + + let rtx = ResolvedTransaction { + transaction, + resolved_cell_deps: vec![spawn_caller_cell], + resolved_inputs: vec![dummy_cell], + resolved_dep_groups: vec![], + }; + let verifier = TransactionScriptsVerifierWithEnv::new(); + let result = verifier.verify_without_limit(script_version, &rtx); + if script_version >= ScriptVersion::V2 { + assert!(result.unwrap_err().to_string().contains("error code 7")) + } else { + assert!(result.is_err()) + } +} + +#[test] +fn check_spawn_big_memory_size() { + let script_version = SCRIPT_VERSION; + + let (spawn_caller_cell, spawn_caller_data_hash) = + load_cell_from_path("testdata/spawn_big_memory_size"); + + let spawn_caller_script = Script::new_builder() + .hash_type(script_version.data_hash_type().into()) + .code_hash(spawn_caller_data_hash) + .build(); + let output = CellOutputBuilder::default() + .capacity(capacity_bytes!(100).pack()) + .lock(spawn_caller_script) + .build(); + let input = CellInput::new(OutPoint::null(), 0); + + let transaction = TransactionBuilder::default().input(input).build(); + let dummy_cell = create_dummy_cell(output); + + let rtx = ResolvedTransaction { + transaction, + resolved_cell_deps: vec![spawn_caller_cell], + resolved_inputs: vec![dummy_cell], + resolved_dep_groups: vec![], + }; + let verifier = TransactionScriptsVerifierWithEnv::new(); + let result = verifier.verify_without_limit(script_version, &rtx); + assert_eq!(result.is_ok(), script_version >= ScriptVersion::V2); +} + +#[test] +fn check_spawn_big_content_length() { + let script_version = SCRIPT_VERSION; + + let (spawn_caller_cell, spawn_caller_data_hash) = + load_cell_from_path("testdata/spawn_big_content_length"); + + let spawn_caller_script = Script::new_builder() + .hash_type(script_version.data_hash_type().into()) + .code_hash(spawn_caller_data_hash) + .build(); + let output = CellOutputBuilder::default() + .capacity(capacity_bytes!(100).pack()) + .lock(spawn_caller_script) + .build(); + let input = CellInput::new(OutPoint::null(), 0); + + let transaction = TransactionBuilder::default().input(input).build(); + let dummy_cell = create_dummy_cell(output); + + let rtx = ResolvedTransaction { + transaction, + resolved_cell_deps: vec![spawn_caller_cell], + resolved_inputs: vec![dummy_cell], + resolved_dep_groups: vec![], + }; + let verifier = TransactionScriptsVerifierWithEnv::new(); + let result = verifier.verify_without_limit(script_version, &rtx); + assert_eq!(result.is_ok(), script_version >= ScriptVersion::V2); +} + +#[test] +fn check_peak_memory_4m_to_32m() { + let script_version = SCRIPT_VERSION; + + let (spawn_caller_cell, spawn_caller_data_hash) = + load_cell_from_path("testdata/spawn_peak_memory_4m_to_32m"); + + let spawn_caller_script = Script::new_builder() + .hash_type(script_version.data_hash_type().into()) + .code_hash(spawn_caller_data_hash) + .build(); + let output = CellOutputBuilder::default() + .capacity(capacity_bytes!(100).pack()) + .lock(spawn_caller_script) + .build(); + let input = CellInput::new(OutPoint::null(), 0); + + let transaction = TransactionBuilder::default().input(input).build(); + let dummy_cell = create_dummy_cell(output); + + let rtx = ResolvedTransaction { + transaction, + resolved_cell_deps: vec![spawn_caller_cell], + resolved_inputs: vec![dummy_cell], + resolved_dep_groups: vec![], + }; + let verifier = TransactionScriptsVerifierWithEnv::new(); + let result = verifier.verify_without_limit(script_version, &rtx); + assert_eq!(result.is_ok(), script_version >= ScriptVersion::V2); +} + +#[test] +fn check_peak_memory_2m_to_32m() { + let script_version = SCRIPT_VERSION; + + let (spawn_caller_cell, spawn_caller_data_hash) = + load_cell_from_path("testdata/spawn_peak_memory_2m_to_32m"); + + let spawn_caller_script = Script::new_builder() + .hash_type(script_version.data_hash_type().into()) + .code_hash(spawn_caller_data_hash) + .build(); + let output = CellOutputBuilder::default() + .capacity(capacity_bytes!(100).pack()) + .lock(spawn_caller_script) + .build(); + let input = CellInput::new(OutPoint::null(), 0); + + let transaction = TransactionBuilder::default().input(input).build(); + let dummy_cell = create_dummy_cell(output); + + let rtx = ResolvedTransaction { + transaction, + resolved_cell_deps: vec![spawn_caller_cell], + resolved_inputs: vec![dummy_cell], + resolved_dep_groups: vec![], + }; + let verifier = TransactionScriptsVerifierWithEnv::new(); + let result = verifier.verify_without_limit(script_version, &rtx); + assert_eq!(result.is_ok(), script_version >= ScriptVersion::V2); +} diff --git a/script/src/verify/tests/ckb_latest/mod.rs b/script/src/verify/tests/ckb_latest/mod.rs index 52924dc1bb..ef6ddd5e75 100644 --- a/script/src/verify/tests/ckb_latest/mod.rs +++ b/script/src/verify/tests/ckb_latest/mod.rs @@ -2,3 +2,4 @@ const SCRIPT_VERSION: crate::ScriptVersion = crate::ScriptVersion::latest(); mod features_since_v2019; mod features_since_v2021; +mod features_since_v2023; diff --git a/script/src/verify/tests/mod.rs b/script/src/verify/tests/mod.rs index 62378eeba9..9729a4f943 100644 --- a/script/src/verify/tests/mod.rs +++ b/script/src/verify/tests/mod.rs @@ -5,5 +5,6 @@ pub(crate) mod utils; mod ckb_2019; -#[path = "ckb_latest/mod.rs"] mod ckb_2021; +#[path = "ckb_latest/mod.rs"] +mod ckb_2023; diff --git a/script/testdata/Makefile b/script/testdata/Makefile index e7951e3b46..3cc2566c06 100644 --- a/script/testdata/Makefile +++ b/script/testdata/Makefile @@ -37,6 +37,7 @@ ALL_BINS := jalr_zero \ current_cycles \ current_cycles_with_snapshot \ vm_version \ + vm_version_2 \ vm_version_with_snapshot \ exec_callee \ exec_caller_from_cell_data \ @@ -48,7 +49,26 @@ ALL_BINS := jalr_zero \ load_is_even_into_global \ load_is_even_with_snapshot \ load_arithmetic \ - debugger + debugger \ + get_memory_limit \ + set_content \ + spawn_big_content_length \ + spawn_big_memory_size \ + spawn_callee_exec_callee \ + spawn_callee_exec_caller \ + spawn_callee_get_memory_limit \ + spawn_callee_out_of_cycles \ + spawn_callee_set_content \ + spawn_callee_strcat \ + spawn_caller_exec \ + spawn_caller_get_memory_limit \ + spawn_caller_out_of_cycles \ + spawn_caller_out_of_cycles_wrap \ + spawn_caller_set_content \ + spawn_caller_strcat_wrap \ + spawn_caller_strcat \ + spawn_caller_strcat_data_hash \ + spawn_recursive ALL_LIBS := is_even.lib \ add1.lib sub1.lib mul2.lib div2.lib @@ -95,6 +115,7 @@ mop_adc_lock: mop_adc_lock.S current_cycles: current_cycles.c current_cycles_with_snapshot: current_cycles_with_snapshot.c vm_version: vm_version.c +vm_version_2: vm_version_2.c vm_version_with_snapshot: vm_version_with_snapshot.c exec_callee: exec_callee.c exec_caller_from_cell_data: exec_caller_from_cell_data.c @@ -111,3 +132,23 @@ sub1.lib: sub1.c mul2.lib: mul2.c div2.lib: div2.c load_arithmetic: load_arithmetic.c + +get_memory_limit: get_memory_limit.c +set_content: set_content.c +spawn_big_content_length: spawn_big_content_length.c +spawn_big_memory_size: spawn_big_memory_size.c +spawn_callee_exec_callee: spawn_callee_exec_callee.c +spawn_callee_exec_caller: spawn_callee_exec_caller.c +spawn_callee_get_memory_limit: spawn_callee_get_memory_limit.c +spawn_callee_out_of_cycles: spawn_callee_out_of_cycles.c +spawn_callee_set_content: spawn_callee_out_of_cycles.c +spawn_callee_strcat: spawn_callee_strcat.c +spawn_caller_exec: spawn_caller_exec.c +spawn_caller_get_memory_limit: spawn_caller_get_memory_limit.c +spawn_caller_out_of_cycles: spawn_caller_out_of_cycles.c +spawn_caller_out_of_cycles_wrap: spawn_caller_out_of_cycles_wrap.c +spawn_caller_set_content: spawn_caller_set_content.c +spawn_caller_strcat_wrap: spawn_caller_strcat_wrap.c +spawn_caller_strcat: spawn_caller_strcat.c +spawn_caller_strcat_data_hash: spawn_caller_strcat_data_hash.c +spawn_recursive: spawn_recursive.c diff --git a/script/testdata/get_memory_limit b/script/testdata/get_memory_limit new file mode 100755 index 0000000000..83529f8502 Binary files /dev/null and b/script/testdata/get_memory_limit differ diff --git a/script/testdata/get_memory_limit.c b/script/testdata/get_memory_limit.c new file mode 100644 index 0000000000..a148ad4713 --- /dev/null +++ b/script/testdata/get_memory_limit.c @@ -0,0 +1,8 @@ +#include "ckb_syscalls.h" + +int main() { + if (ckb_get_memory_limit() == 8) { + return 0; + } + return 1; +} diff --git a/script/testdata/set_content b/script/testdata/set_content new file mode 100755 index 0000000000..9a0ae8c2c2 Binary files /dev/null and b/script/testdata/set_content differ diff --git a/script/testdata/set_content.c b/script/testdata/set_content.c new file mode 100644 index 0000000000..b6b80a211b --- /dev/null +++ b/script/testdata/set_content.c @@ -0,0 +1,12 @@ +#include "ckb_syscalls.h" + +int main() { + uint64_t length = 5; + if (ckb_set_content((uint8_t *)"hello", &length) != 0) { + return 1; + } + if (length != 0) { + return 1; + } + return 0; +} diff --git a/script/testdata/spawn_big_content_length b/script/testdata/spawn_big_content_length new file mode 100755 index 0000000000..b3ee51e0f5 Binary files /dev/null and b/script/testdata/spawn_big_content_length differ diff --git a/script/testdata/spawn_big_content_length.c b/script/testdata/spawn_big_content_length.c new file mode 100644 index 0000000000..94c44fe379 --- /dev/null +++ b/script/testdata/spawn_big_content_length.c @@ -0,0 +1,13 @@ +#include +#include + +#include "ckb_syscalls.h" + +int main() { + uint64_t content_length = 0xffffffff; + int success = ckb_spawn(8, 1, 3, 0, 0, NULL, NULL, NULL, &content_length); + if (success != 5) { + return 1; + } + return 0; +} diff --git a/script/testdata/spawn_big_memory_size b/script/testdata/spawn_big_memory_size new file mode 100755 index 0000000000..d2063a959e Binary files /dev/null and b/script/testdata/spawn_big_memory_size differ diff --git a/script/testdata/spawn_big_memory_size.c b/script/testdata/spawn_big_memory_size.c new file mode 100644 index 0000000000..6490316fdd --- /dev/null +++ b/script/testdata/spawn_big_memory_size.c @@ -0,0 +1,12 @@ +#include +#include + +#include "ckb_syscalls.h" + +int main() { + int success = ckb_spawn(9, 1, 3, 0, 0, NULL, NULL, NULL, NULL); + if (success != 6) { + return 1; + } + return 0; +} diff --git a/script/testdata/spawn_callee_exec_callee b/script/testdata/spawn_callee_exec_callee new file mode 100755 index 0000000000..477a948625 Binary files /dev/null and b/script/testdata/spawn_callee_exec_callee differ diff --git a/script/testdata/spawn_callee_exec_callee.c b/script/testdata/spawn_callee_exec_callee.c new file mode 100644 index 0000000000..76e8197013 --- /dev/null +++ b/script/testdata/spawn_callee_exec_callee.c @@ -0,0 +1 @@ +int main() { return 0; } diff --git a/script/testdata/spawn_callee_exec_caller b/script/testdata/spawn_callee_exec_caller new file mode 100755 index 0000000000..315d852c4b Binary files /dev/null and b/script/testdata/spawn_callee_exec_caller differ diff --git a/script/testdata/spawn_callee_exec_caller.c b/script/testdata/spawn_callee_exec_caller.c new file mode 100644 index 0000000000..22723c1f0d --- /dev/null +++ b/script/testdata/spawn_callee_exec_caller.c @@ -0,0 +1,6 @@ +#include "ckb_syscalls.h" + +int main() { + syscall(2043, 2, 3, 0, 0, 0, NULL); + return -1; +} diff --git a/script/testdata/spawn_callee_get_memory_limit b/script/testdata/spawn_callee_get_memory_limit new file mode 100755 index 0000000000..715691fdc7 Binary files /dev/null and b/script/testdata/spawn_callee_get_memory_limit differ diff --git a/script/testdata/spawn_callee_get_memory_limit.c b/script/testdata/spawn_callee_get_memory_limit.c new file mode 100644 index 0000000000..574544f877 --- /dev/null +++ b/script/testdata/spawn_callee_get_memory_limit.c @@ -0,0 +1,3 @@ +#include "ckb_syscalls.h" + +int main() { return ckb_get_memory_limit(); } diff --git a/script/testdata/spawn_callee_out_of_cycles b/script/testdata/spawn_callee_out_of_cycles new file mode 100755 index 0000000000..46c8861b15 Binary files /dev/null and b/script/testdata/spawn_callee_out_of_cycles differ diff --git a/script/testdata/spawn_callee_out_of_cycles.c b/script/testdata/spawn_callee_out_of_cycles.c new file mode 100644 index 0000000000..dbaa71d22a --- /dev/null +++ b/script/testdata/spawn_callee_out_of_cycles.c @@ -0,0 +1,8 @@ +int fib(int n) { + if (n < 2) { + return n; + } + return fib(n - 1) + fib(n - 2); +} + +int main() { return fib(100); } diff --git a/script/testdata/spawn_callee_set_content b/script/testdata/spawn_callee_set_content new file mode 100755 index 0000000000..34a782f24e Binary files /dev/null and b/script/testdata/spawn_callee_set_content differ diff --git a/script/testdata/spawn_callee_set_content.c b/script/testdata/spawn_callee_set_content.c new file mode 100644 index 0000000000..ed376ba5ce --- /dev/null +++ b/script/testdata/spawn_callee_set_content.c @@ -0,0 +1,18 @@ +#include +#include + +#include "ckb_syscalls.h" + +int main(int argc, char *argv[]) { + uint64_t size = (uint64_t)atoi(argv[0]); + uint64_t real = (uint64_t)atoi(argv[1]); + uint8_t data[20] = {}; + int success = ckb_set_content(&data[0], &size); + if (success != 0) { + return 1; + } + if (size != real) { + return 1; + } + return 0; +} diff --git a/script/testdata/spawn_callee_strcat b/script/testdata/spawn_callee_strcat new file mode 100755 index 0000000000..c2ac8c4342 Binary files /dev/null and b/script/testdata/spawn_callee_strcat differ diff --git a/script/testdata/spawn_callee_strcat.c b/script/testdata/spawn_callee_strcat.c new file mode 100644 index 0000000000..f2f3f56e6c --- /dev/null +++ b/script/testdata/spawn_callee_strcat.c @@ -0,0 +1,17 @@ +#include +#include + +#include "ckb_syscalls.h" + +int main(int argc, char *argv[]) { + char content[80]; + for (int i = 0; i < argc; i++) { + strcat(content, argv[i]); + } + uint64_t content_size = (uint64_t)strlen(content); + ckb_set_content((uint8_t *)&content[0], &content_size); + if (content_size != (uint64_t)strlen(content)) { + return 1; + } + return 0; +} diff --git a/script/testdata/spawn_caller_exec b/script/testdata/spawn_caller_exec new file mode 100755 index 0000000000..cbbefa0504 Binary files /dev/null and b/script/testdata/spawn_caller_exec differ diff --git a/script/testdata/spawn_caller_exec.c b/script/testdata/spawn_caller_exec.c new file mode 100644 index 0000000000..555c01e4d6 --- /dev/null +++ b/script/testdata/spawn_caller_exec.c @@ -0,0 +1,6 @@ +#include +#include + +#include "ckb_syscalls.h" + +int main() { return ckb_spawn(8, 1, 3, 0, 0, NULL, NULL, NULL, NULL); } diff --git a/script/testdata/spawn_caller_get_memory_limit b/script/testdata/spawn_caller_get_memory_limit new file mode 100755 index 0000000000..917118361c Binary files /dev/null and b/script/testdata/spawn_caller_get_memory_limit differ diff --git a/script/testdata/spawn_caller_get_memory_limit.c b/script/testdata/spawn_caller_get_memory_limit.c new file mode 100644 index 0000000000..a07b3edf6d --- /dev/null +++ b/script/testdata/spawn_caller_get_memory_limit.c @@ -0,0 +1,40 @@ +#include +#include + +#include "ckb_syscalls.h" + +int main() { + int8_t exit_code = 255; + uint8_t content[80] = {}; + uint64_t content_length = 80; + uint64_t success = 0; + + success = + ckb_spawn(3, 1, 3, 0, 0, NULL, &exit_code, &content[0], &content_length); + if (success != 0) { + return 1; + } + if (exit_code != 3) { + return 1; + } + + success = + ckb_spawn(7, 1, 3, 0, 0, NULL, &exit_code, &content[0], &content_length); + if (success != 0) { + return 1; + } + if (exit_code != 7) { + return 1; + } + + success = + ckb_spawn(8, 1, 3, 0, 0, NULL, &exit_code, &content[0], &content_length); + if (success != 0) { + return 1; + } + if (exit_code != 8) { + return 1; + } + + return 0; +} diff --git a/script/testdata/spawn_caller_out_of_cycles b/script/testdata/spawn_caller_out_of_cycles new file mode 100755 index 0000000000..0c6c763520 Binary files /dev/null and b/script/testdata/spawn_caller_out_of_cycles differ diff --git a/script/testdata/spawn_caller_out_of_cycles.c b/script/testdata/spawn_caller_out_of_cycles.c new file mode 100644 index 0000000000..3642385036 --- /dev/null +++ b/script/testdata/spawn_caller_out_of_cycles.c @@ -0,0 +1,12 @@ +#include +#include + +#include "ckb_syscalls.h" + +int main() { + uint64_t success = ckb_spawn(8, 1, 3, 0, 0, NULL, NULL, NULL, NULL); + if (success == 0) { + return 1; + } + return 0; +} diff --git a/script/testdata/spawn_caller_out_of_cycles_wrap b/script/testdata/spawn_caller_out_of_cycles_wrap new file mode 100755 index 0000000000..da53965757 Binary files /dev/null and b/script/testdata/spawn_caller_out_of_cycles_wrap differ diff --git a/script/testdata/spawn_caller_out_of_cycles_wrap.c b/script/testdata/spawn_caller_out_of_cycles_wrap.c new file mode 100644 index 0000000000..05ceee4246 --- /dev/null +++ b/script/testdata/spawn_caller_out_of_cycles_wrap.c @@ -0,0 +1,13 @@ +#include +#include + +#include "ckb_syscalls.h" + +int main() { + int8_t exit_code = 255; + uint64_t success = ckb_spawn(8, 2, 3, 0, 0, NULL, &exit_code, NULL, NULL); + if (success != 0) { + return 1; + } + return exit_code; +} diff --git a/script/testdata/spawn_caller_set_content b/script/testdata/spawn_caller_set_content new file mode 100755 index 0000000000..85c5dfd927 Binary files /dev/null and b/script/testdata/spawn_caller_set_content differ diff --git a/script/testdata/spawn_caller_set_content.c b/script/testdata/spawn_caller_set_content.c new file mode 100644 index 0000000000..bb996739b7 --- /dev/null +++ b/script/testdata/spawn_caller_set_content.c @@ -0,0 +1,80 @@ +#include +#include + +#include "ckb_syscalls.h" + +int main_lt_content_length() { + int8_t exit_code = -1; + uint8_t content[10] = {}; + uint64_t content_length = 10; + const char *argv[] = {"8", "8"}; + uint64_t success = 0; + + success = + ckb_spawn(8, 1, 3, 0, 2, argv, &exit_code, &content[0], &content_length); + if (success != 0) { + return 1; + } + if (exit_code != 0) { + return 1; + } + if (content_length != 8) { + return 1; + } + return 0; +} + +int main_eq_content_length() { + int8_t exit_code = -1; + uint8_t content[10] = {}; + uint64_t content_length = 10; + const char *argv[] = {"10", "10"}; + uint64_t success = 0; + + success = + ckb_spawn(8, 1, 3, 0, 2, argv, &exit_code, &content[0], &content_length); + if (success != 0) { + return 1; + } + if (exit_code != 0) { + return 1; + } + if (content_length != 10) { + return 1; + } + return 0; +} + +int main_gt_content_length() { + int8_t exit_code = -1; + uint8_t content[10] = {}; + uint64_t content_length = 10; + const char *argv[] = {"12", "10"}; + uint64_t success = 0; + + success = + ckb_spawn(8, 1, 3, 0, 2, argv, &exit_code, &content[0], &content_length); + if (success != 0) { + return 1; + } + if (exit_code != 0) { + return 1; + } + if (content_length != 10) { + return 1; + } + return 0; +} + +int main() { + if (main_lt_content_length() != 0) { + return 1; + } + if (main_eq_content_length() != 0) { + return 1; + } + if (main_gt_content_length() != 0) { + return 1; + } + return 0; +} diff --git a/script/testdata/spawn_caller_strcat b/script/testdata/spawn_caller_strcat new file mode 100755 index 0000000000..7483b03f8b Binary files /dev/null and b/script/testdata/spawn_caller_strcat differ diff --git a/script/testdata/spawn_caller_strcat.c b/script/testdata/spawn_caller_strcat.c new file mode 100644 index 0000000000..3d581ec00e --- /dev/null +++ b/script/testdata/spawn_caller_strcat.c @@ -0,0 +1,27 @@ +#include +#include + +#include "ckb_syscalls.h" + +int main() { + const char *argv[] = {"hello", "world"}; + int8_t exit_code = 255; + uint8_t content[80] = {}; + uint64_t content_length = 80; + + int success = + ckb_spawn(8, 1, 3, 0, 2, argv, &exit_code, &content[0], &content_length); + if (success != 0) { + return 1; + } + if (exit_code != 0) { + return 1; + } + if (strlen((char *)content) != 10) { + return 1; + } + if (strcmp((char *)content, "helloworld") != 0) { + return 1; + } + return 0; +} diff --git a/script/testdata/spawn_caller_strcat_data_hash b/script/testdata/spawn_caller_strcat_data_hash new file mode 100755 index 0000000000..32ab33f224 Binary files /dev/null and b/script/testdata/spawn_caller_strcat_data_hash differ diff --git a/script/testdata/spawn_caller_strcat_data_hash.c b/script/testdata/spawn_caller_strcat_data_hash.c new file mode 100644 index 0000000000..d5dd9bb484 --- /dev/null +++ b/script/testdata/spawn_caller_strcat_data_hash.c @@ -0,0 +1,33 @@ +#include +#include +#include + +#include "ckb_exec.h" +#include "ckb_syscalls.h" + +int main() { + const char *argv[] = {"hello", "world"}; + int8_t exit_code = 255; + uint8_t content[80] = {}; + uint64_t content_length = 80; + uint8_t hash[32] = {}; + uint32_t hash_len = 0; + _exec_hex2bin( + "b27be1358859d2bedced95e5616941fbbc4e5d4d043ad813cfe37ccec767c303", hash, + 32, &hash_len); + int success = ckb_spawn_cell(8, hash, 0, 0, 0, 2, argv, &exit_code, + &content[0], &content_length); + if (success != 0) { + return 1; + } + if (exit_code != 0) { + return 1; + } + if (strlen((char *)content) != 10) { + return 1; + } + if (strcmp((char *)content, "helloworld") != 0) { + return 1; + } + return 0; +} diff --git a/script/testdata/spawn_caller_strcat_wrap b/script/testdata/spawn_caller_strcat_wrap new file mode 100755 index 0000000000..da53965757 Binary files /dev/null and b/script/testdata/spawn_caller_strcat_wrap differ diff --git a/script/testdata/spawn_caller_strcat_wrap.c b/script/testdata/spawn_caller_strcat_wrap.c new file mode 100644 index 0000000000..05ceee4246 --- /dev/null +++ b/script/testdata/spawn_caller_strcat_wrap.c @@ -0,0 +1,13 @@ +#include +#include + +#include "ckb_syscalls.h" + +int main() { + int8_t exit_code = 255; + uint64_t success = ckb_spawn(8, 2, 3, 0, 0, NULL, &exit_code, NULL, NULL); + if (success != 0) { + return 1; + } + return exit_code; +} diff --git a/script/testdata/spawn_peak_memory_2m_to_32m b/script/testdata/spawn_peak_memory_2m_to_32m new file mode 100755 index 0000000000..c172d7fe77 Binary files /dev/null and b/script/testdata/spawn_peak_memory_2m_to_32m differ diff --git a/script/testdata/spawn_peak_memory_2m_to_32m.c b/script/testdata/spawn_peak_memory_2m_to_32m.c new file mode 100644 index 0000000000..88167a4fda --- /dev/null +++ b/script/testdata/spawn_peak_memory_2m_to_32m.c @@ -0,0 +1,30 @@ +#include +#include +#include + +#include "ckb_syscalls.h" + +int main(int argc, char *argv[]) { + int8_t exit_code = 255; + int8_t can_i_spawn = 0; + if (argc == 0) { + can_i_spawn = 1; + } + uint64_t depth = (uint64_t)atoi(argv[0]); + if (depth < 14) { + can_i_spawn = 1; + } + if (can_i_spawn) { + char buffer[20]; + itoa(depth + 1, buffer, 10); + const char *argv[] = {buffer}; + uint64_t success = ckb_spawn(4, 0, 3, 0, 1, argv, &exit_code, NULL, NULL); + if (success != 0) { + return success; + } + if (exit_code != 0) { + return 1; + } + } + return 0; +} diff --git a/script/testdata/spawn_peak_memory_4m_to_32m b/script/testdata/spawn_peak_memory_4m_to_32m new file mode 100755 index 0000000000..a0b2f3b624 Binary files /dev/null and b/script/testdata/spawn_peak_memory_4m_to_32m differ diff --git a/script/testdata/spawn_peak_memory_4m_to_32m.c b/script/testdata/spawn_peak_memory_4m_to_32m.c new file mode 100644 index 0000000000..2ac0d67b0e --- /dev/null +++ b/script/testdata/spawn_peak_memory_4m_to_32m.c @@ -0,0 +1,30 @@ +#include +#include +#include + +#include "ckb_syscalls.h" + +int main(int argc, char *argv[]) { + int8_t exit_code = 255; + int8_t can_i_spawn = 0; + if (argc == 0) { + can_i_spawn = 1; + } + uint64_t depth = (uint64_t)atoi(argv[0]); + if (depth < 7) { + can_i_spawn = 1; + } + if (can_i_spawn) { + char buffer[20]; + itoa(depth + 1, buffer, 10); + const char *argv[] = {buffer}; + uint64_t success = ckb_spawn(8, 0, 3, 0, 1, argv, &exit_code, NULL, NULL); + if (success != 0) { + return success; + } + if (exit_code != 0) { + return 1; + } + } + return 0; +} diff --git a/script/testdata/spawn_recursive b/script/testdata/spawn_recursive new file mode 100755 index 0000000000..f7a13029a5 Binary files /dev/null and b/script/testdata/spawn_recursive differ diff --git a/script/testdata/spawn_recursive.c b/script/testdata/spawn_recursive.c new file mode 100644 index 0000000000..11f9f0167d --- /dev/null +++ b/script/testdata/spawn_recursive.c @@ -0,0 +1,13 @@ +#include +#include + +#include "ckb_syscalls.h" + +int main() { + int8_t exit_code = 255; + uint64_t success = ckb_spawn(8, 0, 3, 0, 0, NULL, &exit_code, NULL, NULL); + if (success != 0) { + return success; + } + return exit_code; +} diff --git a/script/testdata/vm_version_2 b/script/testdata/vm_version_2 new file mode 100755 index 0000000000..13ea61a6ca Binary files /dev/null and b/script/testdata/vm_version_2 differ diff --git a/script/testdata/vm_version_2.c b/script/testdata/vm_version_2.c new file mode 100644 index 0000000000..2b1cff6e02 --- /dev/null +++ b/script/testdata/vm_version_2.c @@ -0,0 +1,8 @@ +#include "ckb_syscalls.h" + +int main() { + if (syscall(2041, 0, 0, 0, 0, 0, 0) == 2) { + return 0; + } + return 1; +} diff --git a/spec/src/lib.rs b/spec/src/lib.rs index c04991975e..cd69fea49d 100644 --- a/spec/src/lib.rs +++ b/spec/src/lib.rs @@ -706,6 +706,15 @@ impl ChainSpec { .into()); } } + ScriptHashType::Data2 => { + if !data_hashes.contains_key(&lock_script.code_hash()) { + return Err(format!( + "Invalid lock script: code_hash={}, hash_type=data2", + lock_script.code_hash(), + ) + .into()); + } + } } } diff --git a/util/jsonrpc-types/src/blockchain.rs b/util/jsonrpc-types/src/blockchain.rs index 439a35c5ea..d7aabab741 100644 --- a/util/jsonrpc-types/src/blockchain.rs +++ b/util/jsonrpc-types/src/blockchain.rs @@ -25,6 +25,8 @@ pub enum ScriptHashType { Type = 1, /// Type "data1" matches script code via cell data hash, and run the script code in v1 CKB VM. Data1 = 2, + /// Type "data2" matches script code via cell data hash, and run the script code in v2 CKB VM. + Data2 = 3, } impl Default for ScriptHashType { @@ -39,6 +41,7 @@ impl From for core::ScriptHashType { ScriptHashType::Data => core::ScriptHashType::Data, ScriptHashType::Type => core::ScriptHashType::Type, ScriptHashType::Data1 => core::ScriptHashType::Data1, + ScriptHashType::Data2 => core::ScriptHashType::Data2, } } } @@ -49,6 +52,7 @@ impl From for ScriptHashType { core::ScriptHashType::Data => ScriptHashType::Data, core::ScriptHashType::Type => ScriptHashType::Type, core::ScriptHashType::Data1 => ScriptHashType::Data1, + core::ScriptHashType::Data2 => ScriptHashType::Data2, } } } @@ -59,6 +63,7 @@ impl fmt::Display for ScriptHashType { Self::Data => write!(f, "data"), Self::Type => write!(f, "type"), Self::Data1 => write!(f, "data1"), + Self::Data2 => write!(f, "data2"), } } } diff --git a/util/jsonrpc-types/src/tests/blockchain.rs b/util/jsonrpc-types/src/tests/blockchain.rs index 825cf1007a..a82be1a06b 100644 --- a/util/jsonrpc-types/src/tests/blockchain.rs +++ b/util/jsonrpc-types/src/tests/blockchain.rs @@ -94,6 +94,19 @@ fn test_script_serialization() { args: JsonBytes::default(), }, ), + ( + "{\ + \"code_hash\":\"0x00000000000000000000000000000000\ + 00000000000000000000000000000001\",\ + \"hash_type\":\"data2\",\ + \"args\":\"0x\"\ + }", + Script { + code_hash: h256!("0x1"), + hash_type: ScriptHashType::Data2, + args: JsonBytes::default(), + }, + ), ] { let decoded: Script = serde_json::from_str(original).unwrap(); assert_eq!(&decoded, entity); @@ -118,12 +131,6 @@ fn test_script_serialization() { \"hash_type\":type,\ \"args\":\"0x\"\ }", - "{\ - \"code_hash\":\"0x00000000000000000000000000000000\ - 00000000000000000000000000000000\",\ - \"hash_type\":\"data2\",\ - \"args\":\"0x\"\ - }", "{\ \"code_hash\":\"0x00000000000000000000000000000000\ 00000000000000000000000000000000\",\ diff --git a/util/types/src/core/blockchain.rs b/util/types/src/core/blockchain.rs index 9c6e4a1e67..b28a126433 100644 --- a/util/types/src/core/blockchain.rs +++ b/util/types/src/core/blockchain.rs @@ -11,6 +11,8 @@ pub enum ScriptHashType { Type = 1, /// Type "data1" matches script code via cell data hash, and run the script code in v1 CKB VM. Data1 = 2, + /// Type "data2" matches script code via cell data hash, and run the script code in v2 CKB VM. + Data2 = 3, } impl Default for ScriptHashType { @@ -27,6 +29,7 @@ impl TryFrom for ScriptHashType { 0 => Ok(ScriptHashType::Data), 1 => Ok(ScriptHashType::Type), 2 => Ok(ScriptHashType::Data1), + 3 => Ok(ScriptHashType::Data2), _ => Err(OtherError::new(format!("Invalid script hash type {v}"))), } } @@ -43,7 +46,7 @@ impl TryFrom for ScriptHashType { impl ScriptHashType { #[inline] pub(crate) fn verify_value(v: u8) -> bool { - v <= 2 + v <= 3 } } @@ -54,6 +57,7 @@ impl Into for ScriptHashType { Self::Data => 0, Self::Type => 1, Self::Data1 => 2, + Self::Data2 => 3, } } } diff --git a/util/types/src/core/tests/blockchain.rs b/util/types/src/core/tests/blockchain.rs index c849f0becd..0fa63847de 100644 --- a/util/types/src/core/tests/blockchain.rs +++ b/util/types/src/core/tests/blockchain.rs @@ -10,7 +10,7 @@ fn test_script_hash_type() { let default_value: u8 = default.into(); assert_eq!(default_value, 0); - let max_value = 2u8; + let max_value = 3u8; for v in 0..32 { let res = ScriptHashType::try_from(v); if v <= max_value { diff --git a/util/types/src/extension/tests/check_data.rs b/util/types/src/extension/tests/check_data.rs index 54bc2840ed..05489411dd 100644 --- a/util/types/src/extension/tests/check_data.rs +++ b/util/types/src/extension/tests/check_data.rs @@ -28,11 +28,11 @@ fn test_check_data_via_transaction( #[test] fn check_data() { - for ht in 0..3 { + for ht in 0..4 { for dt in 0..2 { let ht_right = ht.into(); let dt_right = dt.into(); - let ht_error = 3.into(); + let ht_error = 4.into(); let dt_error = 2.into(); let script_right = packed::Script::new_builder().hash_type(ht_right).build();