From 672649a666340d1bb800d1e2084ce005e94c6638 Mon Sep 17 00:00:00 2001 From: mohanson Date: Thu, 16 Jan 2025 13:46:59 +0800 Subject: [PATCH] Add exec v2 --- script/Cargo.toml | 2 +- script/src/scheduler.rs | 76 +++++++---- script/src/syscalls/exec.rs | 4 - script/src/syscalls/exec_v2.rs | 124 ++++++++++++++++++ script/src/syscalls/mod.rs | 2 + script/src/types.rs | 10 ++ script/src/verify.rs | 34 ++--- .../tests/ckb_latest/features_since_v2021.rs | 8 +- 8 files changed, 215 insertions(+), 45 deletions(-) create mode 100644 script/src/syscalls/exec_v2.rs diff --git a/script/Cargo.toml b/script/Cargo.toml index 2f88b721188..32baf051021 100644 --- a/script/Cargo.toml +++ b/script/Cargo.toml @@ -23,7 +23,7 @@ byteorder = "1.3.1" ckb-types = { path = "../util/types", version = "= 0.121.0-pre" } ckb-hash = { path = "../util/hash", version = "= 0.121.0-pre" } # ckb-vm = { version = "= 0.24.12", default-features = false } -ckb-vm = { path = "/home/ubuntu/src/ckb-vm", default-features = false } +ckb-vm = { git = "https://github.com/libraries/ckb-vm", branch="args_reader", default-features = false } faster-hex = "0.6" ckb-logger = { path = "../util/logger", version = "= 0.121.0-pre", optional = true } serde = { version = "1.0", features = ["derive"] } diff --git a/script/src/scheduler.rs b/script/src/scheduler.rs index aee90e913af..8eaf93992b5 100644 --- a/script/src/scheduler.rs +++ b/script/src/scheduler.rs @@ -1,7 +1,7 @@ use crate::cost_model::transferred_byte_cycles; use crate::syscalls::{ - INVALID_FD, MAX_FDS_CREATED, MAX_VMS_SPAWNED, OTHER_END_CLOSED, SPAWN_EXTRA_CYCLES_BASE, - SUCCESS, WAIT_FAILURE, + EXEC_LOAD_ELF_V2_CYCLES_BASE, INVALID_FD, MAX_FDS_CREATED, MAX_VMS_SPAWNED, OTHER_END_CLOSED, + SPAWN_EXTRA_CYCLES_BASE, SUCCESS, WAIT_FAILURE, }; use crate::types::MachineContext; use crate::verify::TransactionScriptsSyscallsGenerator; @@ -339,6 +339,29 @@ where let messages: Vec = self.message_box.lock().expect("lock").drain(..).collect(); for message in messages { match message { + Message::Exec(vm_id, args) => { + let (_, old_machine) = self + .instantiated + .get_mut(&vm_id) + .ok_or_else(|| Error::Unexpected("Unable to find VM Id".to_string()))?; + old_machine + .machine + .add_cycles(EXEC_LOAD_ELF_V2_CYCLES_BASE)?; + let old_cycles = old_machine.machine.cycles(); + let max_cycles = old_machine.machine.max_cycles(); + let (context, mut new_machine) = self.create_dummy_vm(&vm_id)?; + new_machine.set_max_cycles(max_cycles); + new_machine.machine.add_cycles_no_checking(old_cycles)?; + self.load_vm_program( + &context, + &mut new_machine, + &args.data_piece_id, + args.offset, + args.length, + Some((vm_id, args.argc, args.argv)), + )?; + self.instantiated.insert(vm_id, (context, new_machine)); + } Message::Spawn(vm_id, args) => { // All fds must belong to the correct owner if args.fds.iter().any(|fd| self.fds.get(fd) != Some(&vm_id)) { @@ -760,26 +783,7 @@ where let id = self.next_vm_id; self.next_vm_id += 1; let (context, mut machine) = self.create_dummy_vm(&id)?; - { - let mut sc = context.snapshot2_context().lock().expect("lock"); - let (program, _) = sc.load_data(data_piece_id, offset, length)?; - let metadata = parse_elf::(&program, machine.machine.version())?; - let bytes = match args { - Some((vm_id, argc, argv)) => { - let (_, machine_from) = self.ensure_get_instantiated(&vm_id)?; - let argv = - FlattenedArgsReader::new(machine_from.machine.memory_mut(), argc, argv); - machine.load_program_with_metadata(&program, &metadata, argv)? - } - None => { - machine.load_program_with_metadata(&program, &metadata, vec![].into_iter())? - } - }; - sc.mark_program(&mut machine.machine, &metadata, data_piece_id, offset)?; - machine - .machine - .add_cycles_no_checking(transferred_byte_cycles(bytes))?; - } + self.load_vm_program(&context, &mut machine, data_piece_id, offset, length, args)?; // Newly booted VM will be instantiated by default while self.instantiated.len() >= MAX_INSTANTIATED_VMS { // Instantiated is a BTreeMap, first_entry will maintain key order @@ -796,6 +800,34 @@ where Ok(id) } + // Load the program into an empty vm. + fn load_vm_program( + &mut self, + context: &MachineContext
, + machine: &mut Machine, + data_piece_id: &DataPieceId, + offset: u64, + length: u64, + args: Option<(u64, u64, u64)>, + ) -> Result { + let mut sc = context.snapshot2_context().lock().expect("lock"); + let (program, _) = sc.load_data(data_piece_id, offset, length)?; + let metadata = parse_elf::(&program, machine.machine.version())?; + let bytes = match args { + Some((vm_id, argc, argv)) => { + let (_, machine_from) = self.ensure_get_instantiated(&vm_id)?; + let argv = FlattenedArgsReader::new(machine_from.machine.memory_mut(), argc, argv); + machine.load_program_with_metadata(&program, &metadata, argv)? + } + None => machine.load_program_with_metadata(&program, &metadata, vec![].into_iter())?, + }; + sc.mark_program(&mut machine.machine, &metadata, data_piece_id, offset)?; + machine + .machine + .add_cycles_no_checking(transferred_byte_cycles(bytes))?; + Ok(bytes) + } + // Create a new VM instance with syscalls attached fn create_dummy_vm(&self, id: &VmId) -> Result<(MachineContext
, Machine), Error> { // The code here looks slightly weird, since I don't want to copy over all syscall diff --git a/script/src/syscalls/exec.rs b/script/src/syscalls/exec.rs index 07e08616c81..0cadb627b98 100644 --- a/script/src/syscalls/exec.rs +++ b/script/src/syscalls/exec.rs @@ -26,7 +26,6 @@ pub struct Exec
{ outputs: Arc>, group_inputs: Indices, group_outputs: Indices, - load_elf_base_fee: u64, } impl Exec
{ @@ -36,7 +35,6 @@ impl Exec
{ outputs: Arc>, group_inputs: Indices, group_outputs: Indices, - load_elf_base_fee: u64, ) -> Exec
{ Exec { data_loader, @@ -44,7 +42,6 @@ impl Exec
{ outputs, group_inputs, group_outputs, - load_elf_base_fee, } } @@ -192,7 +189,6 @@ impl Syscalls for machine.reset(max_cycles)?; machine.set_cycles(cycles); - machine.add_cycles_no_checking(self.load_elf_base_fee)?; match machine.load_elf(&data, true) { Ok(size) => { machine.add_cycles_no_checking(transferred_byte_cycles(size))?; diff --git a/script/src/syscalls/exec_v2.rs b/script/src/syscalls/exec_v2.rs new file mode 100644 index 00000000000..042aee18391 --- /dev/null +++ b/script/src/syscalls/exec_v2.rs @@ -0,0 +1,124 @@ +use crate::syscalls::{ + Place, Source, EXEC, INDEX_OUT_OF_BOUND, SLICE_OUT_OF_BOUND, SOURCE_ENTRY_MASK, + SOURCE_GROUP_FLAG, +}; +use crate::types::{DataPieceId, ExecArgs, Message, TxData, VmId}; +use ckb_traits::{CellDataProvider, ExtensionProvider, HeaderProvider}; +use ckb_vm::{ + registers::{A0, A1, A2, A3, A4, A5, A7}, + snapshot2::Snapshot2Context, + Error as VMError, Register, SupportMachine, Syscalls, +}; +use std::sync::{Arc, Mutex}; + +pub struct Exec
+where + DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, +{ + id: VmId, + message_box: Arc>>, + snapshot2_context: Arc>>>, +} + +impl
Exec
+where + DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, +{ + pub fn new( + id: VmId, + message_box: Arc>>, + snapshot2_context: Arc>>>, + ) -> Exec
{ + Exec { + id, + message_box, + snapshot2_context, + } + } +} + +impl Syscalls for Exec
+where + Mac: SupportMachine, + DL: CellDataProvider + HeaderProvider + ExtensionProvider + 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() != EXEC { + return Ok(false); + } + let index = machine.registers()[A0].to_u64(); + let mut source = machine.registers()[A1].to_u64(); + let place = machine.registers()[A2].to_u64(); + // To keep compatible with the old behavior. When Source is wrong, a + // Vm internal error should be returned. + if let Source::Group(_) = Source::parse_from_u64(source)? { + source = source & SOURCE_ENTRY_MASK | SOURCE_GROUP_FLAG; + } else { + source &= SOURCE_ENTRY_MASK; + } + // To keep compatible with the old behavior. + Place::parse_from_u64(place)?; + + 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); + } + }; + let bounds = machine.registers()[A3].to_u64(); + let offset = bounds >> 32; + let length = bounds as u32 as u64; + + // We are fetching the actual cell here for some in-place validation + let mut sc = self + .snapshot2_context + .lock() + .map_err(|e| VMError::Unexpected(e.to_string()))?; + let (_, full_length) = match sc.load_data(&data_piece_id, 0, 0) { + Ok(val) => val, + Err(VMError::SnapshotDataLoadError) => { + // 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); + } + if length > 0 { + let end = offset.checked_add(length).ok_or(VMError::MemOutOfBound( + offset, + ckb_vm::error::OutOfBoundKind::Memory, + ))?; + if end > full_length { + machine.set_register(A0, Mac::REG::from_u8(SLICE_OUT_OF_BOUND)); + return Ok(true); + } + } + + let argc = machine.registers()[A4].clone(); + let argv = machine.registers()[A5].clone(); + self.message_box + .lock() + .map_err(|e| VMError::Unexpected(e.to_string()))? + .push(Message::Exec( + self.id, + ExecArgs { + data_piece_id, + offset, + length, + argc: argc.to_u64(), + argv: argv.to_u64(), + }, + )); + Err(VMError::Yield) + } +} diff --git a/script/src/syscalls/mod.rs b/script/src/syscalls/mod.rs index a7a3fd0459c..6d495582042 100644 --- a/script/src/syscalls/mod.rs +++ b/script/src/syscalls/mod.rs @@ -2,6 +2,7 @@ mod close; mod current_cycles; mod debugger; mod exec; +mod exec_v2; mod inherited_fd; mod load_block_extension; mod load_cell; @@ -31,6 +32,7 @@ pub use self::close::Close; pub use self::current_cycles::CurrentCycles; pub use self::debugger::Debugger; pub use self::exec::Exec; +pub use self::exec_v2::Exec as ExecV2; pub use self::inherited_fd::InheritedFd; pub use self::load_block_extension::LoadBlockExtension; pub use self::load_cell::LoadCell; diff --git a/script/src/types.rs b/script/src/types.rs index 6e2fdba4bf2..dc3a17f2c11 100644 --- a/script/src/types.rs +++ b/script/src/types.rs @@ -409,6 +409,15 @@ pub enum VmState { WaitForRead(ReadState), } +#[derive(Clone, Debug)] +pub struct ExecArgs { + pub data_piece_id: DataPieceId, + pub offset: u64, + pub length: u64, + pub argc: u64, + pub argv: u64, +} + #[derive(Clone, Debug)] pub struct SpawnArgs { pub data_piece_id: DataPieceId, @@ -442,6 +451,7 @@ pub struct FdArgs { #[derive(Clone, Debug)] pub enum Message { + Exec(VmId, ExecArgs), Spawn(VmId, SpawnArgs), Wait(VmId, WaitArgs), Pipe(VmId, PipeArgs), diff --git a/script/src/verify.rs b/script/src/verify.rs index 141910fc25c..4e2694c42f5 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -8,7 +8,7 @@ use crate::ChunkCommand; use crate::{ error::{ScriptError, TransactionScriptError}, syscalls::{ - Close, CurrentCycles, Debugger, Exec, LoadBlockExtension, LoadCell, LoadCellData, + Close, CurrentCycles, Debugger, Exec, ExecV2, LoadBlockExtension, LoadCell, LoadCellData, LoadHeader, LoadInput, LoadScript, LoadScriptHash, LoadTx, LoadWitness, Pipe, Read, Spawn, VMVersion, Wait, Write, }, @@ -172,22 +172,23 @@ where } /// Build syscall: exec - pub fn build_exec( - &self, - group_inputs: Indices, - group_outputs: Indices, - load_elf_base_fee: u64, - ) -> 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, - load_elf_base_fee, ) } + pub fn build_exec_v2( + &self, + snapshot2_context: Arc>>>, + ) -> ExecV2
{ + ExecV2::new(self.vm_id, Arc::clone(&self.message_box), snapshot2_context) + } + /// Build syscall: load_tx pub fn build_load_tx(&self) -> LoadTx { LoadTx::new(Arc::clone(&self.rtx)) @@ -326,15 +327,14 @@ where if script_version >= ScriptVersion::V1 { syscalls.append(&mut vec![ Box::new(self.build_vm_version()), - Box::new(self.build_exec( - Arc::clone(&script_group_input_indices), - Arc::clone(&script_group_output_indices), - if script_version >= ScriptVersion::V2 { - EXEC_LOAD_ELF_V2_CYCLES_BASE - } else { - 0 - }, - )), + if script_version >= ScriptVersion::V2 { + Box::new(self.build_exec_v2(Arc::clone(&snapshot2_context))) + } else { + Box::new(self.build_exec( + Arc::clone(&script_group_input_indices), + Arc::clone(&script_group_output_indices), + )) + }, Box::new(self.build_current_cycles()), ]); } 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 06ed81947f3..c5b21bce926 100644 --- a/script/src/verify/tests/ckb_latest/features_since_v2021.rs +++ b/script/src/verify/tests/ckb_latest/features_since_v2021.rs @@ -1751,6 +1751,8 @@ fn exec_from_witness_place_error() { #[test] fn exec_slice() { + let script_version = SCRIPT_VERSION; + let (exec_callee_cell, _exec_callee_data_hash) = load_cell_from_path("testdata/exec_configurable_callee"); let exec_callee_cell_data = exec_callee_cell.mem_cell_data.as_ref().unwrap(); @@ -1765,7 +1767,11 @@ fn exec_slice() { test_exec(0b0000, 1, 2, 1, from, res); let from = ExecFrom::OutOfSlice(((length - 1) << 32) | 1); - let res = Err("MemWriteOnExecutablePage".to_string()); + let res = if script_version >= ScriptVersion::V2 { + Err("Malformed entity: Too small".to_string()) + } else { + Err("MemWriteOnExecutablePage".to_string()) + }; test_exec(0b0000, 1, 2, 1, from, res); let from = ExecFrom::Slice((10 << 32) | length);