diff --git a/script/src/lib.rs b/script/src/lib.rs index 91c42e0c16..10f0f96757 100644 --- a/script/src/lib.rs +++ b/script/src/lib.rs @@ -11,7 +11,6 @@ mod verify; mod verify_env; pub use crate::error::{ScriptError, TransactionScriptError}; -pub use crate::syscalls::spawn::update_caller_machine; pub use crate::types::{ ChunkCommand, CoreMachine, MachineContext, ResumableMachine, ScriptGroup, ScriptGroupType, ScriptVersion, TransactionSnapshot, TransactionState, VerifyResult, VmIsa, VmVersion, diff --git a/script/src/syscalls/current_memory.rs b/script/src/syscalls/current_memory.rs deleted file mode 100644 index 401d778191..0000000000 --- a/script/src/syscalls/current_memory.rs +++ /dev/null @@ -1,30 +0,0 @@ -use crate::syscalls::CURRENT_MEMORY; -use ckb_vm::{ - registers::{A0, A7}, - Error as VMError, Register, SupportMachine, Syscalls, -}; - -#[derive(Debug, Default)] -pub struct CurrentMemory { - value: u64, -} - -impl CurrentMemory { - pub fn new(value: u64) -> Self { - Self { value } - } -} - -impl Syscalls for CurrentMemory { - fn initialize(&mut self, _machine: &mut Mac) -> Result<(), VMError> { - Ok(()) - } - - fn ecall(&mut self, machine: &mut Mac) -> Result { - if machine.registers()[A7].to_u64() != CURRENT_MEMORY { - return Ok(false); - } - machine.set_register(A0, Mac::REG::from_u64(self.value)); - Ok(true) - } -} diff --git a/script/src/syscalls/get_memory_limit.rs b/script/src/syscalls/get_memory_limit.rs deleted file mode 100644 index 7b28887ce1..0000000000 --- a/script/src/syscalls/get_memory_limit.rs +++ /dev/null @@ -1,30 +0,0 @@ -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 0bdfdbf4be..15e23e1462 100644 --- a/script/src/syscalls/mod.rs +++ b/script/src/syscalls/mod.rs @@ -1,9 +1,7 @@ mod close; mod current_cycles; -mod current_memory; mod debugger; mod exec; -mod get_memory_limit; mod inherited_fd; mod load_block_extension; mod load_cell; @@ -17,7 +15,6 @@ mod load_witness; mod pipe; mod process_id; mod read; -mod set_content; pub(crate) mod spawn; mod utils; mod vm_version; @@ -32,10 +29,8 @@ mod tests; pub use self::close::Close; pub use self::current_cycles::CurrentCycles; -pub use self::current_memory::CurrentMemory; pub use self::debugger::Debugger; pub use self::exec::Exec; -pub use self::get_memory_limit::GetMemoryLimit; pub use self::inherited_fd::InheritedFd; pub use self::load_block_extension::LoadBlockExtension; pub use self::load_cell::LoadCell; @@ -49,7 +44,6 @@ pub use self::load_witness::LoadWitness; pub use self::pipe::Pipe; pub use self::process_id::ProcessID; pub use self::read::Read; -pub use self::set_content::SetContent; pub use self::spawn::Spawn; pub use self::vm_version::VMVersion; pub use self::wait::Wait; @@ -69,9 +63,6 @@ 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; @@ -98,9 +89,6 @@ pub const WRITE: u64 = 2605; pub const READ: u64 = 2606; pub const INHERITED_FD: u64 = 2607; pub const CLOSE: u64 = 2608; -pub const GET_MEMORY_LIMIT: u64 = 2102; -pub const SET_CONTENT: u64 = 2103; -pub const CURRENT_MEMORY: u64 = 2105; pub const DEBUG_PRINT_SYSCALL_NUMBER: u64 = 2177; #[cfg(test)] pub const DEBUG_PAUSE: u64 = 2178; diff --git a/script/src/syscalls/set_content.rs b/script/src/syscalls/set_content.rs deleted file mode 100644 index c1442c30c9..0000000000 --- a/script/src/syscalls/set_content.rs +++ /dev/null @@ -1,51 +0,0 @@ -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 index ad903e81b8..5a91232c74 100644 --- a/script/src/syscalls/spawn.rs +++ b/script/src/syscalls/spawn.rs @@ -1,95 +1,42 @@ 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_EXTRA_CYCLES_BASE, - SPAWN_EXTRA_CYCLES_PER_MEMORY_PAGE, SPAWN_MAX_CONTENT_LENGTH, SPAWN_MAX_MEMORY, - SPAWN_MAX_PEAK_MEMORY, SPAWN_MEMORY_PAGE_SIZE, SPAWN_WRONG_MEMORY_LIMIT, WRONG_FORMAT, -}; -use crate::types::{ - set_vm_max_cycles, CoreMachineType, Machine, MachineContext, ResumableMachine, SpawnData, -}; -use crate::TransactionScriptsSyscallsGenerator; -use crate::{ScriptGroup, ScriptVersion}; +use crate::syscalls::{INDEX_OUT_OF_BOUND, SLICE_OUT_OF_BOUND, SPAWN, SPAWN_EXTRA_CYCLES_BASE}; +use crate::v2_types::{DataPieceId, Message, PipeId, SpawnArgs, TxData, VmId}; use ckb_traits::{CellDataProvider, ExtensionProvider, HeaderProvider}; -use ckb_types::core::cell::CellMeta; use ckb_vm::{ - cost_model::estimate_cycles, - registers::{A0, A1, A2, A3, A4, A5, A7}, - DefaultMachineBuilder, Error as VMError, Memory, Register, SupportMachine, Syscalls, + machine::SupportMachine, + memory::Memory, + registers::{A0, A1, A2, A3, A4, A7}, + snapshot2::{DataSource, Snapshot2Context}, + syscalls::Syscalls, + Error as VMError, Register, }; use std::sync::{Arc, Mutex}; -pub struct Spawn
{ - script_group: ScriptGroup, - script_version: ScriptVersion, - syscalls_generator: TransactionScriptsSyscallsGenerator
, - peak_memory: u64, - cycles_base: u64, - context: Arc>, +pub struct Spawn
+where + DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, +{ + id: VmId, + message_box: Arc>>, + snapshot2_context: Arc>>>, } -impl Spawn
{ +impl
Spawn
+where + DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, +{ pub fn new( - script_group: ScriptGroup, - script_version: ScriptVersion, - syscalls_generator: TransactionScriptsSyscallsGenerator
, - peak_memory: u64, - cycles_base: u64, - context: Arc>, + id: VmId, + message_box: Arc>>, + snapshot2_context: Arc>>>, ) -> Self { Self { - script_group, - script_version, - syscalls_generator, - peak_memory, - cycles_base, - context, + id, + message_box, + snapshot2_context, } } - - fn data_loader(&self) -> &DL { - &self.syscalls_generator.data_loader - } - - fn outputs(&self) -> &Vec { - &self.syscalls_generator.outputs - } - - #[inline] - fn resolved_inputs(&self) -> &Vec { - &self.syscalls_generator.rtx.resolved_inputs - } - - #[inline] - fn resolved_cell_deps(&self) -> &Vec { - &self.syscalls_generator.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 - .script_group - .input_indices - .get(index) - .and_then(|actual_index| self.resolved_inputs().get(*actual_index)), - Source::Group(SourceEntry::Output) => self - .script_group - .output_indices - .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
@@ -105,243 +52,110 @@ where 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 spawn_data = SpawnData { - callee_peak_memory: self.peak_memory + memory_limit, - callee_memory_limit: memory_limit, - content: Arc::new(Mutex::new(Vec::::new())), - content_length: content_length.to_u64(), - caller_exit_code_addr: exit_code_addr.to_u64(), - caller_content_addr: content_addr.to_u64(), - caller_content_length_addr: content_length_addr.to_u64(), - cycles_base: self.cycles_base + machine.cycles(), - }; - let mut machine_child = build_child_machine( - &self.script_group, - self.script_version, - &self.syscalls_generator, - cycles_limit, - &spawn_data, - &self.context, - )?; - // 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)); + let source = machine.registers()[A1].to_u64(); + let place = machine.registers()[A2].to_u64(); + let data_piece_id = match DataPieceId::try_from((source, index, place)) { + Ok(id) => id, + Err(_) => { + machine.set_register(A0, Mac::REG::from_u8(INDEX_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(); + let bounds = machine.registers()[A3].to_u64(); + let offset = bounds >> 32; + let length = bounds as u32 as u64; + let spgs_addr = machine.registers()[A4].to_u64(); + let argc_addr = spgs_addr; + let argc = machine + .memory_mut() + .load64(&Mac::REG::from_u64(argc_addr))? + .to_u64(); + let argv_addr_addr = spgs_addr.wrapping_add(8); + let argv_addr = machine + .memory_mut() + .load64(&Mac::REG::from_u64(argv_addr_addr))? + .to_u64(); + let mut addr = argv_addr; + let mut argv = 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; + argv.push(cstr); + addr = addr.wrapping_add(8); } - // Deduct cycles used to build the child machine - let extra_cycles = - SPAWN_EXTRA_CYCLES_BASE + memory_limit * SPAWN_EXTRA_CYCLES_PER_MEMORY_PAGE; - machine_child.machine.add_cycles_no_checking(extra_cycles)?; - // Load program into child machine. - match machine_child.load_program(&program, &argv_vec) { - Ok(size) => { - machine_child - .machine - .add_cycles_no_checking(transferred_byte_cycles(size))?; + + let process_id_addr_addr = spgs_addr.wrapping_add(16); + let process_id_addr = machine + .memory_mut() + .load64(&Mac::REG::from_u64(process_id_addr_addr))? + .to_u64(); + let pipes_addr_addr = spgs_addr.wrapping_add(24); + let mut pipes_addr = machine + .memory_mut() + .load64(&Mac::REG::from_u64(pipes_addr_addr))? + .to_u64(); + + let mut pipes = vec![]; + if pipes_addr != 0 { + loop { + let pipe = machine + .memory_mut() + .load64(&Mac::REG::from_u64(pipes_addr))? + .to_u64(); + if pipe == 0 { + break; + } + pipes.push(PipeId(pipe)); + pipes_addr += 8; } - Err(_) => { - // If loading binary fails, we still need to consume extra_cycles. - machine.add_cycles_no_checking(extra_cycles)?; - machine.set_register(A0, Mac::REG::from_u8(WRONG_FORMAT)); + } + + // We are fetching the actual cell here for some in-place validation + let sc = self + .snapshot2_context + .lock() + .map_err(|e| VMError::Unexpected(e.to_string()))?; + let (_, full_length) = match sc.data_source().load_data(&data_piece_id, 0, 0) { + Ok(val) => val, + Err(VMError::External(m)) if m == "INDEX_OUT_OF_BOUND" => { + // This comes from TxData results in an out of bound error, to + // mimic current behavior, we would return INDEX_OUT_OF_BOUND error. + machine.set_register(A0, Mac::REG::from_u8(INDEX_OUT_OF_BOUND)); return Ok(true); } + Err(e) => return Err(e), + }; + if offset >= full_length { + machine.set_register(A0, Mac::REG::from_u8(SLICE_OUT_OF_BOUND)); + return Ok(true); } - // Run the child machine and check result. - match machine_child.run() { - Ok(data) => { - update_caller_machine(machine, data, machine_child.machine.cycles(), &spawn_data)?; - Ok(true) - } - Err(err) => { - // `CyclesExceeded` for old version snapshot - // `Pause` for new version suspend with pause signal - // Maybe we need to cleanup in future - if matches!(err, VMError::Pause | VMError::CyclesExceeded) { - let mut context = self.context.lock().map_err(|e| { - VMError::Unexpected(format!("Failed to acquire lock: {}", e)) - })?; - context - .suspended_machines - .push(ResumableMachine::spawn(machine_child, spawn_data)); - } - Err(err) + if length > 0 { + let end = offset.checked_add(length).ok_or(VMError::MemOutOfBound)?; + if end > full_length { + machine.set_register(A0, Mac::REG::from_u8(SLICE_OUT_OF_BOUND)); + return Ok(true); } } + machine.add_cycles_no_checking(SPAWN_EXTRA_CYCLES_BASE)?; + machine.add_cycles_no_checking(transferred_byte_cycles(full_length))?; + self.message_box + .lock() + .map_err(|e| VMError::Unexpected(e.to_string()))? + .push(Message::Spawn( + self.id, + SpawnArgs { + data_piece_id, + offset, + length, + argv, + pipes, + process_id_addr, + }, + )); + Err(VMError::External("YIELD".to_string())) } } - -pub fn build_child_machine< - DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, ->( - script_group: &ScriptGroup, - script_version: ScriptVersion, - syscalls_generator: &TransactionScriptsSyscallsGenerator
, - cycles_limit: u64, - spawn_data: &SpawnData, - context: &Arc>, -) -> Result { - let SpawnData { - callee_peak_memory, - callee_memory_limit, - content, - content_length, - cycles_base, - .. - } = spawn_data; - - let machine_isa = script_version.vm_isa(); - let machine_version = script_version.vm_version(); - let machine_core = CoreMachineType::new_with_memory( - machine_isa, - machine_version, - cycles_limit, - (callee_memory_limit * SPAWN_MEMORY_PAGE_SIZE) as usize, - ); - let pause = context - .lock() - .map_err(|e| VMError::Unexpected(format!("Failed to acquire lock: {}", e)))? - .pause - .clone(); - let machine_builder = DefaultMachineBuilder::new(machine_core) - .instruction_cycle_func(Box::new(estimate_cycles)) - .pause(pause); - let machine_syscalls = syscalls_generator.generate_same_syscalls(script_version, 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(syscalls_generator.build_current_cycles())); - let machine_builder = machine_builder.syscall(Box::new( - syscalls_generator.build_get_memory_limit(*callee_memory_limit), - )); - let machine_builder = machine_builder.syscall(Box::new( - syscalls_generator.build_set_content(Arc::clone(content), *content_length), - )); - let machine_builder = machine_builder.syscall(Box::new(syscalls_generator.build_spawn( - script_version, - script_group, - *callee_peak_memory, - *cycles_base, - Arc::clone(context), - ))); - let machine_builder = machine_builder.syscall(Box::new( - syscalls_generator.build_current_memory(*callee_peak_memory), - )); - let mut machine_child = Machine::new(machine_builder.build()); - set_vm_max_cycles(&mut machine_child, cycles_limit); - Ok(machine_child) -} - -/// Write the data generated by callee back to the caller memory space. -pub fn update_caller_machine( - caller: &mut Mac, - callee_exit_code: i8, - callee_cycles: u64, - spawn_data: &SpawnData, -) -> Result<(), VMError> { - let SpawnData { - content, - caller_exit_code_addr, - caller_content_addr, - caller_content_length_addr, - .. - } = spawn_data; - - caller.set_register(A0, Mac::REG::from_u32(0)); - caller.memory_mut().store8( - &Mac::REG::from_u64(*caller_exit_code_addr), - &Mac::REG::from_i8(callee_exit_code), - )?; - caller - .memory_mut() - .store_bytes(caller_content_addr.to_u64(), &content.lock().unwrap())?; - caller.memory_mut().store64( - &Mac::REG::from_u64(*caller_content_length_addr), - &Mac::REG::from_u64(content.lock().unwrap().len() as u64), - )?; - caller.add_cycles_no_checking(callee_cycles)?; - - Ok(()) -} diff --git a/script/src/syscalls/tests/vm_latest/syscalls_2.rs b/script/src/syscalls/tests/vm_latest/syscalls_2.rs index bcc7ed5704..4cac872f56 100644 --- a/script/src/syscalls/tests/vm_latest/syscalls_2.rs +++ b/script/src/syscalls/tests/vm_latest/syscalls_2.rs @@ -59,48 +59,6 @@ fn test_current_cycles() { 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(content_data, 5).ecall(&mut machine); - - assert!(result.unwrap()); - assert_eq!(machine.memory_mut().load64(&20000).unwrap(), 5); -} - fn _test_load_extension( data: &[u8], index: u64, diff --git a/script/src/v2_scheduler.rs b/script/src/v2_scheduler.rs index ddfa83ab04..ae899959c4 100644 --- a/script/src/v2_scheduler.rs +++ b/script/src/v2_scheduler.rs @@ -15,6 +15,7 @@ use crate::{ use crate::{ScriptVersion, TransactionScriptsVerifier}; use ckb_traits::{CellDataProvider, ExtensionProvider, HeaderProvider}; use ckb_types::core::Cycle; +use ckb_vm::snapshot2::Snapshot2Context; use ckb_vm::{ bytes::Bytes, cost_model::estimate_cycles, @@ -43,9 +44,10 @@ const MAX_INSTANTIATED_VMS: usize = 4; /// A scheduler holds & manipulates a core, the scheduler also holds /// all CKB-VM machines, each CKB-VM machine also gets a mutable reference /// of the core for IO operations. -pub struct Scheduler< +pub struct Scheduler
+where DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, -> { +{ tx_data: TxData
, // In fact, Scheduler here has the potential to totally replace // TransactionScriptsVerifier, nonetheless much of current syscall @@ -69,8 +71,9 @@ pub struct Scheduler< message_box: Arc>>, } -impl - Scheduler
+impl
Scheduler
+where + DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, { /// Create a new scheduler from empty state pub fn new( @@ -107,6 +110,7 @@ impl, full: FullSuspendedState, ) -> Self { + let message_box = syscalls_generator.message_box.clone(); Self { tx_data, script_version, @@ -127,7 +131,7 @@ impl - MachineContext
+impl
MachineContext
+where + DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, { pub fn new( id: VmId, @@ -57,7 +59,7 @@ impl id, Err(e) => { // Current implementation would throw an error immediately @@ -115,7 +117,7 @@ impl id, Err(e) => { // Current implementation would throw an error immediately @@ -196,133 +198,11 @@ impl(&mut self, machine: &mut Mac) -> Result<(), Error> { - let index = machine.registers()[A0].to_u64(); - let source = machine.registers()[A1].to_u64(); - let place = machine.registers()[A2].to_u64(); // TODO: support reading data from witness - - let data_piece_id = match DataPieceId::try_from((source, index)) { - Ok(id) => id, - Err(e) => { - // Current implementation would throw an error immediately - // for some source values, but return INDEX_OUT_OF_BOUND error - // for other values. Here for simplicity, we would return - // INDEX_OUT_OF_BOUND error in all cases. But the code might - // differ to mimic current on-chain behavior - println!("DataPieceId parsing error: {:?}", e); - machine.set_register(A0, Mac::REG::from_u8(INDEX_OUT_OF_BOUND)); - return Ok(()); - } - }; - - let bounds = machine.registers()[A3].to_u64(); - let offset = bounds >> 32; - let length = bounds as u32 as u64; - - let spgs_addr = machine.registers()[A4].to_u64(); - let argc_addr = spgs_addr; - let argc = machine - .memory_mut() - .load64(&Mac::REG::from_u64(argc_addr))? - .to_u64(); - let mut argv_addr = machine - .memory_mut() - .load64(&Mac::REG::from_u64(spgs_addr.wrapping_add(8)))? - .to_u64(); - let mut argv = Vec::new(); - for _ in 0..argc { - let target_addr = machine - .memory_mut() - .load64(&Mac::REG::from_u64(argv_addr))? - .to_u64(); - let cstr = load_c_string(machine, target_addr)?; - argv.push(cstr); - argv_addr = argv_addr.wrapping_add(8); - } - - let (process_id_addr, pipes) = { - let process_id_addr_addr = spgs_addr.wrapping_add(16); - let process_id_addr = machine - .memory_mut() - .load64(&Mac::REG::from_u64(process_id_addr_addr))? - .to_u64(); - let pipes_addr_addr = spgs_addr.wrapping_add(24); - let mut pipes_addr = machine - .memory_mut() - .load64(&Mac::REG::from_u64(pipes_addr_addr))? - .to_u64(); - - let mut pipes = vec![]; - if pipes_addr != 0 { - loop { - let pipe = machine - .memory_mut() - .load64(&Mac::REG::from_u64(pipes_addr))? - .to_u64(); - if pipe == 0 { - break; - } - pipes.push(PipeId(pipe)); - pipes_addr += 8; - } - } - (process_id_addr, pipes) - }; - - // We are fetching the actual cell here for some in-place validation - { - let sc = self.snapshot2_context().lock().expect("lock"); - let (_, full_length) = match sc.data_source().load_data(&data_piece_id, 0, 0) { - Ok(val) => val, - Err(Error::External(m)) if m == "INDEX_OUT_OF_BOUND" => { - // This comes from TxData results in an out of bound error, to - // mimic current behavior, we would return INDEX_OUT_OF_BOUND error. - machine.set_register(A0, Mac::REG::from_u8(INDEX_OUT_OF_BOUND)); - return Ok(()); - } - Err(e) => return Err(e), - }; - if offset >= full_length { - machine.set_register(A0, Mac::REG::from_u8(SLICE_OUT_OF_BOUND)); - return Ok(()); - } - if length > 0 { - let end = offset.checked_add(length).ok_or(Error::MemOutOfBound)?; - if end > full_length { - machine.set_register(A0, Mac::REG::from_u8(SLICE_OUT_OF_BOUND)); - return Ok(()); - } - } - } - // TODO: update spawn base cycles - machine.add_cycles_no_checking(100_000)?; - self.message_box.lock().expect("lock").push(Message::Spawn( - self.id, - SpawnArgs { - data_piece_id, - offset, - length, - argv, - pipes, - process_id_addr, - }, - )); - - // At this point, all execution has been finished, and it is expected - // to return Ok(()) denoting success. However we want spawn to yield - // its control back to scheduler, so a runnable VM with a higher ID can - // start its execution first. That's why we actually return a yield error - // here. - Err(Error::External("YIELD".to_string())) - } } -impl< - Mac: SupportMachine, - DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, - > Syscalls for MachineContext
+impl Syscalls for MachineContext
+where + DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, { fn initialize(&mut self, _machine: &mut Mac) -> Result<(), Error> { Ok(()) @@ -334,15 +214,6 @@ impl< 2091 => self.load_cell_data_as_code(machine), 2092 => self.load_cell_data(machine), 2177 => self.debug(machine), - // The syscall numbers here are picked intentionally to be different - // than currently assigned syscall numbers for spawn calls - 2601 => { - if self.script_version >= ScriptVersion::V2 { - self.spawn(machine) - } else { - return Ok(false); - } - } _ => return Ok(false), }?; Ok(true) @@ -368,22 +239,3 @@ pub(crate) const SLICE_OUT_OF_BOUND: u8 = 3; pub(crate) const WAIT_FAILURE: u8 = 5; pub(crate) const INVALID_PIPE: u8 = 6; pub(crate) const OTHER_END_CLOSED: u8 = 7; - -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)) -} diff --git a/script/src/v2_types.rs b/script/src/v2_types.rs index 7beb62391f..599dbe6abb 100644 --- a/script/src/v2_types.rs +++ b/script/src/v2_types.rs @@ -109,21 +109,28 @@ pub enum DataPieceId { CellDep(u32), GroupInput(u32), GroupOutput(u32), + Witness(u32), + WitnessGroupInput(u32), + WitnessGroupOutput(u32), } -impl TryFrom<(u64, u64)> for DataPieceId { +impl TryFrom<(u64, u64, u64)> for DataPieceId { type Error = String; - fn try_from(value: (u64, u64)) -> Result { - let (source, index) = value; + fn try_from(value: (u64, u64, u64)) -> Result { + let (source, index, place) = value; let index: u32 = u32::try_from(index).map_err(|e| format!("Error casting index to u32: {}", e))?; - match source { - 1 => Ok(DataPieceId::Input(index)), - 2 => Ok(DataPieceId::Output(index)), - 3 => Ok(DataPieceId::CellDep(index)), - 0x0100000000000001 => Ok(DataPieceId::GroupInput(index)), - 0x0100000000000002 => Ok(DataPieceId::GroupOutput(index)), + match (source, place) { + (1, 0) => Ok(DataPieceId::Input(index)), + (2, 0) => Ok(DataPieceId::Output(index)), + (3, 0) => Ok(DataPieceId::CellDep(index)), + (0x0100000000000001, 0) => Ok(DataPieceId::GroupInput(index)), + (0x0100000000000002, 0) => Ok(DataPieceId::GroupOutput(index)), + (1, 1) => Ok(DataPieceId::Witness(index)), + (2, 1) => Ok(DataPieceId::Witness(index)), + (0x0100000000000001, 1) => Ok(DataPieceId::WitnessGroupInput(index)), + (0x0100000000000002, 1) => Ok(DataPieceId::WitnessGroupOutput(index)), _ => Err(format!("Invalid source value: {:#x}", source)), } } @@ -184,8 +191,9 @@ pub struct TxData
{ pub script_group: Arc, } -impl - DataSource for TxData
+impl
DataSource for TxData
+where + DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, { fn load_data(&self, id: &DataPieceId, offset: u64, length: u64) -> Result<(Bytes, u64), Error> { match id { @@ -250,6 +258,39 @@ impl self + .rtx + .transaction + .witnesses() + .get(*i as usize) + .map(|data| data.raw_data()) + .ok_or_else(|| Error::External("INDEX_OUT_OF_BOUND".to_string())), + DataPieceId::WitnessGroupInput(i) => { + let gi = *self + .script_group + .input_indices + .get(*i as usize) + .ok_or_else(|| Error::External("INDEX_OUT_OF_BOUND".to_string()))?; + self.rtx + .transaction + .witnesses() + .get(gi) + .map(|data| data.raw_data()) + .ok_or_else(|| Error::External("INDEX_OUT_OF_BOUND".to_string())) + } + DataPieceId::WitnessGroupOutput(i) => { + let gi = *self + .script_group + .output_indices + .get(*i as usize) + .ok_or_else(|| Error::External("INDEX_OUT_OF_BOUND".to_string()))?; + self.rtx + .transaction + .witnesses() + .get(gi) + .map(|data| data.raw_data()) + .ok_or_else(|| Error::External("INDEX_OUT_OF_BOUND".to_string())) + } } .map(|data| { let offset = std::cmp::min(offset as usize, data.len()); diff --git a/script/src/verify.rs b/script/src/verify.rs index fa61136bba..5f014cd617 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -2,15 +2,14 @@ use crate::syscalls::Pause; use crate::syscalls::{InheritedFd, ProcessID}; use crate::v2_scheduler::Scheduler; -use crate::v2_types::{Message, RunMode, TxData, VmId, FIRST_VM_ID}; +use crate::v2_types::{DataPieceId, Message, RunMode, TxData, VmId, FIRST_VM_ID}; use crate::{ cost_model::transferred_byte_cycles, error::{ScriptError, TransactionScriptError}, syscalls::{ - spawn::{build_child_machine, update_caller_machine}, - Close, CurrentCycles, CurrentMemory, Debugger, Exec, GetMemoryLimit, LoadBlockExtension, - LoadCell, LoadCellData, LoadHeader, LoadInput, LoadScript, LoadScriptHash, LoadTx, - LoadWitness, Pipe, Read, SetContent, Spawn, VMVersion, Wait, Write, + Close, CurrentCycles, Debugger, Exec, LoadBlockExtension, LoadCell, LoadCellData, + LoadHeader, LoadInput, LoadScript, LoadScriptHash, LoadTx, LoadWitness, Pipe, Read, Spawn, + VMVersion, Wait, Write, }, type_id::TypeIdSystemScript, types::{ @@ -38,6 +37,7 @@ use ckb_types::{ use ckb_vm::{ cost_model::estimate_cycles, snapshot::{resume, Snapshot}, + snapshot2::Snapshot2Context, DefaultMachineBuilder, Error as VMInternalError, SupportMachine, Syscalls, }; use std::sync::{Arc, Mutex}; @@ -144,7 +144,10 @@ impl Binaries { /// /// TransactionScriptsSyscallsGenerator can be cloned. #[derive(Clone)] -pub struct TransactionScriptsSyscallsGenerator
{ +pub struct TransactionScriptsSyscallsGenerator
+where + DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, +{ pub(crate) base_cycles: Arc>, pub(crate) data_loader: DL, pub(crate) debug_printer: DebugPrinter, @@ -153,11 +156,13 @@ pub struct TransactionScriptsSyscallsGenerator
{ pub(crate) rtx: Arc, #[cfg(test)] pub(crate) skip_pause: Arc, + pub(crate) snapshot2_context: Arc>>>, pub(crate) vm_id: VmId, } -impl - TransactionScriptsSyscallsGenerator
+impl
TransactionScriptsSyscallsGenerator
+where + DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, { /// Build syscall: current_cycles pub fn build_current_cycles(&self) -> CurrentCycles { @@ -249,36 +254,12 @@ impl 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) - } - /// Build syscall: spawn - pub fn build_spawn( - &self, - script_version: ScriptVersion, - script_group: &ScriptGroup, - peak_memory: u64, - cycles_base: u64, - context: Arc>, - ) -> Spawn
{ + pub fn build_spawn(&self) -> Spawn
{ Spawn::new( - script_group.clone(), - script_version, - self.clone(), - peak_memory, - cycles_base, - context, + self.vm_id, + self.message_box.clone(), + self.snapshot2_context.clone(), ) } @@ -317,11 +298,6 @@ impl CurrentMemory { - CurrentMemory::new(current_memory) - } - /// Generate same syscalls. The result does not contain spawn syscalls. pub fn generate_same_syscalls( &self, @@ -367,6 +343,7 @@ impl= ScriptVersion::V2 { syscalls.append(&mut vec![ Box::new(self.build_load_block_extension(Arc::clone(&script_group_input_indices))), + Box::new(self.build_spawn()), Box::new(self.build_process_id()), Box::new(self.build_pipe()), Box::new(self.build_wait()), @@ -380,38 +357,16 @@ impl>, - ) -> Vec)>> { - 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(self.build_spawn( - script_version, - script_group, - 8, - 0, - Arc::clone(&context), - )), - Box::new(self.build_current_memory(8)), - ]) - } - syscalls - } } /// This struct leverages CKB VM to verify transaction inputs. /// /// FlatBufferBuilder owned `Vec` that grows as needed, in the /// future, we might refactor this to share buffer to achieve zero-copy -pub struct TransactionScriptsVerifier
{ +pub struct TransactionScriptsVerifier
+where + DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, +{ data_loader: DL, rtx: Arc, @@ -431,8 +386,9 @@ pub struct TransactionScriptsVerifier
{ syscalls_generator: TransactionScriptsSyscallsGenerator
, } -impl - TransactionScriptsVerifier
+impl
TransactionScriptsVerifier
+where + DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, { /// Creates a script verifier for the transaction. /// @@ -533,6 +489,18 @@ impl Result { - if group.script.code_hash() == TYPE_ID_CODE_HASH.pack() - && Into::::into(group.script.hash_type()) == Into::::into(ScriptHashType::Type) - { - let verifier = TypeIdSystemScript { - rtx: &self.rtx, - script_group: group, - max_cycles, - }; - match verifier.verify() { - Ok(cycles) => Ok(ChunkState::Completed(cycles)), - Err(ScriptError::ExceededMaximumCycles(_)) => Ok(ChunkState::suspended_type_id()), - Err(e) => Err(e), - } - } else { - self.chunk_run(group, max_cycles, snaps) - } + unimplemented!() + // if group.script.code_hash() == TYPE_ID_CODE_HASH.pack() + // && Into::::into(group.script.hash_type()) == Into::::into(ScriptHashType::Type) + // { + // let verifier = TypeIdSystemScript { + // rtx: &self.rtx, + // script_group: group, + // max_cycles, + // }; + // match verifier.verify() { + // Ok(cycles) => Ok(ChunkState::Completed(cycles)), + // Err(ScriptError::ExceededMaximumCycles(_)) => Ok(ChunkState::suspended_type_id()), + // Err(e) => Err(e), + // } + // } else { + // self.chunk_run(group, max_cycles, snaps) + // } } async fn verify_group_with_signal( @@ -1112,7 +1081,7 @@ impl>, ) -> Vec)>> { self.syscalls_generator - .generate_root_syscalls(script_version, script_group, context) + .generate_same_syscalls(script_version, script_group) } fn build_machine( @@ -1142,7 +1111,6 @@ impl Result<(i8, Cycle), ScriptError> { let program = self.extract_script(&script_group.script)?; - let tx_data = TxData { rtx: self.rtx.clone(), data_loader: self.data_loader.clone(), @@ -1152,29 +1120,13 @@ impl ScriptError::ExceededMaximumCycles(max_cycles), _ => ScriptError::VMInternalError(error), }; - scheduler .run(RunMode::LimitCycles(max_cycles)) .map_err(map_vm_internal_error) - - // let context = Default::default(); - // let mut machine = self.build_machine(script_group, max_cycles, context)?; - - // let bytes = machine - // .load_program(&program, &[]) - // .map_err(map_vm_internal_error)?; - // machine - // .machine - // .add_cycles_no_checking(transferred_byte_cycles(bytes)) - // .map_err(map_vm_internal_error)?; - // let code = machine.run().map_err(map_vm_internal_error)?; - - // Ok((code, machine)) } fn run(&self, script_group: &ScriptGroup, max_cycles: Cycle) -> Result { @@ -1193,167 +1145,168 @@ impl, ) -> Result { - let context: Arc> = Default::default(); - - let map_vm_internal_error = |error: VMInternalError| match error { - VMInternalError::CyclesExceeded => ScriptError::ExceededMaximumCycles(max_cycles), - _ => ScriptError::VMInternalError(error), - }; - - let machines = { - // No snapshots are available, create machine from scratch - let mut machine = self.build_machine(script_group, max_cycles, Arc::clone(&context))?; - let program = self.extract_script(&script_group.script)?; - let bytes = machine - .load_program(&program, &[]) - .map_err(map_vm_internal_error)?; - let program_bytes_cycles = transferred_byte_cycles(bytes); - // NOTE: previously, we made a distinction between machines - // that completes program loading without errors, and machines - // that fail program loading due to cycle limits. For the latter - // one, we won't generate any snapshots. Starting from this version, - // we will remove this distinction: when loading program exceeds - // maximum cycles, the error will be triggered when executing the - // first instruction. As a result, now all ResumableMachine will - // be transformed to snapshots. This is due to several considerations: - // - // * Let's do a little bit math: right now CKB has a block limit of - // ~570KB, a single transaction is further limited to 512KB in RPC, - // the biggest program one can load is either 512KB or ~570KB depending - // on which limit to use. The cycles consumed to load a program, is - // thus at most 131072 or ~145920, which is far less than the cycle - // limit for running a single transaction (70 million or more). In - // reality it might be extremely rare that loading a program would - // result in exceeding cycle limits. Removing the distinction here, - // would help simply the code. - // * If you pay attention to the code now, we already have this behavior - // in the code: most syscalls use +add_cycles_no_checking+ in the code, - // meaning an error would not be immediately generated when cycle limit - // is reached, the error would be raised when executing the first instruction - // after the syscall. What's more, when spawn is loading a program - // to its child machine, it also uses +add_cycles_no_checking+ so it - // won't generate errors immediately. This means that all spawned machines - // will be in a state that a program is loaded, regardless of the fact if - // loading a program in spawn reaches the cycle limit or not. As a - // result, we definitely want to pull the trigger, so we can have unified - // behavior everywhere. - machine - .machine - .add_cycles_no_checking(program_bytes_cycles) - .map_err(ScriptError::VMInternalError)?; - let mut context = context.lock().unwrap(); - context.set_pause(machine.machine.pause().clone()); - vec![ResumableMachine::initial(machine)] - }; - - run_vms_with_signal(script_group, machines, context, command_rx).await - } - - fn chunk_run( - &self, - script_group: &ScriptGroup, - max_cycles: Cycle, - snaps: &[(Snapshot, Cycle, ResumePoint)], - ) -> Result { - let context: Arc> = Default::default(); - - let map_vm_internal_error = |error: VMInternalError| match error { - VMInternalError::CyclesExceeded => ScriptError::ExceededMaximumCycles(max_cycles), - _ => ScriptError::VMInternalError(error), - }; - - let machines = if !snaps.is_empty() { - // Resume machines from snapshots - let mut machines = vec![]; - for (sp, current_cycle, resume_point) in snaps { - let mut machine = match resume_point { - ResumePoint::Initial => ResumableMachine::initial(self.build_machine( - script_group, - max_cycles, - Arc::clone(&context), - )?), - ResumePoint::Spawn { - callee_peak_memory, - callee_memory_limit, - content, - content_length, - caller_exit_code_addr, - caller_content_addr, - caller_content_length_addr, - cycles_base, - } => { - let spawn_data = SpawnData { - callee_peak_memory: *callee_peak_memory, - callee_memory_limit: *callee_memory_limit, - content: Arc::new(Mutex::new(content.clone())), - content_length: *content_length, - caller_exit_code_addr: *caller_exit_code_addr, - caller_content_addr: *caller_content_addr, - caller_content_length_addr: *caller_content_length_addr, - cycles_base: *cycles_base, - }; - let machine = build_child_machine( - script_group, - self.select_version(&script_group.script)?, - &self.syscalls_generator, - max_cycles, - &spawn_data, - &context, - ) - .map_err(map_vm_internal_error)?; - ResumableMachine::spawn(machine, spawn_data) - } - }; - resume(&mut machine.machine_mut().machine, sp).map_err(map_vm_internal_error)?; - machine.machine_mut().machine.set_cycles(*current_cycle); - machines.push(machine); - } - machines - } else { - // No snapshots are available, create machine from scratch - let mut machine = self.build_machine(script_group, max_cycles, Arc::clone(&context))?; - let program = self.extract_script(&script_group.script)?; - let bytes = machine - .load_program(&program, &[]) - .map_err(map_vm_internal_error)?; - let program_bytes_cycles = transferred_byte_cycles(bytes); - // NOTE: previously, we made a distinction between machines - // that completes program loading without errors, and machines - // that fail program loading due to cycle limits. For the latter - // one, we won't generate any snapshots. Starting from this version, - // we will remove this distinction: when loading program exceeds - // maximum cycles, the error will be triggered when executing the - // first instruction. As a result, now all ResumableMachine will - // be transformed to snapshots. This is due to several considerations: - // - // * Let's do a little bit math: right now CKB has a block limit of - // ~570KB, a single transaction is further limited to 512KB in RPC, - // the biggest program one can load is either 512KB or ~570KB depending - // on which limit to use. The cycles consumed to load a program, is - // thus at most 131072 or ~145920, which is far less than the cycle - // limit for running a single transaction (70 million or more). In - // reality it might be extremely rare that loading a program would - // result in exceeding cycle limits. Removing the distinction here, - // would help simply the code. - // * If you pay attention to the code now, we already have this behavior - // in the code: most syscalls use +add_cycles_no_checking+ in the code, - // meaning an error would not be immediately generated when cycle limit - // is reached, the error would be raised when executing the first instruction - // after the syscall. What's more, when spawn is loading a program - // to its child machine, it also uses +add_cycles_no_checking+ so it - // won't generate errors immediately. This means that all spawned machines - // will be in a state that a program is loaded, regardless of the fact if - // loading a program in spawn reaches the cycle limit or not. As a - // result, we definitely want to pull the trigger, so we can have unified - // behavior everywhere. - machine - .machine - .add_cycles_no_checking(program_bytes_cycles) - .map_err(ScriptError::VMInternalError)?; - vec![ResumableMachine::initial(machine)] - }; - - run_vms(script_group, max_cycles, machines, &context) + unimplemented!() + // let context: Arc> = Default::default(); + + // let map_vm_internal_error = |error: VMInternalError| match error { + // VMInternalError::CyclesExceeded => ScriptError::ExceededMaximumCycles(max_cycles), + // _ => ScriptError::VMInternalError(error), + // }; + + // let machines = { + // // No snapshots are available, create machine from scratch + // let mut machine = self.build_machine(script_group, max_cycles, Arc::clone(&context))?; + // let program = self.extract_script(&script_group.script)?; + // let bytes = machine + // .load_program(&program, &[]) + // .map_err(map_vm_internal_error)?; + // let program_bytes_cycles = transferred_byte_cycles(bytes); + // // NOTE: previously, we made a distinction between machines + // // that completes program loading without errors, and machines + // // that fail program loading due to cycle limits. For the latter + // // one, we won't generate any snapshots. Starting from this version, + // // we will remove this distinction: when loading program exceeds + // // maximum cycles, the error will be triggered when executing the + // // first instruction. As a result, now all ResumableMachine will + // // be transformed to snapshots. This is due to several considerations: + // // + // // * Let's do a little bit math: right now CKB has a block limit of + // // ~570KB, a single transaction is further limited to 512KB in RPC, + // // the biggest program one can load is either 512KB or ~570KB depending + // // on which limit to use. The cycles consumed to load a program, is + // // thus at most 131072 or ~145920, which is far less than the cycle + // // limit for running a single transaction (70 million or more). In + // // reality it might be extremely rare that loading a program would + // // result in exceeding cycle limits. Removing the distinction here, + // // would help simply the code. + // // * If you pay attention to the code now, we already have this behavior + // // in the code: most syscalls use +add_cycles_no_checking+ in the code, + // // meaning an error would not be immediately generated when cycle limit + // // is reached, the error would be raised when executing the first instruction + // // after the syscall. What's more, when spawn is loading a program + // // to its child machine, it also uses +add_cycles_no_checking+ so it + // // won't generate errors immediately. This means that all spawned machines + // // will be in a state that a program is loaded, regardless of the fact if + // // loading a program in spawn reaches the cycle limit or not. As a + // // result, we definitely want to pull the trigger, so we can have unified + // // behavior everywhere. + // machine + // .machine + // .add_cycles_no_checking(program_bytes_cycles) + // .map_err(ScriptError::VMInternalError)?; + // let mut context = context.lock().unwrap(); + // context.set_pause(machine.machine.pause().clone()); + // vec![ResumableMachine::initial(machine)] + // }; + + // run_vms_with_signal(script_group, machines, context, command_rx).await + // } + + // fn chunk_run( + // &self, + // script_group: &ScriptGroup, + // max_cycles: Cycle, + // snaps: &[(Snapshot, Cycle, ResumePoint)], + // ) -> Result { + // let context: Arc> = Default::default(); + + // let map_vm_internal_error = |error: VMInternalError| match error { + // VMInternalError::CyclesExceeded => ScriptError::ExceededMaximumCycles(max_cycles), + // _ => ScriptError::VMInternalError(error), + // }; + + // let machines = if !snaps.is_empty() { + // // Resume machines from snapshots + // let mut machines = vec![]; + // for (sp, current_cycle, resume_point) in snaps { + // let mut machine = match resume_point { + // ResumePoint::Initial => ResumableMachine::initial(self.build_machine( + // script_group, + // max_cycles, + // Arc::clone(&context), + // )?), + // ResumePoint::Spawn { + // callee_peak_memory, + // callee_memory_limit, + // content, + // content_length, + // caller_exit_code_addr, + // caller_content_addr, + // caller_content_length_addr, + // cycles_base, + // } => { + // let spawn_data = SpawnData { + // callee_peak_memory: *callee_peak_memory, + // callee_memory_limit: *callee_memory_limit, + // content: Arc::new(Mutex::new(content.clone())), + // content_length: *content_length, + // caller_exit_code_addr: *caller_exit_code_addr, + // caller_content_addr: *caller_content_addr, + // caller_content_length_addr: *caller_content_length_addr, + // cycles_base: *cycles_base, + // }; + // let machine = build_child_machine( + // script_group, + // self.select_version(&script_group.script)?, + // &self.syscalls_generator, + // max_cycles, + // &spawn_data, + // &context, + // ) + // .map_err(map_vm_internal_error)?; + // ResumableMachine::spawn(machine, spawn_data) + // } + // }; + // resume(&mut machine.machine_mut().machine, sp).map_err(map_vm_internal_error)?; + // machine.machine_mut().machine.set_cycles(*current_cycle); + // machines.push(machine); + // } + // machines + // } else { + // // No snapshots are available, create machine from scratch + // let mut machine = self.build_machine(script_group, max_cycles, Arc::clone(&context))?; + // let program = self.extract_script(&script_group.script)?; + // let bytes = machine + // .load_program(&program, &[]) + // .map_err(map_vm_internal_error)?; + // let program_bytes_cycles = transferred_byte_cycles(bytes); + // // NOTE: previously, we made a distinction between machines + // // that completes program loading without errors, and machines + // // that fail program loading due to cycle limits. For the latter + // // one, we won't generate any snapshots. Starting from this version, + // // we will remove this distinction: when loading program exceeds + // // maximum cycles, the error will be triggered when executing the + // // first instruction. As a result, now all ResumableMachine will + // // be transformed to snapshots. This is due to several considerations: + // // + // // * Let's do a little bit math: right now CKB has a block limit of + // // ~570KB, a single transaction is further limited to 512KB in RPC, + // // the biggest program one can load is either 512KB or ~570KB depending + // // on which limit to use. The cycles consumed to load a program, is + // // thus at most 131072 or ~145920, which is far less than the cycle + // // limit for running a single transaction (70 million or more). In + // // reality it might be extremely rare that loading a program would + // // result in exceeding cycle limits. Removing the distinction here, + // // would help simply the code. + // // * If you pay attention to the code now, we already have this behavior + // // in the code: most syscalls use +add_cycles_no_checking+ in the code, + // // meaning an error would not be immediately generated when cycle limit + // // is reached, the error would be raised when executing the first instruction + // // after the syscall. What's more, when spawn is loading a program + // // to its child machine, it also uses +add_cycles_no_checking+ so it + // // won't generate errors immediately. This means that all spawned machines + // // will be in a state that a program is loaded, regardless of the fact if + // // loading a program in spawn reaches the cycle limit or not. As a + // // result, we definitely want to pull the trigger, so we can have unified + // // behavior everywhere. + // machine + // .machine + // .add_cycles_no_checking(program_bytes_cycles) + // .map_err(ScriptError::VMInternalError)?; + // vec![ResumableMachine::initial(machine)] + // }; + + // run_vms(script_group, max_cycles, machines, &context) } } @@ -1364,68 +1317,69 @@ fn run_vms( mut machines: Vec, context: &Arc>, ) -> Result { - let (mut exit_code, mut cycles, mut spawn_data) = (0, 0, None); - - if machines.is_empty() { - return Err(ScriptError::Other( - "To resume VMs, at least one VM must be available!".to_string(), - )); - } - - let map_vm_internal_error = |error: VMInternalError| match error { - VMInternalError::CyclesExceeded => ScriptError::ExceededMaximumCycles(max_cycles), - _ => ScriptError::VMInternalError(error), - }; - - while let Some(mut machine) = machines.pop() { - if let Some(callee_spawn_data) = &spawn_data { - update_caller_machine( - &mut machine.machine_mut().machine, - exit_code, - cycles, - callee_spawn_data, - ) - .map_err(map_vm_internal_error)?; - } - - match machine.run() { - Ok(code) => { - exit_code = code; - cycles = machine.cycles(); - if let ResumableMachine::Spawn(_, data) = machine { - spawn_data = Some(data); - } else { - spawn_data = None; - } - } - Err(error) => match error { - VMInternalError::CyclesExceeded | VMInternalError::Pause => { - let mut new_suspended_machines: Vec<_> = { - let mut context = context.lock().map_err(|e| { - ScriptError::Other(format!("Failed to acquire lock: {}", e)) - })?; - context.suspended_machines.drain(..).collect() - }; - // The inner most machine lives at the top of the vector, - // reverse the list for natural order. - new_suspended_machines.reverse(); - machines.push(machine); - machines.append(&mut new_suspended_machines); - return Ok(ChunkState::suspended(machines, Arc::clone(context))); - } - _ => return Err(ScriptError::VMInternalError(error)), - }, - }; - } - - if exit_code == 0 { - Ok(ChunkState::Completed(cycles)) - } else { - Err(ScriptError::validation_failure( - &script_group.script, - exit_code, - )) - } + unimplemented!() + // let (mut exit_code, mut cycles, mut spawn_data) = (0, 0, None); + + // if machines.is_empty() { + // return Err(ScriptError::Other( + // "To resume VMs, at least one VM must be available!".to_string(), + // )); + // } + + // let map_vm_internal_error = |error: VMInternalError| match error { + // VMInternalError::CyclesExceeded => ScriptError::ExceededMaximumCycles(max_cycles), + // _ => ScriptError::VMInternalError(error), + // }; + + // while let Some(mut machine) = machines.pop() { + // if let Some(callee_spawn_data) = &spawn_data { + // update_caller_machine( + // &mut machine.machine_mut().machine, + // exit_code, + // cycles, + // callee_spawn_data, + // ) + // .map_err(map_vm_internal_error)?; + // } + + // match machine.run() { + // Ok(code) => { + // exit_code = code; + // cycles = machine.cycles(); + // if let ResumableMachine::Spawn(_, data) = machine { + // spawn_data = Some(data); + // } else { + // spawn_data = None; + // } + // } + // Err(error) => match error { + // VMInternalError::CyclesExceeded | VMInternalError::Pause => { + // let mut new_suspended_machines: Vec<_> = { + // let mut context = context.lock().map_err(|e| { + // ScriptError::Other(format!("Failed to acquire lock: {}", e)) + // })?; + // context.suspended_machines.drain(..).collect() + // }; + // // The inner most machine lives at the top of the vector, + // // reverse the list for natural order. + // new_suspended_machines.reverse(); + // machines.push(machine); + // machines.append(&mut new_suspended_machines); + // return Ok(ChunkState::suspended(machines, Arc::clone(context))); + // } + // _ => return Err(ScriptError::VMInternalError(error)), + // }, + // }; + // } + + // if exit_code == 0 { + // Ok(ChunkState::Completed(cycles)) + // } else { + // Err(ScriptError::validation_failure( + // &script_group.script, + // exit_code, + // )) + // } } // Run a series of VMs with control signal, will only return when verification finished @@ -1495,86 +1449,87 @@ async fn run_vms_child( finish_tx: oneshot::Sender<(Result, u64)>, context: Arc>, ) { - let (mut exit_code, mut cycles, mut spawn_data) = (0, 0, None); - // mark changed to make sure child start to run verification immediately - child_rx.mark_changed(); - loop { - let _ = child_rx.changed().await; - match *child_rx.borrow() { - ChunkCommand::Stop => { - let exit = (Err(ckb_vm::Error::External("stopped".into())), cycles); - let _ = finish_tx.send(exit); - return; - } - ChunkCommand::Suspend => { - continue; - } - ChunkCommand::Resume => { - //info!("[verify-test] run_vms_child: resume"); - } - } - if machines.is_empty() { - finish_tx - .send((Ok(exit_code), cycles)) - .expect("send finished"); - return; - } - - while let Some(mut machine) = machines.pop() { - if let Some(callee_spawn_data) = &spawn_data { - update_caller_machine( - &mut machine.machine_mut().machine, - exit_code, - cycles, - callee_spawn_data, - ) - .unwrap(); - } - - let res = machine.run(); - match res { - Ok(code) => { - exit_code = code; - cycles = machine.cycles(); - if let ResumableMachine::Spawn(_, data) = machine { - spawn_data = Some(data); - } else { - spawn_data = None; - } - if machines.is_empty() { - finish_tx.send((Ok(exit_code), cycles)).unwrap(); - return; - } - } - Err(VMInternalError::Pause) => { - let mut new_suspended_machines: Vec<_> = { - let mut context = context - .lock() - .map_err(|e| { - ScriptError::Other(format!("Failed to acquire lock: {}", e)) - }) - .unwrap(); - context.suspended_machines.drain(..).collect() - }; - // The inner most machine lives at the top of the vector, - // reverse the list for natural order. - new_suspended_machines.reverse(); - machines.push(machine); - machines.append(&mut new_suspended_machines); - // break run machines iteration loop - // wait for Resume command to begin next iteration - // info!("[verify-test] run_vms_child: suspend at {:?}", cycles); - break; - } - _ => { - // other error happened here, for example CyclesExceeded, - // we need to return as verification failed - finish_tx.send((res, machine.cycles())).expect("send error"); - return; - } - }; - } - } + unimplemented!() + // let (mut exit_code, mut cycles, mut spawn_data) = (0, 0, None); + // // mark changed to make sure child start to run verification immediately + // child_rx.mark_changed(); + // loop { + // let _ = child_rx.changed().await; + // match *child_rx.borrow() { + // ChunkCommand::Stop => { + // let exit = (Err(ckb_vm::Error::External("stopped".into())), cycles); + // let _ = finish_tx.send(exit); + // return; + // } + // ChunkCommand::Suspend => { + // continue; + // } + // ChunkCommand::Resume => { + // //info!("[verify-test] run_vms_child: resume"); + // } + // } + // if machines.is_empty() { + // finish_tx + // .send((Ok(exit_code), cycles)) + // .expect("send finished"); + // return; + // } + + // while let Some(mut machine) = machines.pop() { + // if let Some(callee_spawn_data) = &spawn_data { + // update_caller_machine( + // &mut machine.machine_mut().machine, + // exit_code, + // cycles, + // callee_spawn_data, + // ) + // .unwrap(); + // } + + // let res = machine.run(); + // match res { + // Ok(code) => { + // exit_code = code; + // cycles = machine.cycles(); + // if let ResumableMachine::Spawn(_, data) = machine { + // spawn_data = Some(data); + // } else { + // spawn_data = None; + // } + // if machines.is_empty() { + // finish_tx.send((Ok(exit_code), cycles)).unwrap(); + // return; + // } + // } + // Err(VMInternalError::Pause) => { + // let mut new_suspended_machines: Vec<_> = { + // let mut context = context + // .lock() + // .map_err(|e| { + // ScriptError::Other(format!("Failed to acquire lock: {}", e)) + // }) + // .unwrap(); + // context.suspended_machines.drain(..).collect() + // }; + // // The inner most machine lives at the top of the vector, + // // reverse the list for natural order. + // new_suspended_machines.reverse(); + // machines.push(machine); + // machines.append(&mut new_suspended_machines); + // // break run machines iteration loop + // // wait for Resume command to begin next iteration + // // info!("[verify-test] run_vms_child: suspend at {:?}", cycles); + // break; + // } + // _ => { + // // other error happened here, for example CyclesExceeded, + // // we need to return as verification failed + // finish_tx.send((res, machine.cycles())).expect("send error"); + // return; + // } + // }; + // } + // } } fn wrapping_cycles_add( diff --git a/script/src/verify/tests/ckb_latest/features_since_v2023.rs b/script/src/verify/tests/ckb_latest/features_since_v2023.rs index 3f6dc80b73..73c4f263a8 100644 --- a/script/src/verify/tests/ckb_latest/features_since_v2023.rs +++ b/script/src/verify/tests/ckb_latest/features_since_v2023.rs @@ -89,7 +89,6 @@ fn check_spawn_inherited_fds_without_owner() { assert_eq!(result.is_ok(), SCRIPT_VERSION == ScriptVersion::V2); } - #[test] fn check_spawn_read_then_close() { let result = simple_spawn_test("testdata/spawn_cases", &[9]);