diff --git a/Cargo.lock b/Cargo.lock index df03ccce76..1040aef8ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1493,7 +1493,9 @@ dependencies = [ "ckb-traits", "ckb-types", "ckb-vm", + "daggy", "faster-hex", + "molecule", "proptest", "rand 0.8.5", "serde", @@ -1805,8 +1807,9 @@ dependencies = [ [[package]] name = "ckb-vm" -version = "0.24.9" -source = "git+https://github.com/chenyukang/ckb-vm.git?branch=yukang-local-changes#9cd3c20b009ea8e74a46143cafdde0acac9e323b" +version = "0.24.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddff96029d3298cb630e95f29d4b9a93384e938a0b75758684aa8794b53bdd1a" dependencies = [ "byteorder", "bytes", @@ -1822,8 +1825,9 @@ dependencies = [ [[package]] name = "ckb-vm-definitions" -version = "0.24.9" -source = "git+https://github.com/chenyukang/ckb-vm.git?branch=yukang-local-changes#9cd3c20b009ea8e74a46143cafdde0acac9e323b" +version = "0.24.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c280bf1d589d23ab0358f58601c2187fc6be86a131644583ef72ea96a0a13ddd" dependencies = [ "paste", ] @@ -2188,6 +2192,15 @@ dependencies = [ "libc", ] +[[package]] +name = "daggy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91a9304e55e9d601a39ae4deaba85406d5c0980e106f65afcf0460e9af1e7602" +dependencies = [ + "petgraph", +] + [[package]] name = "darling" version = "0.20.8" diff --git a/chain/src/tests/load_code_with_snapshot.rs b/chain/src/tests/load_code_with_snapshot.rs index 42db84283d..9a51e6e82b 100644 --- a/chain/src/tests/load_code_with_snapshot.rs +++ b/chain/src/tests/load_code_with_snapshot.rs @@ -113,7 +113,7 @@ fn test_load_code() { let tx_status = tx_pool.get_tx_status(tx.hash()); assert_eq!( tx_status.unwrap().unwrap(), - (TxStatus::Pending, Some(11174)) + (TxStatus::Pending, Some(11325)) ); } diff --git a/script/Cargo.toml b/script/Cargo.toml index 9bfef58e40..9f2ed4f4b4 100644 --- a/script/Cargo.toml +++ b/script/Cargo.toml @@ -22,7 +22,7 @@ ckb-traits = { path = "../traits", version = "= 0.117.0-pre" } byteorder = "1.3.1" ckb-types = { path = "../util/types", version = "= 0.117.0-pre" } ckb-hash = { path = "../util/hash", version = "= 0.117.0-pre" } -ckb-vm = { git = "https://github.com/chenyukang/ckb-vm.git", version = "=0.24.9", default-features = false, branch = "yukang-local-changes"} +ckb-vm = { version = "= 0.24.12", default-features = false } faster-hex = "0.6" ckb-logger = { path = "../util/logger", version = "= 0.117.0-pre", optional = true } serde = { version = "1.0", features = ["derive"] } @@ -40,3 +40,5 @@ ckb-crypto = { path = "../util/crypto", version = "= 0.117.0-pre" } ckb-db-schema = { path = "../db-schema", version = "= 0.117.0-pre" } tempfile.workspace = true rand = "0.8.4" +daggy = "0.8.0" +molecule = "0.8.0" diff --git a/script/build.rs b/script/build.rs index b9e5a0401e..a201f38439 100644 --- a/script/build.rs +++ b/script/build.rs @@ -19,4 +19,5 @@ fn main() { if cfg!(any(feature = "asm", feature = "detect-asm")) && can_enable_asm { println!("cargo:rustc-cfg=has_asm"); } + println!("cargo:rerun-if-changed=src"); } diff --git a/script/fuzz/.gitignore b/script/fuzz/.gitignore index df51d17bb0..438a59213f 100644 --- a/script/fuzz/.gitignore +++ b/script/fuzz/.gitignore @@ -3,3 +3,7 @@ target corpus artifacts coverage +flamegraph.svg +perf.data +perf.data.old + diff --git a/script/fuzz/Cargo.toml b/script/fuzz/Cargo.toml index 111a6e9753..0b8d4d5bcf 100644 --- a/script/fuzz/Cargo.toml +++ b/script/fuzz/Cargo.toml @@ -47,3 +47,9 @@ name = "syscall_exec" path = "fuzz_targets/syscall_exec.rs" test = false doc = false + +[[bin]] +name = "syscall_spawn" +path = "fuzz_targets/syscall_spawn.rs" +test = false +doc = false diff --git a/script/fuzz/README.md b/script/fuzz/README.md index d4942e9120..d51f1304df 100644 --- a/script/fuzz/README.md +++ b/script/fuzz/README.md @@ -14,7 +14,7 @@ cargo install cargo-fuzz run fuzz test ``` -cargo +nightly fuzz run transaction_scripts_verifier_data1 +cargo +nightly fuzz run -j $(nproc) transaction_scripts_verifier_data1 ``` generate coverage report diff --git a/script/fuzz/fuzz_targets/syscall_spawn.rs b/script/fuzz/fuzz_targets/syscall_spawn.rs new file mode 100644 index 0000000000..8782bee23a --- /dev/null +++ b/script/fuzz/fuzz_targets/syscall_spawn.rs @@ -0,0 +1,136 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; + +use ckb_chain_spec::consensus::ConsensusBuilder; +use ckb_script::{TransactionScriptsVerifier, TxVerifyEnv}; +use ckb_traits::{CellDataProvider, ExtensionProvider, HeaderProvider}; +use ckb_types::{ + bytes::Bytes, + core::{ + capacity_bytes, + cell::{CellMetaBuilder, ResolvedTransaction}, + hardfork::{HardForks, CKB2021, CKB2023}, + Capacity, HeaderView, ScriptHashType, TransactionBuilder, TransactionInfo, + }, + h256, + packed::{ + self, Byte32, CellInput, CellOutput, CellOutputBuilder, OutPoint, Script, + TransactionInfoBuilder, TransactionKeyBuilder, + }, + prelude::*, +}; + +#[derive(Default, PartialEq, Eq, Clone)] +struct MockDataLoader {} + +impl CellDataProvider for MockDataLoader { + fn get_cell_data(&self, _out_point: &OutPoint) -> Option { + None + } + + fn get_cell_data_hash(&self, _out_point: &OutPoint) -> Option { + None + } +} + +impl HeaderProvider for MockDataLoader { + fn get_header(&self, _block_hash: &Byte32) -> Option { + None + } +} + +impl ExtensionProvider for MockDataLoader { + fn get_block_extension(&self, _hash: &Byte32) -> Option { + None + } +} + +fn mock_transaction_info() -> TransactionInfo { + TransactionInfoBuilder::default() + .block_number(1u64.pack()) + .block_epoch(0u64.pack()) + .key( + TransactionKeyBuilder::default() + .block_hash(Byte32::zero()) + .index(1u32.pack()) + .build(), + ) + .build() + .unpack() +} + +static PROGRAM_DATA: &[u8] = include_bytes!("../../testdata/spawn_fuzzing"); + +fn run(data: &[u8]) { + if data.len() < 8 { + return; + } + let split_offset = data[0] as usize; + let split_offset = usize::min(split_offset, data.len() - 1); + let parent_witness = Bytes::copy_from_slice(&data[0..split_offset]); + let child_witness = Bytes::copy_from_slice(&data[split_offset..]); + let witnesses = vec![parent_witness.pack(), child_witness.pack()]; + + let transaction = TransactionBuilder::default() + .input(CellInput::new(OutPoint::null(), 0)) + .set_witnesses(witnesses) + .build(); + + let data: Bytes = (Vec::from(PROGRAM_DATA)).into(); + let script = Script::new_builder() + .hash_type(ScriptHashType::Data2.into()) + .code_hash(CellOutput::calc_data_hash(&data)) + .build(); + let dep_cell = CellMetaBuilder::from_cell_output( + CellOutput::new_builder() + .capacity(Capacity::bytes(data.len()).unwrap().pack()) + .build(), + data, + ) + .transaction_info(mock_transaction_info()) + .out_point(OutPoint::new(h256!("0x0").pack(), 0)) + .build(); + + let input_cell = CellMetaBuilder::from_cell_output( + CellOutputBuilder::default() + .capacity(capacity_bytes!(100).pack()) + .lock(script) + .build(), + Bytes::new(), + ) + .transaction_info(mock_transaction_info()) + .build(); + + let rtx = ResolvedTransaction { + transaction, + resolved_cell_deps: vec![dep_cell], + resolved_inputs: vec![input_cell], + resolved_dep_groups: vec![], + }; + + let provider = MockDataLoader {}; + let hardfork_switch = HardForks { + ckb2021: CKB2021::new_mirana().as_builder().build().unwrap(), + ckb2023: CKB2023::new_mirana() + .as_builder() + .rfc_0049(0) + .build() + .unwrap(), + }; + let consensus = ConsensusBuilder::default() + .hardfork_switch(hardfork_switch) + .build(); + let tx_verify_env = + TxVerifyEnv::new_submit(&HeaderView::new_advanced_builder().epoch(0.pack()).build()); + let verifier = TransactionScriptsVerifier::new( + rtx.into(), + provider, + consensus.into(), + tx_verify_env.into(), + ); + let _ = verifier.verify(70_000_000); +} + +fuzz_target!(|data: &[u8]| { + run(data); +}); diff --git a/script/fuzz/programs/exec_caller.c b/script/fuzz/programs/exec_caller.c index 2f68f1a20f..7403a6b7ee 100644 --- a/script/fuzz/programs/exec_caller.c +++ b/script/fuzz/programs/exec_caller.c @@ -1,3 +1,4 @@ +#include #include static inline long __internal_syscall(long n, long _a0, long _a1, long _a2, diff --git a/script/src/lib.rs b/script/src/lib.rs index b24f94db63..e979f3e411 100644 --- a/script/src/lib.rs +++ b/script/src/lib.rs @@ -1,6 +1,7 @@ //! CKB component to run the type/lock scripts. pub mod cost_model; mod error; +mod scheduler; mod syscalls; mod type_id; mod types; @@ -8,10 +9,10 @@ mod verify; mod verify_env; pub use crate::error::{ScriptError, TransactionScriptError}; -pub use crate::syscalls::spawn::update_caller_machine; +pub use crate::scheduler::{Scheduler, ROOT_VM_ID}; pub use crate::types::{ - ChunkCommand, CoreMachine, MachineContext, ResumableMachine, ScriptGroup, ScriptGroupType, - ScriptVersion, TransactionSnapshot, TransactionState, VerifyResult, VmIsa, VmVersion, + ChunkCommand, CoreMachine, DataPieceId, RunMode, ScriptGroup, ScriptGroupType, ScriptVersion, + TransactionSnapshot, TransactionState, TxData, VerifyResult, VmIsa, VmState, VmVersion, }; pub use crate::verify::{TransactionScriptsSyscallsGenerator, TransactionScriptsVerifier}; pub use crate::verify_env::TxVerifyEnv; diff --git a/script/src/scheduler.rs b/script/src/scheduler.rs new file mode 100644 index 0000000000..3c93c0ee82 --- /dev/null +++ b/script/src/scheduler.rs @@ -0,0 +1,812 @@ +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, +}; +use crate::types::MachineContext; +use crate::verify::TransactionScriptsSyscallsGenerator; +use crate::ScriptVersion; + +use crate::types::{ + CoreMachineType, DataPieceId, Fd, FdArgs, FullSuspendedState, Machine, Message, ReadState, + RunMode, TxData, VmId, VmState, WriteState, FIRST_FD_SLOT, FIRST_VM_ID, +}; +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, + elf::parse_elf, + machine::{CoreMachine, DefaultMachineBuilder, Pause, SupportMachine}, + memory::Memory, + registers::A0, + snapshot2::Snapshot2, + Error, Register, +}; +use std::collections::{BTreeMap, HashMap}; +use std::sync::{Arc, Mutex}; + +/// Root process's id. +pub const ROOT_VM_ID: VmId = FIRST_VM_ID; +/// The maximum number of VMs that can be created at the same time. +pub const MAX_VMS_COUNT: u64 = 16; +/// The maximum number of instantiated VMs. +pub const MAX_INSTANTIATED_VMS: usize = 4; +/// The maximum number of fds. +pub const MAX_FDS: u64 = 64; + +/// A single Scheduler instance is used to verify a single script +/// within a CKB transaction. +/// +/// 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
+where + DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, +{ + /// Context data for current running transaction & script. + pub tx_data: TxData
, + /// In fact, Scheduler here has the potential to totally replace + /// TransactionScriptsVerifier, nonetheless much of current syscall + /// implementation is strictly tied to TransactionScriptsVerifier, we + /// are using it here to save some extra code. + pub script_version: ScriptVersion, + /// Generate system calls. + pub syscalls_generator: TransactionScriptsSyscallsGenerator
, + + /// Total cycles. + pub total_cycles: Cycle, + /// Current iteration cycles. This value is periodically added to + /// total_cycles and cleared + pub current_iteration_cycles: Cycle, + /// Next vm id used by spawn. + pub next_vm_id: VmId, + /// Next fd used by pipe. + pub next_fd_slot: u64, + /// Used to store VM state. + pub states: BTreeMap, + /// Used to confirm the owner of fd. + pub fds: BTreeMap, + /// Verify the VM's inherited fd list. + pub inherited_fd: BTreeMap>, + /// Instantiated vms. + pub instantiated: BTreeMap, Machine)>, + /// Suspended vms. + pub suspended: BTreeMap>, + /// Terminated vms. + pub terminated_vms: BTreeMap, + + /// MessageBox is expected to be empty before returning from `run` + /// function, there is no need to persist messages. + pub message_box: Arc>>, +} + +impl
Scheduler
+where + DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, +{ + /// Create a new scheduler from empty state + pub fn new( + tx_data: TxData
, + script_version: ScriptVersion, + syscalls_generator: TransactionScriptsSyscallsGenerator
, + ) -> Self { + let message_box = Arc::clone(&syscalls_generator.message_box); + Self { + tx_data, + script_version, + syscalls_generator, + total_cycles: 0, + current_iteration_cycles: 0, + next_vm_id: FIRST_VM_ID, + next_fd_slot: FIRST_FD_SLOT, + states: BTreeMap::default(), + fds: BTreeMap::default(), + inherited_fd: BTreeMap::default(), + instantiated: BTreeMap::default(), + suspended: BTreeMap::default(), + message_box, + terminated_vms: BTreeMap::default(), + } + } + + /// Return total cycles. + pub fn consumed_cycles(&self) -> Cycle { + self.total_cycles + } + + /// Add cycles to total cycles. + pub fn consumed_cycles_add(&mut self, cycles: Cycle) -> Result<(), Error> { + self.total_cycles = self + .total_cycles + .checked_add(cycles) + .ok_or(Error::CyclesExceeded)?; + Ok(()) + } + + /// Resume a previously suspended scheduler state + pub fn resume( + tx_data: TxData
, + script_version: ScriptVersion, + syscalls_generator: TransactionScriptsSyscallsGenerator
, + full: FullSuspendedState, + ) -> Self { + let message_box = Arc::clone(&syscalls_generator.message_box); + let mut scheduler = Self { + tx_data, + script_version, + syscalls_generator, + total_cycles: full.total_cycles, + current_iteration_cycles: 0, + next_vm_id: full.next_vm_id, + next_fd_slot: full.next_fd_slot, + states: full + .vms + .iter() + .map(|(id, state, _)| (*id, state.clone())) + .collect(), + fds: full.fds.into_iter().collect(), + inherited_fd: full.inherited_fd.into_iter().collect(), + instantiated: BTreeMap::default(), + suspended: full + .vms + .into_iter() + .map(|(id, _, snapshot)| (id, snapshot)) + .collect(), + message_box, + terminated_vms: full.terminated_vms.into_iter().collect(), + }; + scheduler + .ensure_vms_instantiated(&full.instantiated_ids) + .unwrap(); + scheduler + } + + /// Suspend current scheduler into a serializable full state + pub fn suspend(mut self) -> Result { + assert!(self.message_box.lock().expect("lock").is_empty()); + let mut vms = Vec::with_capacity(self.states.len()); + let instantiated_ids: Vec<_> = self.instantiated.keys().cloned().collect(); + for id in &instantiated_ids { + self.suspend_vm(id)?; + } + for (id, state) in self.states { + let snapshot = self + .suspended + .remove(&id) + .ok_or_else(|| Error::Unexpected("Unable to find VM Id".to_string()))?; + vms.push((id, state, snapshot)); + } + Ok(FullSuspendedState { + total_cycles: self.total_cycles, + next_vm_id: self.next_vm_id, + next_fd_slot: self.next_fd_slot, + vms, + fds: self.fds.into_iter().collect(), + inherited_fd: self.inherited_fd.into_iter().collect(), + terminated_vms: self.terminated_vms.into_iter().collect(), + instantiated_ids, + }) + } + + /// This is the only entrypoint for running the scheduler, + /// both newly created instance and resumed instance are supported. + /// It accepts 2 run mode, one can either limit the cycles to execute, + /// or use a pause signal to trigger termination. + /// + /// Only when the execution terminates without VM errors, will this + /// function return an exit code(could still be non-zero) and total + /// consumed cycles. + /// + /// Err would be returned in the following cases: + /// * Cycle limit reached, the returned error would be ckb_vm::Error::CyclesExceeded, + /// * Pause trigger, the returned error would be ckb_vm::Error::Pause, + /// * Other terminating errors + pub fn run(&mut self, mode: RunMode) -> Result<(i8, Cycle), Error> { + if self.states.is_empty() { + // Booting phase, we will need to initialize the first VM. + assert_eq!( + self.boot_vm(&DataPieceId::Program, 0, u64::max_value(), &[])?, + ROOT_VM_ID + ); + } + assert!(self.states.contains_key(&ROOT_VM_ID)); + + let (pause, mut limit_cycles) = match mode { + RunMode::LimitCycles(limit_cycles) => (Pause::new(), limit_cycles), + RunMode::Pause(pause) => (pause, u64::max_value()), + }; + + while self.states[&ROOT_VM_ID] != VmState::Terminated { + self.current_iteration_cycles = 0; + let iterate_return = self.iterate(pause.clone(), limit_cycles); + self.consumed_cycles_add(self.current_iteration_cycles)?; + limit_cycles = limit_cycles + .checked_sub(self.current_iteration_cycles) + .ok_or(Error::CyclesExceeded)?; + iterate_return?; + } + + // At this point, root VM cannot be suspended + let root_vm = &self.instantiated[&ROOT_VM_ID]; + Ok((root_vm.1.machine.exit_code(), self.total_cycles)) + } + + /// Returns the machine that needs to be executed in the current iterate. + pub fn iterate_prepare_machine( + &mut self, + pause: Pause, + limit_cycles: Cycle, + ) -> Result<(u64, &mut Machine), Error> { + // Process all pending VM reads & writes. + self.process_io()?; + // Find a runnable VM that has the largest ID. + let vm_id_to_run = self + .states + .iter() + .rev() + .filter(|(_, state)| matches!(state, VmState::Runnable)) + .map(|(id, _)| *id) + .next(); + let vm_id_to_run = vm_id_to_run.ok_or_else(|| { + Error::Unexpected("A deadlock situation has been reached!".to_string()) + })?; + let total_cycles = self.total_cycles; + let (context, machine) = self.ensure_get_instantiated(&vm_id_to_run)?; + context.set_base_cycles(total_cycles); + machine.set_max_cycles(limit_cycles); + machine.machine.set_pause(pause); + Ok((vm_id_to_run, machine)) + } + + /// Process machine execution results in the current iterate. + pub fn iterate_process_results( + &mut self, + vm_id_to_run: u64, + result: Result, + cycles: u64, + ) -> Result<(), Error> { + self.current_iteration_cycles = self + .current_iteration_cycles + .checked_add(cycles) + .ok_or(Error::CyclesOverflow)?; + // Process message box, update VM states accordingly + self.process_message_box()?; + assert!(self.message_box.lock().expect("lock").is_empty()); + // If the VM terminates, update VMs in join state, also closes its fds + match result { + Ok(code) => { + self.terminated_vms.insert(vm_id_to_run, code); + // When root VM terminates, the execution stops immediately, we will purge + // all non-root VMs, and only keep root VM in states. + // When non-root VM terminates, we only purge the VM's own states. + if vm_id_to_run == ROOT_VM_ID { + self.ensure_vms_instantiated(&[vm_id_to_run])?; + self.instantiated.retain(|id, _| *id == vm_id_to_run); + self.suspended.clear(); + self.states.clear(); + self.states.insert(vm_id_to_run, VmState::Terminated); + } else { + let joining_vms: Vec<(VmId, u64)> = self + .states + .iter() + .filter_map(|(vm_id, state)| match state { + VmState::Wait { + target_vm_id, + exit_code_addr, + } if *target_vm_id == vm_id_to_run => Some((*vm_id, *exit_code_addr)), + _ => None, + }) + .collect(); + // For all joining VMs, update exit code, then mark them as + // runnable state. + for (vm_id, exit_code_addr) in joining_vms { + let (_, machine) = self.ensure_get_instantiated(&vm_id)?; + machine + .machine + .memory_mut() + .store8(&exit_code_addr, &u64::from_i8(code))?; + machine.machine.set_register(A0, SUCCESS as u64); + self.states.insert(vm_id, VmState::Runnable); + } + // Close fds + self.fds.retain(|_, vm_id| *vm_id != vm_id_to_run); + // Clear terminated VM states + self.states.remove(&vm_id_to_run); + self.instantiated.remove(&vm_id_to_run); + self.suspended.remove(&vm_id_to_run); + } + Ok(()) + } + Err(Error::Yield) => Ok(()), + Err(e) => Err(e), + } + } + + // This is internal function that does the actual VM execution loop. + // Here both pause signal and limit_cycles are provided so as to simplify + // branches. + fn iterate(&mut self, pause: Pause, limit_cycles: Cycle) -> Result<(), Error> { + let (id, vm) = self.iterate_prepare_machine(pause, limit_cycles)?; + let result = vm.run(); + let cycles = vm.machine.cycles(); + vm.machine.set_cycles(0); + self.iterate_process_results(id, result, cycles) + } + + fn process_message_box(&mut self) -> Result<(), Error> { + let messages: Vec = self.message_box.lock().expect("lock").drain(..).collect(); + for message in messages { + match message { + 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)) { + let (_, machine) = self.ensure_get_instantiated(&vm_id)?; + machine.machine.set_register(A0, INVALID_FD as u64); + continue; + } + if self.suspended.len() + self.instantiated.len() > MAX_VMS_COUNT as usize { + let (_, machine) = self.ensure_get_instantiated(&vm_id)?; + machine.machine.set_register(A0, MAX_VMS_SPAWNED as u64); + continue; + } + let spawned_vm_id = + self.boot_vm(&args.data_piece_id, args.offset, args.length, &args.argv)?; + // Move passed fds from spawner to spawnee + for fd in &args.fds { + self.fds.insert(*fd, spawned_vm_id); + } + // Here we keep the original version of file descriptors. + // If one fd is moved afterward, this inherited file descriptors doesn't change. + self.inherited_fd.insert(spawned_vm_id, args.fds.clone()); + + let (_, machine) = self.ensure_get_instantiated(&vm_id)?; + machine + .machine + .memory_mut() + .store64(&args.process_id_addr, &spawned_vm_id)?; + machine.machine.set_register(A0, SUCCESS as u64); + } + Message::Wait(vm_id, args) => { + if let Some(exit_code) = self.terminated_vms.get(&args.target_id).copied() { + let (_, machine) = self.ensure_get_instantiated(&vm_id)?; + machine + .machine + .memory_mut() + .store8(&args.exit_code_addr, &u64::from_i8(exit_code))?; + machine.machine.set_register(A0, SUCCESS as u64); + self.states.insert(vm_id, VmState::Runnable); + self.terminated_vms.retain(|id, _| id != &args.target_id); + continue; + } + if !self.states.contains_key(&args.target_id) { + let (_, machine) = self.ensure_get_instantiated(&vm_id)?; + machine.machine.set_register(A0, WAIT_FAILURE as u64); + continue; + } + // Return code will be updated when the joining VM exits + self.states.insert( + vm_id, + VmState::Wait { + target_vm_id: args.target_id, + exit_code_addr: args.exit_code_addr, + }, + ); + } + Message::Pipe(vm_id, args) => { + if self.fds.len() as u64 >= MAX_FDS { + let (_, machine) = self.ensure_get_instantiated(&vm_id)?; + machine.machine.set_register(A0, MAX_FDS_CREATED as u64); + continue; + } + let (p1, p2, slot) = Fd::create(self.next_fd_slot); + self.next_fd_slot = slot; + self.fds.insert(p1, vm_id); + self.fds.insert(p2, vm_id); + let (_, machine) = self.ensure_get_instantiated(&vm_id)?; + machine + .machine + .memory_mut() + .store64(&args.fd1_addr, &p1.0)?; + machine + .machine + .memory_mut() + .store64(&args.fd2_addr, &p2.0)?; + machine.machine.set_register(A0, SUCCESS as u64); + } + Message::FdRead(vm_id, args) => { + if !(self.fds.contains_key(&args.fd) && (self.fds[&args.fd] == vm_id)) { + let (_, machine) = self.ensure_get_instantiated(&vm_id)?; + machine.machine.set_register(A0, INVALID_FD as u64); + continue; + } + if !self.fds.contains_key(&args.fd.other_fd()) { + let (_, machine) = self.ensure_get_instantiated(&vm_id)?; + machine.machine.set_register(A0, OTHER_END_CLOSED as u64); + continue; + } + // Return code will be updated when the read operation finishes + self.states.insert( + vm_id, + VmState::WaitForRead(ReadState { + fd: args.fd, + length: args.length, + buffer_addr: args.buffer_addr, + length_addr: args.length_addr, + }), + ); + } + Message::FdWrite(vm_id, args) => { + if !(self.fds.contains_key(&args.fd) && (self.fds[&args.fd] == vm_id)) { + let (_, machine) = self.ensure_get_instantiated(&vm_id)?; + machine.machine.set_register(A0, INVALID_FD as u64); + continue; + } + if !self.fds.contains_key(&args.fd.other_fd()) { + let (_, machine) = self.ensure_get_instantiated(&vm_id)?; + machine.machine.set_register(A0, OTHER_END_CLOSED as u64); + continue; + } + // Return code will be updated when the write operation finishes + self.states.insert( + vm_id, + VmState::WaitForWrite(WriteState { + fd: args.fd, + consumed: 0, + length: args.length, + buffer_addr: args.buffer_addr, + length_addr: args.length_addr, + }), + ); + } + Message::InheritedFileDescriptor(vm_id, args) => { + let inherited_fd = self.inherited_fd[&vm_id].clone(); + let (_, machine) = self.ensure_get_instantiated(&vm_id)?; + let FdArgs { + buffer_addr, + length_addr, + .. + } = args; + let full_length = machine + .machine + .inner_mut() + .memory_mut() + .load64(&length_addr)?; + let real_length = inherited_fd.len() as u64; + let copy_length = u64::min(full_length, real_length); + for i in 0..copy_length { + let fd = inherited_fd[i as usize].0; + let addr = buffer_addr.checked_add(i * 8).ok_or(Error::MemOutOfBound)?; + machine + .machine + .inner_mut() + .memory_mut() + .store64(&addr, &fd)?; + } + machine + .machine + .inner_mut() + .memory_mut() + .store64(&length_addr, &real_length)?; + machine.machine.set_register(A0, SUCCESS as u64); + } + Message::Close(vm_id, fd) => { + if self.fds.get(&fd) != Some(&vm_id) { + let (_, machine) = self.ensure_get_instantiated(&vm_id)?; + machine.machine.set_register(A0, INVALID_FD as u64); + } else { + self.fds.remove(&fd); + let (_, machine) = self.ensure_get_instantiated(&vm_id)?; + machine.machine.set_register(A0, SUCCESS as u64); + } + } + } + } + Ok(()) + } + + fn process_io(&mut self) -> Result<(), Error> { + let mut reads: HashMap = HashMap::default(); + let mut closed_fds: Vec = Vec::new(); + self.states.iter().for_each(|(vm_id, state)| { + if let VmState::WaitForRead(inner_state) = state { + if self.fds.contains_key(&inner_state.fd.other_fd()) { + reads.insert(inner_state.fd, (*vm_id, inner_state.clone())); + } else { + closed_fds.push(*vm_id); + } + } + }); + let mut pairs: Vec<(VmId, ReadState, VmId, WriteState)> = Vec::new(); + self.states.iter().for_each(|(vm_id, state)| { + if let VmState::WaitForWrite(inner_state) = state { + if self.fds.contains_key(&inner_state.fd.other_fd()) { + if let Some((read_vm_id, read_state)) = reads.get(&inner_state.fd.other_fd()) { + pairs.push((*read_vm_id, read_state.clone(), *vm_id, inner_state.clone())); + } + } else { + closed_fds.push(*vm_id); + } + } + }); + // Finish read / write syscalls for fds that are closed on the other end + for vm_id in closed_fds { + match self.states[&vm_id].clone() { + VmState::WaitForRead(ReadState { length_addr, .. }) => { + let (_, machine) = self.ensure_get_instantiated(&vm_id)?; + machine.machine.memory_mut().store64(&length_addr, &0)?; + machine.machine.set_register(A0, SUCCESS as u64); + self.states.insert(vm_id, VmState::Runnable); + } + VmState::WaitForWrite(WriteState { + consumed, + length_addr, + .. + }) => { + let (_, machine) = self.ensure_get_instantiated(&vm_id)?; + machine + .machine + .memory_mut() + .store64(&length_addr, &consumed)?; + machine.machine.set_register(A0, SUCCESS as u64); + self.states.insert(vm_id, VmState::Runnable); + } + _ => (), + } + } + // Transfering data from write fds to read fds + for (read_vm_id, read_state, write_vm_id, write_state) in pairs { + let ReadState { + length: read_length, + buffer_addr: read_buffer_addr, + length_addr: read_length_addr, + .. + } = read_state; + let WriteState { + fd: write_fd, + mut consumed, + length: write_length, + buffer_addr: write_buffer_addr, + length_addr: write_length_addr, + } = write_state; + + self.ensure_vms_instantiated(&[read_vm_id, write_vm_id])?; + { + let fillable = read_length; + let consumable = write_length - consumed; + let copiable = std::cmp::min(fillable, consumable); + + // Actual data copying + let (_, write_machine) = self + .instantiated + .get_mut(&write_vm_id) + .ok_or_else(|| Error::Unexpected("Unable to find VM Id".to_string()))?; + write_machine + .machine + .add_cycles_no_checking(transferred_byte_cycles(copiable))?; + let data = write_machine + .machine + .memory_mut() + .load_bytes(write_buffer_addr.wrapping_add(consumed), copiable)?; + let (_, read_machine) = self + .instantiated + .get_mut(&read_vm_id) + .ok_or_else(|| Error::Unexpected("Unable to find VM Id".to_string()))?; + read_machine + .machine + .add_cycles_no_checking(transferred_byte_cycles(copiable))?; + read_machine + .machine + .memory_mut() + .store_bytes(read_buffer_addr, &data)?; + // Read syscall terminates as soon as some data are filled + read_machine + .machine + .memory_mut() + .store64(&read_length_addr, &copiable)?; + read_machine.machine.set_register(A0, SUCCESS as u64); + self.states.insert(read_vm_id, VmState::Runnable); + + // Write syscall, however, terminates only when all the data + // have been written, or when the pairing read fd is closed. + consumed += copiable; + if consumed == write_length { + // Write VM has fulfilled its write request + let (_, write_machine) = self + .instantiated + .get_mut(&write_vm_id) + .ok_or_else(|| Error::Unexpected("Unable to find VM Id".to_string()))?; + write_machine + .machine + .memory_mut() + .store64(&write_length_addr, &write_length)?; + write_machine.machine.set_register(A0, SUCCESS as u64); + self.states.insert(write_vm_id, VmState::Runnable); + } else { + // Only update write VM state + self.states.insert( + write_vm_id, + VmState::WaitForWrite(WriteState { + fd: write_fd, + consumed, + length: write_length, + buffer_addr: write_buffer_addr, + length_addr: write_length_addr, + }), + ); + } + } + } + Ok(()) + } + + // Ensure VMs are instantiated + fn ensure_vms_instantiated(&mut self, ids: &[VmId]) -> Result<(), Error> { + if ids.len() > MAX_INSTANTIATED_VMS { + return Err(Error::Unexpected(format!( + "At most {} VMs can be instantiated but {} are requested!", + MAX_INSTANTIATED_VMS, + ids.len() + ))); + } + + let mut uninstantiated_ids: Vec = ids + .iter() + .filter(|id| !self.instantiated.contains_key(id)) + .copied() + .collect(); + while (!uninstantiated_ids.is_empty()) && (self.instantiated.len() < MAX_INSTANTIATED_VMS) { + let id = uninstantiated_ids + .pop() + .ok_or_else(|| Error::Unexpected("Map should not be empty".to_string()))?; + self.resume_vm(&id)?; + } + + if !uninstantiated_ids.is_empty() { + // Instantiated is a BTreeMap, an iterator on it maintains key order to ensure deterministic behavior + let suspendable_ids: Vec = self + .instantiated + .keys() + .filter(|id| !ids.contains(id)) + .copied() + .collect(); + + assert!(suspendable_ids.len() >= uninstantiated_ids.len()); + for i in 0..uninstantiated_ids.len() { + self.suspend_vm(&suspendable_ids[i])?; + self.resume_vm(&uninstantiated_ids[i])?; + } + } + + Ok(()) + } + + // Ensure corresponding VM is instantiated and return a mutable reference to it + fn ensure_get_instantiated( + &mut self, + id: &VmId, + ) -> Result<&mut (MachineContext
, Machine), Error> { + self.ensure_vms_instantiated(&[*id])?; + self.instantiated + .get_mut(id) + .ok_or_else(|| Error::Unexpected("Unable to find VM Id".to_string())) + } + + // Resume a suspended VM + fn resume_vm(&mut self, id: &VmId) -> Result<(), Error> { + if !self.suspended.contains_key(id) { + return Err(Error::Unexpected(format!("VM {:?} is not suspended!", id))); + } + let snapshot = &self.suspended[id]; + self.current_iteration_cycles = self + .current_iteration_cycles + .checked_add(SPAWN_EXTRA_CYCLES_BASE) + .ok_or(Error::CyclesExceeded)?; + let (context, mut machine) = self.create_dummy_vm(id)?; + { + let mut sc = context.snapshot2_context().lock().expect("lock"); + sc.resume(&mut machine.machine, snapshot)?; + } + self.instantiated.insert(*id, (context, machine)); + self.suspended.remove(id); + Ok(()) + } + + // Suspend an instantiated VM + fn suspend_vm(&mut self, id: &VmId) -> Result<(), Error> { + if !self.instantiated.contains_key(id) { + return Err(Error::Unexpected(format!( + "VM {:?} is not instantiated!", + id + ))); + } + self.current_iteration_cycles = self + .current_iteration_cycles + .checked_add(SPAWN_EXTRA_CYCLES_BASE) + .ok_or(Error::CyclesExceeded)?; + let (context, machine) = self + .instantiated + .get_mut(id) + .ok_or_else(|| Error::Unexpected("Unable to find VM Id".to_string()))?; + let snapshot = { + let sc = context.snapshot2_context().lock().expect("lock"); + sc.make_snapshot(&mut machine.machine)? + }; + self.suspended.insert(*id, snapshot); + self.instantiated.remove(id); + Ok(()) + } + + /// Boot a vm by given program and args. + pub fn boot_vm( + &mut self, + data_piece_id: &DataPieceId, + offset: u64, + length: u64, + args: &[Bytes], + ) -> Result { + // 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 + let id = *self + .instantiated + .first_entry() + .ok_or_else(|| Error::Unexpected("Map should not be empty".to_string()))? + .key(); + self.suspend_vm(&id)?; + } + + 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 = machine.load_program_with_metadata(&program, &metadata, args)?; + sc.mark_program(&mut machine.machine, &metadata, data_piece_id, offset)?; + machine + .machine + .add_cycles_no_checking(transferred_byte_cycles(bytes))?; + } + self.instantiated.insert(id, (context, machine)); + self.states.insert(id, VmState::Runnable); + + Ok(id) + } + + // 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 + // impls here again. Ideally, this scheduler package should be merged with ckb-script, + // or simply replace ckb-script. That way, the quirks here will be eliminated. + let version = self.script_version; + let core_machine = CoreMachineType::new( + version.vm_isa(), + version.vm_version(), + // We will update max_cycles for each machine when it gets a chance to run + u64::max_value(), + ); + let snapshot2_context = Arc::new(Mutex::new(Snapshot2Context::new(self.tx_data.clone()))); + let mut syscalls_generator = self.syscalls_generator.clone(); + syscalls_generator.vm_id = *id; + let mut machine_context = MachineContext::new(self.tx_data.clone()); + machine_context.base_cycles = Arc::clone(&self.syscalls_generator.base_cycles); + machine_context.snapshot2_context = Arc::clone(&snapshot2_context); + + let machine_builder = DefaultMachineBuilder::new(core_machine) + .instruction_cycle_func(Box::new(estimate_cycles)); + let machine_builder = syscalls_generator + .generate_syscalls( + version, + &self.tx_data.script_group, + Arc::clone(&snapshot2_context), + ) + .into_iter() + .fold(machine_builder, |builder, syscall| builder.syscall(syscall)); + let default_machine = machine_builder.build(); + Ok((machine_context, Machine::new(default_machine))) + } +} diff --git a/script/src/syscalls/close.rs b/script/src/syscalls/close.rs new file mode 100644 index 0000000000..1e359095be --- /dev/null +++ b/script/src/syscalls/close.rs @@ -0,0 +1,38 @@ +use crate::syscalls::{CLOSE, SPAWN_YIELD_CYCLES_BASE}; +use crate::types::{Fd, Message, VmId}; +use ckb_vm::{ + registers::{A0, A7}, + Error as VMError, Register, SupportMachine, Syscalls, +}; +use std::sync::{Arc, Mutex}; + +#[derive(Debug)] +pub struct Close { + id: VmId, + message_box: Arc>>, +} + +impl Close { + pub fn new(id: VmId, message_box: Arc>>) -> Self { + Self { id, message_box } + } +} + +impl Syscalls for Close { + fn initialize(&mut self, _machine: &mut Mac) -> Result<(), VMError> { + Ok(()) + } + + fn ecall(&mut self, machine: &mut Mac) -> Result { + if machine.registers()[A7].to_u64() != CLOSE { + return Ok(false); + } + let fd = Fd(machine.registers()[A0].to_u64()); + machine.add_cycles_no_checking(SPAWN_YIELD_CYCLES_BASE)?; + self.message_box + .lock() + .map_err(|e| VMError::Unexpected(e.to_string()))? + .push(Message::Close(self.id, fd)); + Err(VMError::Yield) + } +} diff --git a/script/src/syscalls/current_cycles.rs b/script/src/syscalls/current_cycles.rs index dc87cd281c..a791cef7a4 100644 --- a/script/src/syscalls/current_cycles.rs +++ b/script/src/syscalls/current_cycles.rs @@ -3,14 +3,15 @@ use ckb_vm::{ registers::{A0, A7}, Error as VMError, Register, SupportMachine, Syscalls, }; +use std::sync::{Arc, Mutex}; #[derive(Debug, Default)] pub struct CurrentCycles { - base: u64, + base: Arc>, } impl CurrentCycles { - pub fn new(base: u64) -> Self { + pub fn new(base: Arc>) -> Self { Self { base } } } @@ -26,6 +27,8 @@ impl Syscalls for CurrentCycles { } let cycles = self .base + .lock() + .map_err(|e| VMError::Unexpected(e.to_string()))? .checked_add(machine.cycles()) .ok_or(VMError::CyclesOverflow)?; machine.set_register(A0, Mac::REG::from_u64(cycles)); diff --git a/script/src/syscalls/exec.rs b/script/src/syscalls/exec.rs index 2c33a52165..6eb1d159c4 100644 --- a/script/src/syscalls/exec.rs +++ b/script/src/syscalls/exec.rs @@ -22,6 +22,7 @@ pub struct Exec
{ outputs: Arc>, group_inputs: Indices, group_outputs: Indices, + load_elf_base_fee: u64, } impl Exec
{ @@ -31,6 +32,7 @@ impl Exec
{ outputs: Arc>, group_inputs: Indices, group_outputs: Indices, + load_elf_base_fee: u64, ) -> Exec
{ Exec { data_loader, @@ -38,6 +40,7 @@ impl Exec
{ outputs, group_inputs, group_outputs, + load_elf_base_fee, } } @@ -109,7 +112,6 @@ impl Syscalls for if machine.registers()[A7].to_u64() != EXEC { return Ok(false); } - let index = machine.registers()[A0].to_u64(); let source = Source::parse_from_u64(machine.registers()[A1].to_u64())?; let place = Place::parse_from_u64(machine.registers()[A2].to_u64())?; @@ -176,6 +178,7 @@ 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/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/inherited_fd.rs b/script/src/syscalls/inherited_fd.rs new file mode 100644 index 0000000000..4d041a0ba1 --- /dev/null +++ b/script/src/syscalls/inherited_fd.rs @@ -0,0 +1,47 @@ +use crate::syscalls::{INHERITED_FD, SPAWN_YIELD_CYCLES_BASE}; +use crate::types::{Fd, FdArgs, Message, VmId}; +use ckb_vm::{ + registers::{A0, A1, A7}, + Error as VMError, Register, SupportMachine, Syscalls, +}; +use std::sync::{Arc, Mutex}; + +#[derive(Debug)] +pub struct InheritedFd { + id: VmId, + message_box: Arc>>, +} + +impl InheritedFd { + pub fn new(id: VmId, message_box: Arc>>) -> Self { + Self { id, message_box } + } +} + +impl Syscalls for InheritedFd { + fn initialize(&mut self, _machine: &mut Mac) -> Result<(), VMError> { + Ok(()) + } + + fn ecall(&mut self, machine: &mut Mac) -> Result { + if machine.registers()[A7].to_u64() != INHERITED_FD { + return Ok(false); + } + let buffer_addr = machine.registers()[A0].to_u64(); + let length_addr = machine.registers()[A1].to_u64(); + machine.add_cycles_no_checking(SPAWN_YIELD_CYCLES_BASE)?; + self.message_box + .lock() + .map_err(|e| VMError::Unexpected(e.to_string()))? + .push(Message::InheritedFileDescriptor( + self.id, + FdArgs { + fd: Fd(0), + length: 0, + buffer_addr, + length_addr, + }, + )); + Err(VMError::Yield) + } +} diff --git a/script/src/syscalls/load_cell_data.rs b/script/src/syscalls/load_cell_data.rs index 11adfadca7..65ea6a778b 100644 --- a/script/src/syscalls/load_cell_data.rs +++ b/script/src/syscalls/load_cell_data.rs @@ -1,85 +1,101 @@ -use crate::types::Indices; +use crate::types::{DataPieceId, TxData}; use crate::{ cost_model::transferred_byte_cycles, syscalls::{ - utils::store_data, Source, SourceEntry, INDEX_OUT_OF_BOUND, - LOAD_CELL_DATA_AS_CODE_SYSCALL_NUMBER, LOAD_CELL_DATA_SYSCALL_NUMBER, SLICE_OUT_OF_BOUND, + Source, INDEX_OUT_OF_BOUND, LOAD_CELL_DATA_AS_CODE_SYSCALL_NUMBER, + LOAD_CELL_DATA_SYSCALL_NUMBER, SLICE_OUT_OF_BOUND, SOURCE_ENTRY_MASK, SOURCE_GROUP_FLAG, SUCCESS, }, }; -use ckb_traits::CellDataProvider; -use ckb_types::core::cell::{CellMeta, ResolvedTransaction}; +use ckb_traits::{CellDataProvider, ExtensionProvider, HeaderProvider}; use ckb_vm::{ memory::{Memory, FLAG_EXECUTABLE, FLAG_FREEZED}, registers::{A0, A1, A2, A3, A4, A5, A7}, - Error as VMError, Register, SupportMachine, Syscalls, + snapshot2::Snapshot2Context, + Bytes, Error as VMError, Register, SupportMachine, Syscalls, }; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; -pub struct LoadCellData
{ - data_loader: DL, - rtx: Arc, - outputs: Arc>, - group_inputs: Indices, - group_outputs: Indices, +pub struct LoadCellData
+where + DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, +{ + snapshot2_context: Arc>>>, } -impl LoadCellData
{ +impl
LoadCellData
+where + DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, +{ pub fn new( - data_loader: DL, - rtx: Arc, - outputs: Arc>, - group_inputs: Indices, - group_outputs: Indices, + snapshot2_context: Arc>>>, ) -> LoadCellData
{ - LoadCellData { - data_loader, - rtx, - outputs, - group_inputs, - group_outputs, - } - } - - #[inline] - fn resolved_inputs(&self) -> &Vec { - &self.rtx.resolved_inputs + LoadCellData { snapshot2_context } } - #[inline] - fn resolved_cell_deps(&self) -> &Vec { - &self.rtx.resolved_cell_deps - } + fn load_data(&self, machine: &mut Mac) -> Result<(), VMError> { + let index = machine.registers()[A3].to_u64(); + let mut source = machine.registers()[A4].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 & SOURCE_ENTRY_MASK; + } + let data_piece_id = match DataPieceId::try_from((source, index, 0)) { + Ok(id) => id, + Err(_) => { + machine.set_register(A0, Mac::REG::from_u8(INDEX_OUT_OF_BOUND)); + return Ok(()); + } + }; + let addr = machine.registers()[A0].to_u64(); + let size_addr = machine.registers()[A1].clone(); + let offset = machine.registers()[A2].to_u64(); + let mut sc = self + .snapshot2_context + .lock() + .map_err(|e| VMError::Unexpected(e.to_string()))?; - fn fetch_cell(&self, source: Source, index: usize) -> Result<&CellMeta, u8> { - match source { - Source::Transaction(SourceEntry::Input) => { - self.resolved_inputs().get(index).ok_or(INDEX_OUT_OF_BOUND) + match sc.load_data(&data_piece_id, offset, u64::max_value()) { + Ok((cell, _)) => { + let size = machine.memory_mut().load64(&size_addr)?.to_u64(); + if size == 0 { + machine + .memory_mut() + .store64(&size_addr, &Mac::REG::from_u64(cell.len() as u64))?; + machine.set_register(A0, Mac::REG::from_u8(SUCCESS)); + return Ok(()); + } + let (wrote_size, _) = match sc.store_bytes( + machine, + addr, + &data_piece_id, + offset, + size, + size_addr.to_u64(), + ) { + 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(()); + } + Err(e) => return Err(e), + }; + machine.add_cycles_no_checking(transferred_byte_cycles(wrote_size))?; + machine.set_register(A0, Mac::REG::from_u8(SUCCESS)); + return Ok(()); } - Source::Transaction(SourceEntry::Output) => { - self.outputs.get(index).ok_or(INDEX_OUT_OF_BOUND) + 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(()); } - Source::Transaction(SourceEntry::CellDep) => self - .resolved_cell_deps() - .get(index) - .ok_or(INDEX_OUT_OF_BOUND), - Source::Transaction(SourceEntry::HeaderDep) => Err(INDEX_OUT_OF_BOUND), - Source::Group(SourceEntry::Input) => self - .group_inputs - .get(index) - .ok_or(INDEX_OUT_OF_BOUND) - .and_then(|actual_index| { - self.resolved_inputs() - .get(*actual_index) - .ok_or(INDEX_OUT_OF_BOUND) - }), - Source::Group(SourceEntry::Output) => self - .group_outputs - .get(index) - .ok_or(INDEX_OUT_OF_BOUND) - .and_then(|actual_index| self.outputs.get(*actual_index).ok_or(INDEX_OUT_OF_BOUND)), - Source::Group(SourceEntry::CellDep) => Err(INDEX_OUT_OF_BOUND), - Source::Group(SourceEntry::HeaderDep) => Err(INDEX_OUT_OF_BOUND), + Err(e) => return Err(e), } } @@ -88,75 +104,73 @@ impl LoadCellData
{ let memory_size = machine.registers()[A1].to_u64(); let content_offset = machine.registers()[A2].to_u64(); let content_size = machine.registers()[A3].to_u64(); - let index = machine.registers()[A4].to_u64(); - let source = Source::parse_from_u64(machine.registers()[A5].to_u64())?; - - 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(()); + let mut source = machine.registers()[A5].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 & SOURCE_ENTRY_MASK; } - let cell = cell.unwrap(); - + let data_piece_id = match DataPieceId::try_from((source, index, 0)) { + Ok(id) => id, + Err(_) => { + machine.set_register(A0, Mac::REG::from_u8(INDEX_OUT_OF_BOUND)); + return Ok(()); + } + }; + let mut sc = self + .snapshot2_context + .lock() + .map_err(|e| VMError::Unexpected(e.to_string()))?; + // We are using 0..u64::max_value() to fetch full cell, there is + // also no need to keep the full length value. Since cell's length + // is already full length. + let (cell, _) = match sc.load_data(&data_piece_id, 0, u64::max_value()) { + Ok(val) => { + if content_size == 0 { + (Bytes::new(), val.1) + } else { + 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(()); + } + Err(e) => return Err(e), + }; let content_end = content_offset .checked_add(content_size) .ok_or(VMError::MemOutOfBound)?; - if content_offset >= cell.data_bytes - || content_end > cell.data_bytes + if content_offset >= cell.len() as u64 + || content_end > cell.len() as u64 || content_size > memory_size { machine.set_register(A0, Mac::REG::from_u8(SLICE_OUT_OF_BOUND)); return Ok(()); } - let data = self - .data_loader - .load_cell_data(cell) - .ok_or_else(|| { - VMError::Unexpected(format!( - "Unexpected load_cell_data failed {}", - cell.out_point, - )) - })? - .slice((content_offset as usize)..(content_end as usize)); machine.memory_mut().init_pages( addr, memory_size, FLAG_EXECUTABLE | FLAG_FREEZED, - Some(data), + Some(cell.slice((content_offset as usize)..(content_end as usize))), 0, )?; - + sc.track_pages(machine, addr, memory_size, &data_piece_id, content_offset)?; machine.add_cycles_no_checking(transferred_byte_cycles(memory_size))?; machine.set_register(A0, Mac::REG::from_u8(SUCCESS)); Ok(()) } - - fn load_data(&self, machine: &mut Mac) -> Result<(), VMError> { - let index = machine.registers()[A3].to_u64(); - let source = Source::parse_from_u64(machine.registers()[A4].to_u64())?; - - 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(()); - } - 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 wrote_size = store_data(machine, &data)?; - machine.add_cycles_no_checking(transferred_byte_cycles(wrote_size))?; - machine.set_register(A0, Mac::REG::from_u8(SUCCESS)); - Ok(()) - } } -impl Syscalls for LoadCellData
{ +impl Syscalls for LoadCellData
+where + DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, +{ fn initialize(&mut self, _machine: &mut Mac) -> Result<(), VMError> { Ok(()) } diff --git a/script/src/syscalls/mod.rs b/script/src/syscalls/mod.rs index ed0f5fc839..4e09f8968e 100644 --- a/script/src/syscalls/mod.rs +++ b/script/src/syscalls/mod.rs @@ -1,8 +1,8 @@ +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; mod load_cell_data; @@ -12,10 +12,14 @@ mod load_script; mod load_script_hash; mod load_tx; mod load_witness; -mod set_content; +mod pipe; +mod process_id; +mod read; pub(crate) mod spawn; mod utils; mod vm_version; +mod wait; +mod write; #[cfg(test)] mod pause; @@ -23,11 +27,11 @@ mod pause; #[cfg(test)] 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; pub use self::load_cell_data::LoadCellData; @@ -37,9 +41,13 @@ 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::pipe::Pipe; +pub use self::process_id::ProcessID; +pub use self::read::Read; pub use self::spawn::Spawn; pub use self::vm_version::VMVersion; +pub use self::wait::Wait; +pub use self::write::Write; #[cfg(test)] pub use self::pause::Pause; @@ -55,9 +63,11 @@ 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 WAIT_FAILURE: u8 = 5; +pub const INVALID_FD: u8 = 6; +pub const OTHER_END_CLOSED: u8 = 7; +pub const MAX_VMS_SPAWNED: u8 = 8; +pub const MAX_FDS_CREATED: u8 = 9; pub const VM_VERSION: u64 = 2041; pub const CURRENT_CYCLES: u64 = 2042; @@ -75,21 +85,22 @@ 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 LOAD_BLOCK_EXTENSION: u64 = 2104; -pub const CURRENT_MEMORY: u64 = 2105; +pub const SPAWN: u64 = 2601; +pub const WAIT: u64 = 2602; +pub const PROCESS_ID: u64 = 2603; +pub const PIPE: u64 = 2604; +pub const WRITE: u64 = 2605; +pub const READ: u64 = 2606; +pub const INHERITED_FD: u64 = 2607; +pub const CLOSE: u64 = 2608; 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 +pub const EXEC_LOAD_ELF_V2_CYCLES_BASE: u64 = 75_000; pub const SPAWN_EXTRA_CYCLES_BASE: u64 = 100_000; -pub const SPAWN_EXTRA_CYCLES_PER_MEMORY_PAGE: u64 = 8192; +pub const SPAWN_YIELD_CYCLES_BASE: u64 = 800; #[derive(Debug, PartialEq, Clone, Copy, Eq)] enum CellField { diff --git a/script/src/syscalls/pipe.rs b/script/src/syscalls/pipe.rs new file mode 100644 index 0000000000..3bb61ba22d --- /dev/null +++ b/script/src/syscalls/pipe.rs @@ -0,0 +1,39 @@ +use crate::syscalls::{PIPE, SPAWN_YIELD_CYCLES_BASE}; +use crate::types::{Message, PipeArgs, VmId}; +use ckb_vm::{ + registers::{A0, A7}, + Error as VMError, Register, SupportMachine, Syscalls, +}; +use std::sync::{Arc, Mutex}; + +#[derive(Debug)] +pub struct Pipe { + id: VmId, + message_box: Arc>>, +} + +impl Pipe { + pub fn new(id: VmId, message_box: Arc>>) -> Self { + Self { id, message_box } + } +} + +impl Syscalls for Pipe { + fn initialize(&mut self, _machine: &mut Mac) -> Result<(), VMError> { + Ok(()) + } + + fn ecall(&mut self, machine: &mut Mac) -> Result { + if machine.registers()[A7].to_u64() != PIPE { + return Ok(false); + } + let fd1_addr = machine.registers()[A0].to_u64(); + let fd2_addr = fd1_addr.wrapping_add(8); + machine.add_cycles_no_checking(SPAWN_YIELD_CYCLES_BASE)?; + self.message_box + .lock() + .map_err(|e| VMError::Unexpected(e.to_string()))? + .push(Message::Pipe(self.id, PipeArgs { fd1_addr, fd2_addr })); + Err(VMError::Yield) + } +} diff --git a/script/src/syscalls/current_memory.rs b/script/src/syscalls/process_id.rs similarity index 51% rename from script/src/syscalls/current_memory.rs rename to script/src/syscalls/process_id.rs index 401d778191..55114ae18a 100644 --- a/script/src/syscalls/current_memory.rs +++ b/script/src/syscalls/process_id.rs @@ -1,30 +1,30 @@ -use crate::syscalls::CURRENT_MEMORY; +use crate::syscalls::PROCESS_ID; use ckb_vm::{ registers::{A0, A7}, Error as VMError, Register, SupportMachine, Syscalls, }; #[derive(Debug, Default)] -pub struct CurrentMemory { - value: u64, +pub struct ProcessID { + id: u64, } -impl CurrentMemory { - pub fn new(value: u64) -> Self { - Self { value } +impl ProcessID { + pub fn new(id: u64) -> Self { + Self { id } } } -impl Syscalls for CurrentMemory { +impl Syscalls for ProcessID { 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 { + if machine.registers()[A7].to_u64() != PROCESS_ID { return Ok(false); } - machine.set_register(A0, Mac::REG::from_u64(self.value)); + machine.set_register(A0, Mac::REG::from_u64(self.id)); Ok(true) } } diff --git a/script/src/syscalls/read.rs b/script/src/syscalls/read.rs new file mode 100644 index 0000000000..63976ef096 --- /dev/null +++ b/script/src/syscalls/read.rs @@ -0,0 +1,59 @@ +use crate::syscalls::{INVALID_FD, READ, SPAWN_YIELD_CYCLES_BASE}; +use crate::types::{Fd, FdArgs, Message, VmId}; +use ckb_vm::{ + registers::{A0, A1, A2, A7}, + Error as VMError, Memory, Register, SupportMachine, Syscalls, +}; +use std::sync::{Arc, Mutex}; + +#[derive(Debug)] +pub struct Read { + id: VmId, + message_box: Arc>>, +} + +impl Read { + pub fn new(id: VmId, message_box: Arc>>) -> Self { + Self { id, message_box } + } +} + +impl Syscalls for Read { + fn initialize(&mut self, _machine: &mut Mac) -> Result<(), VMError> { + Ok(()) + } + + fn ecall(&mut self, machine: &mut Mac) -> Result { + if machine.registers()[A7].to_u64() != READ { + return Ok(false); + } + let fd = Fd(machine.registers()[A0].to_u64()); + let buffer_addr = machine.registers()[A1].to_u64(); + let length_addr = machine.registers()[A2].to_u64(); + let length = machine + .memory_mut() + .load64(&Mac::REG::from_u64(length_addr))? + .to_u64(); + + // We can only do basic checks here, when the message is actually processed, + // more complete checks will be performed. + if !fd.is_read() { + machine.set_register(A0, Mac::REG::from_u8(INVALID_FD)); + return Ok(true); + } + machine.add_cycles_no_checking(SPAWN_YIELD_CYCLES_BASE)?; + self.message_box + .lock() + .map_err(|e| VMError::Unexpected(e.to_string()))? + .push(Message::FdRead( + self.id, + FdArgs { + fd, + length, + buffer_addr, + length_addr, + }, + )); + Err(VMError::Yield) + } +} 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 9b7fee0298..2d92e6fd68 100644 --- a/script/src/syscalls/spawn.rs +++ b/script/src/syscalls/spawn.rs @@ -1,95 +1,44 @@ -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, + Source, INDEX_OUT_OF_BOUND, SLICE_OUT_OF_BOUND, SOURCE_ENTRY_MASK, SOURCE_GROUP_FLAG, SPAWN, + SPAWN_EXTRA_CYCLES_BASE, SPAWN_YIELD_CYCLES_BASE, }; -use crate::types::{ - set_vm_max_cycles, CoreMachineType, Machine, MachineContext, ResumableMachine, SpawnData, -}; -use crate::TransactionScriptsSyscallsGenerator; -use crate::{ScriptGroup, ScriptVersion}; +use crate::types::{DataPieceId, Fd, Message, 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::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,244 +54,117 @@ 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); + 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 & SOURCE_ENTRY_MASK; } - 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 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 fds_addr_addr = spgs_addr.wrapping_add(24); + let mut fds_addr = machine + .memory_mut() + .load64(&Mac::REG::from_u64(fds_addr_addr))? + .to_u64(); + + let mut fds = vec![]; + if fds_addr != 0 { + loop { + let fd = machine + .memory_mut() + .load64(&Mac::REG::from_u64(fds_addr))? + .to_u64(); + if fd == 0 { + break; + } + fds.push(Fd(fd)); + fds_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 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); } - // 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(SPAWN_YIELD_CYCLES_BASE)?; + self.message_box + .lock() + .map_err(|e| VMError::Unexpected(e.to_string()))? + .push(Message::Spawn( + self.id, + SpawnArgs { + data_piece_id, + offset, + length, + argv, + fds, + process_id_addr, + }, + )); + Err(VMError::Yield) } } - -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(*cycles_base), - )); - 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_1.rs b/script/src/syscalls/tests/vm_latest/syscalls_1.rs index 391320a556..761537e2ff 100644 --- a/script/src/syscalls/tests/vm_latest/syscalls_1.rs +++ b/script/src/syscalls/tests/vm_latest/syscalls_1.rs @@ -14,14 +14,17 @@ use ckb_types::{ use ckb_vm::{ memory::{FLAG_DIRTY, FLAG_EXECUTABLE, FLAG_FREEZED, FLAG_WRITABLE}, registers::{A0, A1, A2, A3, A4, A5, A7}, + snapshot2::Snapshot2Context, CoreMachine, Error as VMError, Memory, Syscalls, RISCV_PAGESIZE, }; use proptest::{collection::size_range, prelude::*}; use std::collections::HashMap; -use std::sync::Arc; +use std::sync::{Arc, Mutex}; use super::SCRIPT_VERSION; use crate::syscalls::{tests::utils::*, *}; +use crate::types::TxData; +use crate::types::{ScriptGroup, ScriptGroupType}; fn _test_load_cell_not_exist(data: &[u8]) -> Result<(), TestCaseError> { let mut machine = SCRIPT_VERSION.init_core_machine_without_limit(); @@ -1450,20 +1453,28 @@ fn _test_load_cell_data_as_code( let data = Bytes::from(data.to_owned()); let dep_cell = build_cell_meta(10000, data.clone()); - let output = build_cell_meta(100, data.clone()); let input_cell = build_cell_meta(100, data.clone()); - let outputs = Arc::new(vec![output]); - let group_inputs = Arc::new(vec![0]); - let group_outputs = Arc::new(vec![0]); let data_loader = new_mock_data_loader(); let rtx = Arc::new(ResolvedTransaction { - transaction: TransactionBuilder::default().build(), + transaction: TransactionBuilder::default() + .output_data(data.pack()) + .build(), resolved_cell_deps: vec![dep_cell], resolved_inputs: vec![input_cell], resolved_dep_groups: vec![], }); - let mut load_code = LoadCellData::new(data_loader, rtx, outputs, group_inputs, group_outputs); + let mut load_code = LoadCellData::new(Arc::new(Mutex::new(Snapshot2Context::new(TxData { + rtx, + data_loader, + program: Bytes::new(), + script_group: Arc::new(ScriptGroup { + script: Default::default(), + group_type: ScriptGroupType::Lock, + input_indices: vec![0], + output_indices: vec![0], + }), + })))); prop_assert!(machine.memory_mut().store_byte(addr, addr_size, 1).is_ok()); @@ -1474,7 +1485,7 @@ fn _test_load_cell_data_as_code( } else { prop_assert_eq!(machine.registers()[A0], u64::from(SUCCESS)); - let flags = FLAG_EXECUTABLE | FLAG_FREEZED | FLAG_DIRTY; + let flags = FLAG_EXECUTABLE | FLAG_FREEZED; prop_assert_eq!( machine .memory_mut() @@ -1515,21 +1526,28 @@ fn _test_load_cell_data( let data = Bytes::from(data.to_owned()); let dep_cell = build_cell_meta(10000, data.clone()); - let output = build_cell_meta(100, data.clone()); let input_cell = build_cell_meta(100, data.clone()); - - let outputs = Arc::new(vec![output]); - let group_inputs = Arc::new(vec![0]); - let group_outputs = Arc::new(vec![0]); let data_loader = new_mock_data_loader(); let rtx = Arc::new(ResolvedTransaction { - transaction: TransactionBuilder::default().build(), + transaction: TransactionBuilder::default() + .output_data(data.pack()) + .build(), resolved_cell_deps: vec![dep_cell], resolved_inputs: vec![input_cell], resolved_dep_groups: vec![], }); - let mut load_code = LoadCellData::new(data_loader, rtx, outputs, group_inputs, group_outputs); + let mut load_code = LoadCellData::new(Arc::new(Mutex::new(Snapshot2Context::new(TxData { + rtx, + data_loader, + program: Bytes::new(), + script_group: Arc::new(ScriptGroup { + script: Default::default(), + group_type: ScriptGroupType::Lock, + input_indices: vec![0], + output_indices: vec![0], + }), + })))); prop_assert!(load_code.ecall(&mut machine).is_ok()); @@ -1537,8 +1555,11 @@ fn _test_load_cell_data( prop_assert_eq!(machine.registers()[A0], u64::from(code)); } else { prop_assert_eq!(machine.registers()[A0], u64::from(SUCCESS)); - - let flags = FLAG_WRITABLE | FLAG_DIRTY; + let flags = if data.len() < RISCV_PAGESIZE { + FLAG_WRITABLE | FLAG_DIRTY + } else { + FLAG_WRITABLE + }; prop_assert_eq!( machine .memory_mut() @@ -1618,9 +1639,6 @@ fn test_load_overflowed_cell_data_as_code() { let dep_cell = build_cell_meta(10000, dep_cell_data); let data_loader = new_mock_data_loader(); - let outputs = Arc::new(vec![]); - let group_inputs = Arc::new(vec![]); - let group_outputs = Arc::new(vec![]); let rtx = Arc::new(ResolvedTransaction { transaction: TransactionBuilder::default().build(), @@ -1629,7 +1647,17 @@ fn test_load_overflowed_cell_data_as_code() { resolved_dep_groups: vec![], }); - let mut load_code = LoadCellData::new(data_loader, rtx, outputs, group_inputs, group_outputs); + let mut load_code = LoadCellData::new(Arc::new(Mutex::new(Snapshot2Context::new(TxData { + rtx, + data_loader, + program: Bytes::new(), + script_group: Arc::new(ScriptGroup { + script: Default::default(), + group_type: ScriptGroupType::Lock, + input_indices: Default::default(), + output_indices: Default::default(), + }), + })))); assert!(machine.memory_mut().store_byte(addr, addr_size, 1).is_ok()); @@ -1637,7 +1665,61 @@ fn test_load_overflowed_cell_data_as_code() { assert_eq!(result.unwrap_err(), VMError::MemOutOfBound); } -fn _test_load_cell_data_on_freezed_memory(as_code: bool, data: &[u8]) -> Result<(), TestCaseError> { +fn _test_load_cell_data_on_freezed_memory(data: &[u8]) -> Result<(), TestCaseError> { + let mut machine = SCRIPT_VERSION.init_core_machine_without_limit(); + let addr = 8192; + let addr_size = 4096; + let size_addr = 100; + + prop_assert!(machine + .memory_mut() + .store64(&size_addr, &(data.len() as u64)) + .is_ok()); + prop_assert!(machine + .memory_mut() + .init_pages(addr, addr_size, FLAG_EXECUTABLE | FLAG_FREEZED, None, 0) + .is_ok()); + + machine.set_register(A0, addr); // addr + machine.set_register(A1, size_addr); // size + machine.set_register(A2, 0); // content offset + machine.set_register(A3, 0); //index + machine.set_register(A4, u64::from(Source::Transaction(SourceEntry::CellDep))); //source + machine.set_register(A7, LOAD_CELL_DATA_SYSCALL_NUMBER); // syscall number + + let dep_cell_data = Bytes::from(data.to_owned()); + let dep_cell = build_cell_meta(10000, dep_cell_data); + + let data_loader = new_mock_data_loader(); + + let rtx = Arc::new(ResolvedTransaction { + transaction: TransactionBuilder::default().build(), + resolved_cell_deps: vec![dep_cell], + resolved_inputs: vec![], + resolved_dep_groups: vec![], + }); + + let mut load_code = LoadCellData::new(Arc::new(Mutex::new(Snapshot2Context::new(TxData { + rtx, + data_loader, + program: Bytes::new(), + script_group: Arc::new(ScriptGroup { + script: Default::default(), + group_type: ScriptGroupType::Lock, + input_indices: Default::default(), + output_indices: Default::default(), + }), + })))); + + prop_assert!(load_code.ecall(&mut machine).is_err()); + + for i in addr..addr + addr_size { + assert_eq!(machine.memory_mut().load8(&i), Ok(0)); + } + Ok(()) +} + +fn _test_load_cell_data_as_code_on_freezed_memory(data: &[u8]) -> Result<(), TestCaseError> { let mut machine = SCRIPT_VERSION.init_core_machine_without_limit(); let addr = 8192; let addr_size = 4096; @@ -1653,20 +1735,12 @@ fn _test_load_cell_data_on_freezed_memory(as_code: bool, data: &[u8]) -> Result< machine.set_register(A3, data.len() as u64); // content size machine.set_register(A4, 0); //index machine.set_register(A5, u64::from(Source::Transaction(SourceEntry::CellDep))); //source - let syscall = if as_code { - LOAD_CELL_DATA_AS_CODE_SYSCALL_NUMBER - } else { - LOAD_CELL_DATA_SYSCALL_NUMBER - }; - machine.set_register(A7, syscall); // syscall number + machine.set_register(A7, LOAD_CELL_DATA_AS_CODE_SYSCALL_NUMBER); // syscall number let dep_cell_data = Bytes::from(data.to_owned()); let dep_cell = build_cell_meta(10000, dep_cell_data); let data_loader = new_mock_data_loader(); - let outputs = Arc::new(vec![]); - let group_inputs = Arc::new(vec![]); - let group_outputs = Arc::new(vec![]); let rtx = Arc::new(ResolvedTransaction { transaction: TransactionBuilder::default().build(), @@ -1675,7 +1749,17 @@ fn _test_load_cell_data_on_freezed_memory(as_code: bool, data: &[u8]) -> Result< resolved_dep_groups: vec![], }); - let mut load_code = LoadCellData::new(data_loader, rtx, outputs, group_inputs, group_outputs); + let mut load_code = LoadCellData::new(Arc::new(Mutex::new(Snapshot2Context::new(TxData { + rtx, + data_loader, + program: Bytes::new(), + script_group: Arc::new(ScriptGroup { + script: Default::default(), + group_type: ScriptGroupType::Lock, + input_indices: Default::default(), + output_indices: Default::default(), + }), + })))); prop_assert!(load_code.ecall(&mut machine).is_err()); @@ -1691,12 +1775,12 @@ proptest! { })] #[test] fn test_load_code_on_freezed_memory(ref data in any_with::>(size_range(4096).lift())) { - _test_load_cell_data_on_freezed_memory(true, data)?; + _test_load_cell_data_as_code_on_freezed_memory(data)?; } #[test] fn test_load_data_on_freezed_memory(ref data in any_with::>(size_range(4096).lift())) { - _test_load_cell_data_on_freezed_memory(false, data)?; + _test_load_cell_data_on_freezed_memory(data)?; } } @@ -1718,9 +1802,6 @@ fn test_load_code_unaligned_error() { let dep_cell = build_cell_meta(10000, dep_cell_data); let data_loader = new_mock_data_loader(); - let outputs = Arc::new(vec![]); - let group_inputs = Arc::new(vec![]); - let group_outputs = Arc::new(vec![]); let rtx = Arc::new(ResolvedTransaction { transaction: TransactionBuilder::default().build(), @@ -1729,7 +1810,17 @@ fn test_load_code_unaligned_error() { resolved_dep_groups: vec![], }); - let mut load_code = LoadCellData::new(data_loader, rtx, outputs, group_inputs, group_outputs); + let mut load_code = LoadCellData::new(Arc::new(Mutex::new(Snapshot2Context::new(TxData { + rtx, + data_loader, + program: Bytes::new(), + script_group: Arc::new(ScriptGroup { + script: Default::default(), + group_type: ScriptGroupType::Lock, + input_indices: Default::default(), + output_indices: Default::default(), + }), + })))); assert!(machine.memory_mut().store_byte(addr, addr_size, 1).is_ok()); @@ -1759,9 +1850,6 @@ fn test_load_code_slice_out_of_bound_error() { let dep_cell = build_cell_meta(10000, dep_cell_data); let data_loader = new_mock_data_loader(); - let outputs = Arc::new(vec![]); - let group_inputs = Arc::new(vec![]); - let group_outputs = Arc::new(vec![]); let rtx = Arc::new(ResolvedTransaction { transaction: TransactionBuilder::default().build(), @@ -1770,7 +1858,17 @@ fn test_load_code_slice_out_of_bound_error() { resolved_dep_groups: vec![], }); - let mut load_code = LoadCellData::new(data_loader, rtx, outputs, group_inputs, group_outputs); + let mut load_code = LoadCellData::new(Arc::new(Mutex::new(Snapshot2Context::new(TxData { + rtx, + data_loader, + program: Bytes::new(), + script_group: Arc::new(ScriptGroup { + script: Default::default(), + group_type: ScriptGroupType::Lock, + input_indices: Default::default(), + output_indices: Default::default(), + }), + })))); assert!(machine.memory_mut().store_byte(addr, addr_size, 1).is_ok()); @@ -1803,9 +1901,6 @@ fn test_load_code_not_enough_space_error() { let dep_cell = build_cell_meta(10000, dep_cell_data); let data_loader = new_mock_data_loader(); - let outputs = Arc::new(vec![]); - let group_inputs = Arc::new(vec![]); - let group_outputs = Arc::new(vec![]); let rtx = Arc::new(ResolvedTransaction { transaction: TransactionBuilder::default().build(), @@ -1814,7 +1909,17 @@ fn test_load_code_not_enough_space_error() { resolved_dep_groups: vec![], }); - let mut load_code = LoadCellData::new(data_loader, rtx, outputs, group_inputs, group_outputs); + let mut load_code = LoadCellData::new(Arc::new(Mutex::new(Snapshot2Context::new(TxData { + rtx, + data_loader, + program: Bytes::new(), + script_group: Arc::new(ScriptGroup { + script: Default::default(), + group_type: ScriptGroupType::Lock, + input_indices: Default::default(), + output_indices: Default::default(), + }), + })))); assert!(machine.memory_mut().store_byte(addr, addr_size, 1).is_ok()); @@ -1985,3 +2090,91 @@ fn _test_input_field_bound(field: u64) -> Result<(), TestCaseError> { prop_assert!(InputField::parse_from_u64(field).is_err()); Ok(()) } + +#[test] +fn test_load_cell_data_size_zero() { + let mut machine = SCRIPT_VERSION.init_core_machine_without_limit(); + let size_addr: u64 = 100; + let addr = 4096; + let data = [0xff; 256]; + + machine.set_register(A0, addr); // addr + machine.set_register(A1, size_addr); // size + machine.set_register(A2, 0); // offset + machine.set_register(A3, 0); // index + machine.set_register(A4, u64::from(Source::Transaction(SourceEntry::CellDep))); //source + machine.set_register(A7, LOAD_CELL_DATA_SYSCALL_NUMBER); // syscall number + + machine.memory_mut().store64(&size_addr, &0).unwrap(); + + let data = Bytes::from(data.to_vec()); + let dep_cell = build_cell_meta(10000, data.clone()); + let input_cell = build_cell_meta(100, data); + + let data_loader = new_mock_data_loader(); + + let rtx = Arc::new(ResolvedTransaction { + transaction: TransactionBuilder::default().build(), + resolved_cell_deps: vec![dep_cell], + resolved_inputs: vec![input_cell], + resolved_dep_groups: vec![], + }); + let mut load_code = LoadCellData::new(Arc::new(Mutex::new(Snapshot2Context::new(TxData { + rtx, + data_loader, + program: Bytes::new(), + script_group: Arc::new(ScriptGroup { + script: Default::default(), + group_type: ScriptGroupType::Lock, + input_indices: vec![0], + output_indices: vec![0], + }), + })))); + load_code.ecall(&mut machine).unwrap(); + assert_eq!(machine.registers()[A0], u64::from(SUCCESS)); + assert_eq!(machine.memory_mut().load64(&size_addr).unwrap(), 256); + assert_eq!(machine.memory_mut().load64(&addr).unwrap(), 0); +} + +#[test] +fn test_load_cell_data_size_zero_index_out_of_bound() { + let mut machine = SCRIPT_VERSION.init_core_machine_without_limit(); + let size_addr: u64 = 100; + let addr = 4096; + let data = [0xff; 256]; + + machine.set_register(A0, addr); // addr + machine.set_register(A1, size_addr); // size + machine.set_register(A2, 0); // offset + machine.set_register(A3, 8); // index + machine.set_register(A4, u64::from(Source::Transaction(SourceEntry::CellDep))); //source + machine.set_register(A7, LOAD_CELL_DATA_SYSCALL_NUMBER); // syscall number + + machine.memory_mut().store64(&size_addr, &0).unwrap(); + + let data = Bytes::from(data.to_vec()); + let dep_cell = build_cell_meta(10000, data.clone()); + let input_cell = build_cell_meta(100, data); + + let data_loader = new_mock_data_loader(); + + let rtx = Arc::new(ResolvedTransaction { + transaction: TransactionBuilder::default().build(), + resolved_cell_deps: vec![dep_cell], + resolved_inputs: vec![input_cell], + resolved_dep_groups: vec![], + }); + let mut load_code = LoadCellData::new(Arc::new(Mutex::new(Snapshot2Context::new(TxData { + rtx, + data_loader, + program: Bytes::new(), + script_group: Arc::new(ScriptGroup { + script: Default::default(), + group_type: ScriptGroupType::Lock, + input_indices: vec![0], + output_indices: vec![0], + }), + })))); + load_code.ecall(&mut machine).unwrap(); + assert_eq!(machine.registers()[A0], u64::from(INDEX_OUT_OF_BOUND)); +} diff --git a/script/src/syscalls/tests/vm_latest/syscalls_2.rs b/script/src/syscalls/tests/vm_latest/syscalls_2.rs index 372b0377d3..4cac872f56 100644 --- a/script/src/syscalls/tests/vm_latest/syscalls_2.rs +++ b/script/src/syscalls/tests/vm_latest/syscalls_2.rs @@ -53,54 +53,12 @@ fn test_current_cycles() { machine.set_cycles(cycles); - let result = CurrentCycles::new(0).ecall(&mut machine); + let result = CurrentCycles::new(Arc::new(Mutex::new(0))).ecall(&mut machine); 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(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/syscalls/utils.rs b/script/src/syscalls/utils.rs index cacfdfa0fb..a62d0900b3 100644 --- a/script/src/syscalls/utils.rs +++ b/script/src/syscalls/utils.rs @@ -47,21 +47,3 @@ pub fn load_c_string(machine: &mut Mac, addr: u64) -> Resul 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/wait.rs b/script/src/syscalls/wait.rs new file mode 100644 index 0000000000..c9a49566aa --- /dev/null +++ b/script/src/syscalls/wait.rs @@ -0,0 +1,45 @@ +use crate::syscalls::{SPAWN_YIELD_CYCLES_BASE, WAIT}; +use crate::types::{Message, VmId, WaitArgs}; +use ckb_vm::{ + registers::{A0, A1, A7}, + Error as VMError, Register, SupportMachine, Syscalls, +}; +use std::sync::{Arc, Mutex}; + +#[derive(Debug)] +pub struct Wait { + id: VmId, + message_box: Arc>>, +} + +impl Wait { + pub fn new(id: VmId, message_box: Arc>>) -> Self { + Self { id, message_box } + } +} + +impl Syscalls for Wait { + fn initialize(&mut self, _machine: &mut Mac) -> Result<(), VMError> { + Ok(()) + } + + fn ecall(&mut self, machine: &mut Mac) -> Result { + if machine.registers()[A7].to_u64() != WAIT { + return Ok(false); + } + let target_id = machine.registers()[A0].to_u64(); + let exit_code_addr = machine.registers()[A1].to_u64(); + machine.add_cycles_no_checking(SPAWN_YIELD_CYCLES_BASE)?; + self.message_box + .lock() + .map_err(|e| VMError::Unexpected(e.to_string()))? + .push(Message::Wait( + self.id, + WaitArgs { + target_id, + exit_code_addr, + }, + )); + Err(VMError::Yield) + } +} diff --git a/script/src/syscalls/write.rs b/script/src/syscalls/write.rs new file mode 100644 index 0000000000..a193f5589b --- /dev/null +++ b/script/src/syscalls/write.rs @@ -0,0 +1,60 @@ +use crate::syscalls::{INVALID_FD, SPAWN_YIELD_CYCLES_BASE, WRITE}; +use crate::types::{Fd, FdArgs, Message, VmId}; +use ckb_vm::{ + registers::{A0, A1, A2, A7}, + Error as VMError, Memory, Register, SupportMachine, Syscalls, +}; +use std::sync::{Arc, Mutex}; + +#[derive(Debug)] +pub struct Write { + id: VmId, + message_box: Arc>>, +} + +impl Write { + pub fn new(id: VmId, message_box: Arc>>) -> Self { + Self { id, message_box } + } +} + +impl Syscalls for Write { + fn initialize(&mut self, _machine: &mut Mac) -> Result<(), VMError> { + Ok(()) + } + + fn ecall(&mut self, machine: &mut Mac) -> Result { + if machine.registers()[A7].to_u64() != WRITE { + return Ok(false); + } + let fd = Fd(machine.registers()[A0].to_u64()); + let buffer_addr = machine.registers()[A1].to_u64(); + let length_addr = machine.registers()[A2].to_u64(); + let length = machine + .memory_mut() + .load64(&Mac::REG::from_u64(length_addr))? + .to_u64(); + + // We can only do basic checks here, when the message is actually processed, + // more complete checks will be performed. + // We will also leave to the actual write operation to test memory permissions. + if !fd.is_write() { + machine.set_register(A0, Mac::REG::from_u8(INVALID_FD)); + return Ok(true); + } + machine.add_cycles_no_checking(SPAWN_YIELD_CYCLES_BASE)?; + self.message_box + .lock() + .map_err(|e| VMError::Unexpected(e.to_string()))? + .push(Message::FdWrite( + self.id, + FdArgs { + fd, + length, + buffer_addr, + length_addr, + }, + )); + Err(VMError::Yield) + } +} diff --git a/script/src/types.rs b/script/src/types.rs index bc4841b660..6a3900bafe 100644 --- a/script/src/types.rs +++ b/script/src/types.rs @@ -1,13 +1,11 @@ -use crate::ScriptError; use ckb_error::Error; use ckb_types::{ core::{Cycle, ScriptHashType}, packed::{Byte32, Script}, }; use ckb_vm::{ - machine::{Pause, VERSION0, VERSION1, VERSION2}, - snapshot::{make_snapshot, Snapshot}, - Error as VMInternalError, SupportMachine, ISA_A, ISA_B, ISA_IMC, ISA_MOP, + machine::{VERSION0, VERSION1, VERSION2}, + ISA_A, ISA_B, ISA_IMC, ISA_MOP, }; use serde::{Deserialize, Serialize}; use std::fmt; @@ -19,6 +17,18 @@ use ckb_vm::machine::asm::{AsmCoreMachine, AsmMachine}; #[cfg(not(has_asm))] use ckb_vm::{DefaultCoreMachine, TraceMachine, WXorXMemory}; +use ckb_traits::{CellDataProvider, ExtensionProvider, HeaderProvider}; +use ckb_vm::snapshot2::Snapshot2Context; + +use ckb_types::core::cell::ResolvedTransaction; +use ckb_vm::{ + bytes::Bytes, + machine::Pause, + snapshot2::{DataSource, Snapshot2}, + RISCV_GENERAL_REGISTER_NUMBER, +}; +use std::mem::size_of; + /// The type of CKB-VM ISA. pub type VmIsa = u8; /// /// The type of CKB-VM version. @@ -40,6 +50,11 @@ pub type CoreMachine = DefaultCoreMachine>>; +#[cfg(has_asm)] +pub(crate) type Machine = AsmMachine; +#[cfg(not(has_asm))] +pub(crate) type Machine = TraceMachine; + pub(crate) type Indices = Arc>; pub(crate) type DebugPrinter = Arc; @@ -107,155 +122,6 @@ impl ScriptVersion { } } -#[cfg(has_asm)] -pub(crate) type Machine = AsmMachine; -#[cfg(not(has_asm))] -pub(crate) type Machine = TraceMachine; - -/// Common data that would be shared amongst multiple VM instances. -/// One sample usage right now, is to capture suspended machines in -/// a chain of spawned machines. -#[derive(Default)] -pub struct MachineContext { - /// A stack of ResumableMachines. - pub suspended_machines: Vec, - /// A pause will be set for suspend machines. - /// The child machine will reuse parent machine's pause, - /// so that when parent is paused, all its children will be paused. - pub pause: Pause, -} - -impl MachineContext { - /// Creates a new MachineContext struct - pub fn set_pause(&mut self, pause: Pause) { - self.pause = pause; - } -} - -/// Data structure captured all environment data for a suspended machine -#[derive(Clone, Debug)] -pub enum ResumePoint { - Initial, - Spawn { - callee_peak_memory: u64, - callee_memory_limit: u64, - content: Vec, - content_length: u64, - caller_exit_code_addr: u64, - caller_content_addr: u64, - caller_content_length_addr: u64, - cycles_base: u64, - }, -} - -/// Data structure captured all the required data for a spawn syscall -#[derive(Clone, Debug)] -pub struct SpawnData { - pub(crate) callee_peak_memory: u64, - pub(crate) callee_memory_limit: u64, - pub(crate) content: Arc>>, - pub(crate) content_length: u64, - pub(crate) caller_exit_code_addr: u64, - pub(crate) caller_content_addr: u64, - pub(crate) caller_content_length_addr: u64, - pub(crate) cycles_base: u64, -} - -impl TryFrom<&SpawnData> for ResumePoint { - type Error = VMInternalError; - - fn try_from(value: &SpawnData) -> Result { - let SpawnData { - callee_peak_memory, - callee_memory_limit, - content, - content_length, - caller_exit_code_addr, - caller_content_addr, - caller_content_length_addr, - cycles_base, - .. - } = value; - Ok(ResumePoint::Spawn { - callee_peak_memory: *callee_peak_memory, - callee_memory_limit: *callee_memory_limit, - content: content - .lock() - .map_err(|e| VMInternalError::Unexpected(format!("Lock error: {}", e)))? - .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, - }) - } -} - -/// An enumerated type indicating the type of the Machine. -pub enum ResumableMachine { - /// Root machine instance. - Initial(Machine), - /// A machine which created by spawn syscall. - Spawn(Machine, SpawnData), -} - -impl ResumableMachine { - pub(crate) fn initial(machine: Machine) -> Self { - ResumableMachine::Initial(machine) - } - - pub(crate) fn spawn(machine: Machine, data: SpawnData) -> Self { - ResumableMachine::Spawn(machine, data) - } - - pub(crate) fn machine(&self) -> &Machine { - match self { - ResumableMachine::Initial(machine) => machine, - ResumableMachine::Spawn(machine, _) => machine, - } - } - - pub(crate) fn machine_mut(&mut self) -> &mut Machine { - match self { - ResumableMachine::Initial(machine) => machine, - ResumableMachine::Spawn(machine, _) => machine, - } - } - - pub(crate) fn cycles(&self) -> Cycle { - self.machine().machine.cycles() - } - - pub(crate) fn pause(&self) -> Pause { - self.machine().machine.pause() - } - - pub(crate) fn set_max_cycles(&mut self, cycles: Cycle) { - set_vm_max_cycles(self.machine_mut(), cycles) - } - - /// Add cycles to current machine. - pub fn add_cycles(&mut self, cycles: Cycle) -> Result<(), VMInternalError> { - self.machine_mut().machine.add_cycles(cycles) - } - - /// Run machine. - pub fn run(&mut self) -> Result { - self.machine_mut().run() - } -} - -#[cfg(has_asm)] -pub(crate) fn set_vm_max_cycles(vm: &mut Machine, cycles: Cycle) { - vm.set_max_cycles(cycles) -} - -#[cfg(not(has_asm))] -pub(crate) fn set_vm_max_cycles(vm: &mut Machine, cycles: Cycle) { - vm.machine.inner_mut().set_max_cycles(cycles) -} - /// A script group is defined as scripts that share the same hash. /// /// A script group will only be executed once per transaction, the @@ -325,7 +191,7 @@ pub struct TransactionSnapshot { /// current suspended script index pub current: usize, /// vm snapshots - pub snaps: Vec<(Snapshot, Cycle, ResumePoint)>, + pub state: Option, /// current consumed cycle pub current_cycles: Cycle, /// limit cycles when snapshot create @@ -337,29 +203,25 @@ pub struct TransactionSnapshot { pub struct TransactionState { /// current suspended script index pub current: usize, - /// vm states - pub vms: Vec, + /// vm scheduler suspend state + pub state: Option, /// current consumed cycle pub current_cycles: Cycle, /// limit cycles pub limit_cycles: Cycle, - /// machine context for the vms included in this state - pub machine_context: Arc>, } impl TransactionState { /// Creates a new TransactionState struct pub fn new( - vms: Vec, - machine_context: Arc>, + state: Option, current: usize, current_cycles: Cycle, limit_cycles: Cycle, ) -> Self { TransactionState { current, - vms, - machine_context, + state, current_cycles, limit_cycles, } @@ -398,29 +260,15 @@ impl TryFrom for TransactionSnapshot { fn try_from(state: TransactionState) -> Result { let TransactionState { current, - vms, + state, current_cycles, limit_cycles, .. } = state; - let mut snaps = Vec::with_capacity(vms.len()); - for mut vm in vms { - let snapshot = make_snapshot(&mut vm.machine_mut().machine) - .map_err(|e| ScriptError::VMInternalError(e).unknown_source())?; - let cycles = vm.cycles(); - let resume_point = match vm { - ResumableMachine::Initial(_) => ResumePoint::Initial, - ResumableMachine::Spawn(_, data) => (&data) - .try_into() - .map_err(|e| ScriptError::VMInternalError(e).unknown_source())?, - }; - snaps.push((snapshot, cycles, resume_point)); - } - Ok(TransactionSnapshot { current, - snaps, + state, current_cycles, limit_cycles, }) @@ -467,3 +315,323 @@ pub enum ChunkCommand { /// Stop the verification process Stop, } + +#[derive(Clone)] +pub struct MachineContext< + DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, +> { + pub(crate) base_cycles: Arc>, + pub(crate) snapshot2_context: Arc>>>, +} + +impl
MachineContext
+where + DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, +{ + pub fn new(tx_data: TxData
) -> Self { + Self { + base_cycles: Arc::new(Mutex::new(0)), + snapshot2_context: Arc::new(Mutex::new(Snapshot2Context::new(tx_data))), + } + } + + pub fn snapshot2_context(&self) -> &Arc>>> { + &self.snapshot2_context + } + + pub fn set_base_cycles(&mut self, base_cycles: u64) { + *self.base_cycles.lock().expect("lock") = base_cycles; + } +} + +pub type VmId = u64; +pub const FIRST_VM_ID: VmId = 0; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Fd(pub(crate) u64); + +pub const FIRST_FD_SLOT: u64 = 2; + +impl Fd { + pub fn create(slot: u64) -> (Fd, Fd, u64) { + (Fd(slot), Fd(slot + 1), slot + 2) + } + + pub fn other_fd(&self) -> Fd { + Fd(self.0 ^ 0x1) + } + + pub fn is_read(&self) -> bool { + self.0 % 2 == 0 + } + + pub fn is_write(&self) -> bool { + self.0 % 2 == 1 + } +} + +/// VM is in waiting-to-read state. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct ReadState { + pub fd: Fd, + pub length: u64, + pub buffer_addr: u64, + pub length_addr: u64, +} + +/// VM is in waiting-to-write state. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct WriteState { + pub fd: Fd, + pub consumed: u64, + pub length: u64, + pub buffer_addr: u64, + pub length_addr: u64, +} + +/// VM State. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum VmState { + /// Runnable. + Runnable, + /// Terminated. + Terminated, + /// Wait. + Wait { + /// Target vm id. + target_vm_id: VmId, + /// Exit code addr. + exit_code_addr: u64, + }, + /// WaitForWrite. + WaitForWrite(WriteState), + /// WaitForRead. + WaitForRead(ReadState), +} + +#[derive(Clone, Debug)] +pub struct SpawnArgs { + pub data_piece_id: DataPieceId, + pub offset: u64, + pub length: u64, + pub argv: Vec, + pub fds: Vec, + pub process_id_addr: u64, +} + +#[derive(Clone, Debug)] +pub struct WaitArgs { + pub target_id: VmId, + pub exit_code_addr: u64, +} + +#[derive(Clone, Debug)] +pub struct PipeArgs { + pub fd1_addr: u64, + pub fd2_addr: u64, +} + +#[derive(Clone, Debug)] +pub struct FdArgs { + pub fd: Fd, + pub length: u64, + pub buffer_addr: u64, + pub length_addr: u64, +} + +#[derive(Clone, Debug)] +pub enum Message { + Spawn(VmId, SpawnArgs), + Wait(VmId, WaitArgs), + Pipe(VmId, PipeArgs), + FdRead(VmId, FdArgs), + FdWrite(VmId, FdArgs), + InheritedFileDescriptor(VmId, FdArgs), + Close(VmId, Fd), +} + +/// A pointer to the data that is part of the transaction. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum DataPieceId { + /// Target program. Usually located in cell data. + Program, + /// The nth input cell data. + Input(u32), + /// The nth output data. + Output(u32), + /// The nth cell dep cell data. + CellDep(u32), + /// The nth group input cell data. + GroupInput(u32), + /// The nth group output data. + GroupOutput(u32), + /// The nth witness. + Witness(u32), + /// The nth witness group input. + WitnessGroupInput(u32), + /// The nth witness group output. + WitnessGroupOutput(u32), +} + +impl TryFrom<(u64, u64, u64)> for DataPieceId { + type Error = String; + + 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, 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)), + } + } +} + +/// Full state representing all VM instances from verifying a CKB script. +/// It should be serializable to binary formats, while also be able to +/// fully recover the running environment with the full transaction environment. +#[derive(Clone, Debug)] +pub struct FullSuspendedState { + pub total_cycles: Cycle, + pub next_vm_id: VmId, + pub next_fd_slot: u64, + pub vms: Vec<(VmId, VmState, Snapshot2)>, + pub fds: Vec<(Fd, VmId)>, + pub inherited_fd: Vec<(VmId, Vec)>, + pub terminated_vms: Vec<(VmId, i8)>, + pub instantiated_ids: Vec, +} + +impl FullSuspendedState { + pub fn size(&self) -> u64 { + (size_of::() + + size_of::() + + size_of::() + + self.vms.iter().fold(0, |mut acc, (_, _, snapshot)| { + acc += size_of::() + size_of::(); + acc += snapshot.pages_from_source.len() + * (size_of::() + + size_of::() + + size_of::() + + size_of::() + + size_of::()); + for dirty_page in &snapshot.dirty_pages { + acc += size_of::() + size_of::() + dirty_page.2.len(); + } + acc += size_of::() + + RISCV_GENERAL_REGISTER_NUMBER * size_of::() + + size_of::() + + size_of::() + + size_of::(); + acc + }) + + (self.fds.len() * (size_of::() + size_of::()))) as u64 + + (self.inherited_fd.len() * (size_of::())) as u64 + + (self.terminated_vms.len() * (size_of::() + size_of::())) as u64 + + (self.instantiated_ids.len() * size_of::()) as u64 + } +} + +/// Context data for current running transaction & script +#[derive(Clone)] +pub struct TxData
{ + /// ResolvedTransaction. + pub rtx: Arc, + /// Data loader. + pub data_loader: DL, + /// Ideally one might not want to keep program here, since program is totally + /// deducible from rtx + data_loader, however, for a demo here, program + /// does help us save some extra coding. + pub program: Bytes, + /// The script group to which the current program belongs. + pub script_group: Arc, +} + +impl
DataSource for TxData
+where + DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, +{ + fn load_data(&self, id: &DataPieceId, offset: u64, length: u64) -> Option<(Bytes, u64)> { + match id { + DataPieceId::Program => { + // This is just a shortcut so we don't have to copy over the logic in extract_script, + // ideally you can also only define the rest 5, then figure out a way to convert + // script group to the actual cell dep index. + Some(self.program.clone()) + } + DataPieceId::Input(i) => self + .rtx + .resolved_inputs + .get(*i as usize) + .and_then(|cell| self.data_loader.load_cell_data(cell)), + DataPieceId::Output(i) => self + .rtx + .transaction + .outputs_data() + .get(*i as usize) + .map(|data| data.raw_data()), + DataPieceId::CellDep(i) => self + .rtx + .resolved_cell_deps + .get(*i as usize) + .and_then(|cell| self.data_loader.load_cell_data(cell)), + DataPieceId::GroupInput(i) => self + .script_group + .input_indices + .get(*i as usize) + .and_then(|gi| self.rtx.resolved_inputs.get(*gi)) + .and_then(|cell| self.data_loader.load_cell_data(cell)), + DataPieceId::GroupOutput(i) => self + .script_group + .output_indices + .get(*i as usize) + .and_then(|gi| self.rtx.transaction.outputs_data().get(*gi)) + .map(|data| data.raw_data()), + DataPieceId::Witness(i) => self + .rtx + .transaction + .witnesses() + .get(*i as usize) + .map(|data| data.raw_data()), + DataPieceId::WitnessGroupInput(i) => self + .script_group + .input_indices + .get(*i as usize) + .and_then(|gi| self.rtx.transaction.witnesses().get(*gi)) + .map(|data| data.raw_data()), + DataPieceId::WitnessGroupOutput(i) => self + .script_group + .output_indices + .get(*i as usize) + .and_then(|gi| self.rtx.transaction.witnesses().get(*gi)) + .map(|data| data.raw_data()), + } + .map(|data| { + let offset = std::cmp::min(offset as usize, data.len()); + let full_length = data.len() - offset; + let real_length = if length > 0 { + std::cmp::min(full_length, length as usize) + } else { + full_length + }; + (data.slice(offset..offset + real_length), full_length as u64) + }) + } +} + +/// The scheduler's running mode. +#[derive(Clone)] +pub enum RunMode { + /// Continues running until cycles are exhausted. + LimitCycles(Cycle), + /// Continues running until a Pause signal is received. + Pause(Pause), +} diff --git a/script/src/verify.rs b/script/src/verify.rs index 15b5a06499..f91328e598 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -1,19 +1,19 @@ +use crate::scheduler::Scheduler; #[cfg(test)] use crate::syscalls::Pause; +use crate::syscalls::{InheritedFd, ProcessID, EXEC_LOAD_ELF_V2_CYCLES_BASE}; +use crate::types::{DataPieceId, FullSuspendedState, 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}, - CurrentCycles, CurrentMemory, Debugger, Exec, GetMemoryLimit, LoadBlockExtension, LoadCell, - LoadCellData, LoadHeader, LoadInput, LoadScript, LoadScriptHash, LoadTx, LoadWitness, - SetContent, Spawn, VMVersion, + Close, CurrentCycles, Debugger, Exec, LoadBlockExtension, LoadCell, LoadCellData, + LoadHeader, LoadInput, LoadScript, LoadScriptHash, LoadTx, LoadWitness, Pipe, Read, Spawn, + VMVersion, Wait, Write, }, type_id::TypeIdSystemScript, types::{ - CoreMachine, DebugPrinter, Indices, Machine, MachineContext, ResumableMachine, ResumePoint, - ScriptGroup, ScriptGroupType, ScriptVersion, SpawnData, TransactionSnapshot, - TransactionState, VerifyResult, + CoreMachine, DebugPrinter, Indices, ScriptGroup, ScriptGroupType, ScriptVersion, + TransactionSnapshot, TransactionState, VerifyResult, }, verify_env::TxVerifyEnv, ChunkCommand, @@ -32,11 +32,8 @@ use ckb_types::{ packed::{Byte32, CellOutput, OutPoint, Script}, prelude::*, }; -use ckb_vm::{ - cost_model::estimate_cycles, - snapshot::{resume, Snapshot}, - DefaultMachineBuilder, Error as VMInternalError, SupportMachine, Syscalls, -}; +use ckb_vm::machine::Pause as VMPause; +use ckb_vm::{snapshot2::Snapshot2Context, Error as VMInternalError, Syscalls}; use std::sync::{Arc, Mutex}; use std::{ collections::{BTreeMap, HashMap}, @@ -54,17 +51,18 @@ use core::sync::atomic::{AtomicBool, Ordering}; mod tests; pub enum ChunkState { - Suspended(Vec, Arc>), - Completed(Cycle), + Suspended(Option), + // (total_cycles, consumed_cycles in last chunk) + Completed(Cycle, Cycle), } impl ChunkState { - pub fn suspended(machines: Vec, context: Arc>) -> Self { - ChunkState::Suspended(machines, context) + pub fn suspended(state: FullSuspendedState) -> Self { + ChunkState::Suspended(Some(state)) } pub fn suspended_type_id() -> Self { - ChunkState::Suspended(vec![], Default::default()) + ChunkState::Suspended(None) } } @@ -141,21 +139,28 @@ 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, - debug_printer: DebugPrinter, + pub(crate) debug_printer: DebugPrinter, + pub(crate) message_box: Arc>>, pub(crate) outputs: Arc>, pub(crate) rtx: Arc, #[cfg(test)] - skip_pause: Arc, + pub(crate) skip_pause: 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, base: u64) -> CurrentCycles { - CurrentCycles::new(base) + pub fn build_current_cycles(&self) -> CurrentCycles { + CurrentCycles::new(Arc::clone(&self.base_cycles)) } /// Build syscall: vm_version @@ -164,13 +169,19 @@ impl Exec
{ + pub fn build_exec( + &self, + group_inputs: Indices, + group_outputs: Indices, + load_elf_base_fee: u64, + ) -> Exec
{ Exec::new( self.data_loader.clone(), Arc::clone(&self.rtx), Arc::clone(&self.outputs), group_inputs, group_outputs, + load_elf_base_fee, ) } @@ -193,16 +204,9 @@ impl>>>, ) -> LoadCellData
{ - LoadCellData::new( - self.data_loader.clone(), - Arc::clone(&self.rtx), - Arc::clone(&self.outputs), - group_inputs, - group_outputs, - ) + LoadCellData::new(snapshot2_context) } ///Build syscall: load_input @@ -243,49 +247,55 @@ 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>, + snapshot2_context: Arc>>>, ) -> Spawn
{ - Spawn::new( - script_group.clone(), - script_version, - self.clone(), - peak_memory, - cycles_base, - context, - ) + Spawn::new(self.vm_id, Arc::clone(&self.message_box), snapshot2_context) } - /// Build syscall: current_memory - pub fn build_current_memory(&self, current_memory: u64) -> CurrentMemory { - CurrentMemory::new(current_memory) + /// Build syscall: wait + pub fn build_wait(&self) -> Wait { + Wait::new(self.vm_id, Arc::clone(&self.message_box)) } - /// Generate same syscalls. The result does not contain spawn syscalls. - pub fn generate_same_syscalls( + /// Build syscall: process_id + pub fn build_process_id(&self) -> ProcessID { + ProcessID::new(self.vm_id) + } + + /// Build syscall: pipe + pub fn build_pipe(&self) -> Pipe { + Pipe::new(self.vm_id, Arc::clone(&self.message_box)) + } + + /// Build syscall: write + pub fn build_write(&self) -> Write { + Write::new(self.vm_id, Arc::clone(&self.message_box)) + } + + /// Build syscall: read + pub fn build_read(&self) -> Read { + Read::new(self.vm_id, Arc::clone(&self.message_box)) + } + + /// Build syscall: inherited_fd + pub fn inherited_fd(&self) -> InheritedFd { + InheritedFd::new(self.vm_id, Arc::clone(&self.message_box)) + } + + /// Build syscall: close + pub fn close(&self) -> Close { + Close::new(self.vm_id, Arc::clone(&self.message_box)) + } + + /// Generate syscalls. + pub fn generate_syscalls( &self, script_version: ScriptVersion, script_group: &ScriptGroup, + snapshot2_context: Arc>>>, ) -> Vec)>> { let current_script_hash = script_group.script.calc_script_hash(); let script_group_input_indices = Arc::new(script_group.input_indices.clone()); @@ -304,60 +314,42 @@ impl= 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 + }, )), + Box::new(self.build_current_cycles()), ]); } - - if script_version >= ScriptVersion::V2 { - syscalls.push(Box::new( - self.build_load_block_extension(Arc::clone(&script_group_input_indices)), - )); - } - syscalls - } - - /// Generate root syscalls. - pub fn generate_root_syscalls( - &self, - script_version: ScriptVersion, - script_group: &ScriptGroup, - context: Arc>, - ) -> Vec)>> { - let mut syscalls = self.generate_same_syscalls(script_version, script_group); - if script_version >= ScriptVersion::V1 { - syscalls.push(Box::new(self.build_current_cycles(0))); - } 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)), - ]) + Box::new(self.build_load_block_extension(Arc::clone(&script_group_input_indices))), + Box::new(self.build_spawn(Arc::clone(&snapshot2_context))), + Box::new(self.build_process_id()), + Box::new(self.build_pipe()), + Box::new(self.build_wait()), + Box::new(self.build_write()), + Box::new(self.build_read()), + Box::new(self.inherited_fd()), + Box::new(self.close()), + ]); } + #[cfg(test)] + syscalls.push(Box::new(Pause::new(Arc::clone(&self.skip_pause)))); syscalls } } @@ -366,7 +358,10 @@ impl` 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, @@ -383,11 +378,12 @@ pub struct TransactionScriptsVerifier
{ consensus: Arc, tx_env: Arc, - generator: TransactionScriptsSyscallsGenerator
, + syscalls_generator: TransactionScriptsSyscallsGenerator
, } -impl - TransactionScriptsVerifier
+impl
TransactionScriptsVerifier
+where + DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static, { /// Creates a script verifier for the transaction. /// @@ -479,13 +475,16 @@ impl(&mut self, func: F) { - self.generator.debug_printer = Arc::new(func); + self.syscalls_generator.debug_printer = Arc::new(func); } #[cfg(test)] @@ -653,22 +652,27 @@ impl Result { let mut cycles = 0; + let mut current_consumed_cycles = 0; let groups: Vec<_> = self.groups().collect(); for (idx, (_hash, group)) in groups.iter().enumerate() { // vm should early return invalid cycles - let remain_cycles = limit_cycles.checked_sub(cycles).ok_or_else(|| { - ScriptError::Other(format!("expect invalid cycles {limit_cycles} {cycles}")) - .source(group) - })?; + let remain_cycles = limit_cycles + .checked_sub(current_consumed_cycles) + .ok_or_else(|| { + ScriptError::Other(format!("expect invalid cycles {limit_cycles} {cycles}")) + .source(group) + })?; - match self.verify_group_with_chunk(group, remain_cycles, &[]) { - Ok(ChunkState::Completed(used_cycles)) => { + match self.verify_group_with_chunk(group, remain_cycles, &None) { + Ok(ChunkState::Completed(used_cycles, consumed_cycles)) => { + current_consumed_cycles = + wrapping_cycles_add(current_consumed_cycles, consumed_cycles, group)?; cycles = wrapping_cycles_add(cycles, used_cycles, group)?; } - Ok(ChunkState::Suspended(vms, context)) => { + Ok(ChunkState::Suspended(state)) => { let current = idx; - let state = TransactionState::new(vms, context, current, cycles, remain_cycles); + let state = TransactionState::new(state, current, cycles, remain_cycles); return Ok(VerifyResult::Suspended(state)); } Err(e) => { @@ -736,7 +740,6 @@ impl Result { - let current_group_used = snap.snaps.iter().map(|s| s.1).sum(); let mut cycles = snap.current_cycles; let mut current_used = 0; @@ -746,18 +749,14 @@ impl { - current_used = wrapping_cycles_add( - current_used, - wrapping_cycles_sub(used_cycles, current_group_used, current_group)?, - current_group, - )?; + match self.verify_group_with_chunk(current_group, limit_cycles, &snap.state) { + Ok(ChunkState::Completed(used_cycles, consumed_cycles)) => { + current_used = wrapping_cycles_add(current_used, consumed_cycles, current_group)?; cycles = wrapping_cycles_add(cycles, used_cycles, current_group)?; } - Ok(ChunkState::Suspended(vms, context)) => { + Ok(ChunkState::Suspended(state)) => { let current = snap.current; - let state = TransactionState::new(vms, context, current, cycles, limit_cycles); + let state = TransactionState::new(state, current, cycles, limit_cycles); return Ok(VerifyResult::Suspended(state)); } Err(e) => { @@ -767,21 +766,20 @@ impl { - current_used = wrapping_cycles_add(current_used, used_cycles, group)?; + match self.verify_group_with_chunk(group, remain_cycles, &None) { + Ok(ChunkState::Completed(used_cycles, consumed_cycles)) => { + current_used = wrapping_cycles_add(current_used, consumed_cycles, group)?; cycles = wrapping_cycles_add(cycles, used_cycles, group)?; } - Ok(ChunkState::Suspended(vms, context)) => { + Ok(ChunkState::Suspended(state)) => { let current = idx; - let state = TransactionState::new(vms, context, current, cycles, remain_cycles); + let state = TransactionState::new(state, current, cycles, remain_cycles); return Ok(VerifyResult::Suspended(state)); } Err(e) => { @@ -815,9 +813,8 @@ impl Result { let TransactionState { current, - mut vms, + state, current_cycles, - machine_context, .. } = state; @@ -828,20 +825,16 @@ impl { - current_used = wrapping_cycles_add(current_used, used_cycles, current_group)?; + Ok(ChunkState::Completed(used_cycles, consumed_cycles)) => { + current_used = wrapping_cycles_add(current_used, consumed_cycles, current_group)?; cycles = wrapping_cycles_add(cycles, used_cycles, current_group)?; } - Ok(ChunkState::Suspended(vms, context)) => { - let state = TransactionState::new(vms, context, current, cycles, limit_cycles); + Ok(ChunkState::Suspended(state)) => { + let state = TransactionState::new(state, current, cycles, limit_cycles); return Ok(VerifyResult::Suspended(state)); } Err(e) => { @@ -853,18 +846,20 @@ impl { - current_used = wrapping_cycles_add(current_used, used_cycles, group)?; - cycles = wrapping_cycles_add(cycles, used_cycles, group)?; + match self.verify_group_with_chunk(group, remain_cycles, &None) { + Ok(ChunkState::Completed(_, consumed_cycles)) => { + current_used = wrapping_cycles_add(current_used, consumed_cycles, group)?; + cycles = wrapping_cycles_add(cycles, consumed_cycles, group)?; } - Ok(ChunkState::Suspended(vms, context)) => { + Ok(ChunkState::Suspended(state)) => { let current = idx; - let state = TransactionState::new(vms, context, current, cycles, remain_cycles); + let state = TransactionState::new(state, current, cycles, remain_cycles); return Ok(VerifyResult::Suspended(state)); } Err(e) => { @@ -906,11 +901,11 @@ impl { + match self.verify_group_with_chunk(current_group, max_cycles - cycles, &snap.state) { + Ok(ChunkState::Completed(used_cycles, _consumed_cycles)) => { cycles = wrapping_cycles_add(cycles, used_cycles, current_group)?; } - Ok(ChunkState::Suspended(_, _)) => { + Ok(ChunkState::Suspended(_)) => { return Err(ScriptError::ExceededMaximumCycles(max_cycles) .source(current_group) .into()); @@ -928,11 +923,11 @@ impl { + match self.verify_group_with_chunk(group, remain_cycles, &None) { + Ok(ChunkState::Completed(used_cycles, _consumed_cycles)) => { cycles = wrapping_cycles_add(cycles, used_cycles, current_group)?; } - Ok(ChunkState::Suspended(_, _)) => { + Ok(ChunkState::Suspended(_)) => { return Err(ScriptError::ExceededMaximumCycles(max_cycles) .source(group) .into()); @@ -1003,7 +998,7 @@ impl, ) -> Result { if group.script.code_hash() == TYPE_ID_CODE_HASH.pack() && Into::::into(group.script.hash_type()) == Into::::into(ScriptHashType::Type) @@ -1014,12 +1009,66 @@ impl Ok(ChunkState::Completed(cycles)), + Ok(cycles) => Ok(ChunkState::Completed(cycles, cycles)), Err(ScriptError::ExceededMaximumCycles(_)) => Ok(ChunkState::suspended_type_id()), Err(e) => Err(e), } } else { - self.chunk_run(group, max_cycles, snaps) + self.chunk_run(group, max_cycles, state) + } + } + + fn chunk_run( + &self, + script_group: &ScriptGroup, + max_cycles: Cycle, + state: &Option, + ) -> Result { + let program = self.extract_script(&script_group.script)?; + let tx_data = TxData { + rtx: Arc::clone(&self.rtx), + data_loader: self.data_loader.clone(), + program, + script_group: Arc::new(script_group.clone()), + }; + let version = self.select_version(&script_group.script)?; + let mut scheduler = if let Some(state) = state { + Scheduler::resume( + tx_data, + version, + self.syscalls_generator.clone(), + state.clone(), + ) + } else { + Scheduler::new(tx_data, version, self.syscalls_generator.clone()) + }; + let map_vm_internal_error = |error: VMInternalError| match error { + VMInternalError::CyclesExceeded => ScriptError::ExceededMaximumCycles(max_cycles), + _ => ScriptError::VMInternalError(error), + }; + let previous_cycles = scheduler.consumed_cycles(); + let res = scheduler.run(RunMode::LimitCycles(max_cycles)); + match res { + Ok((exit_code, cycles)) => { + if exit_code == 0 { + Ok(ChunkState::Completed( + cycles, + scheduler.consumed_cycles() - previous_cycles, + )) + } else { + Err(ScriptError::validation_failure( + &script_group.script, + exit_code, + )) + } + } + Err(error) => match error { + VMInternalError::CyclesExceeded | VMInternalError::Pause => { + let snapshot = scheduler.suspend().map_err(map_vm_internal_error)?; + Ok(ChunkState::suspended(snapshot)) + } + _ => Err(map_vm_internal_error(error)), + }, } } @@ -1061,29 +1110,30 @@ impl>, + snapshot2_context: Arc>>>, ) -> Vec)>> { - self.generator - .generate_root_syscalls(script_version, script_group, context) + self.syscalls_generator + .generate_syscalls(script_version, script_group, snapshot2_context) } - fn build_machine( + /// Create a scheduler to manage virtual machine instances. + pub fn create_scheduler( &self, script_group: &ScriptGroup, - max_cycles: Cycle, - context: Arc>, - ) -> Result { - 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(estimate_cycles)); - let syscalls = self.generate_syscalls(script_version, script_group, context); - let machine_builder = syscalls - .into_iter() - .fold(machine_builder, |builder, syscall| builder.syscall(syscall)); - let default_machine = machine_builder.build(); - let machine = Machine::new(default_machine); - Ok(machine) + ) -> Result, ScriptError> { + let program = self.extract_script(&script_group.script)?; + let tx_data = TxData { + rtx: Arc::clone(&self.rtx), + data_loader: self.data_loader.clone(), + program, + script_group: Arc::new(script_group.clone()), + }; + let version = self.select_version(&script_group.script)?; + Ok(Scheduler::new( + tx_data, + version, + self.syscalls_generator.clone(), + )) } /// Runs a single program, then returns the exit code together with the entire @@ -1092,33 +1142,22 @@ impl Result<(i8, Machine), ScriptError> { - let program = self.extract_script(&script_group.script)?; - let context = Default::default(); - let mut machine = self.build_machine(script_group, max_cycles, context)?; - + ) -> Result<(i8, Cycle), ScriptError> { + let mut scheduler = self.create_scheduler(script_group)?; let map_vm_internal_error = |error: VMInternalError| match error { VMInternalError::CyclesExceeded => ScriptError::ExceededMaximumCycles(max_cycles), _ => ScriptError::VMInternalError(error), }; - - 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)) + scheduler + .run(RunMode::LimitCycles(max_cycles)) + .map_err(map_vm_internal_error) } fn run(&self, script_group: &ScriptGroup, max_cycles: Cycle) -> Result { - let (code, machine) = self.detailed_run(script_group, max_cycles)?; + let (code, cycles) = self.detailed_run(script_group, max_cycles)?; if code == 0 { - Ok(machine.machine.cycles()) + Ok(cycles) } else { Err(ScriptError::validation_failure(&script_group.script, code)) } @@ -1128,388 +1167,103 @@ impl, + signal: &mut Receiver, ) -> 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()); - vec![ResumableMachine::initial(machine)] + let program = self.extract_script(&script_group.script)?; + let tx_data = TxData { + rtx: Arc::clone(&self.rtx), + data_loader: self.data_loader.clone(), + program, + script_group: Arc::new(script_group.clone()), }; - - 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 version = self.select_version(&script_group.script)?; + let mut scheduler = Scheduler::new(tx_data, version, self.syscalls_generator.clone()); 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.generator, - max_cycles, - &spawn_data, - &context, - ) - .map_err(map_vm_internal_error)?; - ResumableMachine::spawn(machine, spawn_data) + let mut pause = VMPause::new(); + let child_pause = pause.clone(); + let (finish_tx, mut finish_rx) = oneshot::channel::>(); + + // send initial `Resume` command to child + // it's maybe useful to set initial command to `signal.borrow().to_owned()` + // so that we can control the initial state of child, which is useful for testing purpose + let (child_tx, mut child_rx) = watch::channel(ChunkCommand::Resume); + let jh = tokio::spawn(async move { + child_rx.mark_changed(); + loop { + let pause_cloned = child_pause.clone(); + let _ = child_rx.changed().await; + match *child_rx.borrow() { + ChunkCommand::Stop => { + let exit = Err(ckb_vm::Error::External("stopped".into())); + let _ = finish_tx.send(exit); + return; } - }; - 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) - } -} - -// Run a series of VMs that are just freshly resumed -fn run_vms( - script_group: &ScriptGroup, - max_cycles: Cycle, - 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, - )) - } -} - -// Run a series of VMs with control signal, will only return when verification finished -// Or send `Stop` command when verification is suspended -async fn run_vms_with_signal( - script_group: &ScriptGroup, - machines: Vec, - context: Arc>, - signal: &mut Receiver, -) -> Result { - if machines.is_empty() { - return Err(ScriptError::Other( - "To resume VMs, at least one VM must be available!".to_string(), - )); - } - - let mut pause = machines[0].pause(); - let (finish_tx, mut finish_rx) = oneshot::channel::<(Result, u64)>(); - - // send initial `Resume` command to child - // it's maybe useful to set initial command to `signal.borrow().to_owned()` - // so that we can control the initial state of child, which is useful for testing purpose - let (child_tx, child_rx) = watch::channel(ChunkCommand::Resume); - let jh = - tokio::spawn(async move { run_vms_child(machines, child_rx, finish_tx, context).await }); - - loop { - tokio::select! { - Ok(_) = signal.changed() => { - let command = signal.borrow().to_owned(); - //info!("[verify-test] run_vms_with_signal: {:?}", command); - match command { ChunkCommand::Suspend => { - pause.interrupt(); + continue; } - ChunkCommand::Resume | ChunkCommand::Stop => { - pause.free(); - let _ = child_tx.send(command); + ChunkCommand::Resume => { + //info!("[verify-test] run_vms_child: resume"); + let res = scheduler.run(RunMode::Pause(pause_cloned)); + match res { + Ok(_) => { + let _ = finish_tx.send(res); + return; + } + Err(VMInternalError::Pause) => { + // continue to wait for + } + _ => { + let _ = finish_tx.send(res); + return; + } + } } } } - Ok(res) = &mut finish_rx => { - let _ = jh.await; - match res { - (Ok(0), cycles) => { - return Ok(cycles); - } - (Ok(exit_code), _) => { - return Err(ScriptError::validation_failure( - &script_group.script, - exit_code - ))}, - (Err(err), _) => { - return Err(ScriptError::VMInternalError(err)); + }); + + loop { + tokio::select! { + Ok(_) = signal.changed() => { + let command = signal.borrow().to_owned(); + //info!("[verify-test] run_vms_with_signal: {:?}", command); + match command { + ChunkCommand::Suspend => { + pause.interrupt(); + } + ChunkCommand::Stop => { + pause.interrupt(); + let _ = child_tx.send(command); + } + ChunkCommand::Resume => { + pause.free(); + let _ = child_tx.send(command); + } } } - - } - else => { break Err(ScriptError::validation_failure(&script_group.script, 0)) } - } - } -} - -async fn run_vms_child( - mut machines: Vec, - mut child_rx: watch::Receiver, - 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; + Ok(res) = &mut finish_rx => { + let _ = jh.await; + match res { + Ok((0, cycles)) => { + return Ok(cycles); + } + Ok((exit_code, _cycles)) => { + return Err(ScriptError::validation_failure( + &script_group.script, + exit_code + ))}, + Err(err) => { + return Err(map_vm_internal_error(err)); + } } - 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; + } - }; + else => { break Err(ScriptError::validation_failure(&script_group.script, 0)) } + } } } } @@ -1523,15 +1277,6 @@ fn wrapping_cycles_add( .ok_or_else(|| ScriptError::CyclesOverflow(lhs, rhs).source(group)) } -fn wrapping_cycles_sub( - lhs: Cycle, - rhs: Cycle, - group: &ScriptGroup, -) -> Result { - lhs.checked_sub(rhs) - .ok_or_else(|| ScriptError::CyclesOverflow(lhs, rhs).source(group)) -} - #[cfg(feature = "logging")] mod logging { use super::{info, Byte32, ScriptError}; diff --git a/script/src/verify/tests/ckb_latest/features_since_v2019.rs b/script/src/verify/tests/ckb_latest/features_since_v2019.rs index 4a25ef1602..a69c887575 100644 --- a/script/src/verify/tests/ckb_latest/features_since_v2019.rs +++ b/script/src/verify/tests/ckb_latest/features_since_v2019.rs @@ -49,7 +49,7 @@ fn check_always_success_hash() { let verifier = TransactionScriptsVerifierWithEnv::new(); let result = verifier.verify_without_limit(script_version, &rtx); - assert!(result.is_ok()); + assert_eq!(result.ok(), Some(ALWAYS_SUCCESS_SCRIPT_CYCLE)); } #[test] @@ -119,7 +119,7 @@ fn check_signature() { ); let result = verifier.verify_without_limit(script_version, &rtx); - assert!(result.is_ok()); + assert_eq!(result.ok(), Some(ALWAYS_SUCCESS_SCRIPT_CYCLE)); } #[test] @@ -187,7 +187,11 @@ fn check_signature_referenced_via_type_hash() { let verifier = TransactionScriptsVerifierWithEnv::new(); let result = verifier.verify_without_limit(script_version, &rtx); - assert!(result.is_ok()); + if script_version == ScriptVersion::V0 { + assert_eq!(result.ok(), Some(ALWAYS_SUCCESS_SCRIPT_CYCLE)); + } else { + assert_eq!(result.ok(), Some(ALWAYS_SUCCESS_SCRIPT_CYCLE + 2)); + } } #[test] @@ -378,7 +382,7 @@ fn check_output_contract() { let verifier = TransactionScriptsVerifierWithEnv::new(); let result = verifier.verify_without_limit(script_version, &rtx); - assert!(result.is_ok()); + assert_eq!(result.ok(), Some(1074)); } #[test] @@ -574,6 +578,7 @@ fn check_type_id_one_in_one_out() { let max_cycles = TYPE_ID_CYCLES * 2; let verifier = TransactionScriptsVerifierWithEnv::new(); let result = verifier.verify(script_version, &rtx, max_cycles); + assert!( result.is_ok(), "expect ok, but got {:?}", 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 a9b8176bfd..437b90642a 100644 --- a/script/src/verify/tests/ckb_latest/features_since_v2021.rs +++ b/script/src/verify/tests/ckb_latest/features_since_v2021.rs @@ -57,6 +57,8 @@ fn test_hint_instructions() { }; let script_error = ScriptError::VMInternalError(vm_error); assert_error_eq!(result.unwrap_err(), script_error.input_lock_script(0)); + } else { + assert_eq!(result.ok(), Some(540)); } } @@ -100,13 +102,21 @@ fn test_b_extension() { let verifier = TransactionScriptsVerifierWithEnv::new(); let result = verifier.verify_without_limit(script_version, &rtx); assert_eq!(result.is_ok(), script_version >= ScriptVersion::V1,); - if script_version < ScriptVersion::V1 { - let vm_error = VmError::InvalidInstruction { - pc: 0x10182, - instruction: 0x60291913, - }; - let script_error = ScriptError::VMInternalError(vm_error); - assert_error_eq!(result.unwrap_err(), script_error.input_lock_script(0)); + match script_version { + ScriptVersion::V0 => { + let vm_error = VmError::InvalidInstruction { + pc: 65866, + instruction: 0x60291913, + }; + let script_error = ScriptError::VMInternalError(vm_error); + assert_error_eq!(result.unwrap_err(), script_error.input_lock_script(0)); + } + ScriptVersion::V1 => { + assert_eq!(result.ok(), Some(1876)); + } + ScriptVersion::V2 => { + assert_eq!(result.ok(), Some(1875)); + } } } @@ -372,6 +382,11 @@ fn check_exec_from_witness() { let verifier = TransactionScriptsVerifierWithEnv::new(); let result = verifier.verify_without_limit(script_version, &rtx); assert_eq!(result.is_ok(), script_version >= ScriptVersion::V1); + if script_version == ScriptVersion::V1 { + assert_eq!(result.ok(), Some(1200)); + } else if script_version == ScriptVersion::V2 { + assert_eq!(result.ok(), Some(76198)); + } } #[test] @@ -538,29 +553,30 @@ fn _check_type_id_one_in_one_out_resume(step_cycles: Cycle) -> Result<(), TestCa verifier.verify_map(script_version, &rtx, |verifier| { let mut groups: VecDeque<_> = verifier.groups_with_type().collect(); - let mut tmp: Option = None; + let mut tmp: Option = None; + let mut current_group = None; let mut limit = step_cycles; loop { - if let Some(mut vm) = tmp.take() { - vm.set_max_cycles(limit); - match vm.run() { - Ok(code) => { - if code == 0 { - cycles += vm.cycles(); - groups.pop_front(); - } else { - unreachable!() - } + if let Some(cur_state) = tmp.take() { + match verifier.verify_group_with_chunk( + current_group.unwrap(), + limit, + &Some(cur_state), + ) { + Ok(ChunkState::Completed(used_cycles, _consumed_cycles)) => { + cycles += used_cycles; + groups.pop_front(); + tmp = None; + } + Ok(ChunkState::Suspended(suspend_state)) => { + tmp = suspend_state; + limit += step_cycles; + continue; + } + Err(_error) => { + unreachable!(); } - Err(error) => match error { - VMInternalError::CyclesExceeded | VMInternalError::Pause => { - tmp = Some(vm); - limit += step_cycles; - continue; - } - _ => unreachable!(), - }, } } if groups.is_empty() { @@ -568,19 +584,22 @@ fn _check_type_id_one_in_one_out_resume(step_cycles: Cycle) -> Result<(), TestCa } while let Some((ty, _, group)) = groups.front().cloned() { - match verifier.verify_group_with_chunk(group, limit, &[]).unwrap() { - ChunkState::Completed(used_cycles) => { + match verifier + .verify_group_with_chunk(group, limit, &tmp) + .unwrap() + { + ChunkState::Completed(used_cycles, _consumed_cycles) => { cycles += used_cycles; groups.pop_front(); + tmp = None; if groups.front().is_some() { limit = step_cycles; } } - ChunkState::Suspended(mut vms, _) => { - assert!(vms.len() <= 1); - let vm = vms.pop(); - if vm.is_some() { - tmp = vm; + ChunkState::Suspended(suspend_state) => { + if suspend_state.is_some() { + tmp = suspend_state; + current_group = Some(group); } else if ty == ScriptGroupType::Type // fast forward && step_cycles > TYPE_ID_CYCLES && limit < (TYPE_ID_CYCLES - step_cycles) @@ -739,49 +758,33 @@ fn _check_typical_secp256k1_blake160_2_in_2_out_tx_with_chunk(step_cycles: Cycle let verifier = TransactionScriptsVerifierWithEnv::new(); let result = verifier.verify_map(script_version, &rtx, |verifier| { let mut groups: Vec<_> = verifier.groups_with_type().collect(); - let mut tmp: Option = None; + let mut tmp = None; let mut limit = step_cycles; loop { - if let Some(mut vm) = tmp.take() { - vm.set_max_cycles(limit); - match vm.run() { - Ok(code) => { - if code == 0 { - cycles += vm.cycles(); - } else { - unreachable!() - } - } - Err(error) => match error { - VMInternalError::CyclesExceeded | VMInternalError::Pause => { - tmp = Some(vm); - limit += step_cycles; - continue; - } - _ => unreachable!(), - }, - } - } - while let Some((_, _, group)) = groups.pop() { - match verifier.verify_group_with_chunk(group, limit, &[]).unwrap() { - ChunkState::Completed(used_cycles) => { + while let Some(group) = groups.pop() { + match verifier + .verify_group_with_chunk(group.2, limit, &tmp) + .unwrap() + { + ChunkState::Completed(used_cycles, _consumed_cycles) => { cycles += used_cycles; + tmp = None; } - ChunkState::Suspended(mut vms, _) => { - assert!(vms.len() <= 1); - tmp = vms.pop(); + ChunkState::Suspended(snapshot) => { + tmp = snapshot; if limit < (TWO_IN_TWO_OUT_CYCLES - step_cycles) { limit += TWO_IN_TWO_OUT_CYCLES - step_cycles; } else { limit += step_cycles; } + groups.push(group); break; } } } - if tmp.is_none() { + if groups.is_empty() { break; } } @@ -804,6 +807,13 @@ fn _check_typical_secp256k1_blake160_2_in_2_out_tx_with_chunk(step_cycles: Cycle ); } assert_eq!(cycles, cycles_once, "step_cycles {step_cycles}"); + // Note that different rand versions may cause different randomly + // generated tx data, which in turn leads to different final cycles. + if script_version < crate::ScriptVersion::V2 { + assert_eq!(cycles, 3334802); + } else { + assert_eq!(cycles, 3225879); + } } #[test] @@ -876,10 +886,10 @@ fn check_typical_secp256k1_blake160_2_in_2_out_tx_with_state() { fn _check_typical_secp256k1_blake160_2_in_2_out_tx_with_snap(step_cycles: Cycle) { let script_version = SCRIPT_VERSION; - let rtx = random_2_in_2_out_rtx(); let mut cycles = 0; let verifier = TransactionScriptsVerifierWithEnv::new(); + let result = verifier.verify_map(script_version, &rtx, |verifier| { let mut init_snap: Option = None; let mut init_state: Option = None; @@ -999,12 +1009,22 @@ fn check_typical_secp256k1_blake160_2_in_2_out_tx_with_complete() { let cycles_once = result.unwrap(); assert!(cycles <= TWO_IN_TWO_OUT_CYCLES); + if script_version == crate::ScriptVersion::V2 { assert!(cycles >= TWO_IN_TWO_OUT_CYCLES - V2_CYCLE_BOUND); } else { assert!(cycles >= TWO_IN_TWO_OUT_CYCLES - CYCLE_BOUND); } assert_eq!(cycles, cycles_once); + // Note that different rand versions may cause different randomly + // generated tx data, which in turn leads to different final cycles. + if script_version <= ScriptVersion::V0 { + assert_eq!(cycles, 3352333); + } else if script_version == ScriptVersion::V1 { + assert_eq!(cycles, 3334802); + } else if script_version == ScriptVersion::V2 { + assert_eq!(cycles, 3225879); + } } #[test] @@ -1052,10 +1072,14 @@ fn load_code_into_global() { let verifier = TransactionScriptsVerifierWithEnv::new(); let result = verifier.verify_without_limit(script_version, &rtx); assert_eq!(result.is_ok(), script_version >= ScriptVersion::V1,); - if script_version < ScriptVersion::V1 { + if script_version < ScriptVersion::V0 { let vm_error = VmError::MemWriteOnFreezedPage; let script_error = ScriptError::VMInternalError(vm_error); assert_error_eq!(result.unwrap_err(), script_error.input_lock_script(0)); + } else if script_version == ScriptVersion::V1 { + assert_eq!(result.ok(), Some(10529)); + } else if script_version == ScriptVersion::V2 { + assert_eq!(result.ok(), Some(10525)); } } @@ -1129,6 +1153,13 @@ fn load_code_with_snapshot() { let cycles_once = result.unwrap(); assert_eq!(cycles, cycles_once); + if script_version == ScriptVersion::V0 { + assert_eq!(cycles_once, 11062); + } else if script_version == ScriptVersion::V1 { + assert_eq!(cycles_once, 11064); + } else { + assert_eq!(cycles_once, 11060); + } } #[test] @@ -1222,6 +1253,13 @@ fn load_code_with_snapshot_more_times() { let result = verifier.verify_without_pause(script_version, &rtx, max_cycles); let cycles_once = result.unwrap(); assert_eq!(cycles, cycles_once); + if script_version == ScriptVersion::V0 { + assert_eq!(cycles_once, 45740); + } else if script_version == ScriptVersion::V1 { + assert_eq!(cycles_once, 45742); + } else { + assert_eq!(cycles_once, 45729); + } } #[derive(Clone, Copy)] @@ -1822,5 +1860,43 @@ fn check_signature_referenced_via_type_hash_ok_with_multiple_matches() { let verifier = TransactionScriptsVerifierWithEnv::new(); let result = verifier.verify_without_limit(script_version, &rtx); - assert_eq!(result.unwrap(), 539); + assert_eq!(result.ok(), Some(539)); +} + +#[test] +fn check_exec_callee_pause() { + let script_version = SCRIPT_VERSION; + if script_version < crate::ScriptVersion::V1 { + return; + } + + let (exec_caller_cell, exec_caller_data_hash) = + load_cell_from_path("testdata/exec_caller_from_cell_data"); + let (exec_callee_cell, _exec_callee_data_hash) = + load_cell_from_path("testdata/exec_callee_pause"); + + let exec_caller_script = Script::new_builder() + .hash_type(script_version.data_hash_type().into()) + .code_hash(exec_caller_data_hash) + .build(); + let output = CellOutputBuilder::default() + .capacity(capacity_bytes!(100).pack()) + .lock(exec_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![exec_caller_cell, exec_callee_cell], + resolved_inputs: vec![dummy_cell], + resolved_dep_groups: vec![], + }; + + let verifier = TransactionScriptsVerifierWithEnv::new(); + let result = verifier.verify_until_completed(script_version, &rtx); + assert_eq!(result.is_ok(), script_version >= ScriptVersion::V1); + assert_eq!(result.unwrap().1, 6); } 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 85b9b08ad1..4cb4fd8583 100644 --- a/script/src/verify/tests/ckb_latest/features_since_v2023.rs +++ b/script/src/verify/tests/ckb_latest/features_since_v2023.rs @@ -1,48 +1,26 @@ +use super::SCRIPT_VERSION; +use crate::scheduler::{MAX_FDS, MAX_VMS_COUNT}; +use crate::syscalls::SOURCE_GROUP_FLAG; +use crate::verify::{tests::utils::*, *}; use ckb_types::{ - core::{capacity_bytes, Capacity, TransactionBuilder}, + core::{capacity_bytes, cell::CellMetaBuilder, Capacity, TransactionBuilder}, packed::{CellInput, CellOutputBuilder, OutPoint, Script}, }; +use proptest::prelude::*; +use proptest::proptest; -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 < length, = 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 -// check_peak_memory_512k_to_32m: spawn should success when peak memory <= 32M -// check_spawn_snapshot: A spawn B, then B gets suspended to snapshot and resume again. -// check_spawn_state: Like check_spawn_snapshot but invoking verifier.resume_from_state instead. -// check_spawn_current_memory: Use current_memory() to terminate infinite recursion. -// check_spawn_current_cycles: callee's current_cycles should inherit caller's current_cycles. -// check_spawn_times_bug_1: BUG: execution results may be inconsistent -// check_spawn_times_bug_2: BUG: execution results may be inconsistent - -#[test] -fn check_vm_version() { +fn simple_spawn_test(bin_path: &str, args: &[u8]) -> Result { 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() + let (cell, data_hash) = load_cell_from_path(bin_path); + let script = Script::new_builder() .hash_type(script_version.data_hash_type().into()) - .code_hash(vm_version_data_hash) + .code_hash(data_hash) + .args(Bytes::copy_from_slice(args).pack()) .build(); let output = CellOutputBuilder::default() .capacity(capacity_bytes!(100).pack()) - .lock(vm_version_script) + .lock(script) .build(); let input = CellInput::new(OutPoint::null(), 0); @@ -51,163 +29,99 @@ fn check_vm_version() { let rtx = ResolvedTransaction { transaction, - resolved_cell_deps: vec![vm_version_cell], + resolved_cell_deps: vec![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); + verifier.verify_without_limit(script_version, &rtx) } #[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); +fn check_spawn_simple_read_write() { + let result = simple_spawn_test("testdata/spawn_cases", &[1]); + 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); +fn check_spawn_write_dead_lock() { + let result = simple_spawn_test("testdata/spawn_cases", &[2]); + assert_eq!( + result.unwrap_err().to_string().contains("deadlock"), + 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"); +fn check_spawn_invalid_fd() { + let result = simple_spawn_test("testdata/spawn_cases", &[3]); + assert_eq!(result.is_ok(), SCRIPT_VERSION == ScriptVersion::V2); +} - 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); +#[test] +fn check_spawn_wait_dead_lock() { + let result = simple_spawn_test("testdata/spawn_cases", &[4]); + assert_eq!( + result.unwrap_err().to_string().contains("deadlock"), + SCRIPT_VERSION == ScriptVersion::V2 + ); +} - let transaction = TransactionBuilder::default().input(input).build(); - let dummy_cell = create_dummy_cell(output); +#[test] +fn check_spawn_read_write_with_close() { + let result = simple_spawn_test("testdata/spawn_cases", &[5]); + assert_eq!(result.is_ok(), SCRIPT_VERSION == ScriptVersion::V2); +} - 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_wait_multiple() { + let result = simple_spawn_test("testdata/spawn_cases", &[6]); + assert_eq!(result.is_ok(), SCRIPT_VERSION == ScriptVersion::V2); } #[test] -fn check_spawn_strcat_data_hash() { - let script_version = SCRIPT_VERSION; +fn check_spawn_inherited_fds() { + let result = simple_spawn_test("testdata/spawn_cases", &[7]); + assert_eq!(result.is_ok(), SCRIPT_VERSION == ScriptVersion::V2); +} - 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"); +#[test] +fn check_spawn_inherited_fds_without_owner() { + let result = simple_spawn_test("testdata/spawn_cases", &[8]); + assert_eq!(result.is_ok(), SCRIPT_VERSION == ScriptVersion::V2); +} - 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); +#[test] +fn check_spawn_read_then_close() { + let result = simple_spawn_test("testdata/spawn_cases", &[9]); + assert_eq!(result.is_ok(), SCRIPT_VERSION == ScriptVersion::V2); +} - let transaction = TransactionBuilder::default().input(input).build(); - let dummy_cell = create_dummy_cell(output); +#[test] +fn check_spawn_max_vms_count() { + let result = simple_spawn_test("testdata/spawn_cases", &[10]); + assert_eq!(result.is_ok(), SCRIPT_VERSION == ScriptVersion::V2); +} - 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_max_fds_limit() { + let result = simple_spawn_test("testdata/spawn_cases", &[11]); + assert_eq!(result.is_ok(), SCRIPT_VERSION == ScriptVersion::V2); } #[test] -fn check_spawn_get_memory_limit() { +fn check_vm_version() { 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 (vm_version_cell, vm_version_data_hash) = load_cell_from_path("testdata/vm_version_2"); - let spawn_caller_script = Script::new_builder() + let vm_version_script = Script::new_builder() .hash_type(script_version.data_hash_type().into()) - .code_hash(spawn_caller_data_hash) + .code_hash(vm_version_data_hash) .build(); let output = CellOutputBuilder::default() .capacity(capacity_bytes!(100).pack()) - .lock(spawn_caller_script) + .lock(vm_version_script) .build(); let input = CellInput::new(OutPoint::null(), 0); @@ -216,23 +130,24 @@ fn check_spawn_get_memory_limit() { let rtx = ResolvedTransaction { transaction, - resolved_cell_deps: vec![spawn_caller_cell, spawn_callee_cell], + 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); + assert_eq!(result.is_ok(), script_version == ScriptVersion::V2); } #[test] -fn check_spawn_set_content() { +fn check_spawn_strcat() { let script_version = SCRIPT_VERSION; let (spawn_caller_cell, spawn_caller_data_hash) = - load_cell_from_path("testdata/spawn_caller_set_content"); + load_cell_from_path("testdata/spawn_caller_strcat"); let (spawn_callee_cell, _spawn_callee_data_hash) = - load_cell_from_path("testdata/spawn_callee_set_content"); + load_cell_from_path("testdata/spawn_callee_strcat"); let spawn_caller_script = Script::new_builder() .hash_type(script_version.data_hash_type().into()) @@ -412,7 +327,7 @@ fn check_spawn_out_of_cycles_wrap() { .to_string() .contains("ExceededMaximumCycles")) } else { - assert!(result.is_err()) + assert!(result.is_err()); } } @@ -443,20 +358,25 @@ fn check_spawn_recursive() { resolved_dep_groups: vec![], }; let verifier = TransactionScriptsVerifierWithEnv::new(); - let result = verifier.verify_without_limit(script_version, &rtx); + let result = verifier.verify(script_version, &rtx, 70_000_000); if script_version >= ScriptVersion::V2 { - assert!(result.unwrap_err().to_string().contains("error code 7")) + let msg = result.unwrap_err().to_string(); + assert!(msg.contains("error code 8")) } else { assert!(result.is_err()) } } #[test] -fn check_spawn_big_memory_size() { +fn check_spawn_snapshot() { let script_version = SCRIPT_VERSION; + if script_version <= ScriptVersion::V1 { + return; + } let (spawn_caller_cell, spawn_caller_data_hash) = - load_cell_from_path("testdata/spawn_big_memory_size"); + load_cell_from_path("testdata/spawn_caller_exec"); + let (snapshot_cell, _) = load_cell_from_path("testdata/current_cycles_with_snapshot"); let spawn_caller_script = Script::new_builder() .hash_type(script_version.data_hash_type().into()) @@ -473,21 +393,31 @@ fn check_spawn_big_memory_size() { let rtx = ResolvedTransaction { transaction, - resolved_cell_deps: vec![spawn_caller_cell], + resolved_cell_deps: vec![spawn_caller_cell, snapshot_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); + let result = verifier.verify_without_pause(script_version, &rtx, Cycle::MAX); + let cycles_once = result.unwrap(); + + let (cycles, chunks_count) = verifier + .verify_until_completed(script_version, &rtx) + .unwrap(); + assert_eq!(cycles, cycles_once); + assert!(chunks_count > 1); } -#[test] -fn check_spawn_big_content_length() { +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn check_spawn_async() { let script_version = SCRIPT_VERSION; + if script_version <= ScriptVersion::V1 { + return; + } let (spawn_caller_cell, spawn_caller_data_hash) = - load_cell_from_path("testdata/spawn_big_content_length"); + load_cell_from_path("testdata/spawn_caller_exec"); + let (snapshot_cell, _) = load_cell_from_path("testdata/current_cycles_with_snapshot"); let spawn_caller_script = Script::new_builder() .hash_type(script_version.data_hash_type().into()) @@ -504,52 +434,69 @@ fn check_spawn_big_content_length() { let rtx = ResolvedTransaction { transaction, - resolved_cell_deps: vec![spawn_caller_cell], + resolved_cell_deps: vec![spawn_caller_cell, snapshot_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); -} + let result = verifier.verify_without_pause(script_version, &rtx, Cycle::MAX); + let cycles_once = result.unwrap(); -#[test] -fn check_peak_memory_4m_to_32m() { - let script_version = SCRIPT_VERSION; + // we use debug pause to test context resume + // `current_cycles_with_snapshot` will try to pause verifier + // here we use `channel` to send Resume to verifier until it completes + let (command_tx, mut command_rx) = watch::channel(ChunkCommand::Resume); + let _jt = tokio::spawn(async move { + loop { + let res = command_tx.send(ChunkCommand::Resume); + if res.is_err() { + break; + } + tokio::time::sleep(tokio::time::Duration::from_millis(1)).await; + } + }); + let cycles = verifier + .verify_complete_async(script_version, &rtx, &mut command_rx, false) + .await + .unwrap(); + assert_eq!(cycles, cycles_once); - let (spawn_caller_cell, spawn_caller_data_hash) = - load_cell_from_path("testdata/spawn_peak_memory_4m_to_32m"); + // we send Resume/Suspend to command_rx in a loop, make sure cycles is still the same + let (command_tx, mut command_rx) = watch::channel(ChunkCommand::Resume); + let _jt = tokio::spawn(async move { + loop { + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + let _res = command_tx.send(ChunkCommand::Suspend); + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; - 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 _res = command_tx.send(ChunkCommand::Resume); + tokio::time::sleep(tokio::time::Duration::from_millis(1)).await; - let transaction = TransactionBuilder::default().input(input).build(); - let dummy_cell = create_dummy_cell(output); + let _res = command_tx.send(ChunkCommand::Suspend); + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; - 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); + let _res = command_tx.send(ChunkCommand::Resume); + tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + } + }); + + let cycles = verifier + .verify_complete_async(script_version, &rtx, &mut command_rx, true) + .await + .unwrap(); + assert_eq!(cycles, cycles_once); } -#[test] -fn check_peak_memory_2m_to_32m() { +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn check_spawn_suspend_shutdown() { let script_version = SCRIPT_VERSION; + if script_version <= ScriptVersion::V1 { + return; + } let (spawn_caller_cell, spawn_caller_data_hash) = - load_cell_from_path("testdata/spawn_peak_memory_2m_to_32m"); + load_cell_from_path("testdata/spawn_caller_exec"); + let (snapshot_cell, _) = load_cell_from_path("testdata/infinite_loop"); let spawn_caller_script = Script::new_builder() .hash_type(script_version.data_hash_type().into()) @@ -566,48 +513,39 @@ fn check_peak_memory_2m_to_32m() { let rtx = ResolvedTransaction { transaction, - resolved_cell_deps: vec![spawn_caller_cell], + resolved_cell_deps: vec![spawn_caller_cell, snapshot_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_512k_to_32m() { - let script_version = SCRIPT_VERSION; + let (command_tx, mut command_rx) = watch::channel(ChunkCommand::Resume); + let _jt = tokio::spawn(async move { + loop { + let _res = command_tx.send(ChunkCommand::Suspend); + tokio::time::sleep(tokio::time::Duration::from_millis(20)).await; - let (spawn_caller_cell, spawn_caller_data_hash) = - load_cell_from_path("testdata/spawn_peak_memory_512k_to_32m"); + let _res = command_tx.send(ChunkCommand::Resume); + tokio::time::sleep(tokio::time::Duration::from_millis(20)).await; - 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 _res = command_tx.send(ChunkCommand::Suspend); + tokio::time::sleep(tokio::time::Duration::from_millis(20)).await; - let transaction = TransactionBuilder::default().input(input).build(); - let dummy_cell = create_dummy_cell(output); + let _res = command_tx.send(ChunkCommand::Stop); + tokio::time::sleep(tokio::time::Duration::from_millis(20)).await; + } + }); - 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); + let res = verifier + .verify_complete_async(script_version, &rtx, &mut command_rx, true) + .await; + assert!(res.is_err()); + let err = res.unwrap_err().to_string(); + assert!(err.contains("VM Internal Error: External(\"stopped\")")); } #[test] -fn check_spawn_snapshot() { +fn check_spawn_state() { let script_version = SCRIPT_VERSION; if script_version <= ScriptVersion::V1 { return; @@ -641,22 +579,49 @@ fn check_spawn_snapshot() { let cycles_once = result.unwrap(); let (cycles, chunks_count) = verifier - .verify_until_completed(script_version, &rtx) + .verify_map(script_version, &rtx, |verifier| { + let max_cycles = Cycle::MAX; + let cycles; + let mut times = 0usize; + times += 1; + let mut init_state = match verifier.resumable_verify(max_cycles).unwrap() { + VerifyResult::Suspended(state) => Some(state), + VerifyResult::Completed(cycle) => { + cycles = cycle; + return Ok((cycles, times)); + } + }; + + loop { + times += 1; + let state: TransactionSnapshot = + init_state.take().unwrap().try_into().expect("no snapshot"); + match verifier.resume_from_snap(&state, max_cycles).unwrap() { + VerifyResult::Suspended(state) => { + init_state = Some(state); + } + VerifyResult::Completed(cycle) => { + cycles = cycle; + break; + } + } + } + + Ok::<(u64, usize), Error>((cycles, times)) + }) .unwrap(); assert_eq!(cycles, cycles_once); assert!(chunks_count > 1); } -#[tokio::test(flavor = "multi_thread", worker_threads = 4)] -async fn check_spawn_async() { +#[test] +fn check_spawn_current_cycles() { let script_version = SCRIPT_VERSION; - if script_version <= ScriptVersion::V1 { - return; - } let (spawn_caller_cell, spawn_caller_data_hash) = - load_cell_from_path("testdata/spawn_caller_exec"); - let (snapshot_cell, _) = load_cell_from_path("testdata/current_cycles_with_snapshot"); + load_cell_from_path("testdata/spawn_caller_current_cycles"); + let (spawn_callee_cell, _spawn_callee_data_hash) = + load_cell_from_path("testdata/spawn_callee_current_cycles"); let spawn_caller_script = Script::new_builder() .hash_type(script_version.data_hash_type().into()) @@ -673,69 +638,556 @@ async fn check_spawn_async() { let rtx = ResolvedTransaction { transaction, - resolved_cell_deps: vec![spawn_caller_cell, snapshot_cell], + 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_pause(script_version, &rtx, Cycle::MAX); - let cycles_once = result.unwrap(); + let result = verifier.verify_without_limit(script_version, &rtx); + assert_eq!(result.is_ok(), script_version >= ScriptVersion::V2); +} - // we use debug pause to test context resume - // `current_cycles_with_snapshot` will try to pause verifier - // here we use `channel` to send Resume to verifier until it completes - let (command_tx, mut command_rx) = watch::channel(ChunkCommand::Resume); - let _jt = tokio::spawn(async move { - loop { - let res = command_tx.send(ChunkCommand::Resume); - if res.is_err() { +#[derive(Clone, Copy)] +enum SpawnFrom { + TxInputWitness, + GroupInputWitness, + TxOutputWitness, + GroupOutputWitness, + TxCellDep, + TxInputCell, + TxOutputCell, + GroupInputCell, + GroupOutputCell, + Slice(u64, u64), +} + +fn check_spawn_configurable_once(spawn_from: SpawnFrom) { + let script_version = SCRIPT_VERSION; + + let args = { + let mut args: Vec = vec![]; + let position = match spawn_from { + SpawnFrom::TxInputWitness => vec![0, 1, 1, 0], + SpawnFrom::GroupInputWitness => vec![0, SOURCE_GROUP_FLAG | 1, 1, 0], + SpawnFrom::TxOutputWitness => vec![0, 2, 1, 0], + SpawnFrom::GroupOutputWitness => vec![0, SOURCE_GROUP_FLAG | 2, 1, 0], + SpawnFrom::TxCellDep => vec![1, 3, 0, 0], + SpawnFrom::TxInputCell => vec![1, 1, 0, 0], + SpawnFrom::TxOutputCell => vec![0, 2, 0, 0], + SpawnFrom::GroupInputCell => vec![0, SOURCE_GROUP_FLAG | 1, 0, 0], + SpawnFrom::GroupOutputCell => vec![0, SOURCE_GROUP_FLAG | 2, 0, 0], + SpawnFrom::Slice(offset, size) => { + let (spawn_callee_cell, _) = + load_cell_from_path("testdata/spawn_configurable_callee"); + let h = offset << 32; + let l = if size == 0 { + 0 + } else { + spawn_callee_cell.mem_cell_data.unwrap().len() as u64 + }; + vec![0, 1, 1, h | l] + } + }; + for e in position { + args.extend(e.to_le_bytes()); + } + args + }; + + let (spawn_caller_cell, spawn_caller_data_hash) = + load_cell_from_path("testdata/spawn_configurable_caller"); + let (spawn_callee_cell, _) = load_cell_from_path("testdata/spawn_configurable_callee"); + let (always_success_cell, always_success_data_hash) = + load_cell_from_path("testdata/always_success"); + let spawn_callee_cell_data = spawn_callee_cell.mem_cell_data.as_ref().unwrap(); + let spawn_caller_script = Script::new_builder() + .hash_type(script_version.data_hash_type().into()) + .code_hash(spawn_caller_data_hash) + .args(args.pack()) + .build(); + let always_success_script = Script::new_builder() + .hash_type(script_version.data_hash_type().into()) + .code_hash(always_success_data_hash) + .build(); + + let input_spawn_caller = create_dummy_cell( + CellOutputBuilder::default() + .capacity(capacity_bytes!(100).pack()) + .lock(spawn_caller_script.clone()) + .build(), + ); + + let rtx = match spawn_from { + SpawnFrom::TxInputWitness | SpawnFrom::TxOutputWitness | SpawnFrom::GroupInputWitness => { + ResolvedTransaction { + transaction: TransactionBuilder::default() + .set_witnesses(vec![spawn_callee_cell_data.pack()]) + .build(), + resolved_cell_deps: vec![spawn_caller_cell, spawn_callee_cell], + resolved_inputs: vec![input_spawn_caller], + resolved_dep_groups: vec![], + } + } + SpawnFrom::GroupOutputWitness => ResolvedTransaction { + transaction: TransactionBuilder::default() + .output( + CellOutputBuilder::default() + .capacity(capacity_bytes!(100).pack()) + .type_(Some(spawn_caller_script).pack()) + .build(), + ) + .set_witnesses(vec![spawn_callee_cell_data.pack()]) + .build(), + resolved_cell_deps: vec![spawn_caller_cell, spawn_callee_cell], + resolved_inputs: vec![], + resolved_dep_groups: vec![], + }, + SpawnFrom::TxCellDep => ResolvedTransaction { + transaction: TransactionBuilder::default().build(), + resolved_cell_deps: vec![spawn_caller_cell, spawn_callee_cell], + resolved_inputs: vec![input_spawn_caller], + resolved_dep_groups: vec![], + }, + SpawnFrom::TxInputCell => { + let input_spawn_callee_output = CellOutputBuilder::default() + .capacity(capacity_bytes!(1000).pack()) + .lock(always_success_script) + .build(); + let input_spawn_callee = CellMetaBuilder::from_cell_output( + input_spawn_callee_output, + spawn_callee_cell_data.clone(), + ) + .build(); + ResolvedTransaction { + transaction: TransactionBuilder::default().build(), + resolved_cell_deps: vec![spawn_caller_cell, spawn_callee_cell, always_success_cell], + resolved_inputs: vec![input_spawn_caller, input_spawn_callee], + resolved_dep_groups: vec![], + } + } + SpawnFrom::TxOutputCell => ResolvedTransaction { + transaction: TransactionBuilder::default() + .output( + CellOutputBuilder::default() + .capacity(capacity_bytes!(100).pack()) + .lock(always_success_script) + .build(), + ) + .output_data(spawn_callee_cell_data.pack()) + .build(), + resolved_cell_deps: vec![spawn_caller_cell, spawn_callee_cell, always_success_cell], + resolved_inputs: vec![input_spawn_caller], + resolved_dep_groups: vec![], + }, + SpawnFrom::GroupInputCell => { + let input_spawn_caller = CellMetaBuilder::from_cell_output( + CellOutputBuilder::default() + .capacity(capacity_bytes!(100).pack()) + .lock(spawn_caller_script) + .build(), + spawn_callee_cell_data.clone(), + ) + .build(); + ResolvedTransaction { + transaction: TransactionBuilder::default().build(), + resolved_cell_deps: vec![spawn_caller_cell, spawn_callee_cell, always_success_cell], + resolved_inputs: vec![input_spawn_caller], + resolved_dep_groups: vec![], + } + } + SpawnFrom::GroupOutputCell => ResolvedTransaction { + transaction: TransactionBuilder::default() + .output( + CellOutputBuilder::default() + .capacity(capacity_bytes!(100).pack()) + .type_(Some(spawn_caller_script).pack()) + .build(), + ) + .output_data(spawn_callee_cell_data.pack()) + .build(), + resolved_cell_deps: vec![spawn_caller_cell, spawn_callee_cell, always_success_cell], + resolved_inputs: vec![], + resolved_dep_groups: vec![], + }, + SpawnFrom::Slice(offset, size) => { + let mut data = vec![0; offset as usize]; + data.extend(spawn_callee_cell_data); + if size != 0 { + data.extend(vec![0; 0x12]); + } + ResolvedTransaction { + transaction: TransactionBuilder::default() + .set_witnesses(vec![data.pack()]) + .build(), + resolved_cell_deps: vec![spawn_caller_cell, spawn_callee_cell], + resolved_inputs: vec![input_spawn_caller], + 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_configurable() { + check_spawn_configurable_once(SpawnFrom::TxInputWitness); + check_spawn_configurable_once(SpawnFrom::GroupInputWitness); + check_spawn_configurable_once(SpawnFrom::TxOutputWitness); + check_spawn_configurable_once(SpawnFrom::GroupOutputWitness); + check_spawn_configurable_once(SpawnFrom::TxCellDep); + check_spawn_configurable_once(SpawnFrom::TxInputCell); + check_spawn_configurable_once(SpawnFrom::TxOutputCell); + check_spawn_configurable_once(SpawnFrom::GroupInputCell); + check_spawn_configurable_once(SpawnFrom::GroupOutputCell); + check_spawn_configurable_once(SpawnFrom::Slice(0, 0)); + check_spawn_configurable_once(SpawnFrom::Slice(1, 0)); + check_spawn_configurable_once(SpawnFrom::Slice(0, 1)); + check_spawn_configurable_once(SpawnFrom::Slice(1, 1)); +} + +#[allow(dead_code)] +#[path = "../../../../testdata/spawn_dag.rs"] +mod spawn_dag; +use ckb_types::bytes::Bytes; +use daggy::{Dag, Walker}; +use molecule::prelude::Byte; +use rand::{rngs::StdRng, Rng, SeedableRng}; +use spawn_dag as dag; +use std::collections::{HashSet, VecDeque}; + +pub fn generate_data_graph( + seed: u64, + spawns: u32, + writes: u32, + converging_threshold: u32, +) -> Result { + let mut rng = StdRng::seed_from_u64(seed); + + let mut spawn_dag: Dag<(), ()> = Dag::new(); + let mut write_dag: Dag<(), ()> = Dag::new(); + + // Root node denoting entrypoint VM + let spawn_root = spawn_dag.add_node(()); + let write_root = write_dag.add_node(()); + assert_eq!(spawn_root.index(), 0); + assert_eq!(write_root.index(), 0); + + let mut spawn_nodes = vec![spawn_root]; + let mut write_nodes = vec![write_root]; + + for _ in 1..=spawns { + let write_node = write_dag.add_node(()); + write_nodes.push(write_node); + + let previous_node = spawn_nodes[rng.gen_range(0..spawn_nodes.len())]; + let (_, spawn_node) = spawn_dag.add_child(previous_node, (), ()); + spawn_nodes.push(spawn_node); + } + + let mut write_edges = Vec::new(); + if spawns > 0 { + for _ in 1..=writes { + let mut updated = false; + + for _ in 0..converging_threshold { + let first_index = rng.gen_range(0..write_nodes.len()); + let second_index = { + let mut i = first_index; + while i == first_index { + i = rng.gen_range(0..write_nodes.len()); + } + i + }; + + let first_node = write_nodes[first_index]; + let second_node = write_nodes[second_index]; + + if let Ok(e) = write_dag.add_edge(first_node, second_node, ()) { + write_edges.push(e); + updated = true; + break; + } + } + + if !updated { break; } - tokio::time::sleep(tokio::time::Duration::from_millis(1)).await; } - }); - let cycles = verifier - .verify_complete_async(script_version, &rtx, &mut command_rx, false) - .await - .unwrap(); - assert_eq!(cycles, cycles_once); + } - // we send Resume/Suspend to command_rx in a loop, make sure cycles is still the same - let (command_tx, mut command_rx) = watch::channel(ChunkCommand::Resume); - let _jt = tokio::spawn(async move { - loop { - tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; - let _res = command_tx.send(ChunkCommand::Suspend); - tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + // Edge index -> pipe indices. Daggy::edge_endpoints helps us finding + // nodes (vms) from edges (spawns) + let mut spawn_ops: HashMap> = HashMap::default(); + // Node index -> created pipes + let mut pipes_ops: BTreeMap> = BTreeMap::default(); + + let mut spawn_edges = Vec::new(); + // Traversing spawn_dag for spawn operations + let mut processing = VecDeque::from([spawn_root]); + while !processing.is_empty() { + let node = processing.pop_front().unwrap(); + pipes_ops.insert(node.index(), Vec::new()); + let children: Vec<_> = spawn_dag.children(node).iter(&spawn_dag).collect(); + for (e, n) in children.into_iter().rev() { + spawn_ops.insert(e.index(), Vec::new()); + spawn_edges.push(e); + + processing.push_back(n); + } + } - let _res = command_tx.send(ChunkCommand::Resume); - tokio::time::sleep(tokio::time::Duration::from_millis(1)).await; + let mut writes_builder = dag::WritesBuilder::default(); + // Traversing all edges in write_dag + for e in write_edges { + let (writer, reader) = write_dag.edge_endpoints(e).unwrap(); + assert_ne!(writer, reader); + let writer_pipe_index = e.index() * 2 + 1; + let reader_pipe_index = e.index() * 2; + + // Generate finalized write op + { + let data_len = rng.gen_range(1..=1024); + let mut data = vec![0u8; data_len]; + rng.fill(&mut data[..]); + + writes_builder = writes_builder.push( + dag::WriteBuilder::default() + .from(build_vm_index(writer.index() as u64)) + .from_fd(build_fd_index(writer_pipe_index as u64)) + .to(build_vm_index(reader.index() as u64)) + .to_fd(build_fd_index(reader_pipe_index as u64)) + .data( + dag::BytesBuilder::default() + .extend(data.iter().map(|b| Byte::new(*b))) + .build(), + ) + .build(), + ); + } - let _res = command_tx.send(ChunkCommand::Suspend); - tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + // Finding the lowest common ancestor of writer & reader nodes + // in spawn_dag, which will creates the pair of pipes. Note that + // all traversed spawn edges will have to pass the pipes down. + // + // TODO: we use a simple yet slow LCA solution, a faster algorithm + // can be used to replace the code here if needed. + let ancestor = { + let mut a = writer; + let mut b = reader; + + let mut set_a = HashSet::new(); + set_a.insert(a); + let mut set_b = HashSet::new(); + set_b.insert(b); - let _res = command_tx.send(ChunkCommand::Resume); - tokio::time::sleep(tokio::time::Duration::from_millis(10)).await; + loop { + let parents_a: Vec<_> = spawn_dag.parents(a).iter(&spawn_dag).collect(); + let parents_b: Vec<_> = spawn_dag.parents(b).iter(&spawn_dag).collect(); + + assert!( + ((parents_a.len() == 1) && (parents_b.len() == 1)) + || (parents_a.is_empty() && (parents_b.len() == 1)) + || ((parents_a.len() == 1) && parents_b.is_empty()) + ); + + // Update spawn ops to pass down pipes via edges, also update + // each node's path node list + if parents_a.len() == 1 { + let (_, parent_a) = parents_a[0]; + set_a.insert(parent_a); + + a = parent_a; + } + if parents_b.len() == 1 { + let (_, parent_b) = parents_b[0]; + set_b.insert(parent_b); + + b = parent_b; + } + + // Test for ancestor + if parents_a.len() == 1 { + let (_, parent_a) = parents_a[0]; + if set_b.contains(&parent_a) { + break parent_a; + } + } + if parents_b.len() == 1 { + let (_, parent_b) = parents_b[0]; + if set_a.contains(&parent_b) { + break parent_b; + } + } + } + }; + + // Update the path from each node to the LCA so we can pass created + // pipes from LCA to each node + { + let mut a = writer; + while a != ancestor { + let parents_a: Vec<_> = spawn_dag.parents(a).iter(&spawn_dag).collect(); + assert!(parents_a.len() == 1); + let (edge_a, parent_a) = parents_a[0]; + spawn_ops + .get_mut(&edge_a.index()) + .unwrap() + .push(writer_pipe_index); + a = parent_a; + } + + let mut b = reader; + while b != ancestor { + let parents_b: Vec<_> = spawn_dag.parents(b).iter(&spawn_dag).collect(); + assert!(parents_b.len() == 1); + let (edge_b, parent_b) = parents_b[0]; + spawn_ops + .get_mut(&edge_b.index()) + .unwrap() + .push(reader_pipe_index); + b = parent_b; + } } - }); - let cycles = verifier - .verify_complete_async(script_version, &rtx, &mut command_rx, true) - .await - .unwrap(); - assert_eq!(cycles, cycles_once); + // Create the pipes at the ancestor node + pipes_ops + .get_mut(&ancestor.index()) + .unwrap() + .push((reader_pipe_index, writer_pipe_index)); + } + + let mut spawns_builder = dag::SpawnsBuilder::default(); + for e in spawn_edges { + let (parent, child) = spawn_dag.edge_endpoints(e).unwrap(); + + let pipes = { + let mut builder = dag::FdIndicesBuilder::default(); + for p in &spawn_ops[&e.index()] { + builder = builder.push(build_fd_index(*p as u64)); + } + builder.build() + }; + + spawns_builder = spawns_builder.push( + dag::SpawnBuilder::default() + .from(build_vm_index(parent.index() as u64)) + .child(build_vm_index(child.index() as u64)) + .fds(pipes) + .build(), + ); + } + + let mut pipes_builder = dag::PipesBuilder::default(); + for (vm_index, pairs) in pipes_ops { + for (reader_pipe_index, writer_pipe_index) in pairs { + pipes_builder = pipes_builder.push( + dag::PipeBuilder::default() + .vm(build_vm_index(vm_index as u64)) + .read_fd(build_fd_index(reader_pipe_index as u64)) + .write_fd(build_fd_index(writer_pipe_index as u64)) + .build(), + ); + } + } + + Ok(dag::DataBuilder::default() + .spawns(spawns_builder.build()) + .pipes(pipes_builder.build()) + .writes(writes_builder.build()) + .build()) } -#[tokio::test(flavor = "multi_thread", worker_threads = 4)] -async fn check_spawn_suspend_shutdown() { - let script_version = SCRIPT_VERSION; - if script_version <= ScriptVersion::V1 { - return; +fn build_vm_index(val: u64) -> dag::VmIndex { + let mut data = [Byte::new(0); 8]; + for (i, v) in val.to_le_bytes().into_iter().enumerate() { + data[i] = Byte::new(v); } + dag::VmIndexBuilder::default().set(data).build() +} + +fn build_fd_index(val: u64) -> dag::FdIndex { + let mut data = [Byte::new(0); 8]; + for (i, v) in val.to_le_bytes().into_iter().enumerate() { + data[i] = Byte::new(v); + } + dag::FdIndexBuilder::default().set(data).build() +} + +proptest! { + #![proptest_config(ProptestConfig::with_cases(32))] + #[test] + fn test_random_dag( + seed: u64, + spawns in 5u32..MAX_VMS_COUNT as u32, + writes in 3u32..MAX_FDS as u32 / 2, + ) { + let script_version = SCRIPT_VERSION; + let program: Bytes = std::fs::read("./testdata/spawn_dag").unwrap().into(); + let data = generate_data_graph(seed, spawns, writes, 3).unwrap(); + + let (code_dep, code_dep_hash) = load_cell_from_slice(&program[..]); + let spawn_caller_script = Script::new_builder() + .hash_type(script_version.data_hash_type().into()) + .code_hash(code_dep_hash) + .build(); + let output = CellOutputBuilder::default() + .capacity(capacity_bytes!(100).pack()) + .lock(spawn_caller_script) + .build(); + let dummy_cell = create_dummy_cell(output); + + let rtx = ResolvedTransaction { + transaction: TransactionBuilder::default().witness(data.as_bytes().pack()).build(), + resolved_cell_deps: vec![code_dep], + 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_close_invalid_fd() { + let result = simple_spawn_test("testdata/spawn_cases", &[12]); + assert_eq!(result.is_ok(), SCRIPT_VERSION == ScriptVersion::V2); +} + +#[test] +fn check_spawn_write_closed_fd() { + let result = simple_spawn_test("testdata/spawn_cases", &[13]); + assert_eq!(result.is_ok(), SCRIPT_VERSION == ScriptVersion::V2); +} + +#[test] +fn check_spawn_pid() { + let result = simple_spawn_test("testdata/spawn_cases", &[14]); + assert_eq!(result.is_ok(), SCRIPT_VERSION == ScriptVersion::V2); +} + +#[test] +fn check_spawn_offset_out_of_bound() { + let result = simple_spawn_test("testdata/spawn_cases", &[15]); + assert_eq!(result.is_ok(), SCRIPT_VERSION == ScriptVersion::V2); +} + +#[test] +fn check_spawn_length_out_of_bound() { + let result = simple_spawn_test("testdata/spawn_cases", &[16]); + assert_eq!(result.is_ok(), SCRIPT_VERSION == ScriptVersion::V2); +} + +#[test] +fn check_spawn_huge_swap() { + let script_version = SCRIPT_VERSION; let (spawn_caller_cell, spawn_caller_data_hash) = - load_cell_from_path("testdata/spawn_caller_exec"); - let (snapshot_cell, _) = load_cell_from_path("testdata/infinite_loop"); + load_cell_from_path("testdata/spawn_huge_swap"); let spawn_caller_script = Script::new_builder() .hash_type(script_version.data_hash_type().into()) @@ -752,47 +1204,43 @@ async fn check_spawn_suspend_shutdown() { let rtx = ResolvedTransaction { transaction, - resolved_cell_deps: vec![spawn_caller_cell, snapshot_cell], + resolved_cell_deps: vec![spawn_caller_cell], resolved_inputs: vec![dummy_cell], resolved_dep_groups: vec![], }; - let verifier = TransactionScriptsVerifierWithEnv::new(); - let (command_tx, mut command_rx) = watch::channel(ChunkCommand::Resume); - let _jt = tokio::spawn(async move { - loop { - let _res = command_tx.send(ChunkCommand::Suspend); - tokio::time::sleep(tokio::time::Duration::from_millis(20)).await; - - let _res = command_tx.send(ChunkCommand::Resume); - tokio::time::sleep(tokio::time::Duration::from_millis(20)).await; - let _res = command_tx.send(ChunkCommand::Suspend); - tokio::time::sleep(tokio::time::Duration::from_millis(20)).await; + let tic = std::time::Instant::now(); + let result = verifier.verify(script_version, &rtx, 70_000_000); + let toc = tic.elapsed().as_millis(); + if script_version >= ScriptVersion::V2 { + let msg = result.unwrap_err().to_string(); + assert!(msg.contains("ExceededMaximumCycles")); + // Normally, this test should take less than 1 second. + assert!(toc < 5000); + } else { + assert!(result.is_err()) + } +} - let _res = command_tx.send(ChunkCommand::Stop); - tokio::time::sleep(tokio::time::Duration::from_millis(20)).await; - } - }); +#[test] +fn check_spawn_invaild_index() { + let result = simple_spawn_test("testdata/spawn_cases", &[17]); + assert_eq!(result.is_ok(), SCRIPT_VERSION == ScriptVersion::V2); +} - let res = verifier - .verify_complete_async(script_version, &rtx, &mut command_rx, true) - .await; - assert!(res.is_err()); - let err = res.unwrap_err().to_string(); - assert!(err.contains("VM Internal Error: External(\"stopped\")")); +#[test] +fn check_spawn_index_out_of_bound() { + let result = simple_spawn_test("testdata/spawn_cases", &[18]); + assert_eq!(result.is_ok(), SCRIPT_VERSION == ScriptVersion::V2); } #[test] -fn check_spawn_state() { +fn check_spawn_cycles() { let script_version = SCRIPT_VERSION; - if script_version <= ScriptVersion::V1 { - return; - } - let (spawn_caller_cell, spawn_caller_data_hash) = - load_cell_from_path("testdata/spawn_caller_exec"); - let (snapshot_cell, _) = load_cell_from_path("testdata/current_cycles_with_snapshot"); + let (spawn_caller_cell, spawn_caller_data_hash) = load_cell_from_path("testdata/spawn_cycles"); + let (spawn_callee_cell, _spawn_callee_data_hash) = load_cell_from_path("testdata/spawn_cycles"); let spawn_caller_script = Script::new_builder() .hash_type(script_version.data_hash_type().into()) @@ -809,63 +1257,34 @@ fn check_spawn_state() { let rtx = ResolvedTransaction { transaction, - resolved_cell_deps: vec![spawn_caller_cell, snapshot_cell], + 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_pause(script_version, &rtx, Cycle::MAX); - let cycles_once = result.unwrap(); - - let (cycles, chunks_count) = verifier - .verify_map(script_version, &rtx, |verifier| { - let max_cycles = Cycle::MAX; - let cycles; - let mut times = 0usize; - times += 1; - let mut init_state = match verifier.resumable_verify(max_cycles).unwrap() { - VerifyResult::Suspended(state) => Some(state), - VerifyResult::Completed(cycle) => { - cycles = cycle; - return Ok((cycles, times)); - } - }; - - loop { - times += 1; - let state = init_state.take().unwrap(); - match verifier.resume_from_state(state, max_cycles).unwrap() { - VerifyResult::Suspended(state) => { - init_state = Some(state); - } - VerifyResult::Completed(cycle) => { - cycles = cycle; - break; - } - } - } - - Ok::<(u64, usize), Error>((cycles, times)) - }) - .unwrap(); - assert_eq!(cycles, cycles_once); - assert!(chunks_count > 1); + let result = verifier.verify_without_limit(script_version, &rtx); + assert_eq!(result.is_ok(), script_version >= ScriptVersion::V2); + if script_version >= ScriptVersion::V2 { + assert_eq!(result.unwrap(), 1524861); + } } -#[test] -fn check_spawn_current_memory() { +fn spawn_io_test(io_size: u64, enable_check: bool) -> Result { let script_version = SCRIPT_VERSION; - let (spawn_caller_cell, spawn_caller_data_hash) = - load_cell_from_path("testdata/spawn_current_memory"); + let mut args = vec![0u8; 16]; + args[..8].copy_from_slice(&io_size.to_le_bytes()); + args[8] = enable_check as u8; - let spawn_caller_script = Script::new_builder() + let (cell, data_hash) = load_cell_from_path("testdata/spawn_io_cycles"); + let script = Script::new_builder() .hash_type(script_version.data_hash_type().into()) - .code_hash(spawn_caller_data_hash) + .code_hash(data_hash) + .args(Bytes::copy_from_slice(&args).pack()) .build(); let output = CellOutputBuilder::default() .capacity(capacity_bytes!(100).pack()) - .lock(spawn_caller_script) + .lock(script) .build(); let input = CellInput::new(OutPoint::null(), 0); @@ -874,31 +1293,52 @@ fn check_spawn_current_memory() { let rtx = ResolvedTransaction { transaction, - resolved_cell_deps: vec![spawn_caller_cell], + resolved_cell_deps: vec![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); + verifier.verify_without_limit(script_version, &rtx) } #[test] -fn check_spawn_current_cycles() { - let script_version = SCRIPT_VERSION; +fn check_spawn_io_cycles() { + if SCRIPT_VERSION != ScriptVersion::V2 { + return; + } - let (spawn_caller_cell, spawn_caller_data_hash) = - load_cell_from_path("testdata/spawn_caller_current_cycles"); - let (spawn_callee_cell, _spawn_callee_data_hash) = - load_cell_from_path("testdata/spawn_callee_current_cycles"); + let offset_size = 1024; + let r = spawn_io_test(128, true); + r.unwrap(); + let r = spawn_io_test(128 + offset_size, true); + r.unwrap(); - let spawn_caller_script = Script::new_builder() + let r = spawn_io_test(128, false); + let cycles1 = r.unwrap(); + let r = spawn_io_test(128 + offset_size, false); + let cycles2 = r.unwrap(); + + assert_eq!(cycles2 - cycles1, offset_size / 2); +} + +#[test] +fn check_spawn_saturate_memory() { + let result = simple_spawn_test("testdata/spawn_saturate_memory", &[0]); + assert_eq!(result.is_ok(), SCRIPT_VERSION == ScriptVersion::V2); +} + +#[test] +fn check_infinite_exec() { + let script_version = SCRIPT_VERSION; + + let (exec_caller_cell, exec_caller_data_hash) = load_cell_from_path("testdata/infinite_exec"); + let exec_caller_script = Script::new_builder() .hash_type(script_version.data_hash_type().into()) - .code_hash(spawn_caller_data_hash) + .code_hash(exec_caller_data_hash) .build(); let output = CellOutputBuilder::default() .capacity(capacity_bytes!(100).pack()) - .lock(spawn_caller_script) + .lock(exec_caller_script) .build(); let input = CellInput::new(OutPoint::null(), 0); @@ -907,28 +1347,35 @@ fn check_spawn_current_cycles() { let rtx = ResolvedTransaction { transaction, - resolved_cell_deps: vec![spawn_caller_cell, spawn_callee_cell], + resolved_cell_deps: vec![exec_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); + let result = verifier.verify(script_version, &rtx, 70000000); + if script_version >= ScriptVersion::V1 { + assert!(result + .unwrap_err() + .to_string() + .contains("ExceededMaximumCycles")) + } else { + assert!(result.is_err()) + } } #[test] -fn check_spawn_times_bug_1() { +fn check_fuzz_crash_1() { let script_version = SCRIPT_VERSION; - let (spawn_caller_cell, spawn_caller_data_hash) = load_cell_from_path("testdata/spawn_times"); - - let spawn_caller_script = Script::new_builder() + let (exec_caller_cell, exec_caller_data_hash) = load_cell_from_path("testdata/crash-5a27052f"); + let exec_caller_script = Script::new_builder() .hash_type(script_version.data_hash_type().into()) - .code_hash(spawn_caller_data_hash) + .code_hash(exec_caller_data_hash) .build(); let output = CellOutputBuilder::default() .capacity(capacity_bytes!(100).pack()) - .lock(spawn_caller_script) + .lock(exec_caller_script) .build(); let input = CellInput::new(OutPoint::null(), 0); @@ -937,76 +1384,83 @@ fn check_spawn_times_bug_1() { let rtx = ResolvedTransaction { transaction, - resolved_cell_deps: vec![spawn_caller_cell], + resolved_cell_deps: vec![exec_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); + let result = verifier.verify(script_version, &rtx, 70000000); + match script_version { + ScriptVersion::V0 => assert!(result + .unwrap_err() + .to_string() + .contains("MemWriteOnExecutablePage")), + ScriptVersion::V1 | ScriptVersion::V2 => assert!(result + .unwrap_err() + .to_string() + .contains("SourceEntry parse_from_u64 0")), + } } #[test] -fn check_spawn_times_bug_2() { +fn check_fuzz_crash_2() { let script_version = SCRIPT_VERSION; - if script_version <= ScriptVersion::V1 { - return; + let (exec_caller_cell, exec_caller_data_hash) = load_cell_from_path("testdata/crash-45a6098d"); + let exec_caller_script = Script::new_builder() + .hash_type(script_version.data_hash_type().into()) + .code_hash(exec_caller_data_hash) + .build(); + let output = CellOutputBuilder::default() + .capacity(capacity_bytes!(100).pack()) + .lock(exec_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![exec_caller_cell], + resolved_inputs: vec![dummy_cell], + resolved_dep_groups: vec![], + }; + let verifier = TransactionScriptsVerifierWithEnv::new(); + let result = verifier.verify(script_version, &rtx, 70000000); + match script_version { + ScriptVersion::V0 => assert!(result + .unwrap_err() + .to_string() + .contains("MemWriteOnExecutablePage")), + ScriptVersion::V1 => assert_eq!(result.unwrap(), 58741), + ScriptVersion::V2 => assert_eq!(result.unwrap(), 58686), } - let (spawn_caller_cell, spawn_caller_data_hash) = load_cell_from_path("testdata/spawn_times"); +} - let spawn_caller_script = Script::new_builder() +#[test] +fn check_fuzz_crash_3() { + let script_version = SCRIPT_VERSION; + let (exec_caller_cell, exec_caller_data_hash) = load_cell_from_path("testdata/crash-4717eb0e"); + let exec_caller_script = Script::new_builder() .hash_type(script_version.data_hash_type().into()) - .code_hash(spawn_caller_data_hash) + .code_hash(exec_caller_data_hash) .build(); let output = CellOutputBuilder::default() .capacity(capacity_bytes!(100).pack()) - .lock(spawn_caller_script) + .lock(exec_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_cell_deps: vec![exec_caller_cell], resolved_inputs: vec![dummy_cell], resolved_dep_groups: vec![], }; let verifier = TransactionScriptsVerifierWithEnv::new(); - let result = verifier.verify_without_pause(script_version, &rtx, Cycle::MAX); - let cycles_once = result.unwrap(); - - let (cycles, _) = verifier - .verify_map(script_version, &rtx, |verifier| { - let max_cycles = Cycle::MAX; - let cycles; - let mut times = 0usize; - times += 1; - let mut init_state = match verifier.resumable_verify(max_cycles).unwrap() { - VerifyResult::Suspended(state) => Some(state), - VerifyResult::Completed(cycle) => { - cycles = cycle; - return Ok((cycles, times)); - } - }; - - loop { - times += 1; - let state = init_state.take().unwrap(); - match verifier.resume_from_state(state, max_cycles).unwrap() { - VerifyResult::Suspended(state) => { - init_state = Some(state); - } - VerifyResult::Completed(cycle) => { - cycles = cycle; - break; - } - } - } - - Ok::<(u64, usize), Error>((cycles, times)) - }) - .unwrap(); - assert_eq!(cycles, cycles_once); + let result = verifier.verify(script_version, &rtx, 70000000); + assert!(result + .unwrap_err() + .to_string() + .contains("MemWriteOnExecutablePage")); } diff --git a/script/src/verify/tests/utils.rs b/script/src/verify/tests/utils.rs index d9ab364b5f..760921f627 100644 --- a/script/src/verify/tests/utils.rs +++ b/script/src/verify/tests/utils.rs @@ -201,38 +201,6 @@ impl TransactionScriptsVerifierWithEnv { .await } - pub(crate) async fn verify_complete_async( - &self, - version: ScriptVersion, - rtx: &ResolvedTransaction, - command_rx: &mut tokio::sync::watch::Receiver, - skip_debug_pause: bool, - ) -> Result { - let data_loader = self.store.as_data_loader(); - let epoch = match version { - ScriptVersion::V0 => EpochNumberWithFraction::new(0, 0, 1), - ScriptVersion::V1 => EpochNumberWithFraction::new(self.version_1_enabled_at, 0, 1), - ScriptVersion::V2 => EpochNumberWithFraction::new(self.version_2_enabled_at, 0, 1), - }; - let header = HeaderView::new_advanced_builder() - .epoch(epoch.pack()) - .build(); - let tx_env = Arc::new(TxVerifyEnv::new_commit(&header)); - let verifier = TransactionScriptsVerifier::new( - Arc::new(rtx.clone()), - data_loader, - Arc::clone(&self.consensus), - tx_env, - ); - - if skip_debug_pause { - verifier.set_skip_pause(true); - } - verifier - .resumable_verify_with_signal(Cycle::MAX, command_rx) - .await - } - // If the max cycles is meaningless, please use `verify_without_limit`, // so reviewers or developers can understand the intentions easier. pub(crate) fn verify( @@ -266,6 +234,7 @@ impl TransactionScriptsVerifierWithEnv { let cycles; let mut times = 0usize; times += 1; + let mut init_snap = match verifier.resumable_verify(max_cycles).unwrap() { VerifyResult::Suspended(state) => Some(state.try_into().unwrap()), VerifyResult::Completed(cycle) => { @@ -277,14 +246,15 @@ impl TransactionScriptsVerifierWithEnv { loop { times += 1; let snap = init_snap.take().unwrap(); - match verifier.resume_from_snap(&snap, max_cycles).unwrap() { - VerifyResult::Suspended(state) => { + match verifier.resume_from_snap(&snap, max_cycles) { + Ok(VerifyResult::Suspended(state)) => { init_snap = Some(state.try_into().unwrap()); } - VerifyResult::Completed(cycle) => { + Ok(VerifyResult::Completed(cycle)) => { cycles = cycle; break; } + Err(e) => return Err(e), } } @@ -292,6 +262,38 @@ impl TransactionScriptsVerifierWithEnv { }) } + pub(crate) async fn verify_complete_async( + &self, + version: ScriptVersion, + rtx: &ResolvedTransaction, + command_rx: &mut tokio::sync::watch::Receiver, + skip_debug_pause: bool, + ) -> Result { + let data_loader = self.store.as_data_loader(); + let epoch = match version { + ScriptVersion::V0 => EpochNumberWithFraction::new(0, 0, 1), + ScriptVersion::V1 => EpochNumberWithFraction::new(self.version_1_enabled_at, 0, 1), + ScriptVersion::V2 => EpochNumberWithFraction::new(self.version_2_enabled_at, 0, 1), + }; + let header = HeaderView::new_advanced_builder() + .epoch(epoch.pack()) + .build(); + let tx_env = Arc::new(TxVerifyEnv::new_commit(&header)); + let verifier = TransactionScriptsVerifier::new( + Arc::new(rtx.clone()), + data_loader, + Arc::clone(&self.consensus), + tx_env, + ); + + if skip_debug_pause { + verifier.set_skip_pause(true); + } + verifier + .resumable_verify_with_signal(Cycle::MAX, command_rx) + .await + } + pub(crate) fn verify_map( &self, version: ScriptVersion, @@ -311,12 +313,18 @@ impl TransactionScriptsVerifierWithEnv { .epoch(epoch.pack()) .build(); let tx_env = Arc::new(TxVerifyEnv::new_commit(&header)); - let verifier = TransactionScriptsVerifier::new( + let mut verifier = TransactionScriptsVerifier::new( Arc::new(rtx.clone()), data_loader, Arc::clone(&self.consensus), tx_env, ); + verifier.set_debug_printer(Box::new(move |_hash: &Byte32, message: &str| { + print!("{}", message); + if !message.ends_with('\n') { + println!(); + } + })); verify_func(verifier) } } diff --git a/script/testdata/.clang-format b/script/testdata/.clang-format new file mode 100644 index 0000000000..815a156416 --- /dev/null +++ b/script/testdata/.clang-format @@ -0,0 +1,5 @@ +BasedOnStyle: Google +IndentWidth: 4 +TabWidth: 4 +ColumnLimit: 120 +SortIncludes: false diff --git a/script/testdata/Makefile b/script/testdata/Makefile index 70567978d3..9db92fd549 100644 --- a/script/testdata/Makefile +++ b/script/testdata/Makefile @@ -9,20 +9,23 @@ LIB_TARGET := riscv64-unknown-linux-gnu LIB_CC := $(LIB_TARGET)-gcc LIB_OBJCOPY := $(LIB_TARGET)-objcopy -# Tip: add `-DDEBUG` to enable the debug outputs COMMON_CFLAGS := -O3 \ -I deps/ckb-c-stdlib \ + -I deps/ckb-c-stdlib/libc \ -I deps/ckb-c-stdlib/molecule \ - -Wall -Werror -g + -Wall -Werror \ + -fno-builtin -nostdinc -nostartfiles \ + -Wno-stringop-overflow +# enable ckb-c-stdlib's printf +COMMON_CFLAGS += -DCKB_C_STDLIB_PRINTF -DCKB_C_STDLIB_PRINTF_BUFFER_SIZE=1024 + BIN_CFLAGS := $(COMMON_CFLAGS) -LIB_CFLAGS := -shared -fPIC -nostdlib -nostartfiles -fvisibility=hidden $(COMMON_CFLAGS) +LIB_CFLAGS := -shared -fPIC -nostdlib -fvisibility=hidden -D__SHARED_LIBRARY__ $(COMMON_CFLAGS) COMMON_LDFLAGS := -Wl,-static -fdata-sections -ffunction-sections -Wl,--gc-sections BIN_LDFLAGS := $(COMMON_LDFLAGS) LIB_LDFLAGS := $(COMMON_LDFLAGS) -OBJCOPY_FLAGS := --strip-debug --strip-all - # nervos/ckb-riscv-gnu-toolchain:bionic-20210804 BIN_BUILDER_DOCKER := nervos/ckb-riscv-gnu-toolchain@sha256:cfeb97864cf2039a0900bfa83c3f112a780b2281bded395604b8a8a693c95e08 # nervos/ckb-riscv-gnu-toolchain:gnu-bionic-20210727 @@ -45,42 +48,41 @@ ALL_BINS := jalr_zero \ exec_caller_big_offset_length \ exec_configurable_callee \ exec_configurable_caller \ + infinite_exec \ infinite_loop \ load_code_to_stack_then_reuse \ load_is_even_into_global \ load_is_even_with_snapshot \ load_arithmetic \ debugger \ - get_memory_limit \ - set_content \ - spawn_big_content_length \ - spawn_big_memory_size \ - spawn_callee_exec_callee \ + spawn_caller_strcat \ + spawn_callee_strcat \ + spawn_caller_exec \ spawn_callee_exec_caller \ - spawn_callee_get_memory_limit \ + spawn_callee_exec_callee \ + spawn_caller_strcat_wrap \ + spawn_recursive \ 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 \ - spawn_current_memory \ spawn_caller_current_cycles \ spawn_callee_current_cycles \ - spawn_peak_memory_512k_to_32m \ - spawn_peak_memory_2m_to_32m \ - spawn_peak_memory_4m_to_32m + spawn_times \ + spawn_cases \ + spawn_configurable_caller \ + spawn_configurable_callee \ + spawn_dag \ + spawn_fuzzing \ + spawn_huge_swap \ + spawn_cycles \ + spawn_io_cycles \ + spawn_saturate_memory + ALL_LIBS := is_even.lib \ add1.lib sub1.lib mul2.lib div2.lib -all-bins: clean-bins $(ALL_BINS) -all-libs: clean-libs $(ALL_LIBS) +all-bins: $(ALL_BINS) +all-libs: $(ALL_LIBS) bins-in-docker: docker run --rm -v `pwd`:/code $(BIN_BUILDER_DOCKER) bash -c "cd /code && make all-bins" @@ -95,37 +97,38 @@ clean-bins: clean-libs: -rm -f $(ALL_LIBS) +fmt: + clang-format -i *.c *.h + clean: clean-bins clean-libs %: %.c $(BIN_CC) $(BIN_CFLAGS) $(BIN_LDFLAGS) -o $@ $< - $(BIN_OBJCOPY) $(OBJCOPY_FLAGS) $@ %.lib: %.c $(LIB_CC) $(LIB_CFLAGS) $(LIB_LDFLAGS) -o $@ $< - $(LIB_OBJCOPY) $(OBJCOPY_FLAGS) $@ %: %.S $(BIN_AS) -o $@.o $< $(BIN_LD) -o $@ $@.o @rm $@.o - $(BIN_OBJCOPY) $(OBJCOPY_FLAGS) $@ jalr_zero: jalr_zero.S cadd_hint_lock: cadd_hint_lock.S $(BIN_AS) -march=rv64imc -o $@.o $< $(BIN_LD) -o $@ $@.o @rm $@.o - $(BIN_OBJCOPY) $(OBJCOPY_FLAGS) $@ cpop_lock: cpop_lock.c mop_adc_lock: mop_adc_lock.S current_cycles: current_cycles.c current_cycles_with_snapshot: current_cycles_with_snapshot.c +infinite_exec: infinite_exec.c infinite_loop: infinite_loop.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_callee_pause: exec_callee_pause.c exec_caller_from_cell_data: exec_caller_from_cell_data.c exec_caller_from_witness: exec_caller_from_witness.c exec_caller_big_offset_length: exec_caller_big_offset_length.c @@ -141,28 +144,25 @@ 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_current_cycles: spawn_callee_current_cycles.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_current_cycles: spawn_caller_current_cycles.c +spawn_caller_strcat: spawn_caller_strcat.c spawn_utils.h +spawn_callee_strcat: spawn_callee_strcat.c spawn_utils.h spawn_caller_exec: spawn_caller_exec.c -spawn_caller_get_memory_limit: spawn_caller_get_memory_limit.c +spawn_callee_exec_caller: spawn_callee_exec_caller.c +spawn_callee_exec_callee: spawn_callee_exec_callee.c +spawn_caller_strcat_wrap: spawn_caller_strcat_wrap.c spawn_utils.h +spawn_recursive: spawn_recursive.c spawn_utils.h spawn_caller_out_of_cycles: spawn_caller_out_of_cycles.c +spawn_callee_out_of_cycles: spawn_callee_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_peak_memory_512k_to_32m: spawn_peak_memory_512k_to_32m.c -spawn_peak_memory_2m_to_32m: spawn_peak_memory_2m_to_32m.c -spawn_peak_memory_4m_to_32m: spawn_peak_memory_4m_to_32m.c -spawn_recursive: spawn_recursive.c -spawn_current_memory: spawn_current_memory.c +spawn_caller_current_cycles: spawn_caller_current_cycles.c +spawn_callee_current_cycles: spawn_callee_current_cycles.c +spawn_times: spawn_times.c spawn_utils.h +spawn_cases: spawn_cases.c spawn_utils.h +spawn_configurable_caller: spawn_configurable_caller.c spawn_utils.h +spawn_configurable_callee: spawn_configurable_callee.c spawn_utils.h +spawn_dag: spawn_dag.c spawn_dag.h spawn_dag_escape_encoding.h +spawn_fuzzing: spawn_fuzzing.c spawn_utils.h +spawn_huge_swap: spawn_huge_swap.c spawn_utils.h +spawn_cycles: spawn_cycles.c spawn_utils.h +spawn_io_cycles: spawn_io_cycles.c spawn_utils.h +spawn_saturate_memory: spawn_saturate_memory.c spawn_utils.h diff --git a/script/testdata/add1.c b/script/testdata/add1.c index e392d605b7..2e70f13589 100644 --- a/script/testdata/add1.c +++ b/script/testdata/add1.c @@ -1,5 +1,3 @@ #include -__attribute__((visibility("default"))) uint64_t apply (uint64_t num) { - return num + 1; -} +__attribute__((visibility("default"))) uint64_t apply(uint64_t num) { return num + 1; } diff --git a/script/testdata/add1.lib b/script/testdata/add1.lib index 1c916b0a73..e6cd7e9c06 100755 Binary files a/script/testdata/add1.lib and b/script/testdata/add1.lib differ diff --git a/script/testdata/cadd_hint_lock b/script/testdata/cadd_hint_lock index 1f46b6b2ee..0a365e08a4 100755 Binary files a/script/testdata/cadd_hint_lock and b/script/testdata/cadd_hint_lock differ diff --git a/script/testdata/cpop_lock b/script/testdata/cpop_lock index cce506539f..983cacff78 100755 Binary files a/script/testdata/cpop_lock and b/script/testdata/cpop_lock differ diff --git a/script/testdata/cpop_lock.c b/script/testdata/cpop_lock.c index c28cde3fd7..ef88746574 100644 --- a/script/testdata/cpop_lock.c +++ b/script/testdata/cpop_lock.c @@ -4,9 +4,9 @@ * - `num0 == 0 && num1 == 0` * - `cpop(num0) == num 1` */ - -#include "ckb_syscalls.h" +#include #include "blockchain.h" +#include "ckb_syscalls.h" #ifdef DEBUG #include @@ -17,25 +17,22 @@ #define SCRIPT_SIZE 32768 -static uint64_t cpop (uint64_t rs1) { +static uint64_t cpop(uint64_t rs1) { uint64_t rd; - asm volatile ( + asm volatile( "mv s2, %1\n" // "cpop s2, s2\n" ".byte 0x13,0x19,0x29,0x60\n" "mv %0, s2\n" : "=r"(rd) : "r"(rs1) - : "s2" - ); + : "s2"); return rd; } -uint64_t read_u64_le (const uint8_t *src) { - return *(const uint64_t *)src; -} +uint64_t read_u64_le(const uint8_t *src) { return *(const uint64_t *)src; } -int main (int argc, char *argv[]) { +int main(int argc, char *argv[]) { int ret; uint64_t len = SCRIPT_SIZE; uint8_t script[SCRIPT_SIZE]; @@ -67,17 +64,20 @@ int main (int argc, char *argv[]) { } volatile uint64_t num0 = read_u64_le(bytes_seg.ptr); - volatile uint64_t num1 = read_u64_le(bytes_seg.ptr+8); + volatile uint64_t num1 = read_u64_le(bytes_seg.ptr + 8); - sprintf(message, "num0 = %ld", num0); ckb_debug(message); - sprintf(message, "num1 = %ld", num1); ckb_debug(message); + sprintf(message, "num0 = %ld", num0); + ckb_debug(message); + sprintf(message, "num1 = %ld", num1); + ckb_debug(message); if (num0 == 0 && num1 == 0) { return CKB_SUCCESS; } volatile uint64_t num1_actual = cpop(num0); - sprintf(message, "cpop(%lx) = %ld (actual) == %ld (expected)", num0, num1_actual, num1); ckb_debug(message); + sprintf(message, "cpop(%lx) = %ld (actual) == %ld (expected)", num0, num1_actual, num1); + ckb_debug(message); if (num1 != num1_actual) { return -5; diff --git a/script/testdata/crash-45a6098d b/script/testdata/crash-45a6098d new file mode 100644 index 0000000000..1980f8d2d9 Binary files /dev/null and b/script/testdata/crash-45a6098d differ diff --git a/script/testdata/crash-4717eb0e b/script/testdata/crash-4717eb0e new file mode 100644 index 0000000000..f5bf52fb4f Binary files /dev/null and b/script/testdata/crash-4717eb0e differ diff --git a/script/testdata/crash-5a27052f b/script/testdata/crash-5a27052f new file mode 100644 index 0000000000..fd90467c49 Binary files /dev/null and b/script/testdata/crash-5a27052f differ diff --git a/script/testdata/current_cycles b/script/testdata/current_cycles index c524f6d090..fff424aba8 100755 Binary files a/script/testdata/current_cycles and b/script/testdata/current_cycles differ diff --git a/script/testdata/current_cycles.c b/script/testdata/current_cycles.c index 015c6f779d..892364885e 100644 --- a/script/testdata/current_cycles.c +++ b/script/testdata/current_cycles.c @@ -1,13 +1,11 @@ #include "ckb_syscalls.h" -int current_cycles() { - return syscall(2042, 0, 0, 0, 0, 0, 0); -} +int current_cycles() { return syscall(2042, 0, 0, 0, 0, 0, 0); } int main() { int prev = current_cycles(); int curr; - for (int i=0; i<4096; i++) { + for (int i = 0; i < 4096; i++) { curr = current_cycles(); if (curr <= prev) { return -1; diff --git a/script/testdata/current_cycles_with_snapshot b/script/testdata/current_cycles_with_snapshot index 62e6752e66..267ca5cb7c 100755 Binary files a/script/testdata/current_cycles_with_snapshot and b/script/testdata/current_cycles_with_snapshot differ diff --git a/script/testdata/current_cycles_with_snapshot.c b/script/testdata/current_cycles_with_snapshot.c index bc6ac19a93..bed65bb3f8 100644 --- a/script/testdata/current_cycles_with_snapshot.c +++ b/script/testdata/current_cycles_with_snapshot.c @@ -7,13 +7,9 @@ #define sprintf(...) #endif -void try_pause() { - syscall(2178, 0, 0, 0, 0, 0, 0); -} +void try_pause() { syscall(2178, 0, 0, 0, 0, 0, 0); } -int current_cycles() { - return syscall(2042, 0, 0, 0, 0, 0, 0); -} +int current_cycles() { return syscall(2042, 0, 0, 0, 0, 0, 0); } int main() { #ifdef DEBUG @@ -21,9 +17,10 @@ int main() { #endif int prev = current_cycles(); int curr; - for (int i=0; i<4096; i++) { + for (int i = 0; i < 4096; i++) { curr = current_cycles(); - sprintf(message, "prev = %d, curr = %d", prev, curr); ckb_debug(message); + sprintf(message, "prev = %d, curr = %d", prev, curr); + ckb_debug(message); if (i > 16) { try_pause(); } diff --git a/script/testdata/debugger b/script/testdata/debugger index c0ef526adc..c679d6289d 100755 Binary files a/script/testdata/debugger and b/script/testdata/debugger differ diff --git a/script/testdata/debugger.c b/script/testdata/debugger.c index 2bdeb3613e..e3b6452a0f 100644 --- a/script/testdata/debugger.c +++ b/script/testdata/debugger.c @@ -1,10 +1,8 @@ -#include "ckb_syscalls.h" #include -int main() { - char message[2048]; - sprintf(message, "debugger print utf-8 string"); - ckb_debug(message); +#include "ckb_syscalls.h" +int main() { + printf("debugger print utf-8 string"); return CKB_SUCCESS; } diff --git a/script/testdata/div2.c b/script/testdata/div2.c index aef67b1947..8e10b2884a 100644 --- a/script/testdata/div2.c +++ b/script/testdata/div2.c @@ -1,5 +1,3 @@ #include -__attribute__((visibility("default"))) uint64_t apply (uint64_t num) { - return num / 2; -} +__attribute__((visibility("default"))) uint64_t apply(uint64_t num) { return num / 2; } diff --git a/script/testdata/div2.lib b/script/testdata/div2.lib index 1775b76a35..d631c7773a 100755 Binary files a/script/testdata/div2.lib and b/script/testdata/div2.lib differ diff --git a/script/testdata/exec_callee b/script/testdata/exec_callee index 81c802765d..b34d537dd2 100755 Binary files a/script/testdata/exec_callee and b/script/testdata/exec_callee differ diff --git a/script/testdata/exec_callee.c b/script/testdata/exec_callee.c index 3a815b171c..d5d7040852 100644 --- a/script/testdata/exec_callee.c +++ b/script/testdata/exec_callee.c @@ -1,15 +1,17 @@ +#include + int main(int argc, char* argv[]) { - if (argc != 3) { - return 1; - } - if (argv[0][0] != 'a') { - return 2; - } - if (argv[1][0] != 'b') { - return 3; - } - if (argv[2][0] != 'c') { - return 4; - } - return 0; + if (argc != 3) { + return 1; + } + if (argv[0][0] != 'a') { + return 2; + } + if (argv[1][0] != 'b') { + return 3; + } + if (argv[2][0] != 'c') { + return 4; + } + return 0; } diff --git a/script/testdata/exec_callee_pause b/script/testdata/exec_callee_pause new file mode 100755 index 0000000000..5bf0e730bb Binary files /dev/null and b/script/testdata/exec_callee_pause differ diff --git a/script/testdata/exec_callee_pause.c b/script/testdata/exec_callee_pause.c new file mode 100644 index 0000000000..f3cc85fc84 --- /dev/null +++ b/script/testdata/exec_callee_pause.c @@ -0,0 +1,27 @@ +#include "ckb_syscalls.h" + +void try_pause() { + ckb_debug("try_pause"); + syscall(2178, 0, 0, 0, 0, 0, 0); +} + +int main(int argc, char* argv[]) { + try_pause(); + if (argc != 3) { + return 1; + } + try_pause(); + if (argv[0][0] != 'a') { + return 2; + } + try_pause(); + if (argv[1][0] != 'b') { + return 3; + } + try_pause(); + if (argv[2][0] != 'c') { + return 4; + } + try_pause(); + return 0; +} diff --git a/script/testdata/exec_caller_big_offset_length b/script/testdata/exec_caller_big_offset_length index 1ea87c14ca..9cb14cddcf 100755 Binary files a/script/testdata/exec_caller_big_offset_length and b/script/testdata/exec_caller_big_offset_length differ diff --git a/script/testdata/exec_caller_big_offset_length.c b/script/testdata/exec_caller_big_offset_length.c index 833baab339..e0577b3c6b 100644 --- a/script/testdata/exec_caller_big_offset_length.c +++ b/script/testdata/exec_caller_big_offset_length.c @@ -5,7 +5,7 @@ int main() { char *argv[] = {"a", "b", "c"}; int ret = syscall(2043, 1, 3, 0, 0xffffffffffffffff, argc, argv); if (ret != 0) { - return ret; + return ret; } return -1; } diff --git a/script/testdata/exec_caller_from_cell_data b/script/testdata/exec_caller_from_cell_data index 64fa89fc73..2b597da570 100755 Binary files a/script/testdata/exec_caller_from_cell_data and b/script/testdata/exec_caller_from_cell_data differ diff --git a/script/testdata/exec_caller_from_witness b/script/testdata/exec_caller_from_witness index a22f978254..94c560a8fd 100755 Binary files a/script/testdata/exec_caller_from_witness and b/script/testdata/exec_caller_from_witness differ diff --git a/script/testdata/exec_configurable_callee b/script/testdata/exec_configurable_callee index c99bb78cad..0fd19349a2 100755 Binary files a/script/testdata/exec_configurable_callee and b/script/testdata/exec_configurable_callee differ diff --git a/script/testdata/exec_configurable_callee.c b/script/testdata/exec_configurable_callee.c index d03bdd7948..3cc60abe3a 100644 --- a/script/testdata/exec_configurable_callee.c +++ b/script/testdata/exec_configurable_callee.c @@ -25,9 +25,9 @@ * - The `code_hash`(`data_hash`) of a shared library. */ -#include "stdbool.h" #include "ckb_dlfcn.h" #include "ckb_syscalls.h" +#include "stdbool.h" #ifdef DEBUG #include @@ -40,73 +40,74 @@ char message[2048]; #define EXEC_ARGC 9 #define BUFFER_SIZE 32768 -typedef uint64_t(arithmetic_func_t) (uint64_t); +typedef uint64_t(arithmetic_func_t)(uint64_t); uint8_t CODE_BUFFER[BUFFER_SIZE] __attribute__((aligned(RISCV_PGSIZE))); -void try_pause() { - syscall(2178, 0, 0, 0, 0, 0, 0); -} +void try_pause() { syscall(2178, 0, 0, 0, 0, 0, 0); } -void from_hex (uint8_t* dst, char* src, size_t len) { - for (size_t i=0; i> 6) * 9) << 4) | (((lo & 0xf) + (lo >> 6) * 9)); } } void to_hex(char* dst, uint8_t* src, size_t len) { - for (size_t i = 0; i> 4; char lo = src[i] & 0xf; - dst[i*2] = hi + (hi < 10 ? '0' : ('a' - 10)); - dst[i*2+1] = lo + (lo < 10 ? '0' : ('a' - 10)); + dst[i * 2] = hi + (hi < 10 ? '0' : ('a' - 10)); + dst[i * 2 + 1] = lo + (lo < 10 ? '0' : ('a' - 10)); } - dst[len*2] = '\0'; + dst[len * 2] = '\0'; } -volatile uint64_t read_u64_le_from_hex (char* src) { +volatile uint64_t read_u64_le_from_hex(char* src) { uint8_t bytes[8]; from_hex(bytes, src, 8); - return *(const uint64_t *)bytes; + return *(const uint64_t*)bytes; } -void write_u64_le_to_hex (char* dst, uint64_t number) { - uint8_t* bytes = (uint8_t *)(&number); +void write_u64_le_to_hex(char* dst, uint64_t number) { + uint8_t* bytes = (uint8_t*)(&number); to_hex(dst, bytes, 8); } -int try_exec(char*argv[], uint64_t recursion, uint64_t number) { - sprintf(message, "argv[4] = %s", argv[4]); ckb_debug(message); - if (strlen(argv[4]) != 8*2) { +int try_exec(char* argv[], uint64_t recursion, uint64_t number) { + sprintf(message, "argv[4] = %s", argv[4]); + ckb_debug(message); + if (strlen(argv[4]) != 8 * 2) { return -21; } uint64_t index = read_u64_le_from_hex(argv[4]); - sprintf(message, "argv[5] = %s", argv[5]); ckb_debug(message); - if (strlen(argv[5]) != 8*2) { + sprintf(message, "argv[5] = %s", argv[5]); + ckb_debug(message); + if (strlen(argv[5]) != 8 * 2) { return -22; } uint64_t source = read_u64_le_from_hex(argv[5]); - sprintf(message, "argv[6] = %s", argv[6]); ckb_debug(message); - if (strlen(argv[6]) != 8*2) { + sprintf(message, "argv[6] = %s", argv[6]); + ckb_debug(message); + if (strlen(argv[6]) != 8 * 2) { return -23; } uint64_t place = read_u64_le_from_hex(argv[6]); - sprintf(message, "argv[7] = %s", argv[7]); ckb_debug(message); - if (strlen(argv[7]) != 8*2) { + sprintf(message, "argv[7] = %s", argv[7]); + ckb_debug(message); + if (strlen(argv[7]) != 8 * 2) { return -24; } uint64_t bounds = read_u64_le_from_hex(argv[7]); - char recursion_str[8*2+1]; - char number_str[8*2+1]; - char *argv_new[EXEC_ARGC] = { - argv[0], recursion_str, number_str, argv[3], - argv[4], argv[5], argv[6], argv[7], argv[8] }; + char recursion_str[8 * 2 + 1]; + char number_str[8 * 2 + 1]; + char* argv_new[EXEC_ARGC] = {argv[0], recursion_str, number_str, argv[3], argv[4], + argv[5], argv[6], argv[7], argv[8]}; write_u64_le_to_hex(recursion_str, recursion); write_u64_le_to_hex(number_str, number); try_pause(); @@ -115,7 +116,7 @@ int try_exec(char*argv[], uint64_t recursion, uint64_t number) { } int try_load_code(uint64_t* number, uint8_t* code_hash) { - void *handle = NULL; + void* handle = NULL; uint64_t consumed_size = 0; uint8_t hash_type = 0; int ret = ckb_dlopen2(code_hash, hash_type, CODE_BUFFER, BUFFER_SIZE, &handle, &consumed_size); @@ -123,7 +124,7 @@ int try_load_code(uint64_t* number, uint8_t* code_hash) { return -31; } try_pause(); - arithmetic_func_t* func = (arithmetic_func_t*) ckb_dlsym(handle, "apply"); + arithmetic_func_t* func = (arithmetic_func_t*)ckb_dlsym(handle, "apply"); if (func == NULL) { return -32; } @@ -132,54 +133,62 @@ int try_load_code(uint64_t* number, uint8_t* code_hash) { return CKB_SUCCESS; } -int main (int argc, char *argv[]) { - sprintf(message, "argc = %d", argc); ckb_debug(message); +int main(int argc, char* argv[]) { + sprintf(message, "argc = %d", argc); + ckb_debug(message); if (argc != EXEC_ARGC) { return -11; } - sprintf(message, "argv[0] = %s", argv[0]); ckb_debug(message); - if (strlen(argv[0]) != 1*2) { + sprintf(message, "argv[0] = %s", argv[0]); + ckb_debug(message); + if (strlen(argv[0]) != 1 * 2) { return -12; } uint8_t flag; from_hex(&flag, argv[0], 1); - bool if_write_stack = (flag & 0b0010) == 0b0010; + bool if_write_stack = (flag & 0b0010) == 0b0010; if (if_write_stack) { - sprintf(message, "(try update the stack)"); ckb_debug(message); - sprintf(message, "W in [%p, %p)", CODE_BUFFER, CODE_BUFFER+BUFFER_SIZE); ckb_debug(message); - for (int i=0; i 0) { - try_exec(argv, recursion-1, number-1); + try_exec(argv, recursion - 1, number - 1); } - sprintf(message, "argv[3] = %s", argv[3]); ckb_debug(message); - if (strlen(argv[3]) != 8*2) { + sprintf(message, "argv[3] = %s", argv[3]); + ckb_debug(message); + if (strlen(argv[3]) != 8 * 2) { return -15; } uint64_t expected = read_u64_le_from_hex(argv[3]); - bool if_load_after_exec = (flag & 0b0100) == 0b0100; + bool if_load_after_exec = (flag & 0b0100) == 0b0100; if (if_load_after_exec) { - sprintf(message, "argv[8] = %s", argv[8]); ckb_debug(message); - if (strlen(argv[8]) != 32*2) { + sprintf(message, "argv[8] = %s", argv[8]); + ckb_debug(message); + if (strlen(argv[8]) != 32 * 2) { return -16; } uint8_t code_hash[32]; @@ -188,7 +197,8 @@ int main (int argc, char *argv[]) { if (ret != CKB_SUCCESS) { return ret; } - sprintf(message, "(apply after exec) number = %ld", number); ckb_debug(message); + sprintf(message, "(apply after exec) number = %ld", number); + ckb_debug(message); } if (number == expected) { diff --git a/script/testdata/exec_configurable_caller b/script/testdata/exec_configurable_caller index 911eb8d274..9cb002df35 100755 Binary files a/script/testdata/exec_configurable_caller and b/script/testdata/exec_configurable_caller differ diff --git a/script/testdata/exec_configurable_caller.c b/script/testdata/exec_configurable_caller.c index 48f3425ade..7e587a69f7 100644 --- a/script/testdata/exec_configurable_caller.c +++ b/script/testdata/exec_configurable_caller.c @@ -2,10 +2,10 @@ * - Args: * See "exec_configurable_callee.c". */ - +#include +#include "blockchain.h" #include "ckb_dlfcn.h" #include "ckb_syscalls.h" -#include "blockchain.h" #ifdef DEBUG #include @@ -19,36 +19,33 @@ char message[2048]; #define SCRIPT_SIZE 32768 #define BUFFER_SIZE 32768 -typedef uint64_t(arithmetic_func_t) (uint64_t); +typedef uint64_t(arithmetic_func_t)(uint64_t); uint8_t CODE_BUFFER[BUFFER_SIZE] __attribute__((aligned(RISCV_PGSIZE))); void to_hex(char* dst, uint8_t* src, size_t len) { - for (size_t i = 0; i> 4; char lo = src[i] & 0xf; - dst[i*2] = hi + (hi < 10 ? '0' : ('a' - 10)); - dst[i*2+1] = lo + (lo < 10 ? '0' : ('a' - 10)); + dst[i * 2] = hi + (hi < 10 ? '0' : ('a' - 10)); + dst[i * 2 + 1] = lo + (lo < 10 ? '0' : ('a' - 10)); } - dst[len*2] = '\0'; + dst[len * 2] = '\0'; } -void write_u64_le_to_hex (char* dst, uint64_t number) { - uint8_t* bytes = (uint8_t *)(&number); +void write_u64_le_to_hex(char* dst, uint64_t number) { + uint8_t* bytes = (uint8_t*)(&number); to_hex(dst, bytes, 8); } -void try_pause() { - syscall(2178, 0, 0, 0, 0, 0, 0); -} +void try_pause() { syscall(2178, 0, 0, 0, 0, 0, 0); } -uint64_t read_u64_le (const uint8_t *src) { - return *(const uint64_t *)src; -} +uint64_t read_u64_le(const uint8_t* src) { return *(const uint64_t*)src; } int try_load_code(uint64_t* number, uint8_t* code_hash) { - sprintf(message, "X in [%p, %p)", CODE_BUFFER, CODE_BUFFER+BUFFER_SIZE); ckb_debug(message); - void *handle = NULL; + sprintf(message, "X in [%p, %p)", CODE_BUFFER, CODE_BUFFER + BUFFER_SIZE); + ckb_debug(message); + void* handle = NULL; uint64_t consumed_size = 0; uint8_t hash_type = 0; int ret = ckb_dlopen2(code_hash, hash_type, CODE_BUFFER, BUFFER_SIZE, &handle, &consumed_size); @@ -56,7 +53,7 @@ int try_load_code(uint64_t* number, uint8_t* code_hash) { return -6; } try_pause(); - arithmetic_func_t* func = (arithmetic_func_t*) ckb_dlsym(handle, "apply"); + arithmetic_func_t* func = (arithmetic_func_t*)ckb_dlsym(handle, "apply"); if (func == NULL) { return -7; } @@ -65,7 +62,7 @@ int try_load_code(uint64_t* number, uint8_t* code_hash) { return CKB_SUCCESS; } -int main (int argc, char *argv[]) { +int main(int argc, char* argv[]) { int ret; uint64_t len = SCRIPT_SIZE; uint8_t script[SCRIPT_SIZE]; @@ -81,7 +78,7 @@ int main (int argc, char *argv[]) { mol_seg_t script_seg; mol_seg_t args_seg; mol_seg_t bytes_seg; - script_seg.ptr = (uint8_t *)script; + script_seg.ptr = (uint8_t*)script; script_seg.size = len; if (MolReader_Script_verify(&script_seg, false) != MOL_OK) { return -3; @@ -89,27 +86,35 @@ int main (int argc, char *argv[]) { args_seg = MolReader_Script_get_args(&script_seg); bytes_seg = MolReader_Bytes_raw_bytes(&args_seg); - if (bytes_seg.size != 1 + 8*7 + 32) { + if (bytes_seg.size != 1 + 8 * 7 + 32) { return -4; } - uint8_t flag = bytes_seg.ptr[0]; - uint64_t recursion = read_u64_le(bytes_seg.ptr+1); - uint64_t number = read_u64_le(bytes_seg.ptr+1+8); - uint64_t expected = read_u64_le(bytes_seg.ptr+1+8*2); - uint64_t index = read_u64_le(bytes_seg.ptr+1+8*3); - uint64_t source = read_u64_le(bytes_seg.ptr+1+8*4); - uint64_t place = read_u64_le(bytes_seg.ptr+1+8*5); - uint64_t bounds = read_u64_le(bytes_seg.ptr+1+8*6); - - sprintf(message, "flag = %x", flag ); ckb_debug(message); - sprintf(message, "recursion = %ld", recursion); ckb_debug(message); - sprintf(message, "number = %ld", number ); ckb_debug(message); - sprintf(message, "expected = %ld", expected ); ckb_debug(message); - sprintf(message, "index = %ld", index ); ckb_debug(message); - sprintf(message, "source = %ld", source ); ckb_debug(message); - sprintf(message, "place = %ld", place ); ckb_debug(message); - sprintf(message, "bounds = %ld", bounds ); ckb_debug(message); + uint8_t flag = bytes_seg.ptr[0]; + uint64_t recursion = read_u64_le(bytes_seg.ptr + 1); + uint64_t number = read_u64_le(bytes_seg.ptr + 1 + 8); + uint64_t expected = read_u64_le(bytes_seg.ptr + 1 + 8 * 2); + uint64_t index = read_u64_le(bytes_seg.ptr + 1 + 8 * 3); + uint64_t source = read_u64_le(bytes_seg.ptr + 1 + 8 * 4); + uint64_t place = read_u64_le(bytes_seg.ptr + 1 + 8 * 5); + uint64_t bounds = read_u64_le(bytes_seg.ptr + 1 + 8 * 6); + + sprintf(message, "flag = %x", flag); + ckb_debug(message); + sprintf(message, "recursion = %ld", recursion); + ckb_debug(message); + sprintf(message, "number = %ld", number); + ckb_debug(message); + sprintf(message, "expected = %ld", expected); + ckb_debug(message); + sprintf(message, "index = %ld", index); + ckb_debug(message); + sprintf(message, "source = %ld", source); + ckb_debug(message); + sprintf(message, "place = %ld", place); + ckb_debug(message); + sprintf(message, "bounds = %ld", bounds); + ckb_debug(message); try_pause(); @@ -123,37 +128,37 @@ int main (int argc, char *argv[]) { bool if_load_before_exec = (flag & 0b0001) == 0b0001; if (if_load_before_exec) { - ret = try_load_code(&number, bytes_seg.ptr+1+8*7); + ret = try_load_code(&number, bytes_seg.ptr + 1 + 8 * 7); if (ret != CKB_SUCCESS) { return ret; } try_pause(); - sprintf(message, "(apply before exec) number = %ld", number); ckb_debug(message); + sprintf(message, "(apply before exec) number = %ld", number); + ckb_debug(message); } { int argc_new = EXEC_ARGC; - char flag_str[1*2+1]; - char recursion_str[8*2+1]; - char number_str[8*2+1]; - char expected_str[8*2+1]; - char index_str[8*2+1]; - char source_str[8*2+1]; - char place_str[8*2+1]; - char bounds_str[8*2+1]; - char code_hash_str[32*2+1]; - char *argv_new[EXEC_ARGC] = { - flag_str, recursion_str, number_str, expected_str, - index_str, source_str, place_str, bounds_str, code_hash_str }; - to_hex(flag_str, bytes_seg.ptr, 1); - write_u64_le_to_hex(recursion_str, recursion-1); - write_u64_le_to_hex(number_str, number-1); + char flag_str[1 * 2 + 1]; + char recursion_str[8 * 2 + 1]; + char number_str[8 * 2 + 1]; + char expected_str[8 * 2 + 1]; + char index_str[8 * 2 + 1]; + char source_str[8 * 2 + 1]; + char place_str[8 * 2 + 1]; + char bounds_str[8 * 2 + 1]; + char code_hash_str[32 * 2 + 1]; + char* argv_new[EXEC_ARGC] = {flag_str, recursion_str, number_str, expected_str, index_str, + source_str, place_str, bounds_str, code_hash_str}; + to_hex(flag_str, bytes_seg.ptr, 1); + write_u64_le_to_hex(recursion_str, recursion - 1); + write_u64_le_to_hex(number_str, number - 1); write_u64_le_to_hex(expected_str, expected); - to_hex(index_str, bytes_seg.ptr+1+8*3, 8); - to_hex(source_str, bytes_seg.ptr+1+8*4, 8); - to_hex(place_str, bytes_seg.ptr+1+8*5, 8); - to_hex(bounds_str, bytes_seg.ptr+1+8*6, 8); - to_hex(code_hash_str, bytes_seg.ptr+1+8*7, 32); + to_hex(index_str, bytes_seg.ptr + 1 + 8 * 3, 8); + to_hex(source_str, bytes_seg.ptr + 1 + 8 * 4, 8); + to_hex(place_str, bytes_seg.ptr + 1 + 8 * 5, 8); + to_hex(bounds_str, bytes_seg.ptr + 1 + 8 * 6, 8); + to_hex(code_hash_str, bytes_seg.ptr + 1 + 8 * 7, 32); int ret = syscall(2043, index, source, place, bounds, argc_new, argv_new); if (ret != CKB_SUCCESS) { return ret; diff --git a/script/testdata/get_memory_limit b/script/testdata/get_memory_limit deleted file mode 100755 index fbf37219f1..0000000000 Binary files a/script/testdata/get_memory_limit and /dev/null differ diff --git a/script/testdata/get_memory_limit.c b/script/testdata/get_memory_limit.c deleted file mode 100644 index a148ad4713..0000000000 --- a/script/testdata/get_memory_limit.c +++ /dev/null @@ -1,8 +0,0 @@ -#include "ckb_syscalls.h" - -int main() { - if (ckb_get_memory_limit() == 8) { - return 0; - } - return 1; -} diff --git a/script/testdata/infinite_exec b/script/testdata/infinite_exec new file mode 100755 index 0000000000..e5e4aad750 Binary files /dev/null and b/script/testdata/infinite_exec differ diff --git a/script/testdata/infinite_exec.c b/script/testdata/infinite_exec.c new file mode 100644 index 0000000000..3255ae2f64 --- /dev/null +++ b/script/testdata/infinite_exec.c @@ -0,0 +1,8 @@ +#include "ckb_syscalls.h" + +int main() { + int argc = 0; + char *argv[] = {}; + syscall(2043, 0, 3, 0, 0, argc, argv); + return -1; +} diff --git a/script/testdata/infinite_loop b/script/testdata/infinite_loop index 99cd4f7350..778627008a 100755 Binary files a/script/testdata/infinite_loop and b/script/testdata/infinite_loop differ diff --git a/script/testdata/infinite_loop.c b/script/testdata/infinite_loop.c index 2a90f89cc7..ebe4d9a48d 100644 --- a/script/testdata/infinite_loop.c +++ b/script/testdata/infinite_loop.c @@ -7,8 +7,8 @@ #define sprintf(...) #endif - int main() { - for(; ;) {} + for (;;) { + } return CKB_SUCCESS; } diff --git a/script/testdata/is_even.c b/script/testdata/is_even.c index 44a23ed326..5d268ad424 100644 --- a/script/testdata/is_even.c +++ b/script/testdata/is_even.c @@ -1,7 +1,8 @@ +#include #include #include -__attribute__((visibility("default"))) bool is_even (uint64_t num) { +__attribute__((visibility("default"))) bool is_even(uint64_t num) { if (num & 0x1) { return false; } else { diff --git a/script/testdata/is_even.lib b/script/testdata/is_even.lib index 07b308138a..9e45b77c71 100755 Binary files a/script/testdata/is_even.lib and b/script/testdata/is_even.lib differ diff --git a/script/testdata/jalr_zero b/script/testdata/jalr_zero index f77b85d85c..77a89a48d4 100755 Binary files a/script/testdata/jalr_zero and b/script/testdata/jalr_zero differ diff --git a/script/testdata/load_arithmetic b/script/testdata/load_arithmetic index 650df9295c..df1c203761 100755 Binary files a/script/testdata/load_arithmetic and b/script/testdata/load_arithmetic differ diff --git a/script/testdata/load_arithmetic.c b/script/testdata/load_arithmetic.c index b1c34bb181..95c06a831d 100644 --- a/script/testdata/load_arithmetic.c +++ b/script/testdata/load_arithmetic.c @@ -7,9 +7,10 @@ * - `num0 == num1` is zero at last. */ +#include +#include "blockchain.h" #include "ckb_dlfcn.h" #include "ckb_syscalls.h" -#include "blockchain.h" #ifdef DEBUG #include @@ -22,32 +23,28 @@ #define CODE_BUFFER_SIZE (1024 * 32) #define CACHE_CAPACITY 4 -typedef uint64_t(arithmetic_func_t) (uint64_t); +typedef uint64_t(arithmetic_func_t)(uint64_t); -void try_pause() { - syscall(2178, 0, 0, 0, 0, 0, 0); -} +void try_pause() { syscall(2178, 0, 0, 0, 0, 0, 0); } -uint64_t read_u64_le (const uint8_t *src) { - return *(const uint64_t *)src; -} +uint64_t read_u64_le(const uint8_t* src) { return *(const uint64_t*)src; } -int load_arithmetic_func (arithmetic_func_t** func, uint8_t* code_hash, uint8_t* code_buffer) { - void *handle = NULL; +int load_arithmetic_func(arithmetic_func_t** func, uint8_t* code_hash, uint8_t* code_buffer) { + void* handle = NULL; uint64_t consumed_size = 0; uint8_t hash_type = 0; int ret = ckb_dlopen2(code_hash, hash_type, code_buffer, CODE_BUFFER_SIZE, &handle, &consumed_size); if (ret != CKB_SUCCESS) { return -11; } - *func = (arithmetic_func_t*) ckb_dlsym(handle, "apply"); + *func = (arithmetic_func_t*)ckb_dlsym(handle, "apply"); if (*func == NULL) { return -12; } return CKB_SUCCESS; } -int main (int argc, char *argv[]) { +int main(int argc, char* argv[]) { int ret; uint64_t len = SCRIPT_SIZE; uint8_t script[SCRIPT_SIZE]; @@ -66,7 +63,7 @@ int main (int argc, char *argv[]) { mol_seg_t script_seg; mol_seg_t args_seg; mol_seg_t bytes_seg; - script_seg.ptr = (uint8_t *)script; + script_seg.ptr = (uint8_t*)script; script_seg.size = len; if (MolReader_Script_verify(&script_seg, false) != MOL_OK) { return -3; @@ -79,8 +76,9 @@ int main (int argc, char *argv[]) { } volatile uint64_t num0 = read_u64_le(bytes_seg.ptr); - volatile uint64_t num1 = read_u64_le(bytes_seg.ptr+8); - sprintf(message, "before num0 = %ld, num1 = %ld", num0, num1); ckb_debug(message); + volatile uint64_t num1 = read_u64_le(bytes_seg.ptr + 8); + sprintf(message, "before num0 = %ld, num1 = %ld", num0, num1); + ckb_debug(message); if (num0 == num1) { return CKB_SUCCESS; @@ -113,7 +111,7 @@ int main (int argc, char *argv[]) { for (int i = 0; i < cache_size; i++) { if (0 == memcmp(code_hash_ptr, cached_code_hash[i], 32)) { // Find the function from caches. - tmp_func = (arithmetic_func_t*) cached_funcs[i]; + tmp_func = (arithmetic_func_t*)cached_funcs[i]; is_found = true; } } @@ -144,7 +142,7 @@ int main (int argc, char *argv[]) { // Cache the current function. if (cache_size < CACHE_CAPACITY) { cached_code_hash[cache_size] = code_hash_ptr; - cached_funcs[cache_size] = (void*) tmp_func; + cached_funcs[cache_size] = (void*)tmp_func; cache_size += 1; } } @@ -156,7 +154,8 @@ int main (int argc, char *argv[]) { called_funcs_count += 1; } - sprintf(message, "after num0 = %ld, num1 = %ld", num0, num1); ckb_debug(message); + sprintf(message, "after num0 = %ld, num1 = %ld", num0, num1); + ckb_debug(message); if (num0 != num1) { return -5; diff --git a/script/testdata/load_code_to_stack_then_reuse b/script/testdata/load_code_to_stack_then_reuse index 494dcd1a4b..52881964bf 100755 Binary files a/script/testdata/load_code_to_stack_then_reuse and b/script/testdata/load_code_to_stack_then_reuse differ diff --git a/script/testdata/load_code_to_stack_then_reuse.c b/script/testdata/load_code_to_stack_then_reuse.c index 96b6376ceb..a655f8e7e1 100644 --- a/script/testdata/load_code_to_stack_then_reuse.c +++ b/script/testdata/load_code_to_stack_then_reuse.c @@ -4,10 +4,10 @@ * - A little endian unsigned 64 bits integer: `size`. * - The `code_hash`(`data_hash`) of a shared library. */ - +#include +#include "blockchain.h" #include "ckb_dlfcn.h" #include "ckb_syscalls.h" -#include "blockchain.h" #ifdef DEBUG #include @@ -22,13 +22,12 @@ char message[2048]; #define bool2char(b) ((b) ? '+' : '-') -uint64_t read_u64_le (const uint8_t *src) { - return *(const uint64_t *)src; -} +uint64_t read_u64_le(const uint8_t *src) { return *(const uint64_t *)src; } -int try_load_code(bool if_load_code, uint8_t* code_hash) { +int try_load_code(bool if_load_code, uint8_t *code_hash) { uint8_t buf[BUFFER_SIZE] __attribute__((aligned(RISCV_PGSIZE))); - sprintf(message, "%c X in [%p, %p)", bool2char(if_load_code), buf, buf+BUFFER_SIZE); ckb_debug(message); + sprintf(message, "%c X in [%p, %p)", bool2char(if_load_code), buf, buf + BUFFER_SIZE); + ckb_debug(message); if (if_load_code) { void *handle = NULL; uint64_t consumed_size = 0; @@ -43,16 +42,16 @@ int try_load_code(bool if_load_code, uint8_t* code_hash) { volatile void try_write_stack(bool if_write_stack, int size) { volatile uint8_t buf[size] __attribute__((aligned(RISCV_PGSIZE))); - sprintf(message, "%c W in [%p, %p)", bool2char(if_write_stack), buf, buf+size); ckb_debug(message); + sprintf(message, "%c W in [%p, %p)", bool2char(if_write_stack), buf, buf + size); + ckb_debug(message); if (if_write_stack) { - for (int i=0; i +#include "blockchain.h" #include "ckb_dlfcn.h" #include "ckb_syscalls.h" -#include "blockchain.h" #ifdef DEBUG #include @@ -19,15 +20,13 @@ #endif #define SCRIPT_SIZE 32768 -#define CODE_BUFFER_SIZE (100 * 1024) +#define CODE_BUFFER_SIZE (100 * 1024) uint8_t CODE_BUFFER[CODE_BUFFER_SIZE] __attribute__((aligned(RISCV_PGSIZE))); -uint64_t read_u64_le (const uint8_t *src) { - return *(const uint64_t *)src; -} +uint64_t read_u64_le(const uint8_t *src) { return *(const uint64_t *)src; } -int main (int argc, char *argv[]) { +int main(int argc, char *argv[]) { int ret; uint64_t len = SCRIPT_SIZE; uint8_t script[SCRIPT_SIZE]; @@ -59,7 +58,8 @@ int main (int argc, char *argv[]) { } volatile uint64_t number = read_u64_le(bytes_seg.ptr); - sprintf(message, "number = %ld", number); ckb_debug(message); + sprintf(message, "number = %ld", number); + ckb_debug(message); if (number == 0) { return CKB_SUCCESS; @@ -71,7 +71,7 @@ int main (int argc, char *argv[]) { uint64_t consumed_size = 0; uint8_t hash_type = 0; - ret = ckb_dlopen2(bytes_seg.ptr+8, hash_type, CODE_BUFFER, CODE_BUFFER_SIZE, &handle, &consumed_size); + ret = ckb_dlopen2(bytes_seg.ptr + 8, hash_type, CODE_BUFFER, CODE_BUFFER_SIZE, &handle, &consumed_size); if (ret != CKB_SUCCESS) { return ret; } @@ -83,7 +83,8 @@ int main (int argc, char *argv[]) { is_even = func(number); } - sprintf(message, "is_even(%ld) = %d", number, is_even); ckb_debug(message); + sprintf(message, "is_even(%ld) = %d", number, is_even); + ckb_debug(message); if (is_even) { return -8; diff --git a/script/testdata/load_is_even_with_snapshot b/script/testdata/load_is_even_with_snapshot index 16d2b05393..340259290e 100755 Binary files a/script/testdata/load_is_even_with_snapshot and b/script/testdata/load_is_even_with_snapshot differ diff --git a/script/testdata/load_is_even_with_snapshot.c b/script/testdata/load_is_even_with_snapshot.c index 7004277e63..4b305c7882 100644 --- a/script/testdata/load_is_even_with_snapshot.c +++ b/script/testdata/load_is_even_with_snapshot.c @@ -6,10 +6,10 @@ * - `number` is zero. * - `number` is not even. */ - +#include +#include "blockchain.h" #include "ckb_dlfcn.h" #include "ckb_syscalls.h" -#include "blockchain.h" #ifdef DEBUG #include @@ -20,15 +20,11 @@ #define SCRIPT_SIZE 32768 -void try_pause() { - syscall(2178, 0, 0, 0, 0, 0, 0); -} +void try_pause() { syscall(2178, 0, 0, 0, 0, 0, 0); } -uint64_t read_u64_le (const uint8_t *src) { - return *(const uint64_t *)src; -} +uint64_t read_u64_le(const uint8_t *src) { return *(const uint64_t *)src; } -int main (int argc, char *argv[]) { +int main(int argc, char *argv[]) { int ret; uint64_t len = SCRIPT_SIZE; uint8_t script[SCRIPT_SIZE]; @@ -60,7 +56,8 @@ int main (int argc, char *argv[]) { } volatile uint64_t number = read_u64_le(bytes_seg.ptr); - sprintf(message, "number = %ld", number); ckb_debug(message); + sprintf(message, "number = %ld", number); + ckb_debug(message); if (number == 0) { return CKB_SUCCESS; @@ -74,7 +71,7 @@ int main (int argc, char *argv[]) { uint64_t code_buffer_size = 100 * 1024; uint8_t code_buffer[code_buffer_size] __attribute__((aligned(RISCV_PGSIZE))); uint8_t hash_type = 0; - ret = ckb_dlopen2(bytes_seg.ptr+8, hash_type, code_buffer, code_buffer_size, &handle, &consumed_size); + ret = ckb_dlopen2(bytes_seg.ptr + 8, hash_type, code_buffer, code_buffer_size, &handle, &consumed_size); if (ret != CKB_SUCCESS) { return ret; } @@ -87,7 +84,8 @@ int main (int argc, char *argv[]) { is_even = func(number); } - sprintf(message, "is_even(%ld) = %d", number, is_even); ckb_debug(message); + sprintf(message, "is_even(%ld) = %d", number, is_even); + ckb_debug(message); if (is_even) { return -8; diff --git a/script/testdata/mop_adc_lock b/script/testdata/mop_adc_lock index d24fad349c..6593665e13 100755 Binary files a/script/testdata/mop_adc_lock and b/script/testdata/mop_adc_lock differ diff --git a/script/testdata/mul2.c b/script/testdata/mul2.c index 4163351fc6..99c6533122 100644 --- a/script/testdata/mul2.c +++ b/script/testdata/mul2.c @@ -1,5 +1,3 @@ #include -__attribute__((visibility("default"))) uint64_t apply (uint64_t num) { - return num * 2; -} +__attribute__((visibility("default"))) uint64_t apply(uint64_t num) { return num * 2; } diff --git a/script/testdata/mul2.lib b/script/testdata/mul2.lib index 4ce9d55c69..889d6cf06d 100755 Binary files a/script/testdata/mul2.lib and b/script/testdata/mul2.lib differ diff --git a/script/testdata/set_content b/script/testdata/set_content deleted file mode 100755 index 63b8d7ef3e..0000000000 Binary files a/script/testdata/set_content and /dev/null differ diff --git a/script/testdata/set_content.c b/script/testdata/set_content.c deleted file mode 100644 index b6b80a211b..0000000000 --- a/script/testdata/set_content.c +++ /dev/null @@ -1,12 +0,0 @@ -#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 deleted file mode 100755 index 0b63b00c4d..0000000000 Binary files a/script/testdata/spawn_big_content_length and /dev/null differ diff --git a/script/testdata/spawn_big_content_length.c b/script/testdata/spawn_big_content_length.c deleted file mode 100644 index 2620b5e15c..0000000000 --- a/script/testdata/spawn_big_content_length.c +++ /dev/null @@ -1,19 +0,0 @@ -#include -#include - -#include "ckb_syscalls.h" - -int main() { - uint64_t spawn_content_length = 0xffffffff; - spawn_args_t spgs = { - .memory_limit = 8, - .exit_code = NULL, - .content = NULL, - .content_length = &spawn_content_length, - }; - int success = ckb_spawn(1, 3, 0, 0, NULL, &spgs); - if (success != 5) { - return 1; - } - return 0; -} diff --git a/script/testdata/spawn_big_memory_size b/script/testdata/spawn_big_memory_size deleted file mode 100755 index 7c0b7f8ebf..0000000000 Binary files a/script/testdata/spawn_big_memory_size and /dev/null differ diff --git a/script/testdata/spawn_big_memory_size.c b/script/testdata/spawn_big_memory_size.c deleted file mode 100644 index d1b9b0e33c..0000000000 --- a/script/testdata/spawn_big_memory_size.c +++ /dev/null @@ -1,18 +0,0 @@ -#include -#include - -#include "ckb_syscalls.h" - -int main() { - spawn_args_t spgs = { - .memory_limit = 9, - .exit_code = NULL, - .content = NULL, - .content_length = NULL, - }; - int success = ckb_spawn(1, 3, 0, 0, NULL, &spgs); - if (success != 6) { - return 1; - } - return 0; -} diff --git a/script/testdata/spawn_callee_current_cycles b/script/testdata/spawn_callee_current_cycles index 1c2dd40aa0..a49bc961bb 100755 Binary files a/script/testdata/spawn_callee_current_cycles and b/script/testdata/spawn_callee_current_cycles differ diff --git a/script/testdata/spawn_callee_current_cycles.c b/script/testdata/spawn_callee_current_cycles.c index c2005c9da4..1e7e7f11d4 100644 --- a/script/testdata/spawn_callee_current_cycles.c +++ b/script/testdata/spawn_callee_current_cycles.c @@ -1,15 +1,28 @@ #include -#include +#include #include #include "ckb_syscalls.h" -int main(int argc, char *argv[]) { - int caller_cycles = atoi(argv[0]); - // Callee's current cycles must > caller's current cycles. - int callee_cycles = ckb_current_cycles(); - if (callee_cycles < caller_cycles + 100000) { - return 1; - } - return 0; +int atoi(const char *s) { + int n = 0, neg = 0; + switch (*s) { + case '-': + neg = 1; + case '+': + s++; + } + /* Compute n as a negative number to avoid overflow on INT_MIN */ + while (_is_digit(*s)) n = 10 * n - (*s++ - '0'); + return neg ? n : -n; +} + +int main(int argc, const char *argv[]) { + int caller_cycles = atoi(argv[0]); + // Callee's current cycles must > caller's current cycles. + int callee_cycles = ckb_current_cycles(); + if (callee_cycles < caller_cycles + 100000) { + return 1; + } + return 0; } diff --git a/script/testdata/spawn_callee_exec_callee b/script/testdata/spawn_callee_exec_callee index 1686432359..e7e344be7a 100755 Binary files a/script/testdata/spawn_callee_exec_callee 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 index 76e8197013..a53d0f5123 100644 --- a/script/testdata/spawn_callee_exec_callee.c +++ b/script/testdata/spawn_callee_exec_callee.c @@ -1 +1,3 @@ +#include + int main() { return 0; } diff --git a/script/testdata/spawn_callee_exec_caller b/script/testdata/spawn_callee_exec_caller index 0a3f6b6016..671dd0acd2 100755 Binary files a/script/testdata/spawn_callee_exec_caller 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 index 22723c1f0d..289a95b699 100644 --- a/script/testdata/spawn_callee_exec_caller.c +++ b/script/testdata/spawn_callee_exec_caller.c @@ -1,6 +1,8 @@ +#include #include "ckb_syscalls.h" int main() { - syscall(2043, 2, 3, 0, 0, 0, NULL); - return -1; + // syscall exec + 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 deleted file mode 100755 index 94b6c711f7..0000000000 Binary files a/script/testdata/spawn_callee_get_memory_limit and /dev/null differ diff --git a/script/testdata/spawn_callee_get_memory_limit.c b/script/testdata/spawn_callee_get_memory_limit.c deleted file mode 100644 index 574544f877..0000000000 --- a/script/testdata/spawn_callee_get_memory_limit.c +++ /dev/null @@ -1,3 +0,0 @@ -#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 index c0819018bb..3b2f6684d2 100755 Binary files a/script/testdata/spawn_callee_out_of_cycles 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 index dbaa71d22a..caad7a35f3 100644 --- a/script/testdata/spawn_callee_out_of_cycles.c +++ b/script/testdata/spawn_callee_out_of_cycles.c @@ -1,8 +1,10 @@ +#include + int fib(int n) { - if (n < 2) { - return n; - } - return fib(n - 1) + fib(n - 2); + 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 deleted file mode 100755 index b66210df7b..0000000000 Binary files a/script/testdata/spawn_callee_set_content and /dev/null differ diff --git a/script/testdata/spawn_callee_set_content.c b/script/testdata/spawn_callee_set_content.c deleted file mode 100644 index ed376ba5ce..0000000000 --- a/script/testdata/spawn_callee_set_content.c +++ /dev/null @@ -1,18 +0,0 @@ -#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 index ecc962b30c..8f5d76ed3b 100755 Binary files a/script/testdata/spawn_callee_strcat and b/script/testdata/spawn_callee_strcat differ diff --git a/script/testdata/spawn_callee_strcat.c b/script/testdata/spawn_callee_strcat.c index f2f3f56e6c..56daa801d8 100644 --- a/script/testdata/spawn_callee_strcat.c +++ b/script/testdata/spawn_callee_strcat.c @@ -2,16 +2,31 @@ #include #include "ckb_syscalls.h" +#include "spawn_utils.h" + +char *strcat(char *restrict dest, const char *restrict src) { + strcpy(dest + strlen(dest), src); + return dest; +} 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; + int err = 0; + char content[80]; + for (int i = 0; i < argc; i++) { + strcat(content, argv[i]); + } + size_t content_size = (uint64_t)strlen(content); + uint64_t fds[2] = {0}; + uint64_t length = countof(fds); + err = ckb_inherited_file_descriptors(fds, &length); + CHECK(err); + CHECK2(length == 2, ErrorCommon); + size_t content_size2 = content_size; + printf("fds[CKB_STDOUT] = %d", fds[CKB_STDOUT]); + err = ckb_write(fds[CKB_STDOUT], content, &content_size); + CHECK(err); + CHECK2(content_size2 == content_size, ErrorWrite); + +exit: + return err; } diff --git a/script/testdata/spawn_caller_current_cycles b/script/testdata/spawn_caller_current_cycles index efa2fcec4d..d4d464dd27 100755 Binary files a/script/testdata/spawn_caller_current_cycles and b/script/testdata/spawn_caller_current_cycles differ diff --git a/script/testdata/spawn_caller_current_cycles.c b/script/testdata/spawn_caller_current_cycles.c index f508cd3966..8712b2a1d2 100644 --- a/script/testdata/spawn_caller_current_cycles.c +++ b/script/testdata/spawn_caller_current_cycles.c @@ -1,41 +1,26 @@ #include -#include +#include #include #include "ckb_syscalls.h" +#include "spawn_utils.h" int fib(int n) { - if (n < 2) { - return n; - } - return fib(n - 1) + fib(n - 2); + if (n < 2) { + return n; + } + return fib(n - 1) + fib(n - 2); } int main() { - // Use invalid calculations to make the current cycles a larger value. - if (fib(20) != 6765) { - return 1; - } + // Use invalid calculations to make the current cycles a larger value. + if (fib(20) != 6765) { + return 1; + } - int cycles = ckb_current_cycles(); - char buffer[8]; - itoa(cycles, buffer, 10); - const char *argv[] = { &buffer[0] }; - int8_t spawn_exit_code = 255; - uint8_t spawn_content[80] = {}; - uint64_t spawn_content_length = 80; - spawn_args_t spgs = { - .memory_limit = 8, - .exit_code = &spawn_exit_code, - .content = &spawn_content[0], - .content_length = &spawn_content_length, - }; - int success = ckb_spawn(1, 3, 0, 1, argv, &spgs); - if (success != 0) { - return 1; - } - if (spawn_exit_code != 0) { - return 1; - } - return 0; + int cycles = ckb_current_cycles(); + char buffer[16] = {0}; + sprintf_(buffer, "%d", cycles); + const char *argv[] = {&buffer[0], 0}; + return simple_spawn_args(1, 1, argv); } diff --git a/script/testdata/spawn_caller_exec b/script/testdata/spawn_caller_exec index 429e98b60b..c2496f1f4f 100755 Binary files a/script/testdata/spawn_caller_exec and b/script/testdata/spawn_caller_exec differ diff --git a/script/testdata/spawn_caller_exec.c b/script/testdata/spawn_caller_exec.c index 614342713f..7fc5caa6e9 100644 --- a/script/testdata/spawn_caller_exec.c +++ b/script/testdata/spawn_caller_exec.c @@ -1,14 +1,5 @@ -#include -#include +#include -#include "ckb_syscalls.h" +#include "spawn_utils.h" -int main() { - spawn_args_t spgs = { - .memory_limit = 8, - .exit_code = NULL, - .content = NULL, - .content_length = NULL, - }; - return ckb_spawn(1, 3, 0, 0, NULL, &spgs); -} +int main() { return simple_spawn(1); } diff --git a/script/testdata/spawn_caller_get_memory_limit b/script/testdata/spawn_caller_get_memory_limit deleted file mode 100755 index 5b600a24e6..0000000000 Binary files a/script/testdata/spawn_caller_get_memory_limit and /dev/null differ diff --git a/script/testdata/spawn_caller_get_memory_limit.c b/script/testdata/spawn_caller_get_memory_limit.c deleted file mode 100644 index 6b6ea48662..0000000000 --- a/script/testdata/spawn_caller_get_memory_limit.c +++ /dev/null @@ -1,46 +0,0 @@ -#include -#include - -#include "ckb_syscalls.h" - -int main() { - int8_t spawn_exit_code; - uint8_t spawn_content[80] = {}; - uint64_t spawn_content_length = 80; - spawn_args_t spgs = { - .memory_limit = 0, - .exit_code = &spawn_exit_code, - .content = &spawn_content[0], - .content_length = &spawn_content_length, - }; - uint64_t success = 0; - - spgs.memory_limit = 3; - success = ckb_spawn(1, 3, 0, 0, NULL, &spgs); - if (success != 0) { - return 1; - } - if (spawn_exit_code != 3) { - return 1; - } - - spgs.memory_limit = 7; - success = ckb_spawn(1, 3, 0, 0, NULL, &spgs); - if (success != 0) { - return 1; - } - if (spawn_exit_code != 7) { - return 1; - } - - spgs.memory_limit = 8; - success = ckb_spawn(1, 3, 0, 0, NULL, &spgs); - if (success != 0) { - return 1; - } - if (spawn_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 index d4ac5f515c..158c256802 100755 Binary files a/script/testdata/spawn_caller_out_of_cycles 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 index 3818a84001..ee5e94f414 100644 --- a/script/testdata/spawn_caller_out_of_cycles.c +++ b/script/testdata/spawn_caller_out_of_cycles.c @@ -1,18 +1,3 @@ -#include -#include +#include "spawn_utils.h" -#include "ckb_syscalls.h" - -int main() { - spawn_args_t spgs = { - .memory_limit = 8, - .exit_code = NULL, - .content = NULL, - .content_length = NULL, - }; - uint64_t success = ckb_spawn(1, 3, 0, 0, NULL, &spgs); - if (success == 0) { - return 1; - } - return 0; -} +int main() { return simple_spawn(1); } diff --git a/script/testdata/spawn_caller_out_of_cycles_wrap b/script/testdata/spawn_caller_out_of_cycles_wrap index 1c3a8da922..3a1090c90c 100755 Binary files a/script/testdata/spawn_caller_out_of_cycles_wrap 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 index bbb717e58c..b823b50fd6 100644 --- a/script/testdata/spawn_caller_out_of_cycles_wrap.c +++ b/script/testdata/spawn_caller_out_of_cycles_wrap.c @@ -1,19 +1,3 @@ -#include -#include +#include "spawn_utils.h" -#include "ckb_syscalls.h" - -int main() { - int8_t spawn_exit_code = 255; - spawn_args_t spgs = { - .memory_limit = 8, - .exit_code = &spawn_exit_code, - .content = NULL, - .content_length = NULL, - }; - uint64_t success = ckb_spawn(2, 3, 0, 0, NULL, &spgs); - if (success != 0) { - return 1; - } - return spawn_exit_code; -} +int main() { return simple_spawn(2); } diff --git a/script/testdata/spawn_caller_set_content b/script/testdata/spawn_caller_set_content deleted file mode 100755 index f610c38817..0000000000 Binary files a/script/testdata/spawn_caller_set_content and /dev/null differ diff --git a/script/testdata/spawn_caller_set_content.c b/script/testdata/spawn_caller_set_content.c deleted file mode 100644 index 026ef1bcfa..0000000000 --- a/script/testdata/spawn_caller_set_content.c +++ /dev/null @@ -1,95 +0,0 @@ -#include -#include - -#include "ckb_syscalls.h" - -int main_lt_content_length() { - int8_t spawn_exit_code = -1; - uint8_t spawn_content[10] = {}; - uint64_t spawn_content_length = 10; - spawn_args_t spgs = { - .memory_limit = 8, - .exit_code = &spawn_exit_code, - .content = &spawn_content[0], - .content_length = &spawn_content_length, - }; - const char *argv[] = {"8", "8"}; - uint64_t success = 0; - - success = ckb_spawn(1, 3, 0, 2, argv, &spgs); - if (success != 0) { - return 1; - } - if (spawn_exit_code != 0) { - return 1; - } - if (spawn_content_length != 8) { - return 1; - } - return 0; -} - -int main_eq_content_length() { - int8_t spawn_exit_code = -1; - uint8_t spawn_content[10] = {}; - uint64_t spawn_content_length = 10; - spawn_args_t spgs = { - .memory_limit = 8, - .exit_code = &spawn_exit_code, - .content = &spawn_content[0], - .content_length = &spawn_content_length, - }; - const char *argv[] = {"10", "10"}; - uint64_t success = 0; - - success = ckb_spawn(1, 3, 0, 2, argv, &spgs); - if (success != 0) { - return 1; - } - if (spawn_exit_code != 0) { - return 1; - } - if (spawn_content_length != 10) { - return 1; - } - return 0; -} - -int main_gt_content_length() { - int8_t spawn_exit_code = -1; - uint8_t spawn_content[10] = {}; - uint64_t spawn_content_length = 10; - spawn_args_t spgs = { - .memory_limit = 8, - .exit_code = &spawn_exit_code, - .content = &spawn_content[0], - .content_length = &spawn_content_length, - }; - const char *argv[] = {"12", "10"}; - uint64_t success = 0; - - success = ckb_spawn(1, 3, 0, 2, argv, &spgs); - if (success != 0) { - return 1; - } - if (spawn_exit_code != 0) { - return 1; - } - if (spawn_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 index 1e1ed6edb3..f56aafa13f 100755 Binary files a/script/testdata/spawn_caller_strcat and b/script/testdata/spawn_caller_strcat differ diff --git a/script/testdata/spawn_caller_strcat.c b/script/testdata/spawn_caller_strcat.c index 466547502d..98eed197f0 100644 --- a/script/testdata/spawn_caller_strcat.c +++ b/script/testdata/spawn_caller_strcat.c @@ -2,30 +2,32 @@ #include #include "ckb_syscalls.h" +#include "spawn_utils.h" int main() { - const char *argv[] = {"hello", "world"}; - int8_t spawn_exit_code = 255; - uint8_t spawn_content[80] = {}; - uint64_t spawn_content_length = 80; - spawn_args_t spgs = { - .memory_limit = 8, - .exit_code = &spawn_exit_code, - .content = &spawn_content[0], - .content_length = &spawn_content_length, - }; - int success = ckb_spawn(1, 3, 0, 2, argv, &spgs); - if (success != 0) { - return 1; - } - if (spawn_exit_code != 0) { - return 1; - } - if (strlen((char *)spawn_content) != 10) { - return 1; - } - if (strcmp((char *)spawn_content, "helloworld") != 0) { - return 1; - } - return 0; + int err = 0; + const char *argv[] = {"hello", "world"}; + uint64_t pid = 0; + uint64_t fds[2] = {0}; + uint64_t inherited_fds[3] = {0}; + err = create_std_fds(fds, inherited_fds); + CHECK(err); + + spawn_args_t spgs = { + .argc = 2, + .argv = argv, + .process_id = &pid, + .inherited_fds = inherited_fds, + }; + err = ckb_spawn(1, CKB_SOURCE_CELL_DEP, 0, 0, &spgs); + CHECK(err); + uint8_t buffer[1024] = {0}; + size_t length = 1024; + err = ckb_read(fds[CKB_STDIN], buffer, &length); + CHECK(err); + err = memcmp("helloworld", buffer, length); + CHECK(err); + +exit: + return err; } diff --git a/script/testdata/spawn_caller_strcat_data_hash b/script/testdata/spawn_caller_strcat_data_hash deleted file mode 100755 index 9d32803cd0..0000000000 Binary files a/script/testdata/spawn_caller_strcat_data_hash and /dev/null differ diff --git a/script/testdata/spawn_caller_strcat_data_hash.c b/script/testdata/spawn_caller_strcat_data_hash.c deleted file mode 100644 index f21987dffc..0000000000 --- a/script/testdata/spawn_caller_strcat_data_hash.c +++ /dev/null @@ -1,39 +0,0 @@ -#include -#include -#include - -#include "ckb_exec.h" -#include "ckb_syscalls.h" - -int main() { - const char *argv[] = {"hello", "world"}; - - int8_t spawn_exit_code = -1; - uint8_t spawn_content[80] = {}; - uint64_t spawn_content_length = 80; - spawn_args_t spgs = { - .memory_limit = 8, - .exit_code = &spawn_exit_code, - .content = &spawn_content[0], - .content_length = &spawn_content_length, - }; - uint8_t hash[32] = {}; - uint32_t hash_len = 0; - _exec_hex2bin( - "1dc91c6a0d93ffba6d11bae8bc74d4cb89506e58203e7361434e77f24eb7b11f", hash, - 32, &hash_len); - int success = ckb_spawn_cell(hash, 0, 0, 0, 2, argv, &spgs); - if (success != 0) { - return 1; - } - if (spawn_exit_code != 0) { - return 1; - } - if (strlen((char *)spawn_content) != 10) { - return 1; - } - if (strcmp((char *)spawn_content, "helloworld") != 0) { - return 1; - } - return 0; -} diff --git a/script/testdata/spawn_caller_strcat_wrap b/script/testdata/spawn_caller_strcat_wrap index 1c3a8da922..10aa1fa266 100755 Binary files a/script/testdata/spawn_caller_strcat_wrap 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 index bbb717e58c..25604f856f 100644 --- a/script/testdata/spawn_caller_strcat_wrap.c +++ b/script/testdata/spawn_caller_strcat_wrap.c @@ -1,19 +1,7 @@ +#include #include #include -#include "ckb_syscalls.h" +#include "spawn_utils.h" -int main() { - int8_t spawn_exit_code = 255; - spawn_args_t spgs = { - .memory_limit = 8, - .exit_code = &spawn_exit_code, - .content = NULL, - .content_length = NULL, - }; - uint64_t success = ckb_spawn(2, 3, 0, 0, NULL, &spgs); - if (success != 0) { - return 1; - } - return spawn_exit_code; -} +int main() { return simple_spawn(2); } diff --git a/script/testdata/spawn_cases b/script/testdata/spawn_cases new file mode 100755 index 0000000000..2742af8b3e Binary files /dev/null and b/script/testdata/spawn_cases differ diff --git a/script/testdata/spawn_cases.c b/script/testdata/spawn_cases.c new file mode 100644 index 0000000000..1729bf0bea --- /dev/null +++ b/script/testdata/spawn_cases.c @@ -0,0 +1,644 @@ +#include "spawn_utils.h" + +int parent_simple_read_write(uint64_t* pid) { + int err = 0; + const char* argv[] = {"", 0}; + uint64_t fds[2] = {0}; + + err = full_spawn(0, 1, argv, fds, pid); + // write + uint8_t block[11] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + for (size_t i = 0; i < 7; i++) { + size_t actual_length = 0; + err = write_exact(fds[CKB_STDOUT], block, sizeof(block), &actual_length); + CHECK(err); + CHECK2(actual_length == sizeof(block), -2); + } + // read + for (size_t i = 0; i < 7; i++) { + uint8_t block[11] = {0}; + size_t actual_length = 0; + err = read_exact(fds[CKB_STDIN], block, sizeof(block), &actual_length); + CHECK(err); + CHECK2(actual_length == sizeof(block), -2); + for (size_t j = 0; j < sizeof(block); j++) { + CHECK2(block[j] == 0xFF, -2); + } + } +exit: + return err; +} + +int child_simple_read_write() { + int err = 0; + uint64_t inherited_fds[2]; + size_t inherited_fds_length = 2; + err = ckb_inherited_file_descriptors(inherited_fds, &inherited_fds_length); + // read + for (size_t i = 0; i < 11; i++) { + uint8_t block[7] = {0}; + size_t actual_length = 0; + err = read_exact(inherited_fds[CKB_STDIN], block, sizeof(block), &actual_length); + CHECK(err); + CHECK2(actual_length == sizeof(block), -2); + for (size_t j = 0; j < sizeof(block); j++) { + CHECK2(block[j] == 0xFF, -3); + } + } + // write + uint8_t block[11] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + for (size_t i = 0; i < 7; i++) { + size_t actual_length = 0; + err = write_exact(inherited_fds[CKB_STDOUT], block, sizeof(block), &actual_length); + CHECK(err); + CHECK2(actual_length == sizeof(block), -2); + } +exit: + return err; +} + +int parent_write_dead_lock(uint64_t* pid) { + int err = 0; + const char* argv[] = {"", 0}; + uint64_t fds[2] = {0}; + err = full_spawn(0, 1, argv, fds, pid); + CHECK(err); + uint8_t data[10]; + size_t data_length = sizeof(data); + err = ckb_write(fds[CKB_STDOUT], data, &data_length); + CHECK(err); + +exit: + return err; +} + +int child_write_dead_lock() { + int err = 0; + uint64_t inherited_fds[3] = {0}; + size_t inherited_fds_length = 3; + err = ckb_inherited_file_descriptors(inherited_fds, &inherited_fds_length); + CHECK(err); + uint8_t data[10]; + size_t data_length = sizeof(data); + err = ckb_write(inherited_fds[CKB_STDOUT], data, &data_length); + CHECK(err); +exit: + return err; +} + +int parent_invalid_fd(uint64_t* pid) { + uint64_t invalid_fd = 0xff; + uint8_t data[4]; + size_t data_length = sizeof(data); + int err = ckb_read(invalid_fd, data, &data_length); + CHECK2(err != 0, -2); + err = ckb_write(invalid_fd, data, &data_length); + CHECK2(err != 0, -2); + + uint64_t fds[2] = {0}; + err = ckb_pipe(fds); + // read on write fd + err = ckb_read(fds[CKB_STDOUT], data, &data_length); + CHECK2(err != 0, -3); + // write on read fd + err = ckb_write(fds[CKB_STDIN], data, &data_length); + CHECK2(err != 0, -3); + + // pass fd to child to make it invalid + const char* argv[] = {"", 0}; + uint64_t inherited_fds[2] = {fds[0], 0}; + spawn_args_t spgs = {.argc = 1, .argv = argv, .process_id = pid, .inherited_fds = inherited_fds}; + err = ckb_spawn(0, CKB_SOURCE_CELL_DEP, 0, 0, &spgs); + CHECK(err); + err = ckb_read(fds[0], data, &data_length); + CHECK2(err != 0, -3); + + // write to fd but the other end is closed + err = ckb_pipe(fds); + CHECK(err); + err = ckb_close(fds[CKB_STDIN]); + CHECK(err); + err = ckb_write(fds[CKB_STDOUT], data, &data_length); + CHECK2(err == CKB_OTHER_END_CLOSED, -2); + + // read from fd but the ohter end is closed + err = ckb_pipe(fds); + CHECK(err); + err = ckb_close(fds[CKB_STDOUT]); + CHECK(err); + err = ckb_read(fds[CKB_STDIN], data, &data_length); + CHECK2(err == CKB_OTHER_END_CLOSED, -2); + err = 0; +exit: + return err; +} + +int parent_wait_dead_lock(uint64_t* pid) { + int err = 0; + const char* argv[] = {"", 0}; + uint64_t fds[2] = {0}; + err = full_spawn(0, 1, argv, fds, pid); + CHECK(err); + +exit: + return err; +} + +int child_wait_dead_lock() { + uint64_t pid = 0; // parent pid + int8_t exit_code = 0; + return ckb_wait(pid, &exit_code); +} + +int parent_read_write_with_close(uint64_t* pid) { + int err = 0; + const char* argv[] = {"", 0}; + uint64_t fds[2] = {0}; + err = full_spawn(0, 1, argv, fds, pid); + // write util the other end is closed + uint8_t block[100]; + for (size_t i = 0; i < sizeof(block); i++) { + block[i] = 0xFF; + } + size_t actual_length = 0; + err = write_exact(fds[CKB_STDOUT], block, sizeof(block), &actual_length); + CHECK(err); + CHECK2(actual_length == sizeof(block), -2); + + err = 0; +exit: + return err; +} + +int child_read_write_with_close() { + int err = 0; + uint64_t inherited_fds[2]; + size_t inherited_fds_length = 2; + err = ckb_inherited_file_descriptors(inherited_fds, &inherited_fds_length); + CHECK(err); + + // read 100 bytes and close + uint8_t block[100] = {0}; + size_t actual_length = 0; + err = read_exact(inherited_fds[CKB_STDIN], block, sizeof(block), &actual_length); + CHECK(err); + CHECK2(actual_length == sizeof(block), -2); + for (size_t j = 0; j < sizeof(block); j++) { + CHECK2(block[j] == 0xFF, -3); + } + err = ckb_close(inherited_fds[CKB_STDIN]); + CHECK(err); + +exit: + return err; +} + +int parent_wait_multiple(uint64_t* pid) { + int err = 0; + const char* argv[] = {"", 0}; + uint64_t fds[2] = {0}; + full_spawn(0, 1, argv, fds, pid); + CHECK(err); + + int8_t exit_code = 0; + err = ckb_wait(*pid, &exit_code); + CHECK(err); + // second wait is not allowed + err = ckb_wait(*pid, &exit_code); + CHECK2(err != 0, -2); + err = 0; + // spawn a new valid one, make ckb_wait happy + full_spawn(0, 1, argv, fds, pid); + CHECK(err); + +exit: + return err; +} + +int parent_inherited_fds(uint64_t* pid) { + int err = 0; + const char* argv[] = {"", 0}; + uint64_t inherited_fds[11] = {0}; + for (size_t i = 0; i < 5; i++) { + err = ckb_pipe(&inherited_fds[i * 2]); + CHECK(err); + } + spawn_args_t spgs = {.argc = 1, .argv = argv, .process_id = pid, .inherited_fds = inherited_fds}; + err = ckb_spawn(0, CKB_SOURCE_CELL_DEP, 0, 0, &spgs); + CHECK(err); +exit: + return err; +} + +int child_inherited_fds() { + int err = 0; + + // correct way to get fd length + size_t fds_length = 0; + err = ckb_inherited_file_descriptors(0, &fds_length); + CHECK2(fds_length == 10, -2); + + // get part of fds + uint64_t fds[11] = {0}; + fds_length = 1; + err = ckb_inherited_file_descriptors(fds, &fds_length); + CHECK(err); + CHECK2(fds_length == 10, -2); + CHECK2(fds[0] == 2, -2); + + // get all fds + fds_length = 10; + err = ckb_inherited_file_descriptors(fds, &fds_length); + CHECK(err); + CHECK2(fds_length == 10, -2); + for (size_t i = 0; i < 10; i++) { + CHECK2(fds[i] == (i + 2), -2); + } +exit: + return err; +} + +int parent_inherited_fds_without_owner(uint64_t* pid) { + int err = 0; + const char* argv[] = {"", 0}; + uint64_t fds[3] = {0xFF, 0xEF, 0}; + + spawn_args_t spgs = {.argc = 1, .argv = argv, .process_id = pid, .inherited_fds = fds}; + err = ckb_spawn(0, CKB_SOURCE_CELL_DEP, 0, 0, &spgs); + CHECK2(err == CKB_INVALID_FD, -2); + + // create valid fds + err = ckb_pipe(fds); + CHECK(err); + // then transferred by spawn + err = ckb_spawn(0, CKB_SOURCE_CELL_DEP, 0, 0, &spgs); + CHECK(err); + + // the fds are already transferred. An error expected. + err = ckb_spawn(0, CKB_SOURCE_CELL_DEP, 0, 0, &spgs); + CHECK2(err == CKB_INVALID_FD, -2); + err = 0; +exit: + return err; +} + +int parent_read_then_close(uint64_t* pid) { + int err = 0; + const char* argv[] = {"", 0}; + uint64_t fds[2] = {0}; + err = full_spawn(0, 1, argv, fds, pid); + CHECK(err); + err = ckb_close(fds[CKB_STDOUT]); + CHECK(err); +exit: + return err; +} + +int child_read_then_close() { + int err = 0; + uint64_t fds[2] = {0}; + uint64_t fds_length = 2; + err = ckb_inherited_file_descriptors(fds, &fds_length); + CHECK(err); + uint8_t data[8]; + size_t data_len = sizeof(data); + // first read to return 0 byte without error + err = ckb_read(fds[CKB_STDIN], data, &data_len); + CHECK(err); + CHECK2(data_len == 0, -2); + // second read to return error(other end closed) + err = ckb_read(fds[CKB_STDIN], data, &data_len); + CHECK2(err == CKB_OTHER_END_CLOSED, -2); + + err = 0; +exit: + return err; +} + +int parent_max_vms_count() { + const char* argv[2] = {"", 0}; + return simple_spawn_args(0, 1, argv); +} + +int child_max_vms_count() { + const char* argv[2] = {"", 0}; + int err = simple_spawn_args(0, 1, argv); + CHECK2(err == 0 || err == CKB_MAX_VMS_SPAWNED, -2); + err = 0; +exit: + return err; +} + +int parent_max_fds_limit() { + const char* argv[2] = {"", 0}; + int err = 0; + uint64_t fd[2] = {0}; + for (int i = 0; i < 16; i++) { + err = ckb_pipe(fd); + CHECK(err); + } + err = simple_spawn_args(0, 1, argv); +exit: + return err; +} + +int child_max_fds_limit() { + int err = 0; + uint64_t fd[2] = {0}; + for (int i = 0; i < 16; i++) { + err = ckb_pipe(fd); + CHECK(err); + } + // Create up to 64 pipes. + err = ckb_pipe(fd); + err = CKB_MAX_FDS_CREATED - 9; + +exit: + return err; +} + +int parent_close_invalid_fd() { + uint64_t fds[2] = {0}; + int err = ckb_pipe(fds); + CHECK(err); + + err = ckb_close(fds[CKB_STDIN] + 32); + CHECK2(err == 6, -1); + + err = ckb_close(fds[CKB_STDIN]); + CHECK(err); + err = ckb_close(fds[CKB_STDOUT]); + CHECK(err); + + err = ckb_close(fds[CKB_STDIN]); + CHECK2(err == 6, -1); + err = ckb_close(fds[CKB_STDOUT]); + CHECK2(err == 6, -1); + + err = 0; +exit: + return err; +} + +int parent_write_closed_fd(uint64_t* pid) { + int err = 0; + const char* argv[] = {"", 0}; + uint64_t fds[2] = {0}; + err = full_spawn(0, 1, argv, fds, pid); + CHECK(err); + + // int exit_code = 0; + uint8_t block[7] = {1, 2, 3, 4, 5, 6, 7}; + size_t actual_length = 0; + err = read_exact(fds[CKB_STDIN], block, sizeof(block), &actual_length); + CHECK(err); + err = ckb_close(fds[CKB_STDIN]); + CHECK(err); + + err = ckb_close(fds[CKB_STDOUT]); +exit: + return err; +} + +int child_write_closed_fd() { + int err = 0; + uint64_t inherited_fds[2]; + size_t inherited_fds_length = 2; + err = ckb_inherited_file_descriptors(inherited_fds, &inherited_fds_length); + CHECK(err); + + uint8_t block[7] = {0}; + size_t actual_length = 0; + err = write_exact(inherited_fds[CKB_STDOUT], block, sizeof(block), &actual_length); + CHECK(err); + err = write_exact(inherited_fds[CKB_STDOUT], block, sizeof(block), &actual_length); + CHECK(err); + + ckb_close(inherited_fds[CKB_STDIN]); + ckb_close(inherited_fds[CKB_STDOUT]); + +exit: + return err; +} + +int parent_pid(uint64_t* pid) { + int err = 0; + + uint64_t cur_pid = ckb_process_id(); + + uint64_t pid_c1 = 0; + const char* argv[] = {"", 0}; + uint64_t fds_1[2] = {0}; + err = full_spawn(0, 1, argv, fds_1, &pid_c1); + CHECK2(pid_c1 != cur_pid, -1); + + uint64_t pid_c2 = 0; + uint64_t fds_2[2] = {0}; + err = full_spawn(0, 1, argv, fds_2, &pid_c2); + CHECK(err); + CHECK2(pid_c2 != cur_pid, -1); + + uint64_t child_pid_1 = 0; + size_t actual_length = 0; + err = read_exact(fds_1[CKB_STDIN], &child_pid_1, sizeof(child_pid_1), &actual_length); + CHECK(err); + CHECK2(child_pid_1 == pid_c1, -1); + + uint64_t child_pid_2 = 0; + err = read_exact(fds_2[CKB_STDIN], &child_pid_2, sizeof(child_pid_2), &actual_length); + CHECK(err); + CHECK2(child_pid_2 == pid_c2, -1); + +exit: + return err; +} + +int child_pid() { + uint64_t pid = ckb_process_id(); + + int err = 0; + uint64_t fds[2] = {0}; + uint64_t fds_length = 2; + err = ckb_inherited_file_descriptors(fds, &fds_length); + CHECK(err); + + // send pid + size_t actual_length = 0; + err = write_exact(fds[CKB_STDOUT], &pid, sizeof(pid), &actual_length); + CHECK(err); + +exit: + return err; +} + +int parent_spawn_offset_out_of_bound(uint64_t* pid) { + int err = 0; + + const char* argv[] = {"", 0}; + spawn_args_t spgs = {.argc = 1, .argv = argv, .process_id = pid, .inherited_fds = NULL}; + uint64_t offset = 1024 * 1024 * 1024 * 1; + uint64_t length = 0; + uint64_t bounds = (offset << 32) + length; + err = ckb_spawn(0, CKB_SOURCE_CELL_DEP, 0, bounds, &spgs); + CHECK2(err == 3, -1); // SLICE_OUT_OF_BOUND + err = 0; +exit: + return err; +} + +int parent_spawn_length_out_of_bound(uint64_t* pid) { + int err = 0; + + const char* argv[] = {"", 0}; + spawn_args_t spgs = {.argc = 1, .argv = argv, .process_id = pid, .inherited_fds = NULL}; + uint64_t offset = 1024 * 15; + uint64_t length = 1024; + uint64_t bounds = (offset << 32) + length; + + err = ckb_spawn(0, CKB_SOURCE_CELL_DEP, 0, bounds, &spgs); + CHECK2(err == 3, -1); // SLICE_OUT_OF_BOUND + err = 0; +exit: + return err; +} + +int parent_invaild_index(uint64_t* pid) { + int err = 0; + const char* argv[] = {"", 0}; + uint64_t inherited_fds[11] = {0}; + for (size_t i = 0; i < 5; i++) { + err = ckb_pipe(&inherited_fds[i * 2]); + CHECK(err); + } + spawn_args_t spgs = {.argc = 1, .argv = argv, .process_id = pid, .inherited_fds = inherited_fds}; + err = ckb_spawn(0xFFFFFFFFF, CKB_SOURCE_CELL_DEP, 0, 0, &spgs); + CHECK2(err == 1, -1); // INDEX_OUT_OF_BOUND + err = 0; + +exit: + return err; +} + +int parent_index_out_of_bound(uint64_t* pid) { + int err = 0; + const char* argv[] = {"", 0}; + uint64_t inherited_fds[11] = {0}; + for (size_t i = 0; i < 5; i++) { + err = ckb_pipe(&inherited_fds[i * 2]); + CHECK(err); + } + spawn_args_t spgs = {.argc = 1, .argv = argv, .process_id = pid, .inherited_fds = inherited_fds}; + err = ckb_spawn(2, CKB_SOURCE_CELL_DEP, 0, 0, &spgs); + CHECK2(err == 1, -1); // INDEX_OUT_OF_BOUND + err = 0; + +exit: + return err; +} + +int parent_entry(int case_id) { + int err = 0; + uint64_t pid = 0; + if (case_id == 1) { + err = parent_simple_read_write(&pid); + } else if (case_id == 2) { + err = parent_write_dead_lock(&pid); + } else if (case_id == 3) { + err = parent_invalid_fd(&pid); + } else if (case_id == 4) { + err = parent_wait_dead_lock(&pid); + } else if (case_id == 5) { + err = parent_read_write_with_close(&pid); + } else if (case_id == 6) { + err = parent_wait_multiple(&pid); + } else if (case_id == 7) { + err = parent_inherited_fds(&pid); + } else if (case_id == 8) { + err = parent_inherited_fds_without_owner(&pid); + } else if (case_id == 9) { + err = parent_read_then_close(&pid); + } else if (case_id == 10) { + err = parent_max_vms_count(&pid); + return err; + } else if (case_id == 11) { + err = parent_max_fds_limit(&pid); + return err; + } else if (case_id == 12) { + return parent_close_invalid_fd(&pid); + } else if (case_id == 13) { + return parent_write_closed_fd(&pid); + } else if (case_id == 14) { + return parent_pid(&pid); + } else if (case_id == 15) { + return parent_spawn_offset_out_of_bound(&pid); + } else if (case_id == 16) { + return parent_spawn_length_out_of_bound(&pid); + } else if (case_id == 17) { + return parent_invaild_index(&pid); + } else if (case_id == 18) { + return parent_index_out_of_bound(&pid); + } else { + CHECK2(false, -2); + } + CHECK(err); + int8_t exit_code = 0; + err = ckb_wait(pid, &exit_code); + CHECK(err); + CHECK(exit_code); + +exit: + return err; +} + +int child_entry(int case_id) { + if (case_id == 1) { + return child_simple_read_write(); + } else if (case_id == 2) { + return child_write_dead_lock(); + } else if (case_id == 3) { + return 0; + } else if (case_id == 4) { + return child_wait_dead_lock(); + } else if (case_id == 5) { + return child_read_write_with_close(); + } else if (case_id == 6) { + return 0; + } else if (case_id == 7) { + return child_inherited_fds(); + } else if (case_id == 8) { + return 0; + } else if (case_id == 9) { + return child_read_then_close(); + } else if (case_id == 10) { + return child_max_vms_count(); + } else if (case_id == 11) { + return child_max_fds_limit(); + } else if (case_id == 12) { + return 0; + } else if (case_id == 13) { + return child_write_closed_fd(); + } else if (case_id == 14) { + return child_pid(); + } else if (case_id == 15) { + return 0; + } else if (case_id == 16) { + return 0; + } else { + return -1; + } +} + +int main(int argc, const char* argv[]) { + uint8_t script_args[8]; + size_t script_args_length = 8; + int err = load_script_args(script_args, &script_args_length); + if (err) { + return err; + } + int case_id = (int)script_args[0]; + if (argc > 0) { + return child_entry(case_id); + } else { + return parent_entry(case_id); + } +} diff --git a/script/testdata/spawn_configurable_callee b/script/testdata/spawn_configurable_callee new file mode 100755 index 0000000000..20b1024ecd Binary files /dev/null and b/script/testdata/spawn_configurable_callee differ diff --git a/script/testdata/spawn_configurable_callee.c b/script/testdata/spawn_configurable_callee.c new file mode 100644 index 0000000000..92b849b35b --- /dev/null +++ b/script/testdata/spawn_configurable_callee.c @@ -0,0 +1,29 @@ +#include +#include + +#include "ckb_syscalls.h" +#include "spawn_utils.h" + +int main() { + int err = 0; + + uint64_t fds[2] = {0}; + uint64_t fds_len = countof(fds); + err = ckb_inherited_file_descriptors(fds, &fds_len); + CHECK(err); + CHECK2(fds_len == 2, ErrorCommon); + + uint8_t buffer[1024] = {0}; + size_t length; + length = 1024; + ckb_read_all(fds[CKB_STDIN], buffer, &length); + CHECK2(length == 12, ErrorCommon); + + err = ckb_write(fds[CKB_STDOUT], buffer, &length); + CHECK(err); + err = ckb_close(fds[CKB_STDOUT]); + CHECK(err); + +exit: + return err; +} diff --git a/script/testdata/spawn_configurable_caller b/script/testdata/spawn_configurable_caller new file mode 100755 index 0000000000..f1620466f9 Binary files /dev/null and b/script/testdata/spawn_configurable_caller differ diff --git a/script/testdata/spawn_configurable_caller.c b/script/testdata/spawn_configurable_caller.c new file mode 100644 index 0000000000..ba3184c133 --- /dev/null +++ b/script/testdata/spawn_configurable_caller.c @@ -0,0 +1,60 @@ +#include +#include + +#include "ckb_syscalls.h" +#include "spawn_utils.h" + +uint64_t read_u64_le(const uint8_t* src) { return *(const uint64_t*)src; } + +int main() { + int err = 0; + uint64_t n = 0; + + uint8_t args[32] = {0}; + n = countof(args); + err = load_script_args(args, &n); + CHECK(err); + CHECK2(n == 32, ErrorCommon); + uint64_t args_index = read_u64_le(&args[0x00]); + uint64_t args_source = read_u64_le(&args[0x08]); + uint64_t args_place = read_u64_le(&args[0x10]); + uint64_t args_bounds = read_u64_le(&args[0x18]); + printf("args.index = %llu", args_index); + printf("args.source = %llu", args_source); + printf("args.place = %llu", args_place); + printf("args.bounds = %llu", args_bounds); + + const char* argv[] = {}; + uint64_t pid = 0; + uint64_t fds[2] = {0}; + uint64_t inherited_fds[3] = {0}; + err = create_std_fds(fds, inherited_fds); + CHECK(err); + + spawn_args_t spgs = { + .argc = countof(argv), + .argv = argv, + .process_id = &pid, + .inherited_fds = inherited_fds, + }; + err = ckb_spawn(args_index, args_source, args_place, args_bounds, &spgs); + CHECK(err); + + size_t length = 0; + length = 12; + err = ckb_write(fds[CKB_STDOUT], "Hello World!", &length); + CHECK(err); + err = ckb_close(fds[CKB_STDOUT]); + CHECK(err); + + uint8_t buffer[1024] = {0}; + length = 1024; + err = ckb_read_all(fds[CKB_STDIN], buffer, &length); + CHECK(err); + CHECK2(length == 12, ErrorCommon); + err = memcmp("Hello World!", buffer, length); + CHECK(err); + +exit: + return err; +} diff --git a/script/testdata/spawn_current_memory b/script/testdata/spawn_current_memory deleted file mode 100755 index 417eaca599..0000000000 Binary files a/script/testdata/spawn_current_memory and /dev/null differ diff --git a/script/testdata/spawn_current_memory.c b/script/testdata/spawn_current_memory.c deleted file mode 100644 index b09c62cb0e..0000000000 --- a/script/testdata/spawn_current_memory.c +++ /dev/null @@ -1,28 +0,0 @@ -#include -#include - -#include "ckb_syscalls.h" - -int main(int argc, char *argv[]) { - int peak_memory = ckb_current_memory(); - if (peak_memory != (argc + 1) * 8) { - return 1; - } - if (peak_memory < 56) { - int spawn_argc = argc + 1; - const char *spawn_argv[] = {"", "", "", "", "", "", "", ""}; - int8_t spawn_exit_code = 255; - spawn_args_t spgs = { - .memory_limit = 8, - .exit_code = &spawn_exit_code, - .content = NULL, - .content_length = NULL, - }; - uint64_t success = ckb_spawn(0, 3, 0, spawn_argc, spawn_argv, &spgs); - if (success != 0) { - return success; - } - } else { - return 0; - } -} diff --git a/script/testdata/spawn_cycles b/script/testdata/spawn_cycles new file mode 100755 index 0000000000..77d40c54fe Binary files /dev/null and b/script/testdata/spawn_cycles differ diff --git a/script/testdata/spawn_cycles.c b/script/testdata/spawn_cycles.c new file mode 100644 index 0000000000..a338d73d4b --- /dev/null +++ b/script/testdata/spawn_cycles.c @@ -0,0 +1,179 @@ +#include +#include + +#include "ckb_syscalls.h" +#include "spawn_utils.h" + +const uint64_t SYSCALL_CYCLES_BASE = 500; +const uint64_t SPAWN_EXTRA_CYCLES_BASE = 100000; +const uint64_t SPAWN_SPAWN_YIELD_CYCLES_BASE = 800; + +int tic() { + static uint64_t tic = 0; + uint64_t cur_cycles = ckb_current_cycles(); + uint64_t toc = cur_cycles - tic; + tic = cur_cycles; + return toc; +} + +uint64_t cal_cycles(uint64_t nbase, uint64_t yield, uint64_t extra) { + uint64_t r = 0; + r += SYSCALL_CYCLES_BASE * nbase; + r += SPAWN_SPAWN_YIELD_CYCLES_BASE * yield; + r += SPAWN_EXTRA_CYCLES_BASE * extra; + return r; +} + +uint64_t cal_cycles_floor(uint64_t nbase, uint64_t yield, uint64_t extra) { + return cal_cycles(nbase, yield, extra); +} + +uint64_t cal_cycles_upper(uint64_t nbase, uint64_t yield, uint64_t extra) { + return cal_cycles(nbase, yield, extra) + 8192; +} + +int main() { + int err = 0; + int toc = 0; + uint64_t cid = ckb_process_id(); + uint64_t pid[5] = {0}; + uint64_t fds[5][2][3] = {0}; + uint64_t buf[256] = {0}; + uint64_t len = 0; + + switch (cid) { + case 0: + const char* argv[1] = {0}; + + for (int i = 1; i < 5; i++) { + toc = tic(); + err = ckb_pipe(buf); + CHECK(err); + toc = tic(); + CHECK2(toc > cal_cycles_floor(1, 1, 0), ErrorCommon); + CHECK2(toc < cal_cycles_upper(1, 1, 0), ErrorCommon); + fds[i][0][0] = buf[0]; + fds[i][1][1] = buf[1]; + + toc = tic(); + err = ckb_pipe(buf); + CHECK(err); + toc = tic(); + CHECK2(toc > cal_cycles_floor(1, 1, 0), ErrorCommon); + CHECK2(toc < cal_cycles_upper(1, 1, 0), ErrorCommon); + fds[i][0][1] = buf[1]; + fds[i][1][0] = buf[0]; + } + + // Living Process: 0 + // Living Process: 0, 1 + // Living Process: 0, 1, 2 + // Living Process: 0, 1, 2, 3 + // Living Process: 1, 2, 3, 4 + // Living Process: 0, 2, 3, 4 + for (int i = 1; i < 5; i++) { + toc = tic(); + spawn_args_t spgs = {.argc = 0, .argv = argv, .process_id = &pid[i], .inherited_fds = fds[i][1]}; + err = ckb_spawn(0, CKB_SOURCE_CELL_DEP, 0, 0, &spgs); + CHECK(err); + toc = tic(); + if (i < 4) { + CHECK2(toc > cal_cycles_floor(1, 1, 1), ErrorCommon); + CHECK2(toc < cal_cycles_upper(1, 1, 1), ErrorCommon); + } else { + CHECK2(toc > cal_cycles_floor(1, 1, 4), ErrorCommon); + CHECK2(toc < cal_cycles_upper(1, 1, 4), ErrorCommon); + } + } + + // Living Process: 0, 2, 3, 4 + // Living Process: 0, 1, 3, 4 + // Living Process: 0, 2, 3, 4 + for (int i = 1; i < 5; i++) { + len = 12; + toc = tic(); + err = ckb_write(fds[i][0][1], "Hello World!", &len); + toc = tic(); + CHECK(err); + if (i < 3) { + CHECK2(toc > cal_cycles_floor(1, 1, 2), ErrorCommon); + CHECK2(toc < cal_cycles_upper(1, 1, 2), ErrorCommon); + } else { + CHECK2(toc > cal_cycles_floor(1, 1, 0), ErrorCommon); + CHECK2(toc < cal_cycles_upper(1, 1, 0), ErrorCommon); + } + err = ckb_close(fds[i][0][1]); + CHECK(err); + toc = tic(); + CHECK2(toc > cal_cycles_floor(1, 1, 0), ErrorCommon); + CHECK2(toc < cal_cycles_upper(1, 1, 0), ErrorCommon); + } + + // Living Process: 0, 2, 3, 4 + // Living Process: 0, 1, 3, 4 + // Living Process: 0, 2, 3, 4 + // Living Process: 0, 3, 4 + // Living Process: 0, 4 + // Living Process: 0 + for (int i = 1; i < 5; i++) { + len = 1024; + toc = tic(); + err = ckb_read_all(fds[i][0][0], buf, &len); + CHECK(err); + toc = tic(); + if (i == 1) { + CHECK2(toc > cal_cycles_floor(1, 1, 2), ErrorCommon); + CHECK2(toc < cal_cycles_upper(1, 1, 2), ErrorCommon); + } + if (i == 2) { + CHECK2(toc > cal_cycles_floor(1, 1, 1), ErrorCommon); + CHECK2(toc < cal_cycles_upper(1, 1, 1), ErrorCommon); + } + if (i >= 3) { + CHECK2(toc > cal_cycles_floor(1, 1, 0), ErrorCommon); + CHECK2(toc < cal_cycles_upper(1, 1, 0), ErrorCommon); + } + CHECK2(len == 12, ErrorCommon); + err = memcmp("Hello World!", buf, len); + CHECK(err); + } + + for (int i = 1; i < 5; i++) { + int8_t exit_code = 255; + toc = tic(); + err = ckb_wait(pid[i], &exit_code); + CHECK(err); + toc = tic(); + CHECK2(toc > cal_cycles_floor(1, 1, 0), ErrorCommon); + CHECK2(toc < cal_cycles_upper(1, 1, 0), ErrorCommon); + CHECK(exit_code); + } + break; + case 1: + case 2: + case 3: + case 4: + len = 2; + toc = tic(); + err = ckb_inherited_file_descriptors(fds[cid][1], &len); + CHECK(err); + toc = tic(); + CHECK2(toc > cal_cycles_floor(1, 1, 0), ErrorCommon); + CHECK2(toc < cal_cycles_upper(1, 1, 0), ErrorCommon); + CHECK2(len == 2, ErrorCommon); + len = 1024; + err = ckb_read_all(fds[cid][1][0], buf, &len); + CHECK(err); + CHECK2(len == 12, ErrorCommon); + err = memcmp("Hello World!", buf, len); + CHECK(err); + err = ckb_write(fds[cid][1][1], buf, &len); + CHECK(err); + err = ckb_close(fds[cid][1][1]); + CHECK(err); + break; + } + +exit: + return err; +} diff --git a/script/testdata/spawn_dag b/script/testdata/spawn_dag new file mode 100755 index 0000000000..b88dedb7fd Binary files /dev/null and b/script/testdata/spawn_dag differ diff --git a/script/testdata/spawn_dag.c b/script/testdata/spawn_dag.c new file mode 100644 index 0000000000..b813e01797 --- /dev/null +++ b/script/testdata/spawn_dag.c @@ -0,0 +1,318 @@ +#define CKB_C_STDLIB_PRINTF 1 +#include +#include + +#include "spawn_dag.h" +#include "spawn_dag_escape_encoding.h" +#include "ckb_syscalls.h" + +#define INPUT_DATA_LENGTH (600 * 1024) +#define MAX_PIPE_COUNT 3200 +#define MAX_SPAWNED_VMS 1024 + +#define _BASE_ERROR_CODE 42 +#define ERROR_NO_SPACE_FOR_PIPES (_BASE_ERROR_CODE + 1) +#define ERROR_NOT_FOUND (_BASE_ERROR_CODE + 2) +#define ERROR_ENCODING (_BASE_ERROR_CODE + 3) +#define ERROR_ARGV (_BASE_ERROR_CODE + 4) +#define ERROR_TOO_MANY_SPAWNS (_BASE_ERROR_CODE + 5) +#define ERROR_PIPE_CLOSED (_BASE_ERROR_CODE + 6) +#define ERROR_CORRUPTED_DATA (_BASE_ERROR_CODE + 7) + +typedef struct { + uint64_t indices[MAX_PIPE_COUNT]; + uint64_t ids[MAX_PIPE_COUNT + 1]; + size_t used; +} pipes_t; + +void pipes_init(pipes_t *pipes) { + pipes->used = 0; + pipes->ids[pipes->used] = 0; +} + +int pipes_add(pipes_t *pipes, uint64_t index, uint64_t id) { + if (pipes->used >= MAX_PIPE_COUNT) { + return ERROR_NO_SPACE_FOR_PIPES; + } + pipes->indices[pipes->used] = index; + pipes->ids[pipes->used] = id; + pipes->used++; + pipes->ids[pipes->used] = 0; + return CKB_SUCCESS; +} + +int pipes_find(const pipes_t *pipes, uint64_t index, uint64_t *id) { + for (size_t i = 0; i < pipes->used; i++) { + if (pipes->indices[i] == index) { + *id = pipes->ids[i]; + return CKB_SUCCESS; + } + } + return ERROR_NOT_FOUND; +} + +int main(int argc, char *argv[]) { + uint8_t data_buffer[INPUT_DATA_LENGTH]; + pipes_t current_pipes; + pipes_init(¤t_pipes); + + uint64_t data_length = INPUT_DATA_LENGTH; + int ret = ckb_load_witness(data_buffer, &data_length, 0, 0, CKB_SOURCE_INPUT); + if (ret != CKB_SUCCESS) { + return ret; + } + mol_seg_t data_seg; + data_seg.ptr = data_buffer; + data_seg.size = data_length; + + if (MolReader_Data_verify(&data_seg, false) != MOL_OK) { + return ERROR_ENCODING; + } + + mol_seg_t spawns_seg = MolReader_Data_get_spawns(&data_seg); + uint64_t vm_index = 0; + if (argc != 0) { + // For spawned VMs, read current VM index and passed pipes from argv + if (argc != 2) { + return ERROR_ARGV; + } + + uint64_t decoded_length = 0; + ret = ee_decode_char_string_in_place(argv[0], &decoded_length); + if (ret != 0) { + return ret; + } + if (decoded_length != 8) { + return ERROR_ARGV; + } + vm_index = *((uint64_t *)argv[0]); + + int spawn_found = 0; + mol_seg_t spawn_seg; + for (mol_num_t i = 0; i < MolReader_Spawns_length(&spawns_seg); i++) { + mol_seg_res_t spawn_res = MolReader_Spawns_get(&spawns_seg, i); + if (spawn_res.errno != MOL_OK) { + return ERROR_ENCODING; + } + mol_seg_t child_seg = MolReader_Spawn_get_child(&spawn_res.seg); + uint64_t child_index = *((uint64_t *)child_seg.ptr); + if (child_index == vm_index) { + spawn_seg = spawn_res.seg; + spawn_found = 1; + break; + } + } + if (spawn_found == 0) { + return ERROR_ARGV; + } + mol_seg_t passed_pipes_seg = MolReader_Spawn_get_fds(&spawn_seg); + + decoded_length = 0; + ret = ee_decode_char_string_in_place(argv[1], &decoded_length); + if (ret != 0) { + return ret; + } + if (decoded_length != MolReader_FdIndices_length(&passed_pipes_seg) * 8) { + return ERROR_ARGV; + } + for (mol_num_t i = 0; i < MolReader_FdIndices_length(&passed_pipes_seg); i++) { + mol_seg_res_t pipe_res = MolReader_FdIndices_get(&passed_pipes_seg, i); + if (pipe_res.errno != MOL_OK) { + return ERROR_ENCODING; + } + uint64_t pipe_index = *((uint64_t *)pipe_res.seg.ptr); + uint64_t pipe_id = *((uint64_t *)&argv[1][i * 8]); + + ret = pipes_add(¤t_pipes, pipe_index, pipe_id); + if (ret != 0) { + return ret; + } + } + } + + // Create new pipes that should be created from current VM + mol_seg_t pipes_seg = MolReader_Data_get_pipes(&data_seg); + for (mol_num_t i = 0; i < MolReader_Pipes_length(&pipes_seg); i++) { + mol_seg_res_t pipe_pair_res = MolReader_Pipes_get(&pipes_seg, i); + if (pipe_pair_res.errno != MOL_OK) { + return ERROR_ENCODING; + } + mol_seg_t pipe_pair_seg = pipe_pair_res.seg; + + uint64_t pair_vm_index = *((uint64_t *)MolReader_Pipe_get_vm(&pipe_pair_seg).ptr); + if (pair_vm_index == vm_index) { + uint64_t read_index = *((uint64_t *)MolReader_Pipe_get_read_fd(&pipe_pair_seg).ptr); + uint64_t write_index = *((uint64_t *)MolReader_Pipe_get_write_fd(&pipe_pair_seg).ptr); + + uint64_t fildes[2]; + ret = ckb_pipe(fildes); + if (ret != 0) { + return ret; + } + ret = pipes_add(¤t_pipes, read_index, fildes[0]); + if (ret != 0) { + return ret; + } + ret = pipes_add(¤t_pipes, write_index, fildes[1]); + if (ret != 0) { + return ret; + } + } + } + + uint64_t spawned_vms[MAX_SPAWNED_VMS]; + size_t spawned_count = 0; + + // Issue spawn syscalls for child VMs + for (mol_num_t i = 0; i < MolReader_Spawns_length(&spawns_seg); i++) { + mol_seg_res_t spawn_res = MolReader_Spawns_get(&spawns_seg, i); + if (spawn_res.errno != MOL_OK) { + return ERROR_ENCODING; + } + mol_seg_t spawn_seg = spawn_res.seg; + + uint64_t from_index = *((uint64_t *)MolReader_Spawn_get_from(&spawn_seg).ptr); + if (from_index == vm_index) { + if (spawned_count >= MAX_SPAWNED_VMS) { + return ERROR_TOO_MANY_SPAWNS; + } + + uint64_t child_index = *((uint64_t *)MolReader_Spawn_get_child(&spawn_seg).ptr); + + pipes_t passed_pipes; + pipes_init(&passed_pipes); + + mol_seg_t pipe_indices = MolReader_Spawn_get_fds(&spawn_seg); + for (mol_num_t i = 0; i < MolReader_FdIndices_length(&pipe_indices); i++) { + mol_seg_res_t index_res = MolReader_FdIndices_get(&pipe_indices, i); + if (index_res.errno != MOL_OK) { + return ERROR_ENCODING; + } + mol_seg_t index_seg = index_res.seg; + uint64_t index = *((uint64_t *)index_seg.ptr); + + uint64_t id = 0; + ret = pipes_find(¤t_pipes, index, &id); + if (ret != 0) { + return ret; + } + + ret = pipes_add(&passed_pipes, index, id); + if (ret != 0) { + return ret; + } + } + + size_t src_len = 8; + size_t dst_len = ee_maximum_encoding_length(src_len); + uint8_t encoded_child_index[dst_len + 1]; + ret = ee_encode(encoded_child_index, &dst_len, (const uint8_t *)&child_index, &src_len); + if (ret != 0) { + return ret; + } + encoded_child_index[dst_len] = '\0'; + + src_len = passed_pipes.used * 8; + dst_len = ee_maximum_encoding_length(src_len); + uint8_t encoded_ids[dst_len + 1]; + ret = ee_encode(encoded_ids, &dst_len, (const uint8_t *)passed_pipes.ids, &src_len); + if (ret != 0) { + return ret; + } + encoded_ids[dst_len] = '\0'; + + const char *argv[2] = {(char *)encoded_child_index, (char *)encoded_ids}; + spawn_args_t sargs; + sargs.argc = 2; + sargs.argv = argv; + sargs.process_id = &spawned_vms[spawned_count++]; + sargs.inherited_fds = (const uint64_t *)passed_pipes.ids; + + ret = ckb_spawn(0, CKB_SOURCE_CELL_DEP, 0, 0, &sargs); + if (ret != 0) { + return ret; + } + } + } + + // Process all reads & writes + mol_seg_t writes_seg = MolReader_Data_get_writes(&data_seg); + for (mol_num_t i = 0; i < MolReader_Writes_length(&writes_seg); i++) { + mol_seg_res_t write_res = MolReader_Writes_get(&writes_seg, i); + if (write_res.errno != MOL_OK) { + return ERROR_ENCODING; + } + mol_seg_t write_seg = write_res.seg; + + uint64_t from = *((uint64_t *)MolReader_Write_get_from(&write_seg).ptr); + uint64_t to = *((uint64_t *)MolReader_Write_get_to(&write_seg).ptr); + + if (from == vm_index) { + // Write data + uint64_t from_pipe = *((uint64_t *)MolReader_Write_get_from_fd(&write_seg).ptr); + mol_seg_t data_seg = MolReader_Write_get_data(&write_seg); + + uint64_t pipe_id = 0; + ret = pipes_find(¤t_pipes, from_pipe, &pipe_id); + if (ret != 0) { + return ret; + } + + uint32_t written = 0; + while (written < data_seg.size) { + size_t length = data_seg.size - written; + ret = ckb_write(pipe_id, &data_seg.ptr[written], &length); + if (ret != 0) { + return ret; + } + if (length == 0) { + return ERROR_PIPE_CLOSED; + } + written += length; + } + } else if (to == vm_index) { + // Read data + uint64_t to_pipe = *((uint64_t *)MolReader_Write_get_to_fd(&write_seg).ptr); + mol_seg_t data_seg = MolReader_Write_get_data(&write_seg); + + uint64_t pipe_id = 0; + ret = pipes_find(¤t_pipes, to_pipe, &pipe_id); + if (ret != 0) { + return ret; + } + + uint32_t read = 0; + while (read < data_seg.size) { + size_t length = data_seg.size - read; + uint8_t data[length]; + memset(data, 0, length); + ret = ckb_read(pipe_id, data, &length); + if (ret != 0) { + return ret; + } + if (length == 0) { + return ERROR_PIPE_CLOSED; + } + if (memcmp(&data_seg.ptr[read], data, length) != 0) { + return ERROR_CORRUPTED_DATA; + } + read += length; + } + } + } + + // Join all spawned VMs + for (size_t i = 0; i < spawned_count; i++) { + size_t j = spawned_count - i - 1; + int8_t exit_code = 0xFF; + ret = ckb_wait(spawned_vms[j], &exit_code); + if (ret != 0) { + return ret; + } + if (exit_code != 0) { + return exit_code; + } + } + + return 0; +} diff --git a/script/testdata/spawn_dag.h b/script/testdata/spawn_dag.h new file mode 100644 index 0000000000..a7bebf57ca --- /dev/null +++ b/script/testdata/spawn_dag.h @@ -0,0 +1,887 @@ +// Generated by Molecule 0.8.0 + +#define MOLECULEC_VERSION 8000 +#define MOLECULE_API_VERSION_MIN 7000 + +#include "molecule_reader.h" +#include "molecule_builder.h" + +#ifndef SPAWN_DAG_H +#define SPAWN_DAG_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#ifndef MOLECULE_API_DECORATOR +#define __DEFINE_MOLECULE_API_DECORATOR_SPAWN_DAG +#define MOLECULE_API_DECORATOR +#endif /* MOLECULE_API_DECORATOR */ + +/* + * Reader APIs + */ + +#define MolReader_VmIndex_verify(s, c) mol_verify_fixed_size(s, 8) +#define MolReader_VmIndex_get_nth0(s) mol_slice_by_offset(s, 0, 1) +#define MolReader_VmIndex_get_nth1(s) mol_slice_by_offset(s, 1, 1) +#define MolReader_VmIndex_get_nth2(s) mol_slice_by_offset(s, 2, 1) +#define MolReader_VmIndex_get_nth3(s) mol_slice_by_offset(s, 3, 1) +#define MolReader_VmIndex_get_nth4(s) mol_slice_by_offset(s, 4, 1) +#define MolReader_VmIndex_get_nth5(s) mol_slice_by_offset(s, 5, 1) +#define MolReader_VmIndex_get_nth6(s) mol_slice_by_offset(s, 6, 1) +#define MolReader_VmIndex_get_nth7(s) mol_slice_by_offset(s, 7, 1) +#define MolReader_FdIndex_verify(s, c) mol_verify_fixed_size(s, 8) +#define MolReader_FdIndex_get_nth0(s) mol_slice_by_offset(s, 0, 1) +#define MolReader_FdIndex_get_nth1(s) mol_slice_by_offset(s, 1, 1) +#define MolReader_FdIndex_get_nth2(s) mol_slice_by_offset(s, 2, 1) +#define MolReader_FdIndex_get_nth3(s) mol_slice_by_offset(s, 3, 1) +#define MolReader_FdIndex_get_nth4(s) mol_slice_by_offset(s, 4, 1) +#define MolReader_FdIndex_get_nth5(s) mol_slice_by_offset(s, 5, 1) +#define MolReader_FdIndex_get_nth6(s) mol_slice_by_offset(s, 6, 1) +#define MolReader_FdIndex_get_nth7(s) mol_slice_by_offset(s, 7, 1) +#define MolReader_FdIndices_verify(s, c) mol_fixvec_verify(s, 8) +#define MolReader_FdIndices_length(s) mol_fixvec_length(s) +#define MolReader_FdIndices_get(s, i) mol_fixvec_slice_by_index(s, 8, i) +#define MolReader_Bytes_verify(s, c) mol_fixvec_verify(s, 1) +#define MolReader_Bytes_length(s) mol_fixvec_length(s) +#define MolReader_Bytes_get(s, i) mol_fixvec_slice_by_index(s, 1, i) +#define MolReader_Bytes_raw_bytes(s) mol_fixvec_slice_raw_bytes(s) +MOLECULE_API_DECORATOR mol_errno MolReader_Pipe_verify (const mol_seg_t*, bool); +#define MolReader_Pipe_actual_field_count(s) mol_table_actual_field_count(s) +#define MolReader_Pipe_has_extra_fields(s) mol_table_has_extra_fields(s, 3) +#define MolReader_Pipe_get_vm(s) mol_table_slice_by_index(s, 0) +#define MolReader_Pipe_get_read_fd(s) mol_table_slice_by_index(s, 1) +#define MolReader_Pipe_get_write_fd(s) mol_table_slice_by_index(s, 2) +MOLECULE_API_DECORATOR mol_errno MolReader_Pipes_verify (const mol_seg_t*, bool); +#define MolReader_Pipes_length(s) mol_dynvec_length(s) +#define MolReader_Pipes_get(s, i) mol_dynvec_slice_by_index(s, i) +MOLECULE_API_DECORATOR mol_errno MolReader_Write_verify (const mol_seg_t*, bool); +#define MolReader_Write_actual_field_count(s) mol_table_actual_field_count(s) +#define MolReader_Write_has_extra_fields(s) mol_table_has_extra_fields(s, 5) +#define MolReader_Write_get_from(s) mol_table_slice_by_index(s, 0) +#define MolReader_Write_get_from_fd(s) mol_table_slice_by_index(s, 1) +#define MolReader_Write_get_to(s) mol_table_slice_by_index(s, 2) +#define MolReader_Write_get_to_fd(s) mol_table_slice_by_index(s, 3) +#define MolReader_Write_get_data(s) mol_table_slice_by_index(s, 4) +MOLECULE_API_DECORATOR mol_errno MolReader_Writes_verify (const mol_seg_t*, bool); +#define MolReader_Writes_length(s) mol_dynvec_length(s) +#define MolReader_Writes_get(s, i) mol_dynvec_slice_by_index(s, i) +MOLECULE_API_DECORATOR mol_errno MolReader_Spawn_verify (const mol_seg_t*, bool); +#define MolReader_Spawn_actual_field_count(s) mol_table_actual_field_count(s) +#define MolReader_Spawn_has_extra_fields(s) mol_table_has_extra_fields(s, 3) +#define MolReader_Spawn_get_from(s) mol_table_slice_by_index(s, 0) +#define MolReader_Spawn_get_child(s) mol_table_slice_by_index(s, 1) +#define MolReader_Spawn_get_fds(s) mol_table_slice_by_index(s, 2) +MOLECULE_API_DECORATOR mol_errno MolReader_Spawns_verify (const mol_seg_t*, bool); +#define MolReader_Spawns_length(s) mol_dynvec_length(s) +#define MolReader_Spawns_get(s, i) mol_dynvec_slice_by_index(s, i) +MOLECULE_API_DECORATOR mol_errno MolReader_Data_verify (const mol_seg_t*, bool); +#define MolReader_Data_actual_field_count(s) mol_table_actual_field_count(s) +#define MolReader_Data_has_extra_fields(s) mol_table_has_extra_fields(s, 3) +#define MolReader_Data_get_spawns(s) mol_table_slice_by_index(s, 0) +#define MolReader_Data_get_pipes(s) mol_table_slice_by_index(s, 1) +#define MolReader_Data_get_writes(s) mol_table_slice_by_index(s, 2) + +/* + * Builder APIs + */ + +#define MolBuilder_VmIndex_init(b) mol_builder_initialize_fixed_size(b, 8) +#define MolBuilder_VmIndex_set_nth0(b, p) mol_builder_set_byte_by_offset(b, 0, p) +#define MolBuilder_VmIndex_set_nth1(b, p) mol_builder_set_byte_by_offset(b, 1, p) +#define MolBuilder_VmIndex_set_nth2(b, p) mol_builder_set_byte_by_offset(b, 2, p) +#define MolBuilder_VmIndex_set_nth3(b, p) mol_builder_set_byte_by_offset(b, 3, p) +#define MolBuilder_VmIndex_set_nth4(b, p) mol_builder_set_byte_by_offset(b, 4, p) +#define MolBuilder_VmIndex_set_nth5(b, p) mol_builder_set_byte_by_offset(b, 5, p) +#define MolBuilder_VmIndex_set_nth6(b, p) mol_builder_set_byte_by_offset(b, 6, p) +#define MolBuilder_VmIndex_set_nth7(b, p) mol_builder_set_byte_by_offset(b, 7, p) +#define MolBuilder_VmIndex_build(b) mol_builder_finalize_simple(b) +#define MolBuilder_VmIndex_clear(b) mol_builder_discard(b) +#define MolBuilder_FdIndex_init(b) mol_builder_initialize_fixed_size(b, 8) +#define MolBuilder_FdIndex_set_nth0(b, p) mol_builder_set_byte_by_offset(b, 0, p) +#define MolBuilder_FdIndex_set_nth1(b, p) mol_builder_set_byte_by_offset(b, 1, p) +#define MolBuilder_FdIndex_set_nth2(b, p) mol_builder_set_byte_by_offset(b, 2, p) +#define MolBuilder_FdIndex_set_nth3(b, p) mol_builder_set_byte_by_offset(b, 3, p) +#define MolBuilder_FdIndex_set_nth4(b, p) mol_builder_set_byte_by_offset(b, 4, p) +#define MolBuilder_FdIndex_set_nth5(b, p) mol_builder_set_byte_by_offset(b, 5, p) +#define MolBuilder_FdIndex_set_nth6(b, p) mol_builder_set_byte_by_offset(b, 6, p) +#define MolBuilder_FdIndex_set_nth7(b, p) mol_builder_set_byte_by_offset(b, 7, p) +#define MolBuilder_FdIndex_build(b) mol_builder_finalize_simple(b) +#define MolBuilder_FdIndex_clear(b) mol_builder_discard(b) +#define MolBuilder_FdIndices_init(b) mol_fixvec_builder_initialize(b, 128) +#define MolBuilder_FdIndices_push(b, p) mol_fixvec_builder_push(b, p, 8) +#define MolBuilder_FdIndices_build(b) mol_fixvec_builder_finalize(b) +#define MolBuilder_FdIndices_clear(b) mol_builder_discard(b) +#define MolBuilder_Bytes_init(b) mol_fixvec_builder_initialize(b, 16) +#define MolBuilder_Bytes_push(b, p) mol_fixvec_builder_push_byte(b, p) +#define MolBuilder_Bytes_build(b) mol_fixvec_builder_finalize(b) +#define MolBuilder_Bytes_clear(b) mol_builder_discard(b) +#define MolBuilder_Pipe_init(b) mol_table_builder_initialize(b, 256, 3) +#define MolBuilder_Pipe_set_vm(b, p, l) mol_table_builder_add(b, 0, p, l) +#define MolBuilder_Pipe_set_read_fd(b, p, l) mol_table_builder_add(b, 1, p, l) +#define MolBuilder_Pipe_set_write_fd(b, p, l) mol_table_builder_add(b, 2, p, l) +MOLECULE_API_DECORATOR mol_seg_res_t MolBuilder_Pipe_build (mol_builder_t); +#define MolBuilder_Pipe_clear(b) mol_builder_discard(b) +#define MolBuilder_Pipes_init(b) mol_builder_initialize_with_capacity(b, 1024, 64) +#define MolBuilder_Pipes_push(b, p, l) mol_dynvec_builder_push(b, p, l) +#define MolBuilder_Pipes_build(b) mol_dynvec_builder_finalize(b) +#define MolBuilder_Pipes_clear(b) mol_builder_discard(b) +#define MolBuilder_Write_init(b) mol_table_builder_initialize(b, 256, 5) +#define MolBuilder_Write_set_from(b, p, l) mol_table_builder_add(b, 0, p, l) +#define MolBuilder_Write_set_from_fd(b, p, l) mol_table_builder_add(b, 1, p, l) +#define MolBuilder_Write_set_to(b, p, l) mol_table_builder_add(b, 2, p, l) +#define MolBuilder_Write_set_to_fd(b, p, l) mol_table_builder_add(b, 3, p, l) +#define MolBuilder_Write_set_data(b, p, l) mol_table_builder_add(b, 4, p, l) +MOLECULE_API_DECORATOR mol_seg_res_t MolBuilder_Write_build (mol_builder_t); +#define MolBuilder_Write_clear(b) mol_builder_discard(b) +#define MolBuilder_Writes_init(b) mol_builder_initialize_with_capacity(b, 1024, 64) +#define MolBuilder_Writes_push(b, p, l) mol_dynvec_builder_push(b, p, l) +#define MolBuilder_Writes_build(b) mol_dynvec_builder_finalize(b) +#define MolBuilder_Writes_clear(b) mol_builder_discard(b) +#define MolBuilder_Spawn_init(b) mol_table_builder_initialize(b, 256, 3) +#define MolBuilder_Spawn_set_from(b, p, l) mol_table_builder_add(b, 0, p, l) +#define MolBuilder_Spawn_set_child(b, p, l) mol_table_builder_add(b, 1, p, l) +#define MolBuilder_Spawn_set_fds(b, p, l) mol_table_builder_add(b, 2, p, l) +MOLECULE_API_DECORATOR mol_seg_res_t MolBuilder_Spawn_build (mol_builder_t); +#define MolBuilder_Spawn_clear(b) mol_builder_discard(b) +#define MolBuilder_Spawns_init(b) mol_builder_initialize_with_capacity(b, 1024, 64) +#define MolBuilder_Spawns_push(b, p, l) mol_dynvec_builder_push(b, p, l) +#define MolBuilder_Spawns_build(b) mol_dynvec_builder_finalize(b) +#define MolBuilder_Spawns_clear(b) mol_builder_discard(b) +#define MolBuilder_Data_init(b) mol_table_builder_initialize(b, 128, 3) +#define MolBuilder_Data_set_spawns(b, p, l) mol_table_builder_add(b, 0, p, l) +#define MolBuilder_Data_set_pipes(b, p, l) mol_table_builder_add(b, 1, p, l) +#define MolBuilder_Data_set_writes(b, p, l) mol_table_builder_add(b, 2, p, l) +MOLECULE_API_DECORATOR mol_seg_res_t MolBuilder_Data_build (mol_builder_t); +#define MolBuilder_Data_clear(b) mol_builder_discard(b) + +/* + * Default Value + */ + +#define ____ 0x00 + +MOLECULE_API_DECORATOR const uint8_t MolDefault_VmIndex[8] = { + ____, ____, ____, ____, ____, ____, ____, ____, +}; +MOLECULE_API_DECORATOR const uint8_t MolDefault_FdIndex[8] = { + ____, ____, ____, ____, ____, ____, ____, ____, +}; +MOLECULE_API_DECORATOR const uint8_t MolDefault_FdIndices[4] = {____, ____, ____, ____}; +MOLECULE_API_DECORATOR const uint8_t MolDefault_Bytes[4] = {____, ____, ____, ____}; +MOLECULE_API_DECORATOR const uint8_t MolDefault_Pipe[40] = { + 0x28, ____, ____, ____, 0x10, ____, ____, ____, 0x18, ____, ____, ____, + 0x20, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, + ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, + ____, ____, ____, ____, +}; +MOLECULE_API_DECORATOR const uint8_t MolDefault_Pipes[4] = {0x04, ____, ____, ____}; +MOLECULE_API_DECORATOR const uint8_t MolDefault_Write[60] = { + 0x3c, ____, ____, ____, 0x18, ____, ____, ____, 0x20, ____, ____, ____, + 0x28, ____, ____, ____, 0x30, ____, ____, ____, 0x38, ____, ____, ____, + ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, + ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, + ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, +}; +MOLECULE_API_DECORATOR const uint8_t MolDefault_Writes[4] = {0x04, ____, ____, ____}; +MOLECULE_API_DECORATOR const uint8_t MolDefault_Spawn[36] = { + 0x24, ____, ____, ____, 0x10, ____, ____, ____, 0x18, ____, ____, ____, + 0x20, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, + ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, ____, +}; +MOLECULE_API_DECORATOR const uint8_t MolDefault_Spawns[4] = {0x04, ____, ____, ____}; +MOLECULE_API_DECORATOR const uint8_t MolDefault_Data[28] = { + 0x1c, ____, ____, ____, 0x10, ____, ____, ____, 0x14, ____, ____, ____, + 0x18, ____, ____, ____, 0x04, ____, ____, ____, 0x04, ____, ____, ____, + 0x04, ____, ____, ____, +}; + +#undef ____ + +/* + * Reader Functions + */ + +MOLECULE_API_DECORATOR mol_errno MolReader_Pipe_verify (const mol_seg_t *input, bool compatible) { + if (input->size < MOL_NUM_T_SIZE) { + return MOL_ERR_HEADER; + } + uint8_t *ptr = input->ptr; + mol_num_t total_size = mol_unpack_number(ptr); + if (input->size != total_size) { + return MOL_ERR_TOTAL_SIZE; + } + if (input->size < MOL_NUM_T_SIZE * 2) { + return MOL_ERR_HEADER; + } + ptr += MOL_NUM_T_SIZE; + mol_num_t offset = mol_unpack_number(ptr); + if (offset % 4 > 0 || offset < MOL_NUM_T_SIZE*2) { + return MOL_ERR_OFFSET; + } + mol_num_t field_count = offset / 4 - 1; + if (field_count < 3) { + return MOL_ERR_FIELD_COUNT; + } else if (!compatible && field_count > 3) { + return MOL_ERR_FIELD_COUNT; + } + if (input->size < MOL_NUM_T_SIZE*(field_count+1)){ + return MOL_ERR_HEADER; + } + mol_num_t offsets[field_count+1]; + offsets[0] = offset; + for (mol_num_t i=1; i offsets[i]) { + return MOL_ERR_OFFSET; + } + } + if (offsets[field_count-1] > total_size) { + return MOL_ERR_OFFSET; + } + offsets[field_count] = total_size; + mol_seg_t inner; + mol_errno errno; + inner.ptr = input->ptr + offsets[0]; + inner.size = offsets[1] - offsets[0]; + errno = MolReader_VmIndex_verify(&inner, compatible); + if (errno != MOL_OK) { + return MOL_ERR_DATA; + } + inner.ptr = input->ptr + offsets[1]; + inner.size = offsets[2] - offsets[1]; + errno = MolReader_FdIndex_verify(&inner, compatible); + if (errno != MOL_OK) { + return MOL_ERR_DATA; + } + inner.ptr = input->ptr + offsets[2]; + inner.size = offsets[3] - offsets[2]; + errno = MolReader_FdIndex_verify(&inner, compatible); + if (errno != MOL_OK) { + return MOL_ERR_DATA; + } + return MOL_OK; +} +MOLECULE_API_DECORATOR mol_errno MolReader_Pipes_verify (const mol_seg_t *input, bool compatible) { + if (input->size < MOL_NUM_T_SIZE) { + return MOL_ERR_HEADER; + } + uint8_t *ptr = input->ptr; + mol_num_t total_size = mol_unpack_number(ptr); + if (input->size != total_size) { + return MOL_ERR_TOTAL_SIZE; + } + if (input->size == MOL_NUM_T_SIZE) { + return MOL_OK; + } + if (input->size < MOL_NUM_T_SIZE * 2) { + return MOL_ERR_HEADER; + } + ptr += MOL_NUM_T_SIZE; + mol_num_t offset = mol_unpack_number(ptr); + if (offset % 4 > 0 || offset < MOL_NUM_T_SIZE*2) { + return MOL_ERR_OFFSET; + } + mol_num_t item_count = offset / 4 - 1; + if (input->size < MOL_NUM_T_SIZE*(item_count+1)) { + return MOL_ERR_HEADER; + } + mol_num_t end; + for (mol_num_t i=1; i end) { + return MOL_ERR_OFFSET; + } + mol_seg_t inner; + inner.ptr = input->ptr + offset; + inner.size = end - offset; + mol_errno errno = MolReader_Pipe_verify(&inner, compatible); + if (errno != MOL_OK) { + return MOL_ERR_DATA; + } + offset = end; + } + if (offset > total_size) { + return MOL_ERR_OFFSET; + } + mol_seg_t inner; + inner.ptr = input->ptr + offset; + inner.size = total_size - offset; + return MolReader_Pipe_verify(&inner, compatible); +} +MOLECULE_API_DECORATOR mol_errno MolReader_Write_verify (const mol_seg_t *input, bool compatible) { + if (input->size < MOL_NUM_T_SIZE) { + return MOL_ERR_HEADER; + } + uint8_t *ptr = input->ptr; + mol_num_t total_size = mol_unpack_number(ptr); + if (input->size != total_size) { + return MOL_ERR_TOTAL_SIZE; + } + if (input->size < MOL_NUM_T_SIZE * 2) { + return MOL_ERR_HEADER; + } + ptr += MOL_NUM_T_SIZE; + mol_num_t offset = mol_unpack_number(ptr); + if (offset % 4 > 0 || offset < MOL_NUM_T_SIZE*2) { + return MOL_ERR_OFFSET; + } + mol_num_t field_count = offset / 4 - 1; + if (field_count < 5) { + return MOL_ERR_FIELD_COUNT; + } else if (!compatible && field_count > 5) { + return MOL_ERR_FIELD_COUNT; + } + if (input->size < MOL_NUM_T_SIZE*(field_count+1)){ + return MOL_ERR_HEADER; + } + mol_num_t offsets[field_count+1]; + offsets[0] = offset; + for (mol_num_t i=1; i offsets[i]) { + return MOL_ERR_OFFSET; + } + } + if (offsets[field_count-1] > total_size) { + return MOL_ERR_OFFSET; + } + offsets[field_count] = total_size; + mol_seg_t inner; + mol_errno errno; + inner.ptr = input->ptr + offsets[0]; + inner.size = offsets[1] - offsets[0]; + errno = MolReader_VmIndex_verify(&inner, compatible); + if (errno != MOL_OK) { + return MOL_ERR_DATA; + } + inner.ptr = input->ptr + offsets[1]; + inner.size = offsets[2] - offsets[1]; + errno = MolReader_FdIndex_verify(&inner, compatible); + if (errno != MOL_OK) { + return MOL_ERR_DATA; + } + inner.ptr = input->ptr + offsets[2]; + inner.size = offsets[3] - offsets[2]; + errno = MolReader_VmIndex_verify(&inner, compatible); + if (errno != MOL_OK) { + return MOL_ERR_DATA; + } + inner.ptr = input->ptr + offsets[3]; + inner.size = offsets[4] - offsets[3]; + errno = MolReader_FdIndex_verify(&inner, compatible); + if (errno != MOL_OK) { + return MOL_ERR_DATA; + } + inner.ptr = input->ptr + offsets[4]; + inner.size = offsets[5] - offsets[4]; + errno = MolReader_Bytes_verify(&inner, compatible); + if (errno != MOL_OK) { + return MOL_ERR_DATA; + } + return MOL_OK; +} +MOLECULE_API_DECORATOR mol_errno MolReader_Writes_verify (const mol_seg_t *input, bool compatible) { + if (input->size < MOL_NUM_T_SIZE) { + return MOL_ERR_HEADER; + } + uint8_t *ptr = input->ptr; + mol_num_t total_size = mol_unpack_number(ptr); + if (input->size != total_size) { + return MOL_ERR_TOTAL_SIZE; + } + if (input->size == MOL_NUM_T_SIZE) { + return MOL_OK; + } + if (input->size < MOL_NUM_T_SIZE * 2) { + return MOL_ERR_HEADER; + } + ptr += MOL_NUM_T_SIZE; + mol_num_t offset = mol_unpack_number(ptr); + if (offset % 4 > 0 || offset < MOL_NUM_T_SIZE*2) { + return MOL_ERR_OFFSET; + } + mol_num_t item_count = offset / 4 - 1; + if (input->size < MOL_NUM_T_SIZE*(item_count+1)) { + return MOL_ERR_HEADER; + } + mol_num_t end; + for (mol_num_t i=1; i end) { + return MOL_ERR_OFFSET; + } + mol_seg_t inner; + inner.ptr = input->ptr + offset; + inner.size = end - offset; + mol_errno errno = MolReader_Write_verify(&inner, compatible); + if (errno != MOL_OK) { + return MOL_ERR_DATA; + } + offset = end; + } + if (offset > total_size) { + return MOL_ERR_OFFSET; + } + mol_seg_t inner; + inner.ptr = input->ptr + offset; + inner.size = total_size - offset; + return MolReader_Write_verify(&inner, compatible); +} +MOLECULE_API_DECORATOR mol_errno MolReader_Spawn_verify (const mol_seg_t *input, bool compatible) { + if (input->size < MOL_NUM_T_SIZE) { + return MOL_ERR_HEADER; + } + uint8_t *ptr = input->ptr; + mol_num_t total_size = mol_unpack_number(ptr); + if (input->size != total_size) { + return MOL_ERR_TOTAL_SIZE; + } + if (input->size < MOL_NUM_T_SIZE * 2) { + return MOL_ERR_HEADER; + } + ptr += MOL_NUM_T_SIZE; + mol_num_t offset = mol_unpack_number(ptr); + if (offset % 4 > 0 || offset < MOL_NUM_T_SIZE*2) { + return MOL_ERR_OFFSET; + } + mol_num_t field_count = offset / 4 - 1; + if (field_count < 3) { + return MOL_ERR_FIELD_COUNT; + } else if (!compatible && field_count > 3) { + return MOL_ERR_FIELD_COUNT; + } + if (input->size < MOL_NUM_T_SIZE*(field_count+1)){ + return MOL_ERR_HEADER; + } + mol_num_t offsets[field_count+1]; + offsets[0] = offset; + for (mol_num_t i=1; i offsets[i]) { + return MOL_ERR_OFFSET; + } + } + if (offsets[field_count-1] > total_size) { + return MOL_ERR_OFFSET; + } + offsets[field_count] = total_size; + mol_seg_t inner; + mol_errno errno; + inner.ptr = input->ptr + offsets[0]; + inner.size = offsets[1] - offsets[0]; + errno = MolReader_VmIndex_verify(&inner, compatible); + if (errno != MOL_OK) { + return MOL_ERR_DATA; + } + inner.ptr = input->ptr + offsets[1]; + inner.size = offsets[2] - offsets[1]; + errno = MolReader_VmIndex_verify(&inner, compatible); + if (errno != MOL_OK) { + return MOL_ERR_DATA; + } + inner.ptr = input->ptr + offsets[2]; + inner.size = offsets[3] - offsets[2]; + errno = MolReader_FdIndices_verify(&inner, compatible); + if (errno != MOL_OK) { + return MOL_ERR_DATA; + } + return MOL_OK; +} +MOLECULE_API_DECORATOR mol_errno MolReader_Spawns_verify (const mol_seg_t *input, bool compatible) { + if (input->size < MOL_NUM_T_SIZE) { + return MOL_ERR_HEADER; + } + uint8_t *ptr = input->ptr; + mol_num_t total_size = mol_unpack_number(ptr); + if (input->size != total_size) { + return MOL_ERR_TOTAL_SIZE; + } + if (input->size == MOL_NUM_T_SIZE) { + return MOL_OK; + } + if (input->size < MOL_NUM_T_SIZE * 2) { + return MOL_ERR_HEADER; + } + ptr += MOL_NUM_T_SIZE; + mol_num_t offset = mol_unpack_number(ptr); + if (offset % 4 > 0 || offset < MOL_NUM_T_SIZE*2) { + return MOL_ERR_OFFSET; + } + mol_num_t item_count = offset / 4 - 1; + if (input->size < MOL_NUM_T_SIZE*(item_count+1)) { + return MOL_ERR_HEADER; + } + mol_num_t end; + for (mol_num_t i=1; i end) { + return MOL_ERR_OFFSET; + } + mol_seg_t inner; + inner.ptr = input->ptr + offset; + inner.size = end - offset; + mol_errno errno = MolReader_Spawn_verify(&inner, compatible); + if (errno != MOL_OK) { + return MOL_ERR_DATA; + } + offset = end; + } + if (offset > total_size) { + return MOL_ERR_OFFSET; + } + mol_seg_t inner; + inner.ptr = input->ptr + offset; + inner.size = total_size - offset; + return MolReader_Spawn_verify(&inner, compatible); +} +MOLECULE_API_DECORATOR mol_errno MolReader_Data_verify (const mol_seg_t *input, bool compatible) { + if (input->size < MOL_NUM_T_SIZE) { + return MOL_ERR_HEADER; + } + uint8_t *ptr = input->ptr; + mol_num_t total_size = mol_unpack_number(ptr); + if (input->size != total_size) { + return MOL_ERR_TOTAL_SIZE; + } + if (input->size < MOL_NUM_T_SIZE * 2) { + return MOL_ERR_HEADER; + } + ptr += MOL_NUM_T_SIZE; + mol_num_t offset = mol_unpack_number(ptr); + if (offset % 4 > 0 || offset < MOL_NUM_T_SIZE*2) { + return MOL_ERR_OFFSET; + } + mol_num_t field_count = offset / 4 - 1; + if (field_count < 3) { + return MOL_ERR_FIELD_COUNT; + } else if (!compatible && field_count > 3) { + return MOL_ERR_FIELD_COUNT; + } + if (input->size < MOL_NUM_T_SIZE*(field_count+1)){ + return MOL_ERR_HEADER; + } + mol_num_t offsets[field_count+1]; + offsets[0] = offset; + for (mol_num_t i=1; i offsets[i]) { + return MOL_ERR_OFFSET; + } + } + if (offsets[field_count-1] > total_size) { + return MOL_ERR_OFFSET; + } + offsets[field_count] = total_size; + mol_seg_t inner; + mol_errno errno; + inner.ptr = input->ptr + offsets[0]; + inner.size = offsets[1] - offsets[0]; + errno = MolReader_Spawns_verify(&inner, compatible); + if (errno != MOL_OK) { + return MOL_ERR_DATA; + } + inner.ptr = input->ptr + offsets[1]; + inner.size = offsets[2] - offsets[1]; + errno = MolReader_Pipes_verify(&inner, compatible); + if (errno != MOL_OK) { + return MOL_ERR_DATA; + } + inner.ptr = input->ptr + offsets[2]; + inner.size = offsets[3] - offsets[2]; + errno = MolReader_Writes_verify(&inner, compatible); + if (errno != MOL_OK) { + return MOL_ERR_DATA; + } + return MOL_OK; +} + +/* + * Builder Functions + */ + +MOLECULE_API_DECORATOR mol_seg_res_t MolBuilder_Pipe_build (mol_builder_t builder) { + mol_seg_res_t res; + res.errno = MOL_OK; + mol_num_t offset = 16; + mol_num_t len; + res.seg.size = offset; + len = builder.number_ptr[1]; + res.seg.size += len == 0 ? 8 : len; + len = builder.number_ptr[3]; + res.seg.size += len == 0 ? 8 : len; + len = builder.number_ptr[5]; + res.seg.size += len == 0 ? 8 : len; + res.seg.ptr = (uint8_t*)malloc(res.seg.size); + uint8_t *dst = res.seg.ptr; + mol_pack_number(dst, &res.seg.size); + dst += MOL_NUM_T_SIZE; + mol_pack_number(dst, &offset); + dst += MOL_NUM_T_SIZE; + len = builder.number_ptr[1]; + offset += len == 0 ? 8 : len; + mol_pack_number(dst, &offset); + dst += MOL_NUM_T_SIZE; + len = builder.number_ptr[3]; + offset += len == 0 ? 8 : len; + mol_pack_number(dst, &offset); + dst += MOL_NUM_T_SIZE; + len = builder.number_ptr[5]; + offset += len == 0 ? 8 : len; + uint8_t *src = builder.data_ptr; + len = builder.number_ptr[1]; + if (len == 0) { + len = 8; + memcpy(dst, &MolDefault_VmIndex, len); + } else { + mol_num_t of = builder.number_ptr[0]; + memcpy(dst, src+of, len); + } + dst += len; + len = builder.number_ptr[3]; + if (len == 0) { + len = 8; + memcpy(dst, &MolDefault_FdIndex, len); + } else { + mol_num_t of = builder.number_ptr[2]; + memcpy(dst, src+of, len); + } + dst += len; + len = builder.number_ptr[5]; + if (len == 0) { + len = 8; + memcpy(dst, &MolDefault_FdIndex, len); + } else { + mol_num_t of = builder.number_ptr[4]; + memcpy(dst, src+of, len); + } + dst += len; + mol_builder_discard(builder); + return res; +} +MOLECULE_API_DECORATOR mol_seg_res_t MolBuilder_Write_build (mol_builder_t builder) { + mol_seg_res_t res; + res.errno = MOL_OK; + mol_num_t offset = 24; + mol_num_t len; + res.seg.size = offset; + len = builder.number_ptr[1]; + res.seg.size += len == 0 ? 8 : len; + len = builder.number_ptr[3]; + res.seg.size += len == 0 ? 8 : len; + len = builder.number_ptr[5]; + res.seg.size += len == 0 ? 8 : len; + len = builder.number_ptr[7]; + res.seg.size += len == 0 ? 8 : len; + len = builder.number_ptr[9]; + res.seg.size += len == 0 ? 4 : len; + res.seg.ptr = (uint8_t*)malloc(res.seg.size); + uint8_t *dst = res.seg.ptr; + mol_pack_number(dst, &res.seg.size); + dst += MOL_NUM_T_SIZE; + mol_pack_number(dst, &offset); + dst += MOL_NUM_T_SIZE; + len = builder.number_ptr[1]; + offset += len == 0 ? 8 : len; + mol_pack_number(dst, &offset); + dst += MOL_NUM_T_SIZE; + len = builder.number_ptr[3]; + offset += len == 0 ? 8 : len; + mol_pack_number(dst, &offset); + dst += MOL_NUM_T_SIZE; + len = builder.number_ptr[5]; + offset += len == 0 ? 8 : len; + mol_pack_number(dst, &offset); + dst += MOL_NUM_T_SIZE; + len = builder.number_ptr[7]; + offset += len == 0 ? 8 : len; + mol_pack_number(dst, &offset); + dst += MOL_NUM_T_SIZE; + len = builder.number_ptr[9]; + offset += len == 0 ? 4 : len; + uint8_t *src = builder.data_ptr; + len = builder.number_ptr[1]; + if (len == 0) { + len = 8; + memcpy(dst, &MolDefault_VmIndex, len); + } else { + mol_num_t of = builder.number_ptr[0]; + memcpy(dst, src+of, len); + } + dst += len; + len = builder.number_ptr[3]; + if (len == 0) { + len = 8; + memcpy(dst, &MolDefault_FdIndex, len); + } else { + mol_num_t of = builder.number_ptr[2]; + memcpy(dst, src+of, len); + } + dst += len; + len = builder.number_ptr[5]; + if (len == 0) { + len = 8; + memcpy(dst, &MolDefault_VmIndex, len); + } else { + mol_num_t of = builder.number_ptr[4]; + memcpy(dst, src+of, len); + } + dst += len; + len = builder.number_ptr[7]; + if (len == 0) { + len = 8; + memcpy(dst, &MolDefault_FdIndex, len); + } else { + mol_num_t of = builder.number_ptr[6]; + memcpy(dst, src+of, len); + } + dst += len; + len = builder.number_ptr[9]; + if (len == 0) { + len = 4; + memcpy(dst, &MolDefault_Bytes, len); + } else { + mol_num_t of = builder.number_ptr[8]; + memcpy(dst, src+of, len); + } + dst += len; + mol_builder_discard(builder); + return res; +} +MOLECULE_API_DECORATOR mol_seg_res_t MolBuilder_Spawn_build (mol_builder_t builder) { + mol_seg_res_t res; + res.errno = MOL_OK; + mol_num_t offset = 16; + mol_num_t len; + res.seg.size = offset; + len = builder.number_ptr[1]; + res.seg.size += len == 0 ? 8 : len; + len = builder.number_ptr[3]; + res.seg.size += len == 0 ? 8 : len; + len = builder.number_ptr[5]; + res.seg.size += len == 0 ? 4 : len; + res.seg.ptr = (uint8_t*)malloc(res.seg.size); + uint8_t *dst = res.seg.ptr; + mol_pack_number(dst, &res.seg.size); + dst += MOL_NUM_T_SIZE; + mol_pack_number(dst, &offset); + dst += MOL_NUM_T_SIZE; + len = builder.number_ptr[1]; + offset += len == 0 ? 8 : len; + mol_pack_number(dst, &offset); + dst += MOL_NUM_T_SIZE; + len = builder.number_ptr[3]; + offset += len == 0 ? 8 : len; + mol_pack_number(dst, &offset); + dst += MOL_NUM_T_SIZE; + len = builder.number_ptr[5]; + offset += len == 0 ? 4 : len; + uint8_t *src = builder.data_ptr; + len = builder.number_ptr[1]; + if (len == 0) { + len = 8; + memcpy(dst, &MolDefault_VmIndex, len); + } else { + mol_num_t of = builder.number_ptr[0]; + memcpy(dst, src+of, len); + } + dst += len; + len = builder.number_ptr[3]; + if (len == 0) { + len = 8; + memcpy(dst, &MolDefault_VmIndex, len); + } else { + mol_num_t of = builder.number_ptr[2]; + memcpy(dst, src+of, len); + } + dst += len; + len = builder.number_ptr[5]; + if (len == 0) { + len = 4; + memcpy(dst, &MolDefault_FdIndices, len); + } else { + mol_num_t of = builder.number_ptr[4]; + memcpy(dst, src+of, len); + } + dst += len; + mol_builder_discard(builder); + return res; +} +MOLECULE_API_DECORATOR mol_seg_res_t MolBuilder_Data_build (mol_builder_t builder) { + mol_seg_res_t res; + res.errno = MOL_OK; + mol_num_t offset = 16; + mol_num_t len; + res.seg.size = offset; + len = builder.number_ptr[1]; + res.seg.size += len == 0 ? 4 : len; + len = builder.number_ptr[3]; + res.seg.size += len == 0 ? 4 : len; + len = builder.number_ptr[5]; + res.seg.size += len == 0 ? 4 : len; + res.seg.ptr = (uint8_t*)malloc(res.seg.size); + uint8_t *dst = res.seg.ptr; + mol_pack_number(dst, &res.seg.size); + dst += MOL_NUM_T_SIZE; + mol_pack_number(dst, &offset); + dst += MOL_NUM_T_SIZE; + len = builder.number_ptr[1]; + offset += len == 0 ? 4 : len; + mol_pack_number(dst, &offset); + dst += MOL_NUM_T_SIZE; + len = builder.number_ptr[3]; + offset += len == 0 ? 4 : len; + mol_pack_number(dst, &offset); + dst += MOL_NUM_T_SIZE; + len = builder.number_ptr[5]; + offset += len == 0 ? 4 : len; + uint8_t *src = builder.data_ptr; + len = builder.number_ptr[1]; + if (len == 0) { + len = 4; + memcpy(dst, &MolDefault_Spawns, len); + } else { + mol_num_t of = builder.number_ptr[0]; + memcpy(dst, src+of, len); + } + dst += len; + len = builder.number_ptr[3]; + if (len == 0) { + len = 4; + memcpy(dst, &MolDefault_Pipes, len); + } else { + mol_num_t of = builder.number_ptr[2]; + memcpy(dst, src+of, len); + } + dst += len; + len = builder.number_ptr[5]; + if (len == 0) { + len = 4; + memcpy(dst, &MolDefault_Writes, len); + } else { + mol_num_t of = builder.number_ptr[4]; + memcpy(dst, src+of, len); + } + dst += len; + mol_builder_discard(builder); + return res; +} + +#ifdef __DEFINE_MOLECULE_API_DECORATOR_SPAWN_DAG +#undef MOLECULE_API_DECORATOR +#undef __DEFINE_MOLECULE_API_DECORATOR_SPAWN_DAG +#endif /* __DEFINE_MOLECULE_API_DECORATOR_SPAWN_DAG */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SPAWN_DAG_H */ diff --git a/script/testdata/spawn_dag.mol b/script/testdata/spawn_dag.mol new file mode 100644 index 0000000000..0e4c9fab1b --- /dev/null +++ b/script/testdata/spawn_dag.mol @@ -0,0 +1,34 @@ +array VmIndex [byte; 8]; +array FdIndex [byte; 8]; + +vector FdIndices ; +vector Bytes ; + +table Pipe { + vm: VmIndex, + read_fd: FdIndex, + write_fd: FdIndex, +} +vector Pipes ; + +table Write { + from: VmIndex, + from_fd: FdIndex, + to: VmIndex, + to_fd: FdIndex, + data: Bytes, +} +vector Writes ; + +table Spawn { + from: VmIndex, + child: VmIndex, + fds: FdIndices, +} +vector Spawns ; + +table Data { + spawns: Spawns, + pipes: Pipes, + writes: Writes, +} diff --git a/script/testdata/spawn_dag.rs b/script/testdata/spawn_dag.rs new file mode 100644 index 0000000000..f3a20a27c5 --- /dev/null +++ b/script/testdata/spawn_dag.rs @@ -0,0 +1,3416 @@ +// Generated by Molecule 0.8.0 +#![allow(clippy::needless_lifetimes)] +#![allow(clippy::needless_borrow)] +#![allow(clippy::wrong_self_convention)] +#![allow(clippy::if_same_then_else)] +#![allow(clippy::derivable_impls)] +#![allow(clippy::useless_conversion)] +#![allow(clippy::write_literal)] +#![allow(clippy::clone_on_copy)] +#![allow(clippy::fallible_impl_from)] +use molecule::prelude::*; +#[derive(Clone)] +pub struct VmIndex(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for VmIndex { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for VmIndex { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for VmIndex { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + let raw_data = hex_string(&self.raw_data()); + write!(f, "{}(0x{})", Self::NAME, raw_data) + } +} +impl ::core::default::Default for VmIndex { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + VmIndex::new_unchecked(v) + } +} +impl VmIndex { + const DEFAULT_VALUE: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 0]; + pub const TOTAL_SIZE: usize = 8; + pub const ITEM_SIZE: usize = 1; + pub const ITEM_COUNT: usize = 8; + pub fn nth0(&self) -> Byte { + Byte::new_unchecked(self.0.slice(0..1)) + } + pub fn nth1(&self) -> Byte { + Byte::new_unchecked(self.0.slice(1..2)) + } + pub fn nth2(&self) -> Byte { + Byte::new_unchecked(self.0.slice(2..3)) + } + pub fn nth3(&self) -> Byte { + Byte::new_unchecked(self.0.slice(3..4)) + } + pub fn nth4(&self) -> Byte { + Byte::new_unchecked(self.0.slice(4..5)) + } + pub fn nth5(&self) -> Byte { + Byte::new_unchecked(self.0.slice(5..6)) + } + pub fn nth6(&self) -> Byte { + Byte::new_unchecked(self.0.slice(6..7)) + } + pub fn nth7(&self) -> Byte { + Byte::new_unchecked(self.0.slice(7..8)) + } + pub fn raw_data(&self) -> molecule::bytes::Bytes { + self.as_bytes() + } + pub fn as_reader<'r>(&'r self) -> VmIndexReader<'r> { + VmIndexReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for VmIndex { + type Builder = VmIndexBuilder; + const NAME: &'static str = "VmIndex"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + VmIndex(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + VmIndexReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + VmIndexReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder().set([ + self.nth0(), + self.nth1(), + self.nth2(), + self.nth3(), + self.nth4(), + self.nth5(), + self.nth6(), + self.nth7(), + ]) + } +} +#[derive(Clone, Copy)] +pub struct VmIndexReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for VmIndexReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for VmIndexReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for VmIndexReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + let raw_data = hex_string(&self.raw_data()); + write!(f, "{}(0x{})", Self::NAME, raw_data) + } +} +impl<'r> VmIndexReader<'r> { + pub const TOTAL_SIZE: usize = 8; + pub const ITEM_SIZE: usize = 1; + pub const ITEM_COUNT: usize = 8; + pub fn nth0(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[0..1]) + } + pub fn nth1(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[1..2]) + } + pub fn nth2(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[2..3]) + } + pub fn nth3(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[3..4]) + } + pub fn nth4(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[4..5]) + } + pub fn nth5(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[5..6]) + } + pub fn nth6(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[6..7]) + } + pub fn nth7(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[7..8]) + } + pub fn raw_data(&self) -> &'r [u8] { + self.as_slice() + } +} +impl<'r> molecule::prelude::Reader<'r> for VmIndexReader<'r> { + type Entity = VmIndex; + const NAME: &'static str = "VmIndexReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + VmIndexReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], _compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len != Self::TOTAL_SIZE { + return ve!(Self, TotalSizeNotMatch, Self::TOTAL_SIZE, slice_len); + } + Ok(()) + } +} +#[derive(Clone)] +pub struct VmIndexBuilder(pub(crate) [Byte; 8]); +impl ::core::fmt::Debug for VmIndexBuilder { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:?})", Self::NAME, &self.0[..]) + } +} +impl ::core::default::Default for VmIndexBuilder { + fn default() -> Self { + VmIndexBuilder([ + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + ]) + } +} +impl VmIndexBuilder { + pub const TOTAL_SIZE: usize = 8; + pub const ITEM_SIZE: usize = 1; + pub const ITEM_COUNT: usize = 8; + pub fn set(mut self, v: [Byte; 8]) -> Self { + self.0 = v; + self + } + pub fn nth0(mut self, v: Byte) -> Self { + self.0[0] = v; + self + } + pub fn nth1(mut self, v: Byte) -> Self { + self.0[1] = v; + self + } + pub fn nth2(mut self, v: Byte) -> Self { + self.0[2] = v; + self + } + pub fn nth3(mut self, v: Byte) -> Self { + self.0[3] = v; + self + } + pub fn nth4(mut self, v: Byte) -> Self { + self.0[4] = v; + self + } + pub fn nth5(mut self, v: Byte) -> Self { + self.0[5] = v; + self + } + pub fn nth6(mut self, v: Byte) -> Self { + self.0[6] = v; + self + } + pub fn nth7(mut self, v: Byte) -> Self { + self.0[7] = v; + self + } +} +impl molecule::prelude::Builder for VmIndexBuilder { + type Entity = VmIndex; + const NAME: &'static str = "VmIndexBuilder"; + fn expected_length(&self) -> usize { + Self::TOTAL_SIZE + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + writer.write_all(self.0[0].as_slice())?; + writer.write_all(self.0[1].as_slice())?; + writer.write_all(self.0[2].as_slice())?; + writer.write_all(self.0[3].as_slice())?; + writer.write_all(self.0[4].as_slice())?; + writer.write_all(self.0[5].as_slice())?; + writer.write_all(self.0[6].as_slice())?; + writer.write_all(self.0[7].as_slice())?; + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + VmIndex::new_unchecked(inner.into()) + } +} +impl From<[Byte; 8usize]> for VmIndex { + fn from(value: [Byte; 8usize]) -> Self { + Self::new_builder().set(value).build() + } +} +impl ::core::convert::TryFrom<&[Byte]> for VmIndex { + type Error = ::core::array::TryFromSliceError; + fn try_from(value: &[Byte]) -> Result { + Ok(Self::new_builder() + .set(<&[Byte; 8usize]>::try_from(value)?.clone()) + .build()) + } +} +impl From for [Byte; 8usize] { + #[track_caller] + fn from(value: VmIndex) -> Self { + [ + value.nth0(), + value.nth1(), + value.nth2(), + value.nth3(), + value.nth4(), + value.nth5(), + value.nth6(), + value.nth7(), + ] + } +} +impl From<[u8; 8usize]> for VmIndex { + fn from(value: [u8; 8usize]) -> Self { + VmIndexReader::new_unchecked(&value).to_entity() + } +} +impl ::core::convert::TryFrom<&[u8]> for VmIndex { + type Error = ::core::array::TryFromSliceError; + fn try_from(value: &[u8]) -> Result { + Ok(<[u8; 8usize]>::try_from(value)?.into()) + } +} +impl From for [u8; 8usize] { + #[track_caller] + fn from(value: VmIndex) -> Self { + ::core::convert::TryFrom::try_from(value.as_slice()).unwrap() + } +} +impl<'a> From> for &'a [u8; 8usize] { + #[track_caller] + fn from(value: VmIndexReader<'a>) -> Self { + ::core::convert::TryFrom::try_from(value.as_slice()).unwrap() + } +} +impl<'a> From<&'a VmIndexReader<'a>> for &'a [u8; 8usize] { + #[track_caller] + fn from(value: &'a VmIndexReader<'a>) -> Self { + ::core::convert::TryFrom::try_from(value.as_slice()).unwrap() + } +} +#[derive(Clone)] +pub struct FdIndex(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for FdIndex { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for FdIndex { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for FdIndex { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + let raw_data = hex_string(&self.raw_data()); + write!(f, "{}(0x{})", Self::NAME, raw_data) + } +} +impl ::core::default::Default for FdIndex { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + FdIndex::new_unchecked(v) + } +} +impl FdIndex { + const DEFAULT_VALUE: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 0]; + pub const TOTAL_SIZE: usize = 8; + pub const ITEM_SIZE: usize = 1; + pub const ITEM_COUNT: usize = 8; + pub fn nth0(&self) -> Byte { + Byte::new_unchecked(self.0.slice(0..1)) + } + pub fn nth1(&self) -> Byte { + Byte::new_unchecked(self.0.slice(1..2)) + } + pub fn nth2(&self) -> Byte { + Byte::new_unchecked(self.0.slice(2..3)) + } + pub fn nth3(&self) -> Byte { + Byte::new_unchecked(self.0.slice(3..4)) + } + pub fn nth4(&self) -> Byte { + Byte::new_unchecked(self.0.slice(4..5)) + } + pub fn nth5(&self) -> Byte { + Byte::new_unchecked(self.0.slice(5..6)) + } + pub fn nth6(&self) -> Byte { + Byte::new_unchecked(self.0.slice(6..7)) + } + pub fn nth7(&self) -> Byte { + Byte::new_unchecked(self.0.slice(7..8)) + } + pub fn raw_data(&self) -> molecule::bytes::Bytes { + self.as_bytes() + } + pub fn as_reader<'r>(&'r self) -> FdIndexReader<'r> { + FdIndexReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for FdIndex { + type Builder = FdIndexBuilder; + const NAME: &'static str = "FdIndex"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + FdIndex(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + FdIndexReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + FdIndexReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder().set([ + self.nth0(), + self.nth1(), + self.nth2(), + self.nth3(), + self.nth4(), + self.nth5(), + self.nth6(), + self.nth7(), + ]) + } +} +#[derive(Clone, Copy)] +pub struct FdIndexReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for FdIndexReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for FdIndexReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for FdIndexReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + let raw_data = hex_string(&self.raw_data()); + write!(f, "{}(0x{})", Self::NAME, raw_data) + } +} +impl<'r> FdIndexReader<'r> { + pub const TOTAL_SIZE: usize = 8; + pub const ITEM_SIZE: usize = 1; + pub const ITEM_COUNT: usize = 8; + pub fn nth0(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[0..1]) + } + pub fn nth1(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[1..2]) + } + pub fn nth2(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[2..3]) + } + pub fn nth3(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[3..4]) + } + pub fn nth4(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[4..5]) + } + pub fn nth5(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[5..6]) + } + pub fn nth6(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[6..7]) + } + pub fn nth7(&self) -> ByteReader<'r> { + ByteReader::new_unchecked(&self.as_slice()[7..8]) + } + pub fn raw_data(&self) -> &'r [u8] { + self.as_slice() + } +} +impl<'r> molecule::prelude::Reader<'r> for FdIndexReader<'r> { + type Entity = FdIndex; + const NAME: &'static str = "FdIndexReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + FdIndexReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], _compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len != Self::TOTAL_SIZE { + return ve!(Self, TotalSizeNotMatch, Self::TOTAL_SIZE, slice_len); + } + Ok(()) + } +} +#[derive(Clone)] +pub struct FdIndexBuilder(pub(crate) [Byte; 8]); +impl ::core::fmt::Debug for FdIndexBuilder { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:?})", Self::NAME, &self.0[..]) + } +} +impl ::core::default::Default for FdIndexBuilder { + fn default() -> Self { + FdIndexBuilder([ + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + Byte::default(), + ]) + } +} +impl FdIndexBuilder { + pub const TOTAL_SIZE: usize = 8; + pub const ITEM_SIZE: usize = 1; + pub const ITEM_COUNT: usize = 8; + pub fn set(mut self, v: [Byte; 8]) -> Self { + self.0 = v; + self + } + pub fn nth0(mut self, v: Byte) -> Self { + self.0[0] = v; + self + } + pub fn nth1(mut self, v: Byte) -> Self { + self.0[1] = v; + self + } + pub fn nth2(mut self, v: Byte) -> Self { + self.0[2] = v; + self + } + pub fn nth3(mut self, v: Byte) -> Self { + self.0[3] = v; + self + } + pub fn nth4(mut self, v: Byte) -> Self { + self.0[4] = v; + self + } + pub fn nth5(mut self, v: Byte) -> Self { + self.0[5] = v; + self + } + pub fn nth6(mut self, v: Byte) -> Self { + self.0[6] = v; + self + } + pub fn nth7(mut self, v: Byte) -> Self { + self.0[7] = v; + self + } +} +impl molecule::prelude::Builder for FdIndexBuilder { + type Entity = FdIndex; + const NAME: &'static str = "FdIndexBuilder"; + fn expected_length(&self) -> usize { + Self::TOTAL_SIZE + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + writer.write_all(self.0[0].as_slice())?; + writer.write_all(self.0[1].as_slice())?; + writer.write_all(self.0[2].as_slice())?; + writer.write_all(self.0[3].as_slice())?; + writer.write_all(self.0[4].as_slice())?; + writer.write_all(self.0[5].as_slice())?; + writer.write_all(self.0[6].as_slice())?; + writer.write_all(self.0[7].as_slice())?; + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + FdIndex::new_unchecked(inner.into()) + } +} +impl From<[Byte; 8usize]> for FdIndex { + fn from(value: [Byte; 8usize]) -> Self { + Self::new_builder().set(value).build() + } +} +impl ::core::convert::TryFrom<&[Byte]> for FdIndex { + type Error = ::core::array::TryFromSliceError; + fn try_from(value: &[Byte]) -> Result { + Ok(Self::new_builder() + .set(<&[Byte; 8usize]>::try_from(value)?.clone()) + .build()) + } +} +impl From for [Byte; 8usize] { + #[track_caller] + fn from(value: FdIndex) -> Self { + [ + value.nth0(), + value.nth1(), + value.nth2(), + value.nth3(), + value.nth4(), + value.nth5(), + value.nth6(), + value.nth7(), + ] + } +} +impl From<[u8; 8usize]> for FdIndex { + fn from(value: [u8; 8usize]) -> Self { + FdIndexReader::new_unchecked(&value).to_entity() + } +} +impl ::core::convert::TryFrom<&[u8]> for FdIndex { + type Error = ::core::array::TryFromSliceError; + fn try_from(value: &[u8]) -> Result { + Ok(<[u8; 8usize]>::try_from(value)?.into()) + } +} +impl From for [u8; 8usize] { + #[track_caller] + fn from(value: FdIndex) -> Self { + ::core::convert::TryFrom::try_from(value.as_slice()).unwrap() + } +} +impl<'a> From> for &'a [u8; 8usize] { + #[track_caller] + fn from(value: FdIndexReader<'a>) -> Self { + ::core::convert::TryFrom::try_from(value.as_slice()).unwrap() + } +} +impl<'a> From<&'a FdIndexReader<'a>> for &'a [u8; 8usize] { + #[track_caller] + fn from(value: &'a FdIndexReader<'a>) -> Self { + ::core::convert::TryFrom::try_from(value.as_slice()).unwrap() + } +} +#[derive(Clone)] +pub struct FdIndices(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for FdIndices { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for FdIndices { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for FdIndices { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} [", Self::NAME)?; + for i in 0..self.len() { + if i == 0 { + write!(f, "{}", self.get_unchecked(i))?; + } else { + write!(f, ", {}", self.get_unchecked(i))?; + } + } + write!(f, "]") + } +} +impl ::core::default::Default for FdIndices { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + FdIndices::new_unchecked(v) + } +} +impl FdIndices { + const DEFAULT_VALUE: [u8; 4] = [0, 0, 0, 0]; + pub const ITEM_SIZE: usize = 8; + pub fn total_size(&self) -> usize { + molecule::NUMBER_SIZE + Self::ITEM_SIZE * self.item_count() + } + pub fn item_count(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn len(&self) -> usize { + self.item_count() + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn get(&self, idx: usize) -> Option { + if idx >= self.len() { + None + } else { + Some(self.get_unchecked(idx)) + } + } + pub fn get_unchecked(&self, idx: usize) -> FdIndex { + let start = molecule::NUMBER_SIZE + Self::ITEM_SIZE * idx; + let end = start + Self::ITEM_SIZE; + FdIndex::new_unchecked(self.0.slice(start..end)) + } + pub fn as_reader<'r>(&'r self) -> FdIndicesReader<'r> { + FdIndicesReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for FdIndices { + type Builder = FdIndicesBuilder; + const NAME: &'static str = "FdIndices"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + FdIndices(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + FdIndicesReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + FdIndicesReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder().extend(self.into_iter()) + } +} +#[derive(Clone, Copy)] +pub struct FdIndicesReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for FdIndicesReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for FdIndicesReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for FdIndicesReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} [", Self::NAME)?; + for i in 0..self.len() { + if i == 0 { + write!(f, "{}", self.get_unchecked(i))?; + } else { + write!(f, ", {}", self.get_unchecked(i))?; + } + } + write!(f, "]") + } +} +impl<'r> FdIndicesReader<'r> { + pub const ITEM_SIZE: usize = 8; + pub fn total_size(&self) -> usize { + molecule::NUMBER_SIZE + Self::ITEM_SIZE * self.item_count() + } + pub fn item_count(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn len(&self) -> usize { + self.item_count() + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn get(&self, idx: usize) -> Option> { + if idx >= self.len() { + None + } else { + Some(self.get_unchecked(idx)) + } + } + pub fn get_unchecked(&self, idx: usize) -> FdIndexReader<'r> { + let start = molecule::NUMBER_SIZE + Self::ITEM_SIZE * idx; + let end = start + Self::ITEM_SIZE; + FdIndexReader::new_unchecked(&self.as_slice()[start..end]) + } +} +impl<'r> molecule::prelude::Reader<'r> for FdIndicesReader<'r> { + type Entity = FdIndices; + const NAME: &'static str = "FdIndicesReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + FdIndicesReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], _compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let item_count = molecule::unpack_number(slice) as usize; + if item_count == 0 { + if slice_len != molecule::NUMBER_SIZE { + return ve!(Self, TotalSizeNotMatch, molecule::NUMBER_SIZE, slice_len); + } + return Ok(()); + } + let total_size = molecule::NUMBER_SIZE + Self::ITEM_SIZE * item_count; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + Ok(()) + } +} +#[derive(Clone, Debug, Default)] +pub struct FdIndicesBuilder(pub(crate) Vec); +impl FdIndicesBuilder { + pub const ITEM_SIZE: usize = 8; + pub fn set(mut self, v: Vec) -> Self { + self.0 = v; + self + } + pub fn push(mut self, v: FdIndex) -> Self { + self.0.push(v); + self + } + pub fn extend>(mut self, iter: T) -> Self { + for elem in iter { + self.0.push(elem); + } + self + } + pub fn replace(&mut self, index: usize, v: FdIndex) -> Option { + self.0 + .get_mut(index) + .map(|item| ::core::mem::replace(item, v)) + } +} +impl molecule::prelude::Builder for FdIndicesBuilder { + type Entity = FdIndices; + const NAME: &'static str = "FdIndicesBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE + Self::ITEM_SIZE * self.0.len() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + writer.write_all(&molecule::pack_number(self.0.len() as molecule::Number))?; + for inner in &self.0[..] { + writer.write_all(inner.as_slice())?; + } + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + FdIndices::new_unchecked(inner.into()) + } +} +pub struct FdIndicesIterator(FdIndices, usize, usize); +impl ::core::iter::Iterator for FdIndicesIterator { + type Item = FdIndex; + fn next(&mut self) -> Option { + if self.1 >= self.2 { + None + } else { + let ret = self.0.get_unchecked(self.1); + self.1 += 1; + Some(ret) + } + } +} +impl ::core::iter::ExactSizeIterator for FdIndicesIterator { + fn len(&self) -> usize { + self.2 - self.1 + } +} +impl ::core::iter::IntoIterator for FdIndices { + type Item = FdIndex; + type IntoIter = FdIndicesIterator; + fn into_iter(self) -> Self::IntoIter { + let len = self.len(); + FdIndicesIterator(self, 0, len) + } +} +impl<'r> FdIndicesReader<'r> { + pub fn iter<'t>(&'t self) -> FdIndicesReaderIterator<'t, 'r> { + FdIndicesReaderIterator(&self, 0, self.len()) + } +} +pub struct FdIndicesReaderIterator<'t, 'r>(&'t FdIndicesReader<'r>, usize, usize); +impl<'t: 'r, 'r> ::core::iter::Iterator for FdIndicesReaderIterator<'t, 'r> { + type Item = FdIndexReader<'t>; + fn next(&mut self) -> Option { + if self.1 >= self.2 { + None + } else { + let ret = self.0.get_unchecked(self.1); + self.1 += 1; + Some(ret) + } + } +} +impl<'t: 'r, 'r> ::core::iter::ExactSizeIterator for FdIndicesReaderIterator<'t, 'r> { + fn len(&self) -> usize { + self.2 - self.1 + } +} +impl ::core::iter::FromIterator for FdIndices { + fn from_iter>(iter: T) -> Self { + Self::new_builder().extend(iter).build() + } +} +#[derive(Clone)] +pub struct Bytes(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for Bytes { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for Bytes { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for Bytes { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + let raw_data = hex_string(&self.raw_data()); + write!(f, "{}(0x{})", Self::NAME, raw_data) + } +} +impl ::core::default::Default for Bytes { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + Bytes::new_unchecked(v) + } +} +impl Bytes { + const DEFAULT_VALUE: [u8; 4] = [0, 0, 0, 0]; + pub const ITEM_SIZE: usize = 1; + pub fn total_size(&self) -> usize { + molecule::NUMBER_SIZE + Self::ITEM_SIZE * self.item_count() + } + pub fn item_count(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn len(&self) -> usize { + self.item_count() + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn get(&self, idx: usize) -> Option { + if idx >= self.len() { + None + } else { + Some(self.get_unchecked(idx)) + } + } + pub fn get_unchecked(&self, idx: usize) -> Byte { + let start = molecule::NUMBER_SIZE + Self::ITEM_SIZE * idx; + let end = start + Self::ITEM_SIZE; + Byte::new_unchecked(self.0.slice(start..end)) + } + pub fn raw_data(&self) -> molecule::bytes::Bytes { + self.0.slice(molecule::NUMBER_SIZE..) + } + pub fn as_reader<'r>(&'r self) -> BytesReader<'r> { + BytesReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for Bytes { + type Builder = BytesBuilder; + const NAME: &'static str = "Bytes"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + Bytes(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + BytesReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + BytesReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder().extend(self.into_iter()) + } +} +#[derive(Clone, Copy)] +pub struct BytesReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for BytesReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for BytesReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for BytesReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + let raw_data = hex_string(&self.raw_data()); + write!(f, "{}(0x{})", Self::NAME, raw_data) + } +} +impl<'r> BytesReader<'r> { + pub const ITEM_SIZE: usize = 1; + pub fn total_size(&self) -> usize { + molecule::NUMBER_SIZE + Self::ITEM_SIZE * self.item_count() + } + pub fn item_count(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn len(&self) -> usize { + self.item_count() + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn get(&self, idx: usize) -> Option> { + if idx >= self.len() { + None + } else { + Some(self.get_unchecked(idx)) + } + } + pub fn get_unchecked(&self, idx: usize) -> ByteReader<'r> { + let start = molecule::NUMBER_SIZE + Self::ITEM_SIZE * idx; + let end = start + Self::ITEM_SIZE; + ByteReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn raw_data(&self) -> &'r [u8] { + &self.as_slice()[molecule::NUMBER_SIZE..] + } +} +impl<'r> molecule::prelude::Reader<'r> for BytesReader<'r> { + type Entity = Bytes; + const NAME: &'static str = "BytesReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + BytesReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], _compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let item_count = molecule::unpack_number(slice) as usize; + if item_count == 0 { + if slice_len != molecule::NUMBER_SIZE { + return ve!(Self, TotalSizeNotMatch, molecule::NUMBER_SIZE, slice_len); + } + return Ok(()); + } + let total_size = molecule::NUMBER_SIZE + Self::ITEM_SIZE * item_count; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + Ok(()) + } +} +#[derive(Clone, Debug, Default)] +pub struct BytesBuilder(pub(crate) Vec); +impl BytesBuilder { + pub const ITEM_SIZE: usize = 1; + pub fn set(mut self, v: Vec) -> Self { + self.0 = v; + self + } + pub fn push(mut self, v: Byte) -> Self { + self.0.push(v); + self + } + pub fn extend>(mut self, iter: T) -> Self { + for elem in iter { + self.0.push(elem); + } + self + } + pub fn replace(&mut self, index: usize, v: Byte) -> Option { + self.0 + .get_mut(index) + .map(|item| ::core::mem::replace(item, v)) + } +} +impl molecule::prelude::Builder for BytesBuilder { + type Entity = Bytes; + const NAME: &'static str = "BytesBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE + Self::ITEM_SIZE * self.0.len() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + writer.write_all(&molecule::pack_number(self.0.len() as molecule::Number))?; + for inner in &self.0[..] { + writer.write_all(inner.as_slice())?; + } + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + Bytes::new_unchecked(inner.into()) + } +} +pub struct BytesIterator(Bytes, usize, usize); +impl ::core::iter::Iterator for BytesIterator { + type Item = Byte; + fn next(&mut self) -> Option { + if self.1 >= self.2 { + None + } else { + let ret = self.0.get_unchecked(self.1); + self.1 += 1; + Some(ret) + } + } +} +impl ::core::iter::ExactSizeIterator for BytesIterator { + fn len(&self) -> usize { + self.2 - self.1 + } +} +impl ::core::iter::IntoIterator for Bytes { + type Item = Byte; + type IntoIter = BytesIterator; + fn into_iter(self) -> Self::IntoIter { + let len = self.len(); + BytesIterator(self, 0, len) + } +} +impl ::core::iter::FromIterator for Bytes { + fn from_iter>(iter: T) -> Self { + Self::new_builder().extend(iter).build() + } +} +impl ::core::iter::FromIterator for Bytes { + fn from_iter>(iter: T) -> Self { + Self::new_builder() + .extend(iter.into_iter().map(Into::into)) + .build() + } +} +#[derive(Clone)] +pub struct Pipe(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for Pipe { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for Pipe { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for Pipe { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "vm", self.vm())?; + write!(f, ", {}: {}", "read_fd", self.read_fd())?; + write!(f, ", {}: {}", "write_fd", self.write_fd())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl ::core::default::Default for Pipe { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + Pipe::new_unchecked(v) + } +} +impl Pipe { + const DEFAULT_VALUE: [u8; 40] = [ + 40, 0, 0, 0, 16, 0, 0, 0, 24, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + pub const FIELD_COUNT: usize = 3; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn vm(&self) -> VmIndex { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + VmIndex::new_unchecked(self.0.slice(start..end)) + } + pub fn read_fd(&self) -> FdIndex { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + let end = molecule::unpack_number(&slice[12..]) as usize; + FdIndex::new_unchecked(self.0.slice(start..end)) + } + pub fn write_fd(&self) -> FdIndex { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[12..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[16..]) as usize; + FdIndex::new_unchecked(self.0.slice(start..end)) + } else { + FdIndex::new_unchecked(self.0.slice(start..)) + } + } + pub fn as_reader<'r>(&'r self) -> PipeReader<'r> { + PipeReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for Pipe { + type Builder = PipeBuilder; + const NAME: &'static str = "Pipe"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + Pipe(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + PipeReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + PipeReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder() + .vm(self.vm()) + .read_fd(self.read_fd()) + .write_fd(self.write_fd()) + } +} +#[derive(Clone, Copy)] +pub struct PipeReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for PipeReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for PipeReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for PipeReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "vm", self.vm())?; + write!(f, ", {}: {}", "read_fd", self.read_fd())?; + write!(f, ", {}: {}", "write_fd", self.write_fd())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl<'r> PipeReader<'r> { + pub const FIELD_COUNT: usize = 3; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn vm(&self) -> VmIndexReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + VmIndexReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn read_fd(&self) -> FdIndexReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + let end = molecule::unpack_number(&slice[12..]) as usize; + FdIndexReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn write_fd(&self) -> FdIndexReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[12..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[16..]) as usize; + FdIndexReader::new_unchecked(&self.as_slice()[start..end]) + } else { + FdIndexReader::new_unchecked(&self.as_slice()[start..]) + } + } +} +impl<'r> molecule::prelude::Reader<'r> for PipeReader<'r> { + type Entity = Pipe; + const NAME: &'static str = "PipeReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + PipeReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let total_size = molecule::unpack_number(slice) as usize; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + if slice_len < molecule::NUMBER_SIZE * 2 { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE * 2, slice_len); + } + let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize; + if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 { + return ve!(Self, OffsetsNotMatch); + } + if slice_len < offset_first { + return ve!(Self, HeaderIsBroken, offset_first, slice_len); + } + let field_count = offset_first / molecule::NUMBER_SIZE - 1; + if field_count < Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + } else if !compatible && field_count > Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + }; + let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first] + .chunks_exact(molecule::NUMBER_SIZE) + .map(|x| molecule::unpack_number(x) as usize) + .collect(); + offsets.push(total_size); + if offsets.windows(2).any(|i| i[0] > i[1]) { + return ve!(Self, OffsetsNotMatch); + } + VmIndexReader::verify(&slice[offsets[0]..offsets[1]], compatible)?; + FdIndexReader::verify(&slice[offsets[1]..offsets[2]], compatible)?; + FdIndexReader::verify(&slice[offsets[2]..offsets[3]], compatible)?; + Ok(()) + } +} +#[derive(Clone, Debug, Default)] +pub struct PipeBuilder { + pub(crate) vm: VmIndex, + pub(crate) read_fd: FdIndex, + pub(crate) write_fd: FdIndex, +} +impl PipeBuilder { + pub const FIELD_COUNT: usize = 3; + pub fn vm(mut self, v: VmIndex) -> Self { + self.vm = v; + self + } + pub fn read_fd(mut self, v: FdIndex) -> Self { + self.read_fd = v; + self + } + pub fn write_fd(mut self, v: FdIndex) -> Self { + self.write_fd = v; + self + } +} +impl molecule::prelude::Builder for PipeBuilder { + type Entity = Pipe; + const NAME: &'static str = "PipeBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1) + + self.vm.as_slice().len() + + self.read_fd.as_slice().len() + + self.write_fd.as_slice().len() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1); + let mut offsets = Vec::with_capacity(Self::FIELD_COUNT); + offsets.push(total_size); + total_size += self.vm.as_slice().len(); + offsets.push(total_size); + total_size += self.read_fd.as_slice().len(); + offsets.push(total_size); + total_size += self.write_fd.as_slice().len(); + writer.write_all(&molecule::pack_number(total_size as molecule::Number))?; + for offset in offsets.into_iter() { + writer.write_all(&molecule::pack_number(offset as molecule::Number))?; + } + writer.write_all(self.vm.as_slice())?; + writer.write_all(self.read_fd.as_slice())?; + writer.write_all(self.write_fd.as_slice())?; + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + Pipe::new_unchecked(inner.into()) + } +} +#[derive(Clone)] +pub struct Pipes(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for Pipes { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for Pipes { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for Pipes { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} [", Self::NAME)?; + for i in 0..self.len() { + if i == 0 { + write!(f, "{}", self.get_unchecked(i))?; + } else { + write!(f, ", {}", self.get_unchecked(i))?; + } + } + write!(f, "]") + } +} +impl ::core::default::Default for Pipes { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + Pipes::new_unchecked(v) + } +} +impl Pipes { + const DEFAULT_VALUE: [u8; 4] = [4, 0, 0, 0]; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn item_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn len(&self) -> usize { + self.item_count() + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn get(&self, idx: usize) -> Option { + if idx >= self.len() { + None + } else { + Some(self.get_unchecked(idx)) + } + } + pub fn get_unchecked(&self, idx: usize) -> Pipe { + let slice = self.as_slice(); + let start_idx = molecule::NUMBER_SIZE * (1 + idx); + let start = molecule::unpack_number(&slice[start_idx..]) as usize; + if idx == self.len() - 1 { + Pipe::new_unchecked(self.0.slice(start..)) + } else { + let end_idx = start_idx + molecule::NUMBER_SIZE; + let end = molecule::unpack_number(&slice[end_idx..]) as usize; + Pipe::new_unchecked(self.0.slice(start..end)) + } + } + pub fn as_reader<'r>(&'r self) -> PipesReader<'r> { + PipesReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for Pipes { + type Builder = PipesBuilder; + const NAME: &'static str = "Pipes"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + Pipes(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + PipesReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + PipesReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder().extend(self.into_iter()) + } +} +#[derive(Clone, Copy)] +pub struct PipesReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for PipesReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for PipesReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for PipesReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} [", Self::NAME)?; + for i in 0..self.len() { + if i == 0 { + write!(f, "{}", self.get_unchecked(i))?; + } else { + write!(f, ", {}", self.get_unchecked(i))?; + } + } + write!(f, "]") + } +} +impl<'r> PipesReader<'r> { + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn item_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn len(&self) -> usize { + self.item_count() + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn get(&self, idx: usize) -> Option> { + if idx >= self.len() { + None + } else { + Some(self.get_unchecked(idx)) + } + } + pub fn get_unchecked(&self, idx: usize) -> PipeReader<'r> { + let slice = self.as_slice(); + let start_idx = molecule::NUMBER_SIZE * (1 + idx); + let start = molecule::unpack_number(&slice[start_idx..]) as usize; + if idx == self.len() - 1 { + PipeReader::new_unchecked(&self.as_slice()[start..]) + } else { + let end_idx = start_idx + molecule::NUMBER_SIZE; + let end = molecule::unpack_number(&slice[end_idx..]) as usize; + PipeReader::new_unchecked(&self.as_slice()[start..end]) + } + } +} +impl<'r> molecule::prelude::Reader<'r> for PipesReader<'r> { + type Entity = Pipes; + const NAME: &'static str = "PipesReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + PipesReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let total_size = molecule::unpack_number(slice) as usize; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + if slice_len == molecule::NUMBER_SIZE { + return Ok(()); + } + if slice_len < molecule::NUMBER_SIZE * 2 { + return ve!( + Self, + TotalSizeNotMatch, + molecule::NUMBER_SIZE * 2, + slice_len + ); + } + let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize; + if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 { + return ve!(Self, OffsetsNotMatch); + } + if slice_len < offset_first { + return ve!(Self, HeaderIsBroken, offset_first, slice_len); + } + let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first] + .chunks_exact(molecule::NUMBER_SIZE) + .map(|x| molecule::unpack_number(x) as usize) + .collect(); + offsets.push(total_size); + if offsets.windows(2).any(|i| i[0] > i[1]) { + return ve!(Self, OffsetsNotMatch); + } + for pair in offsets.windows(2) { + let start = pair[0]; + let end = pair[1]; + PipeReader::verify(&slice[start..end], compatible)?; + } + Ok(()) + } +} +#[derive(Clone, Debug, Default)] +pub struct PipesBuilder(pub(crate) Vec); +impl PipesBuilder { + pub fn set(mut self, v: Vec) -> Self { + self.0 = v; + self + } + pub fn push(mut self, v: Pipe) -> Self { + self.0.push(v); + self + } + pub fn extend>(mut self, iter: T) -> Self { + for elem in iter { + self.0.push(elem); + } + self + } + pub fn replace(&mut self, index: usize, v: Pipe) -> Option { + self.0 + .get_mut(index) + .map(|item| ::core::mem::replace(item, v)) + } +} +impl molecule::prelude::Builder for PipesBuilder { + type Entity = Pipes; + const NAME: &'static str = "PipesBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE * (self.0.len() + 1) + + self + .0 + .iter() + .map(|inner| inner.as_slice().len()) + .sum::() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + let item_count = self.0.len(); + if item_count == 0 { + writer.write_all(&molecule::pack_number( + molecule::NUMBER_SIZE as molecule::Number, + ))?; + } else { + let (total_size, offsets) = self.0.iter().fold( + ( + molecule::NUMBER_SIZE * (item_count + 1), + Vec::with_capacity(item_count), + ), + |(start, mut offsets), inner| { + offsets.push(start); + (start + inner.as_slice().len(), offsets) + }, + ); + writer.write_all(&molecule::pack_number(total_size as molecule::Number))?; + for offset in offsets.into_iter() { + writer.write_all(&molecule::pack_number(offset as molecule::Number))?; + } + for inner in self.0.iter() { + writer.write_all(inner.as_slice())?; + } + } + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + Pipes::new_unchecked(inner.into()) + } +} +pub struct PipesIterator(Pipes, usize, usize); +impl ::core::iter::Iterator for PipesIterator { + type Item = Pipe; + fn next(&mut self) -> Option { + if self.1 >= self.2 { + None + } else { + let ret = self.0.get_unchecked(self.1); + self.1 += 1; + Some(ret) + } + } +} +impl ::core::iter::ExactSizeIterator for PipesIterator { + fn len(&self) -> usize { + self.2 - self.1 + } +} +impl ::core::iter::IntoIterator for Pipes { + type Item = Pipe; + type IntoIter = PipesIterator; + fn into_iter(self) -> Self::IntoIter { + let len = self.len(); + PipesIterator(self, 0, len) + } +} +impl<'r> PipesReader<'r> { + pub fn iter<'t>(&'t self) -> PipesReaderIterator<'t, 'r> { + PipesReaderIterator(&self, 0, self.len()) + } +} +pub struct PipesReaderIterator<'t, 'r>(&'t PipesReader<'r>, usize, usize); +impl<'t: 'r, 'r> ::core::iter::Iterator for PipesReaderIterator<'t, 'r> { + type Item = PipeReader<'t>; + fn next(&mut self) -> Option { + if self.1 >= self.2 { + None + } else { + let ret = self.0.get_unchecked(self.1); + self.1 += 1; + Some(ret) + } + } +} +impl<'t: 'r, 'r> ::core::iter::ExactSizeIterator for PipesReaderIterator<'t, 'r> { + fn len(&self) -> usize { + self.2 - self.1 + } +} +impl ::core::iter::FromIterator for Pipes { + fn from_iter>(iter: T) -> Self { + Self::new_builder().extend(iter).build() + } +} +#[derive(Clone)] +pub struct Write(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for Write { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for Write { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for Write { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "from", self.from())?; + write!(f, ", {}: {}", "from_fd", self.from_fd())?; + write!(f, ", {}: {}", "to", self.to())?; + write!(f, ", {}: {}", "to_fd", self.to_fd())?; + write!(f, ", {}: {}", "data", self.data())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl ::core::default::Default for Write { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + Write::new_unchecked(v) + } +} +impl Write { + const DEFAULT_VALUE: [u8; 60] = [ + 60, 0, 0, 0, 24, 0, 0, 0, 32, 0, 0, 0, 40, 0, 0, 0, 48, 0, 0, 0, 56, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, + ]; + pub const FIELD_COUNT: usize = 5; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn from(&self) -> VmIndex { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + VmIndex::new_unchecked(self.0.slice(start..end)) + } + pub fn from_fd(&self) -> FdIndex { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + let end = molecule::unpack_number(&slice[12..]) as usize; + FdIndex::new_unchecked(self.0.slice(start..end)) + } + pub fn to(&self) -> VmIndex { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[12..]) as usize; + let end = molecule::unpack_number(&slice[16..]) as usize; + VmIndex::new_unchecked(self.0.slice(start..end)) + } + pub fn to_fd(&self) -> FdIndex { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[16..]) as usize; + let end = molecule::unpack_number(&slice[20..]) as usize; + FdIndex::new_unchecked(self.0.slice(start..end)) + } + pub fn data(&self) -> Bytes { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[20..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[24..]) as usize; + Bytes::new_unchecked(self.0.slice(start..end)) + } else { + Bytes::new_unchecked(self.0.slice(start..)) + } + } + pub fn as_reader<'r>(&'r self) -> WriteReader<'r> { + WriteReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for Write { + type Builder = WriteBuilder; + const NAME: &'static str = "Write"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + Write(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + WriteReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + WriteReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder() + .from(self.from()) + .from_fd(self.from_fd()) + .to(self.to()) + .to_fd(self.to_fd()) + .data(self.data()) + } +} +#[derive(Clone, Copy)] +pub struct WriteReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for WriteReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for WriteReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for WriteReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "from", self.from())?; + write!(f, ", {}: {}", "from_fd", self.from_fd())?; + write!(f, ", {}: {}", "to", self.to())?; + write!(f, ", {}: {}", "to_fd", self.to_fd())?; + write!(f, ", {}: {}", "data", self.data())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl<'r> WriteReader<'r> { + pub const FIELD_COUNT: usize = 5; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn from(&self) -> VmIndexReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + VmIndexReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn from_fd(&self) -> FdIndexReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + let end = molecule::unpack_number(&slice[12..]) as usize; + FdIndexReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn to(&self) -> VmIndexReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[12..]) as usize; + let end = molecule::unpack_number(&slice[16..]) as usize; + VmIndexReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn to_fd(&self) -> FdIndexReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[16..]) as usize; + let end = molecule::unpack_number(&slice[20..]) as usize; + FdIndexReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn data(&self) -> BytesReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[20..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[24..]) as usize; + BytesReader::new_unchecked(&self.as_slice()[start..end]) + } else { + BytesReader::new_unchecked(&self.as_slice()[start..]) + } + } +} +impl<'r> molecule::prelude::Reader<'r> for WriteReader<'r> { + type Entity = Write; + const NAME: &'static str = "WriteReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + WriteReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let total_size = molecule::unpack_number(slice) as usize; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + if slice_len < molecule::NUMBER_SIZE * 2 { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE * 2, slice_len); + } + let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize; + if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 { + return ve!(Self, OffsetsNotMatch); + } + if slice_len < offset_first { + return ve!(Self, HeaderIsBroken, offset_first, slice_len); + } + let field_count = offset_first / molecule::NUMBER_SIZE - 1; + if field_count < Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + } else if !compatible && field_count > Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + }; + let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first] + .chunks_exact(molecule::NUMBER_SIZE) + .map(|x| molecule::unpack_number(x) as usize) + .collect(); + offsets.push(total_size); + if offsets.windows(2).any(|i| i[0] > i[1]) { + return ve!(Self, OffsetsNotMatch); + } + VmIndexReader::verify(&slice[offsets[0]..offsets[1]], compatible)?; + FdIndexReader::verify(&slice[offsets[1]..offsets[2]], compatible)?; + VmIndexReader::verify(&slice[offsets[2]..offsets[3]], compatible)?; + FdIndexReader::verify(&slice[offsets[3]..offsets[4]], compatible)?; + BytesReader::verify(&slice[offsets[4]..offsets[5]], compatible)?; + Ok(()) + } +} +#[derive(Clone, Debug, Default)] +pub struct WriteBuilder { + pub(crate) from: VmIndex, + pub(crate) from_fd: FdIndex, + pub(crate) to: VmIndex, + pub(crate) to_fd: FdIndex, + pub(crate) data: Bytes, +} +impl WriteBuilder { + pub const FIELD_COUNT: usize = 5; + pub fn from(mut self, v: VmIndex) -> Self { + self.from = v; + self + } + pub fn from_fd(mut self, v: FdIndex) -> Self { + self.from_fd = v; + self + } + pub fn to(mut self, v: VmIndex) -> Self { + self.to = v; + self + } + pub fn to_fd(mut self, v: FdIndex) -> Self { + self.to_fd = v; + self + } + pub fn data(mut self, v: Bytes) -> Self { + self.data = v; + self + } +} +impl molecule::prelude::Builder for WriteBuilder { + type Entity = Write; + const NAME: &'static str = "WriteBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1) + + self.from.as_slice().len() + + self.from_fd.as_slice().len() + + self.to.as_slice().len() + + self.to_fd.as_slice().len() + + self.data.as_slice().len() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1); + let mut offsets = Vec::with_capacity(Self::FIELD_COUNT); + offsets.push(total_size); + total_size += self.from.as_slice().len(); + offsets.push(total_size); + total_size += self.from_fd.as_slice().len(); + offsets.push(total_size); + total_size += self.to.as_slice().len(); + offsets.push(total_size); + total_size += self.to_fd.as_slice().len(); + offsets.push(total_size); + total_size += self.data.as_slice().len(); + writer.write_all(&molecule::pack_number(total_size as molecule::Number))?; + for offset in offsets.into_iter() { + writer.write_all(&molecule::pack_number(offset as molecule::Number))?; + } + writer.write_all(self.from.as_slice())?; + writer.write_all(self.from_fd.as_slice())?; + writer.write_all(self.to.as_slice())?; + writer.write_all(self.to_fd.as_slice())?; + writer.write_all(self.data.as_slice())?; + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + Write::new_unchecked(inner.into()) + } +} +#[derive(Clone)] +pub struct Writes(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for Writes { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for Writes { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for Writes { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} [", Self::NAME)?; + for i in 0..self.len() { + if i == 0 { + write!(f, "{}", self.get_unchecked(i))?; + } else { + write!(f, ", {}", self.get_unchecked(i))?; + } + } + write!(f, "]") + } +} +impl ::core::default::Default for Writes { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + Writes::new_unchecked(v) + } +} +impl Writes { + const DEFAULT_VALUE: [u8; 4] = [4, 0, 0, 0]; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn item_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn len(&self) -> usize { + self.item_count() + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn get(&self, idx: usize) -> Option { + if idx >= self.len() { + None + } else { + Some(self.get_unchecked(idx)) + } + } + pub fn get_unchecked(&self, idx: usize) -> Write { + let slice = self.as_slice(); + let start_idx = molecule::NUMBER_SIZE * (1 + idx); + let start = molecule::unpack_number(&slice[start_idx..]) as usize; + if idx == self.len() - 1 { + Write::new_unchecked(self.0.slice(start..)) + } else { + let end_idx = start_idx + molecule::NUMBER_SIZE; + let end = molecule::unpack_number(&slice[end_idx..]) as usize; + Write::new_unchecked(self.0.slice(start..end)) + } + } + pub fn as_reader<'r>(&'r self) -> WritesReader<'r> { + WritesReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for Writes { + type Builder = WritesBuilder; + const NAME: &'static str = "Writes"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + Writes(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + WritesReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + WritesReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder().extend(self.into_iter()) + } +} +#[derive(Clone, Copy)] +pub struct WritesReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for WritesReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for WritesReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for WritesReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} [", Self::NAME)?; + for i in 0..self.len() { + if i == 0 { + write!(f, "{}", self.get_unchecked(i))?; + } else { + write!(f, ", {}", self.get_unchecked(i))?; + } + } + write!(f, "]") + } +} +impl<'r> WritesReader<'r> { + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn item_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn len(&self) -> usize { + self.item_count() + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn get(&self, idx: usize) -> Option> { + if idx >= self.len() { + None + } else { + Some(self.get_unchecked(idx)) + } + } + pub fn get_unchecked(&self, idx: usize) -> WriteReader<'r> { + let slice = self.as_slice(); + let start_idx = molecule::NUMBER_SIZE * (1 + idx); + let start = molecule::unpack_number(&slice[start_idx..]) as usize; + if idx == self.len() - 1 { + WriteReader::new_unchecked(&self.as_slice()[start..]) + } else { + let end_idx = start_idx + molecule::NUMBER_SIZE; + let end = molecule::unpack_number(&slice[end_idx..]) as usize; + WriteReader::new_unchecked(&self.as_slice()[start..end]) + } + } +} +impl<'r> molecule::prelude::Reader<'r> for WritesReader<'r> { + type Entity = Writes; + const NAME: &'static str = "WritesReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + WritesReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let total_size = molecule::unpack_number(slice) as usize; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + if slice_len == molecule::NUMBER_SIZE { + return Ok(()); + } + if slice_len < molecule::NUMBER_SIZE * 2 { + return ve!( + Self, + TotalSizeNotMatch, + molecule::NUMBER_SIZE * 2, + slice_len + ); + } + let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize; + if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 { + return ve!(Self, OffsetsNotMatch); + } + if slice_len < offset_first { + return ve!(Self, HeaderIsBroken, offset_first, slice_len); + } + let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first] + .chunks_exact(molecule::NUMBER_SIZE) + .map(|x| molecule::unpack_number(x) as usize) + .collect(); + offsets.push(total_size); + if offsets.windows(2).any(|i| i[0] > i[1]) { + return ve!(Self, OffsetsNotMatch); + } + for pair in offsets.windows(2) { + let start = pair[0]; + let end = pair[1]; + WriteReader::verify(&slice[start..end], compatible)?; + } + Ok(()) + } +} +#[derive(Clone, Debug, Default)] +pub struct WritesBuilder(pub(crate) Vec); +impl WritesBuilder { + pub fn set(mut self, v: Vec) -> Self { + self.0 = v; + self + } + pub fn push(mut self, v: Write) -> Self { + self.0.push(v); + self + } + pub fn extend>(mut self, iter: T) -> Self { + for elem in iter { + self.0.push(elem); + } + self + } + pub fn replace(&mut self, index: usize, v: Write) -> Option { + self.0 + .get_mut(index) + .map(|item| ::core::mem::replace(item, v)) + } +} +impl molecule::prelude::Builder for WritesBuilder { + type Entity = Writes; + const NAME: &'static str = "WritesBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE * (self.0.len() + 1) + + self + .0 + .iter() + .map(|inner| inner.as_slice().len()) + .sum::() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + let item_count = self.0.len(); + if item_count == 0 { + writer.write_all(&molecule::pack_number( + molecule::NUMBER_SIZE as molecule::Number, + ))?; + } else { + let (total_size, offsets) = self.0.iter().fold( + ( + molecule::NUMBER_SIZE * (item_count + 1), + Vec::with_capacity(item_count), + ), + |(start, mut offsets), inner| { + offsets.push(start); + (start + inner.as_slice().len(), offsets) + }, + ); + writer.write_all(&molecule::pack_number(total_size as molecule::Number))?; + for offset in offsets.into_iter() { + writer.write_all(&molecule::pack_number(offset as molecule::Number))?; + } + for inner in self.0.iter() { + writer.write_all(inner.as_slice())?; + } + } + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + Writes::new_unchecked(inner.into()) + } +} +pub struct WritesIterator(Writes, usize, usize); +impl ::core::iter::Iterator for WritesIterator { + type Item = Write; + fn next(&mut self) -> Option { + if self.1 >= self.2 { + None + } else { + let ret = self.0.get_unchecked(self.1); + self.1 += 1; + Some(ret) + } + } +} +impl ::core::iter::ExactSizeIterator for WritesIterator { + fn len(&self) -> usize { + self.2 - self.1 + } +} +impl ::core::iter::IntoIterator for Writes { + type Item = Write; + type IntoIter = WritesIterator; + fn into_iter(self) -> Self::IntoIter { + let len = self.len(); + WritesIterator(self, 0, len) + } +} +impl<'r> WritesReader<'r> { + pub fn iter<'t>(&'t self) -> WritesReaderIterator<'t, 'r> { + WritesReaderIterator(&self, 0, self.len()) + } +} +pub struct WritesReaderIterator<'t, 'r>(&'t WritesReader<'r>, usize, usize); +impl<'t: 'r, 'r> ::core::iter::Iterator for WritesReaderIterator<'t, 'r> { + type Item = WriteReader<'t>; + fn next(&mut self) -> Option { + if self.1 >= self.2 { + None + } else { + let ret = self.0.get_unchecked(self.1); + self.1 += 1; + Some(ret) + } + } +} +impl<'t: 'r, 'r> ::core::iter::ExactSizeIterator for WritesReaderIterator<'t, 'r> { + fn len(&self) -> usize { + self.2 - self.1 + } +} +impl ::core::iter::FromIterator for Writes { + fn from_iter>(iter: T) -> Self { + Self::new_builder().extend(iter).build() + } +} +#[derive(Clone)] +pub struct Spawn(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for Spawn { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for Spawn { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for Spawn { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "from", self.from())?; + write!(f, ", {}: {}", "child", self.child())?; + write!(f, ", {}: {}", "fds", self.fds())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl ::core::default::Default for Spawn { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + Spawn::new_unchecked(v) + } +} +impl Spawn { + const DEFAULT_VALUE: [u8; 36] = [ + 36, 0, 0, 0, 16, 0, 0, 0, 24, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + ]; + pub const FIELD_COUNT: usize = 3; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn from(&self) -> VmIndex { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + VmIndex::new_unchecked(self.0.slice(start..end)) + } + pub fn child(&self) -> VmIndex { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + let end = molecule::unpack_number(&slice[12..]) as usize; + VmIndex::new_unchecked(self.0.slice(start..end)) + } + pub fn fds(&self) -> FdIndices { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[12..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[16..]) as usize; + FdIndices::new_unchecked(self.0.slice(start..end)) + } else { + FdIndices::new_unchecked(self.0.slice(start..)) + } + } + pub fn as_reader<'r>(&'r self) -> SpawnReader<'r> { + SpawnReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for Spawn { + type Builder = SpawnBuilder; + const NAME: &'static str = "Spawn"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + Spawn(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + SpawnReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + SpawnReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder() + .from(self.from()) + .child(self.child()) + .fds(self.fds()) + } +} +#[derive(Clone, Copy)] +pub struct SpawnReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for SpawnReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for SpawnReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for SpawnReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "from", self.from())?; + write!(f, ", {}: {}", "child", self.child())?; + write!(f, ", {}: {}", "fds", self.fds())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl<'r> SpawnReader<'r> { + pub const FIELD_COUNT: usize = 3; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn from(&self) -> VmIndexReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + VmIndexReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn child(&self) -> VmIndexReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + let end = molecule::unpack_number(&slice[12..]) as usize; + VmIndexReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn fds(&self) -> FdIndicesReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[12..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[16..]) as usize; + FdIndicesReader::new_unchecked(&self.as_slice()[start..end]) + } else { + FdIndicesReader::new_unchecked(&self.as_slice()[start..]) + } + } +} +impl<'r> molecule::prelude::Reader<'r> for SpawnReader<'r> { + type Entity = Spawn; + const NAME: &'static str = "SpawnReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + SpawnReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let total_size = molecule::unpack_number(slice) as usize; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + if slice_len < molecule::NUMBER_SIZE * 2 { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE * 2, slice_len); + } + let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize; + if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 { + return ve!(Self, OffsetsNotMatch); + } + if slice_len < offset_first { + return ve!(Self, HeaderIsBroken, offset_first, slice_len); + } + let field_count = offset_first / molecule::NUMBER_SIZE - 1; + if field_count < Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + } else if !compatible && field_count > Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + }; + let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first] + .chunks_exact(molecule::NUMBER_SIZE) + .map(|x| molecule::unpack_number(x) as usize) + .collect(); + offsets.push(total_size); + if offsets.windows(2).any(|i| i[0] > i[1]) { + return ve!(Self, OffsetsNotMatch); + } + VmIndexReader::verify(&slice[offsets[0]..offsets[1]], compatible)?; + VmIndexReader::verify(&slice[offsets[1]..offsets[2]], compatible)?; + FdIndicesReader::verify(&slice[offsets[2]..offsets[3]], compatible)?; + Ok(()) + } +} +#[derive(Clone, Debug, Default)] +pub struct SpawnBuilder { + pub(crate) from: VmIndex, + pub(crate) child: VmIndex, + pub(crate) fds: FdIndices, +} +impl SpawnBuilder { + pub const FIELD_COUNT: usize = 3; + pub fn from(mut self, v: VmIndex) -> Self { + self.from = v; + self + } + pub fn child(mut self, v: VmIndex) -> Self { + self.child = v; + self + } + pub fn fds(mut self, v: FdIndices) -> Self { + self.fds = v; + self + } +} +impl molecule::prelude::Builder for SpawnBuilder { + type Entity = Spawn; + const NAME: &'static str = "SpawnBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1) + + self.from.as_slice().len() + + self.child.as_slice().len() + + self.fds.as_slice().len() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1); + let mut offsets = Vec::with_capacity(Self::FIELD_COUNT); + offsets.push(total_size); + total_size += self.from.as_slice().len(); + offsets.push(total_size); + total_size += self.child.as_slice().len(); + offsets.push(total_size); + total_size += self.fds.as_slice().len(); + writer.write_all(&molecule::pack_number(total_size as molecule::Number))?; + for offset in offsets.into_iter() { + writer.write_all(&molecule::pack_number(offset as molecule::Number))?; + } + writer.write_all(self.from.as_slice())?; + writer.write_all(self.child.as_slice())?; + writer.write_all(self.fds.as_slice())?; + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + Spawn::new_unchecked(inner.into()) + } +} +#[derive(Clone)] +pub struct Spawns(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for Spawns { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for Spawns { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for Spawns { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} [", Self::NAME)?; + for i in 0..self.len() { + if i == 0 { + write!(f, "{}", self.get_unchecked(i))?; + } else { + write!(f, ", {}", self.get_unchecked(i))?; + } + } + write!(f, "]") + } +} +impl ::core::default::Default for Spawns { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + Spawns::new_unchecked(v) + } +} +impl Spawns { + const DEFAULT_VALUE: [u8; 4] = [4, 0, 0, 0]; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn item_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn len(&self) -> usize { + self.item_count() + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn get(&self, idx: usize) -> Option { + if idx >= self.len() { + None + } else { + Some(self.get_unchecked(idx)) + } + } + pub fn get_unchecked(&self, idx: usize) -> Spawn { + let slice = self.as_slice(); + let start_idx = molecule::NUMBER_SIZE * (1 + idx); + let start = molecule::unpack_number(&slice[start_idx..]) as usize; + if idx == self.len() - 1 { + Spawn::new_unchecked(self.0.slice(start..)) + } else { + let end_idx = start_idx + molecule::NUMBER_SIZE; + let end = molecule::unpack_number(&slice[end_idx..]) as usize; + Spawn::new_unchecked(self.0.slice(start..end)) + } + } + pub fn as_reader<'r>(&'r self) -> SpawnsReader<'r> { + SpawnsReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for Spawns { + type Builder = SpawnsBuilder; + const NAME: &'static str = "Spawns"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + Spawns(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + SpawnsReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + SpawnsReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder().extend(self.into_iter()) + } +} +#[derive(Clone, Copy)] +pub struct SpawnsReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for SpawnsReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for SpawnsReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for SpawnsReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} [", Self::NAME)?; + for i in 0..self.len() { + if i == 0 { + write!(f, "{}", self.get_unchecked(i))?; + } else { + write!(f, ", {}", self.get_unchecked(i))?; + } + } + write!(f, "]") + } +} +impl<'r> SpawnsReader<'r> { + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn item_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn len(&self) -> usize { + self.item_count() + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + pub fn get(&self, idx: usize) -> Option> { + if idx >= self.len() { + None + } else { + Some(self.get_unchecked(idx)) + } + } + pub fn get_unchecked(&self, idx: usize) -> SpawnReader<'r> { + let slice = self.as_slice(); + let start_idx = molecule::NUMBER_SIZE * (1 + idx); + let start = molecule::unpack_number(&slice[start_idx..]) as usize; + if idx == self.len() - 1 { + SpawnReader::new_unchecked(&self.as_slice()[start..]) + } else { + let end_idx = start_idx + molecule::NUMBER_SIZE; + let end = molecule::unpack_number(&slice[end_idx..]) as usize; + SpawnReader::new_unchecked(&self.as_slice()[start..end]) + } + } +} +impl<'r> molecule::prelude::Reader<'r> for SpawnsReader<'r> { + type Entity = Spawns; + const NAME: &'static str = "SpawnsReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + SpawnsReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let total_size = molecule::unpack_number(slice) as usize; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + if slice_len == molecule::NUMBER_SIZE { + return Ok(()); + } + if slice_len < molecule::NUMBER_SIZE * 2 { + return ve!( + Self, + TotalSizeNotMatch, + molecule::NUMBER_SIZE * 2, + slice_len + ); + } + let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize; + if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 { + return ve!(Self, OffsetsNotMatch); + } + if slice_len < offset_first { + return ve!(Self, HeaderIsBroken, offset_first, slice_len); + } + let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first] + .chunks_exact(molecule::NUMBER_SIZE) + .map(|x| molecule::unpack_number(x) as usize) + .collect(); + offsets.push(total_size); + if offsets.windows(2).any(|i| i[0] > i[1]) { + return ve!(Self, OffsetsNotMatch); + } + for pair in offsets.windows(2) { + let start = pair[0]; + let end = pair[1]; + SpawnReader::verify(&slice[start..end], compatible)?; + } + Ok(()) + } +} +#[derive(Clone, Debug, Default)] +pub struct SpawnsBuilder(pub(crate) Vec); +impl SpawnsBuilder { + pub fn set(mut self, v: Vec) -> Self { + self.0 = v; + self + } + pub fn push(mut self, v: Spawn) -> Self { + self.0.push(v); + self + } + pub fn extend>(mut self, iter: T) -> Self { + for elem in iter { + self.0.push(elem); + } + self + } + pub fn replace(&mut self, index: usize, v: Spawn) -> Option { + self.0 + .get_mut(index) + .map(|item| ::core::mem::replace(item, v)) + } +} +impl molecule::prelude::Builder for SpawnsBuilder { + type Entity = Spawns; + const NAME: &'static str = "SpawnsBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE * (self.0.len() + 1) + + self + .0 + .iter() + .map(|inner| inner.as_slice().len()) + .sum::() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + let item_count = self.0.len(); + if item_count == 0 { + writer.write_all(&molecule::pack_number( + molecule::NUMBER_SIZE as molecule::Number, + ))?; + } else { + let (total_size, offsets) = self.0.iter().fold( + ( + molecule::NUMBER_SIZE * (item_count + 1), + Vec::with_capacity(item_count), + ), + |(start, mut offsets), inner| { + offsets.push(start); + (start + inner.as_slice().len(), offsets) + }, + ); + writer.write_all(&molecule::pack_number(total_size as molecule::Number))?; + for offset in offsets.into_iter() { + writer.write_all(&molecule::pack_number(offset as molecule::Number))?; + } + for inner in self.0.iter() { + writer.write_all(inner.as_slice())?; + } + } + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + Spawns::new_unchecked(inner.into()) + } +} +pub struct SpawnsIterator(Spawns, usize, usize); +impl ::core::iter::Iterator for SpawnsIterator { + type Item = Spawn; + fn next(&mut self) -> Option { + if self.1 >= self.2 { + None + } else { + let ret = self.0.get_unchecked(self.1); + self.1 += 1; + Some(ret) + } + } +} +impl ::core::iter::ExactSizeIterator for SpawnsIterator { + fn len(&self) -> usize { + self.2 - self.1 + } +} +impl ::core::iter::IntoIterator for Spawns { + type Item = Spawn; + type IntoIter = SpawnsIterator; + fn into_iter(self) -> Self::IntoIter { + let len = self.len(); + SpawnsIterator(self, 0, len) + } +} +impl<'r> SpawnsReader<'r> { + pub fn iter<'t>(&'t self) -> SpawnsReaderIterator<'t, 'r> { + SpawnsReaderIterator(&self, 0, self.len()) + } +} +pub struct SpawnsReaderIterator<'t, 'r>(&'t SpawnsReader<'r>, usize, usize); +impl<'t: 'r, 'r> ::core::iter::Iterator for SpawnsReaderIterator<'t, 'r> { + type Item = SpawnReader<'t>; + fn next(&mut self) -> Option { + if self.1 >= self.2 { + None + } else { + let ret = self.0.get_unchecked(self.1); + self.1 += 1; + Some(ret) + } + } +} +impl<'t: 'r, 'r> ::core::iter::ExactSizeIterator for SpawnsReaderIterator<'t, 'r> { + fn len(&self) -> usize { + self.2 - self.1 + } +} +impl ::core::iter::FromIterator for Spawns { + fn from_iter>(iter: T) -> Self { + Self::new_builder().extend(iter).build() + } +} +#[derive(Clone)] +pub struct Data(molecule::bytes::Bytes); +impl ::core::fmt::LowerHex for Data { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl ::core::fmt::Debug for Data { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl ::core::fmt::Display for Data { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "spawns", self.spawns())?; + write!(f, ", {}: {}", "pipes", self.pipes())?; + write!(f, ", {}: {}", "writes", self.writes())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl ::core::default::Default for Data { + fn default() -> Self { + let v = molecule::bytes::Bytes::from_static(&Self::DEFAULT_VALUE); + Data::new_unchecked(v) + } +} +impl Data { + const DEFAULT_VALUE: [u8; 28] = [ + 28, 0, 0, 0, 16, 0, 0, 0, 20, 0, 0, 0, 24, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, + ]; + pub const FIELD_COUNT: usize = 3; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn spawns(&self) -> Spawns { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + Spawns::new_unchecked(self.0.slice(start..end)) + } + pub fn pipes(&self) -> Pipes { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + let end = molecule::unpack_number(&slice[12..]) as usize; + Pipes::new_unchecked(self.0.slice(start..end)) + } + pub fn writes(&self) -> Writes { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[12..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[16..]) as usize; + Writes::new_unchecked(self.0.slice(start..end)) + } else { + Writes::new_unchecked(self.0.slice(start..)) + } + } + pub fn as_reader<'r>(&'r self) -> DataReader<'r> { + DataReader::new_unchecked(self.as_slice()) + } +} +impl molecule::prelude::Entity for Data { + type Builder = DataBuilder; + const NAME: &'static str = "Data"; + fn new_unchecked(data: molecule::bytes::Bytes) -> Self { + Data(data) + } + fn as_bytes(&self) -> molecule::bytes::Bytes { + self.0.clone() + } + fn as_slice(&self) -> &[u8] { + &self.0[..] + } + fn from_slice(slice: &[u8]) -> molecule::error::VerificationResult { + DataReader::from_slice(slice).map(|reader| reader.to_entity()) + } + fn from_compatible_slice(slice: &[u8]) -> molecule::error::VerificationResult { + DataReader::from_compatible_slice(slice).map(|reader| reader.to_entity()) + } + fn new_builder() -> Self::Builder { + ::core::default::Default::default() + } + fn as_builder(self) -> Self::Builder { + Self::new_builder() + .spawns(self.spawns()) + .pipes(self.pipes()) + .writes(self.writes()) + } +} +#[derive(Clone, Copy)] +pub struct DataReader<'r>(&'r [u8]); +impl<'r> ::core::fmt::LowerHex for DataReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + use molecule::hex_string; + if f.alternate() { + write!(f, "0x")?; + } + write!(f, "{}", hex_string(self.as_slice())) + } +} +impl<'r> ::core::fmt::Debug for DataReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{}({:#x})", Self::NAME, self) + } +} +impl<'r> ::core::fmt::Display for DataReader<'r> { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + write!(f, "{} {{ ", Self::NAME)?; + write!(f, "{}: {}", "spawns", self.spawns())?; + write!(f, ", {}: {}", "pipes", self.pipes())?; + write!(f, ", {}: {}", "writes", self.writes())?; + let extra_count = self.count_extra_fields(); + if extra_count != 0 { + write!(f, ", .. ({} fields)", extra_count)?; + } + write!(f, " }}") + } +} +impl<'r> DataReader<'r> { + pub const FIELD_COUNT: usize = 3; + pub fn total_size(&self) -> usize { + molecule::unpack_number(self.as_slice()) as usize + } + pub fn field_count(&self) -> usize { + if self.total_size() == molecule::NUMBER_SIZE { + 0 + } else { + (molecule::unpack_number(&self.as_slice()[molecule::NUMBER_SIZE..]) as usize / 4) - 1 + } + } + pub fn count_extra_fields(&self) -> usize { + self.field_count() - Self::FIELD_COUNT + } + pub fn has_extra_fields(&self) -> bool { + Self::FIELD_COUNT != self.field_count() + } + pub fn spawns(&self) -> SpawnsReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[4..]) as usize; + let end = molecule::unpack_number(&slice[8..]) as usize; + SpawnsReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn pipes(&self) -> PipesReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[8..]) as usize; + let end = molecule::unpack_number(&slice[12..]) as usize; + PipesReader::new_unchecked(&self.as_slice()[start..end]) + } + pub fn writes(&self) -> WritesReader<'r> { + let slice = self.as_slice(); + let start = molecule::unpack_number(&slice[12..]) as usize; + if self.has_extra_fields() { + let end = molecule::unpack_number(&slice[16..]) as usize; + WritesReader::new_unchecked(&self.as_slice()[start..end]) + } else { + WritesReader::new_unchecked(&self.as_slice()[start..]) + } + } +} +impl<'r> molecule::prelude::Reader<'r> for DataReader<'r> { + type Entity = Data; + const NAME: &'static str = "DataReader"; + fn to_entity(&self) -> Self::Entity { + Self::Entity::new_unchecked(self.as_slice().to_owned().into()) + } + fn new_unchecked(slice: &'r [u8]) -> Self { + DataReader(slice) + } + fn as_slice(&self) -> &'r [u8] { + self.0 + } + fn verify(slice: &[u8], compatible: bool) -> molecule::error::VerificationResult<()> { + use molecule::verification_error as ve; + let slice_len = slice.len(); + if slice_len < molecule::NUMBER_SIZE { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE, slice_len); + } + let total_size = molecule::unpack_number(slice) as usize; + if slice_len != total_size { + return ve!(Self, TotalSizeNotMatch, total_size, slice_len); + } + if slice_len < molecule::NUMBER_SIZE * 2 { + return ve!(Self, HeaderIsBroken, molecule::NUMBER_SIZE * 2, slice_len); + } + let offset_first = molecule::unpack_number(&slice[molecule::NUMBER_SIZE..]) as usize; + if offset_first % molecule::NUMBER_SIZE != 0 || offset_first < molecule::NUMBER_SIZE * 2 { + return ve!(Self, OffsetsNotMatch); + } + if slice_len < offset_first { + return ve!(Self, HeaderIsBroken, offset_first, slice_len); + } + let field_count = offset_first / molecule::NUMBER_SIZE - 1; + if field_count < Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + } else if !compatible && field_count > Self::FIELD_COUNT { + return ve!(Self, FieldCountNotMatch, Self::FIELD_COUNT, field_count); + }; + let mut offsets: Vec = slice[molecule::NUMBER_SIZE..offset_first] + .chunks_exact(molecule::NUMBER_SIZE) + .map(|x| molecule::unpack_number(x) as usize) + .collect(); + offsets.push(total_size); + if offsets.windows(2).any(|i| i[0] > i[1]) { + return ve!(Self, OffsetsNotMatch); + } + SpawnsReader::verify(&slice[offsets[0]..offsets[1]], compatible)?; + PipesReader::verify(&slice[offsets[1]..offsets[2]], compatible)?; + WritesReader::verify(&slice[offsets[2]..offsets[3]], compatible)?; + Ok(()) + } +} +#[derive(Clone, Debug, Default)] +pub struct DataBuilder { + pub(crate) spawns: Spawns, + pub(crate) pipes: Pipes, + pub(crate) writes: Writes, +} +impl DataBuilder { + pub const FIELD_COUNT: usize = 3; + pub fn spawns(mut self, v: Spawns) -> Self { + self.spawns = v; + self + } + pub fn pipes(mut self, v: Pipes) -> Self { + self.pipes = v; + self + } + pub fn writes(mut self, v: Writes) -> Self { + self.writes = v; + self + } +} +impl molecule::prelude::Builder for DataBuilder { + type Entity = Data; + const NAME: &'static str = "DataBuilder"; + fn expected_length(&self) -> usize { + molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1) + + self.spawns.as_slice().len() + + self.pipes.as_slice().len() + + self.writes.as_slice().len() + } + fn write(&self, writer: &mut W) -> molecule::io::Result<()> { + let mut total_size = molecule::NUMBER_SIZE * (Self::FIELD_COUNT + 1); + let mut offsets = Vec::with_capacity(Self::FIELD_COUNT); + offsets.push(total_size); + total_size += self.spawns.as_slice().len(); + offsets.push(total_size); + total_size += self.pipes.as_slice().len(); + offsets.push(total_size); + total_size += self.writes.as_slice().len(); + writer.write_all(&molecule::pack_number(total_size as molecule::Number))?; + for offset in offsets.into_iter() { + writer.write_all(&molecule::pack_number(offset as molecule::Number))?; + } + writer.write_all(self.spawns.as_slice())?; + writer.write_all(self.pipes.as_slice())?; + writer.write_all(self.writes.as_slice())?; + Ok(()) + } + fn build(&self) -> Self::Entity { + let mut inner = Vec::with_capacity(self.expected_length()); + self.write(&mut inner) + .unwrap_or_else(|_| panic!("{} build should be ok", Self::NAME)); + Data::new_unchecked(inner.into()) + } +} diff --git a/script/testdata/spawn_dag_escape_encoding.h b/script/testdata/spawn_dag_escape_encoding.h new file mode 100644 index 0000000000..2b1590da46 --- /dev/null +++ b/script/testdata/spawn_dag_escape_encoding.h @@ -0,0 +1,80 @@ +#ifndef ESCAPE_ENCODING_H_ +#define ESCAPE_ENCODING_H_ + +#include +#include + +#ifndef ESCAPE_ERROR_ENCODING +#define ESCAPE_ERROR_ENCODING 1 +#endif /* ESCAPE_ERROR_ENCODING */ + +size_t ee_maximum_encoding_length(size_t length) { return length * 2; } + +int ee_decode(uint8_t *dst, size_t *dst_length, const uint8_t *src, size_t *src_length) { + size_t ds = 0; + size_t dl = *dst_length; + size_t ss = 0; + size_t sl = *src_length; + + while ((ss < sl) && (ds < dl)) { + if (src[ss] == 0xFE) { + if (ss + 1 >= sl) { + return ESCAPE_ERROR_ENCODING; + } + dst[ds++] = src[ss + 1] + 1; + ss += 2; + } else { + dst[ds++] = src[ss++]; + } + } + + *dst_length = ds; + *src_length = ss; + return 0; +} + +int ee_decode_char_string_in_place(char *buf, size_t *length) { + size_t ss = 0; + size_t ds = 0; + + while (buf[ss] != '\0') { + if (((uint8_t)buf[ss]) == 0xFE) { + if (buf[ss + 1] == '\0') { + return ESCAPE_ERROR_ENCODING; + } + buf[ds++] = buf[ss + 1] + 1; + ss += 2; + } else { + buf[ds++] = buf[ss++]; + } + } + + *length = ds; + return 0; +} + +int ee_encode(uint8_t *dst, size_t *dst_length, const uint8_t *src, size_t *src_length) { + size_t ds = 0; + size_t dl = *dst_length; + size_t ss = 0; + size_t sl = *src_length; + + while ((ss < sl) && (ds < dl)) { + if ((src[ss] == 0x0) || (src[ss] == 0xFE)) { + if (ds + 1 >= dl) { + break; + } + dst[ds] = 0xFE; + dst[ds + 1] = src[ss++] - 1; + ds += 2; + } else { + dst[ds++] = src[ss++]; + } + } + + *dst_length = ds; + *src_length = ss; + return 0; +} + +#endif /* ESCAPE_ENCODING_H_ */ diff --git a/script/testdata/spawn_fuzzing b/script/testdata/spawn_fuzzing new file mode 100755 index 0000000000..0c1602e09a Binary files /dev/null and b/script/testdata/spawn_fuzzing differ diff --git a/script/testdata/spawn_fuzzing.c b/script/testdata/spawn_fuzzing.c new file mode 100644 index 0000000000..7702717c0a --- /dev/null +++ b/script/testdata/spawn_fuzzing.c @@ -0,0 +1,114 @@ +#include "spawn_utils.h" + +typedef enum SyscallId { SyscallRead, SyscallWrite, SyscallClose } SyscallId; + +typedef struct Command { + SyscallId id; + uint64_t buf_ptr; + uint64_t len_ptr; + size_t fd_index; +} Command; + +typedef struct Data { + uint8_t* ptr; + uint64_t offset; + uint64_t total_size; +} Data; + +int extract_command(Data* data, Command* cmd) { + if ((data->offset + 1) > data->total_size) { + return -1; + } + uint8_t id = data->ptr[0]; + + if (id > 250) { + cmd->id = SyscallClose; + cmd->fd_index = (size_t)(id % 2); + data->offset += 1; + } else if (id > 128) { + if ((data->offset + 7) > data->total_size) { + return -1; + } + cmd->id = SyscallRead; + memcpy(&cmd->buf_ptr, &data->ptr[data->offset + 1], 3); + memcpy(&cmd->len_ptr, &data->ptr[data->offset + 4], 3); + data->offset += 7; + } else { + if ((data->offset + 7) > data->total_size) { + return -1; + } + cmd->id = SyscallWrite; + memcpy(&cmd->buf_ptr, &data->ptr[data->offset + 1], 3); + memcpy(&cmd->len_ptr, &data->ptr[data->offset + 4], 3); + data->offset += 7; + } + return 0; +} + +int random_read_write(uint64_t fds[2], size_t index) { + int err = 0; + uint8_t cmd_buf[4096] = {0}; + uint64_t cmd_len = sizeof(cmd_buf); + + err = ckb_load_witness(cmd_buf, &cmd_len, 0, index, CKB_SOURCE_INPUT); + CHECK(err); + Data data = {.ptr = cmd_buf, .total_size = cmd_len, .offset = 0}; + + while (true) { + Command cmd = {0}; + err = extract_command(&data, &cmd); + if (err) break; + if (cmd.id == SyscallRead) { + ckb_read(fds[CKB_STDIN], (void*)cmd.buf_ptr, (uint64_t*)cmd.len_ptr); + // ignore error + } else if (cmd.id == SyscallWrite) { + ckb_write(fds[CKB_STDOUT], (void*)cmd.buf_ptr, (uint64_t*)cmd.len_ptr); + // ignore error + } else if (cmd.id == SyscallClose) { + ckb_close(fds[cmd.fd_index]); + // ignore error + } else { + CHECK2(false, -1); + } + } +exit: + return err; +} + +int parent_entry() { + int err = 0; + uint64_t pid = 0; + const char* argv[] = {"", 0}; + uint64_t fds[2] = {0}; + + err = full_spawn(0, 1, argv, fds, &pid); + CHECK(err); + random_read_write(fds, 0); + + int8_t exit_code = 0; + err = ckb_wait(pid, &exit_code); + CHECK(err); + CHECK(exit_code); +exit: + return err; +} + +int child_entry() { + int err = 0; + uint64_t inherited_fds[2]; + size_t inherited_fds_length = 2; + err = ckb_inherited_file_descriptors(inherited_fds, &inherited_fds_length); + CHECK(err); + random_read_write(inherited_fds, 0); + +exit: + return err; +} + +int main(int argc, const char* argv[]) { + if (argc > 0) { + return child_entry(); + } else { + return parent_entry(); + } +} diff --git a/script/testdata/spawn_huge_swap b/script/testdata/spawn_huge_swap new file mode 100755 index 0000000000..3d7ab1ca7a Binary files /dev/null and b/script/testdata/spawn_huge_swap differ diff --git a/script/testdata/spawn_huge_swap.c b/script/testdata/spawn_huge_swap.c new file mode 100644 index 0000000000..2666857631 --- /dev/null +++ b/script/testdata/spawn_huge_swap.c @@ -0,0 +1,65 @@ +#include "spawn_utils.h" + +// 2.4 M bytes +static uint64_t g_data[300 * 1024]; + +int main() { + int err = 0; + uint64_t fds[2] = {0}; + uint64_t pid = 0; + uint64_t current_pid = ckb_process_id(); + size_t argc = 1; + const char* argv[2] = {"", 0}; + int8_t exit_code = 0; + + printf("current pid = %d", current_pid); + for (size_t i = 0; i < sizeof(g_data) / sizeof(uint64_t); i++) { + g_data[i] = current_pid; + } + + if (current_pid == 7) { + // wait forever + ckb_wait(0, &exit_code); + } else { + err = full_spawn(0, argc, argv, fds, &pid); + CHECK(err); + if (current_pid == 0) { + uint8_t buf[1] = {0}; + while (true) { + size_t len = 1; + ckb_read(fds[CKB_STDIN], buf, &len); + } + } else if (current_pid == 1) { + uint64_t inherited_fds[3]; + size_t fds_len = 3; + err = ckb_inherited_file_descriptors(inherited_fds, &fds_len); + CHECK(err); + uint8_t buf[1] = {0}; + while (true) { + size_t len = 1; + ckb_write(inherited_fds[CKB_STDOUT], buf, &len); + ckb_read(fds[CKB_STDIN], buf, &len); + } + } else if (current_pid == 2) { + uint64_t inherited_fds[3]; + size_t fds_len = 3; + err = ckb_inherited_file_descriptors(inherited_fds, &fds_len); + CHECK(err); + uint8_t buf[1] = {0}; + while (true) { + size_t len = 1; + ckb_write(inherited_fds[CKB_STDOUT], buf, &len); + } + } else { + // wait forever + ckb_wait(0, &exit_code); + } + } + // avoid g_data to be optimized + for (size_t i = 0; i < sizeof(g_data) / sizeof(uint64_t); i++) { + err += (int8_t)g_data[i]; + } + +exit: + return err; +} \ No newline at end of file diff --git a/script/testdata/spawn_io_cycles b/script/testdata/spawn_io_cycles new file mode 100755 index 0000000000..e77022ac8b Binary files /dev/null and b/script/testdata/spawn_io_cycles differ diff --git a/script/testdata/spawn_io_cycles.c b/script/testdata/spawn_io_cycles.c new file mode 100644 index 0000000000..e58151f4d8 --- /dev/null +++ b/script/testdata/spawn_io_cycles.c @@ -0,0 +1,81 @@ +#include +#include + +#include "ckb_syscalls.h" +#include "spawn_utils.h" + +#define BUFFER_SIZE 1024 * 4 + +typedef struct { + uint64_t io_size; + bool check_buffer; +} ScriptArgs; + +int parent(ScriptArgs* args, uint8_t* buffer) { + int err = 0; + const char* argv[] = {"", 0}; + uint64_t fds[2] = {0}; + uint64_t pid = 0; + err = full_spawn(0, 1, argv, fds, &pid); + CHECK(err); + + uint64_t buf_len = args->io_size; + + err = ckb_read(fds[CKB_STDIN], buffer, &buf_len); + CHECK(err); + CHECK2(buf_len == args->io_size, -1); + if (args->check_buffer) { + for (size_t i = 0; i < args->io_size; i++) + CHECK2(buffer[i] == (uint8_t)i, -1); + } + + int8_t exit_code = 0; + err = ckb_wait(pid, &exit_code); + CHECK(err); + CHECK(exit_code); + +exit: + return err; +} + +int child(ScriptArgs* args, uint8_t* buffer) { + int err = 0; + uint64_t inherited_fds[2]; + size_t inherited_fds_length = 2; + err = ckb_inherited_file_descriptors(inherited_fds, &inherited_fds_length); + CHECK(err); + + uint64_t buf_len = args->io_size; + + if (args->check_buffer) { + for (size_t i = 0; i < args->io_size; i++) buffer[i] = i; + } + + err = ckb_write(inherited_fds[CKB_STDOUT], buffer, &buf_len); + + CHECK(err); + CHECK2(buf_len == args->io_size, -1); +exit: + return err; +} + +int main() { + int err = 0; + ScriptArgs script_args; + size_t script_args_length = sizeof(script_args); + err = load_script_args((uint8_t*)&script_args, &script_args_length); + CHECK(err); + CHECK2(script_args_length == sizeof(script_args), -1); + + uint64_t cid = ckb_process_id(); + uint8_t buffer[BUFFER_SIZE] = {0}; + + if (cid == 0) { + return parent(&script_args, buffer); + } else { + return child(&script_args, buffer); + } + +exit: + return err; +} diff --git a/script/testdata/spawn_peak_memory_2m_to_32m b/script/testdata/spawn_peak_memory_2m_to_32m deleted file mode 100755 index ebe14775db..0000000000 Binary files a/script/testdata/spawn_peak_memory_2m_to_32m and /dev/null differ diff --git a/script/testdata/spawn_peak_memory_2m_to_32m.c b/script/testdata/spawn_peak_memory_2m_to_32m.c deleted file mode 100644 index 11beb1e02a..0000000000 --- a/script/testdata/spawn_peak_memory_2m_to_32m.c +++ /dev/null @@ -1,36 +0,0 @@ -#include -#include -#include - -#include "ckb_syscalls.h" - -int main(int argc, char *argv[]) { - int8_t spawn_exit_code = 255; - spawn_args_t spgs = { - .memory_limit = 4, - .exit_code = &spawn_exit_code, - .content = NULL, - .content_length = NULL, - }; - 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(0, 3, 0, 1, argv, &spgs); - if (success != 0) { - return success; - } - if (spawn_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 deleted file mode 100755 index 28d8274b57..0000000000 Binary files a/script/testdata/spawn_peak_memory_4m_to_32m and /dev/null differ diff --git a/script/testdata/spawn_peak_memory_4m_to_32m.c b/script/testdata/spawn_peak_memory_4m_to_32m.c deleted file mode 100644 index bd91167ce0..0000000000 --- a/script/testdata/spawn_peak_memory_4m_to_32m.c +++ /dev/null @@ -1,36 +0,0 @@ -#include -#include -#include - -#include "ckb_syscalls.h" - -int main(int argc, char *argv[]) { - int8_t spawn_exit_code = 255; - spawn_args_t spgs = { - .memory_limit = 8, - .exit_code = &spawn_exit_code, - .content = NULL, - .content_length = NULL, - }; - 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(0, 3, 0, 1, argv, &spgs); - if (success != 0) { - return success; - } - if (spawn_exit_code != 0) { - return 1; - } - } - return 0; -} diff --git a/script/testdata/spawn_peak_memory_512k_to_32m b/script/testdata/spawn_peak_memory_512k_to_32m deleted file mode 100755 index 118c5ab1d3..0000000000 Binary files a/script/testdata/spawn_peak_memory_512k_to_32m and /dev/null differ diff --git a/script/testdata/spawn_peak_memory_512k_to_32m.c b/script/testdata/spawn_peak_memory_512k_to_32m.c deleted file mode 100644 index 21680926ce..0000000000 --- a/script/testdata/spawn_peak_memory_512k_to_32m.c +++ /dev/null @@ -1,36 +0,0 @@ -#include -#include -#include - -#include "ckb_syscalls.h" - -int main(int argc, char *argv[]) { - int8_t spawn_exit_code = 255; - spawn_args_t spgs = { - .memory_limit = 1, - .exit_code = &spawn_exit_code, - .content = NULL, - .content_length = NULL, - }; - int8_t can_i_spawn = 0; - if (argc == 0) { - can_i_spawn = 1; - } - uint64_t depth = (uint64_t)atoi(argv[0]); - if (depth < 56) { - 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(0, 3, 0, 1, argv, &spgs); - if (success != 0) { - return success; - } - if (spawn_exit_code != 0) { - return 1; - } - } - return 0; -} diff --git a/script/testdata/spawn_recursive b/script/testdata/spawn_recursive index ce415ca010..80d777b5be 100755 Binary files a/script/testdata/spawn_recursive and b/script/testdata/spawn_recursive differ diff --git a/script/testdata/spawn_recursive.c b/script/testdata/spawn_recursive.c index 8b3510572a..d82b0b38cb 100644 --- a/script/testdata/spawn_recursive.c +++ b/script/testdata/spawn_recursive.c @@ -1,19 +1,3 @@ -#include -#include +#include "spawn_utils.h" -#include "ckb_syscalls.h" - -int main() { - int8_t spawn_exit_code = 255; - spawn_args_t spgs = { - .memory_limit = 8, - .exit_code = &spawn_exit_code, - .content = NULL, - .content_length = NULL, - }; - uint64_t success = ckb_spawn(0, 3, 0, 0, NULL, &spgs); - if (success != 0) { - return success; - } - return spawn_exit_code; -} +int main() { return simple_spawn(0); } diff --git a/script/testdata/spawn_saturate_memory b/script/testdata/spawn_saturate_memory new file mode 100755 index 0000000000..08e51f96c8 Binary files /dev/null and b/script/testdata/spawn_saturate_memory differ diff --git a/script/testdata/spawn_saturate_memory.c b/script/testdata/spawn_saturate_memory.c new file mode 100644 index 0000000000..de977b10db --- /dev/null +++ b/script/testdata/spawn_saturate_memory.c @@ -0,0 +1,45 @@ +#include "spawn_utils.h" + +#define MAX_MEMORY (4 * 1024 * 1024) +#define PAGE_SIZE (4 * 1024) + +extern char _end[]; + +void dirty_all_pages() { + uint64_t addr = (uint64_t)_end; + while (addr < MAX_MEMORY) { + uint8_t* ptr = (uint8_t*)addr; + *ptr = 0; + addr += PAGE_SIZE; + } +} + +int main(int argc, const char* argv[]) { + int err = 0; + if (argc > 0) { + // child + dirty_all_pages(); + uint64_t inherited_fds[2]; + size_t inherited_fds_length = 2; + err = ckb_inherited_file_descriptors(inherited_fds, &inherited_fds_length); + uint64_t length = MAX_MEMORY; + // Write a piece of data starting from address 0 with a size of 4M. + // It should not consume any memory. + err = ckb_write(inherited_fds[CKB_STDOUT], 0, &length); + // should be blocked forever since there is no reading on other end + CHECK(err); + } else { + // parent + for (size_t i = 0; i < 15; i++) { + uint64_t pid = 0; + const char* argv[] = {"", 0}; + uint64_t fds[2] = {0}; + err = full_spawn(0, 1, argv, fds, &pid); + CHECK(err); + } + dirty_all_pages(); + } + +exit: + return err; +} diff --git a/script/testdata/spawn_times b/script/testdata/spawn_times index c7dcd39e58..0eed0c30ae 100755 Binary files a/script/testdata/spawn_times and b/script/testdata/spawn_times differ diff --git a/script/testdata/spawn_times.c b/script/testdata/spawn_times.c new file mode 100644 index 0000000000..c40100f16b --- /dev/null +++ b/script/testdata/spawn_times.c @@ -0,0 +1,12 @@ +#include "spawn_utils.h" + +int main() { + int err = 0; + for (size_t i = 0; i < 10000; i++) { + err = simple_spawn(0); + CHECK(err); + } + +exit: + return err; +} diff --git a/script/testdata/spawn_times.md b/script/testdata/spawn_times.md deleted file mode 100644 index 351817947d..0000000000 --- a/script/testdata/spawn_times.md +++ /dev/null @@ -1,81 +0,0 @@ -This binary comes from: . Since I couldn't build a binary in C that would cause the same bug, I just added the binary to the project. - -```rs -#![no_std] -#![cfg_attr(not(test), no_main)] - -#[cfg(test)] -extern crate alloc; - -#[cfg(not(test))] -use ckb_std::default_alloc; -#[cfg(not(test))] -ckb_std::entry!(program_entry); -#[cfg(not(test))] -default_alloc!(); - -use core::result::Result; - -use alloc::{vec}; -use core::ffi::{CStr}; - -use ckb_std::{debug, syscalls}; -use ckb_std::ckb_constants::Source; -use ckb_std::env::argv; -use ckb_std::syscalls::{current_cycles, get_memory_limit, set_content, spawn}; - - -/// -/// test case : -/// invoke int ckb_spawn( uint64_t memory_limit, -/// size_t index, -/// size_t source, -/// size_t bounds, -/// int argc, char* argv[], -/// int8_t* exit_code, -/// uint8_t* content, -/// uint64_t* content_length); -/// -/// for { -/// spawn(xxx) -/// } -/// case1 : for { -/// spawn(xxx) -/// } -/// -/// result: -/// return ERROR : ExceededMaximumCycles -/// -pub fn program_entry() -> i8 { - // let argvs = argv(); - // debug!("argvs length:{:?}:{:?}",argvs.len(),argvs); - - if get_memory_limit() != 8 { - return 0; - } - let mut exit_code: i8 = 0; - let mut content: [u8; 10] = [1; 10]; - - let content_length: u64 = content.len() as u64; - let mut spawn_args = syscalls::SpawnArgs { - memory_limit: 8, - exit_code: &mut exit_code as *mut i8, - content: content.as_mut_ptr(), - content_length: &content_length as *const u64 as *mut u64, - }; - // let cstr1 = CStr::from_bytes_with_nul(b"arg0\0").unwrap(); - //argv is empty - let cstrs = vec![]; - - spawn_args.memory_limit = 1; - for i in 0..10000 { - debug!("current idx:{:?}",i); - let result = spawn(0, Source::CellDep, 0, cstrs.as_slice(), &spawn_args); - assert_eq!(exit_code, 0); - // debug!("result:{:?}",result); - let cycles = current_cycles(); - debug!("cycle:{:?}",cycles); - } - return 0; -} -``` diff --git a/script/testdata/spawn_utils.h b/script/testdata/spawn_utils.h new file mode 100644 index 0000000000..cb385ba560 --- /dev/null +++ b/script/testdata/spawn_utils.h @@ -0,0 +1,239 @@ + +#ifndef __UTILS_H__ +#define __UTILS_H__ + +#include +#include + +#include "ckb_consts.h" +#include "ckb_syscalls.h" +#include "blockchain.h" + +enum CkbSpawnError { + ErrorCommon = 31, + ErrorRead, + ErrorWrite, + ErrorPipe, + ErrorSpawn, +}; + +#define CHECK2(cond, code) \ + do { \ + if (!(cond)) { \ + printf("error at %s:%d, error code %d", __FILE__, __LINE__, code); \ + err = code; \ + goto exit; \ + } \ + } while (0) + +#define CHECK(_code) \ + do { \ + int code = (_code); \ + if (code != 0) { \ + printf("error at %s:%d, error code %d", __FILE__, __LINE__, code); \ + err = code; \ + goto exit; \ + } \ + } while (0) +#endif + +#define countof(array) (sizeof(array) / sizeof(array[0])) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +// conventions +#define CKB_STDIN (0) +#define CKB_STDOUT (1) + +// mimic stdio fds on linux +int create_std_fds(uint64_t* fds, uint64_t* inherited_fds) { + int err = 0; + + uint64_t to_child[2] = {0}; + uint64_t to_parent[2] = {0}; + err = ckb_pipe(to_child); + CHECK(err); + err = ckb_pipe(to_parent); + CHECK(err); + + inherited_fds[0] = to_child[0]; + inherited_fds[1] = to_parent[1]; + inherited_fds[2] = 0; + + fds[CKB_STDIN] = to_parent[0]; + fds[CKB_STDOUT] = to_child[1]; + +exit: + return err; +} + +// spawn script at `index` in cell_deps without any argc, argv +int simple_spawn(size_t index) { + int err = 0; + int8_t spawn_exit_code = 255; + const char* argv[1] = {0}; + uint64_t pid = 0; + uint64_t fds[1] = {0}; + spawn_args_t spgs = {.argc = 0, .argv = argv, .process_id = &pid, .inherited_fds = fds}; + err = ckb_spawn(index, CKB_SOURCE_CELL_DEP, 0, 0, &spgs); + CHECK(err); + err = ckb_wait(pid, &spawn_exit_code); + CHECK(err); + CHECK(spawn_exit_code); + +exit: + return err; +} + +// spawn script at `index` in cell_deps with argv +int simple_spawn_args(size_t index, int argc, const char* argv[]) { + int err = 0; + int8_t spawn_exit_code = 255; + uint64_t pid = 0; + uint64_t fds[1] = {0}; + spawn_args_t spgs = {.argc = argc, .argv = argv, .process_id = &pid, .inherited_fds = fds}; + err = ckb_spawn(index, CKB_SOURCE_CELL_DEP, 0, 0, &spgs); + CHECK(err); + err = ckb_wait(pid, &spawn_exit_code); + CHECK(err); + CHECK(spawn_exit_code); +exit: + return err; +} + +int full_spawn(size_t index, int argc, const char* argv[], uint64_t fds[2], uint64_t* pid) { + int err = 0; + uint64_t inherited_fds[3] = {0}; + err = create_std_fds(fds, inherited_fds); + CHECK(err); + spawn_args_t spgs = {.argc = argc, .argv = argv, .process_id = pid, .inherited_fds = inherited_fds}; + err = ckb_spawn(0, CKB_SOURCE_CELL_DEP, 0, 0, &spgs); + CHECK(err); +exit: + return err; +} + +// read exact `length` bytes into buffer. +// Will wait forever when less bytes are written on write fd. +int read_exact(uint64_t fd, void* buffer, size_t length, size_t* actual_length) { + int err = 0; + size_t remaining_length = length; + uint8_t* start_buffer = buffer; + while (true) { + size_t n = remaining_length; + err = ckb_read(fd, start_buffer, &n); + if (err == CKB_OTHER_END_CLOSED) { + err = 0; + break; + } else { + CHECK(err); + } + start_buffer += n; + remaining_length -= n; + *actual_length = length - remaining_length; + if (remaining_length == 0) { + break; + } + } + +exit: + return err; +} + +// Function read_all reads from fd until an error or EOF and returns the data it read. +int ckb_read_all(uint64_t fd, void* buffer, size_t* length) { + int err = 0; + size_t read_length = 0; + size_t full_length = *length; + uint8_t* b = buffer; + while (true) { + size_t n = full_length - read_length; + err = ckb_read(fd, b, &n); + if (err == CKB_OTHER_END_CLOSED) { + err = 0; + *length = read_length; + break; + } else { + CHECK(err); + } + if (full_length - read_length == 0) { + err = CKB_LENGTH_NOT_ENOUGH; + CHECK(err); + } + b += n; + read_length += n; + *length = read_length; + } + +exit: + return err; +} + +// write exact `length` bytes into buffer. +// Will wait forever when less bytes are read on read fd. +int write_exact(uint64_t fd, void* buffer, size_t length, size_t* actual_length) { + int err = 0; + size_t remaining_length = length; + uint8_t* start_buffer = buffer; + while (true) { + size_t n = remaining_length; + err = ckb_write(fd, start_buffer, &n); + if (err == CKB_OTHER_END_CLOSED) { + err = 0; + break; + } else { + CHECK(err); + } + start_buffer += n; + remaining_length -= n; + *actual_length = length - remaining_length; + if (remaining_length == 0) { + break; + } + } +exit: + return err; +} + +#define SCRIPT_SIZE 4096 + +int load_script_args(uint8_t* args, size_t* length) { + int err = 0; + uint64_t len = SCRIPT_SIZE; + uint8_t script[SCRIPT_SIZE]; + err = ckb_load_script(script, &len, 0); + CHECK(err); + CHECK2(len <= SCRIPT_SIZE, -2); + mol_seg_t script_seg = {0}; + script_seg.ptr = (uint8_t*)script; + script_seg.size = len; + CHECK2(MolReader_Script_verify(&script_seg, false) == MOL_OK, -3); + mol_seg_t args_seg = MolReader_Script_get_args(&script_seg); + mol_seg_t bytes_seg = MolReader_Bytes_raw_bytes(&args_seg); + size_t copy_length = MIN(bytes_seg.size, *length); + memcpy(args, bytes_seg.ptr, copy_length); + *length = copy_length; + +exit: + return err; +} + +void print_hex(const uint8_t* buf, size_t length) { + char dst[65] = {0}; + int j = 0; + for (int i = 0; i < length; i++) { + char hi = buf[i] >> 4; + char lo = buf[i] & 0xf; + dst[j * 2] = hi + (hi < 10 ? '0' : ('a' - 10)); + dst[j * 2 + 1] = lo + (lo < 10 ? '0' : ('a' - 10)); + j += 1; + if (j > 31) { + j = 0; + printf("%s", dst); + } + } + if (j != 0) { + dst[j * 2] = 0; + printf("%s", dst); + } +} diff --git a/script/testdata/sub1.c b/script/testdata/sub1.c index 90d790729d..a31aaf63c4 100644 --- a/script/testdata/sub1.c +++ b/script/testdata/sub1.c @@ -1,5 +1,3 @@ #include -__attribute__((visibility("default"))) uint64_t apply (uint64_t num) { - return num - 1; -} +__attribute__((visibility("default"))) uint64_t apply(uint64_t num) { return num - 1; } diff --git a/script/testdata/sub1.lib b/script/testdata/sub1.lib index 9afeae0776..b1a89a227b 100755 Binary files a/script/testdata/sub1.lib and b/script/testdata/sub1.lib differ diff --git a/script/testdata/verify.c b/script/testdata/verify.c index 9c3a1a4402..c42eb92b76 100644 --- a/script/testdata/verify.c +++ b/script/testdata/verify.c @@ -1,4 +1,5 @@ #include + #include "sha3.h" #define SHA3_BLOCK_SIZE 32 @@ -7,15 +8,11 @@ #define CUSTOM_PRINT_ERR 1 #include "syscall.h" -void custom_abort() -{ - syscall_errno(93, 10, 0, 0, 0, 0, 0); -} +void custom_abort() { syscall_errno(93, 10, 0, 0, 0, 0, 0); } -int custom_print_err(const char * arg, ...) -{ - (void) arg; - return 0; +int custom_print_err(const char* arg, ...) { + (void)arg; + return 0; } #include @@ -26,39 +23,40 @@ int custom_print_err(const char * arg, ...) */ #include -int char_to_int(char ch) -{ - if (ch >= '0' && ch <= '9') { - return ch - '0'; - } - if (ch >= 'a' && ch <= 'f') { - return ch - 'a' + 10; - } - return -1; +int char_to_int(char ch) { + if (ch >= '0' && ch <= '9') { + return ch - '0'; + } + if (ch >= 'a' && ch <= 'f') { + return ch - 'a' + 10; + } + return -1; } -int hex_to_bin(char* buf, size_t buf_len, const char* hex) -{ - int i = 0; +int hex_to_bin(char* buf, size_t buf_len, const char* hex) { + int i = 0; - for (; i < buf_len && hex[i * 2] != '\0' && hex[i * 2 + 1] != '\0'; i++) { - int a = char_to_int(hex[i * 2]); - int b = char_to_int(hex[i * 2 + 1]); + for (; i < buf_len && hex[i * 2] != '\0' && hex[i * 2 + 1] != '\0'; i++) { + int a = char_to_int(hex[i * 2]); + int b = char_to_int(hex[i * 2 + 1]); - if (a < 0 || b < 0) { - return -1; - } + if (a < 0 || b < 0) { + return -1; + } - buf[i] = ((a & 0xF) << 4) | (b & 0xF); - } + buf[i] = ((a & 0xF) << 4) | (b & 0xF); + } - if (i == buf_len && hex[i * 2] != '\0') { - return -1; - } - return i; + if (i == buf_len && hex[i * 2] != '\0') { + return -1; + } + return i; } -#define CHECK_LEN(x) if ((x) <= 0) { return x; } +#define CHECK_LEN(x) \ + if ((x) <= 0) { \ + return x; \ + } /* * Arguments are listed in the following order: @@ -80,56 +78,55 @@ int hex_to_bin(char* buf, size_t buf_len, const char* hex) * Note all hex values passed in as arguments must have lower case letters for * deterministic behavior. */ -int main(int argc, char* argv[]) -{ - char buf[256]; - int len; +int main(int argc, char* argv[]) { + char buf[256]; + int len; - if (argc < 4) { - return -1; - } - - secp256k1_context context; - int ret = secp256k1_context_initialize(&context, SECP256K1_CONTEXT_VERIFY); - if (ret == 0) { - return 4; - } - - len = hex_to_bin(buf, 65, argv[argc - 2]); - CHECK_LEN(len); - secp256k1_pubkey pubkey; - - ret = secp256k1_ec_pubkey_parse(&context, &pubkey, buf, len); - if (ret == 0) { - return 1; - } - - len = hex_to_bin(buf, 256, argv[argc - 1]); - CHECK_LEN(len); - secp256k1_ecdsa_signature signature; - secp256k1_ecdsa_signature_parse_der(&context, &signature, buf, len); - if (ret == 0) { - return 3; - } - - sha3_ctx_t sha3_ctx; - unsigned char hash[SHA3_BLOCK_SIZE]; - sha3_init(&sha3_ctx, SHA3_BLOCK_SIZE); - for (int i = 1; i < argc -2; i++) { - sha3_update(&sha3_ctx, argv[i], strlen(argv[i])); - } - sha3_final(hash, &sha3_ctx); - - sha3_init(&sha3_ctx, SHA3_BLOCK_SIZE); - sha3_update(&sha3_ctx, hash, SHA3_BLOCK_SIZE); - sha3_final(hash, &sha3_ctx); - - ret = secp256k1_ecdsa_verify(&context, &signature, hash, &pubkey); - if (ret == 1) { - ret = 0; - } else { - ret = 2; - } - - return ret; + if (argc < 4) { + return -1; + } + + secp256k1_context context; + int ret = secp256k1_context_initialize(&context, SECP256K1_CONTEXT_VERIFY); + if (ret == 0) { + return 4; + } + + len = hex_to_bin(buf, 65, argv[argc - 2]); + CHECK_LEN(len); + secp256k1_pubkey pubkey; + + ret = secp256k1_ec_pubkey_parse(&context, &pubkey, buf, len); + if (ret == 0) { + return 1; + } + + len = hex_to_bin(buf, 256, argv[argc - 1]); + CHECK_LEN(len); + secp256k1_ecdsa_signature signature; + secp256k1_ecdsa_signature_parse_der(&context, &signature, buf, len); + if (ret == 0) { + return 3; + } + + sha3_ctx_t sha3_ctx; + unsigned char hash[SHA3_BLOCK_SIZE]; + sha3_init(&sha3_ctx, SHA3_BLOCK_SIZE); + for (int i = 1; i < argc - 2; i++) { + sha3_update(&sha3_ctx, argv[i], strlen(argv[i])); + } + sha3_final(hash, &sha3_ctx); + + sha3_init(&sha3_ctx, SHA3_BLOCK_SIZE); + sha3_update(&sha3_ctx, hash, SHA3_BLOCK_SIZE); + sha3_final(hash, &sha3_ctx); + + ret = secp256k1_ecdsa_verify(&context, &signature, hash, &pubkey); + if (ret == 1) { + ret = 0; + } else { + ret = 2; + } + + return ret; } diff --git a/script/testdata/vm_version b/script/testdata/vm_version index bf39e3d56a..e2c0225e5b 100755 Binary files a/script/testdata/vm_version and b/script/testdata/vm_version differ diff --git a/script/testdata/vm_version.c b/script/testdata/vm_version.c index 427f1227f6..d7397d8c98 100644 --- a/script/testdata/vm_version.c +++ b/script/testdata/vm_version.c @@ -2,7 +2,7 @@ int main() { if (syscall(2041, 0, 0, 0, 0, 0, 0) == 1) { - return 0; + return 0; } return 1; } diff --git a/script/testdata/vm_version_2 b/script/testdata/vm_version_2 index 13ea61a6ca..ebc92ae7c8 100755 Binary files a/script/testdata/vm_version_2 and b/script/testdata/vm_version_2 differ diff --git a/script/testdata/vm_version_2.c b/script/testdata/vm_version_2.c index 2b1cff6e02..0576c517a0 100644 --- a/script/testdata/vm_version_2.c +++ b/script/testdata/vm_version_2.c @@ -2,7 +2,7 @@ int main() { if (syscall(2041, 0, 0, 0, 0, 0, 0) == 2) { - return 0; + return 0; } return 1; } diff --git a/script/testdata/vm_version_with_snapshot b/script/testdata/vm_version_with_snapshot index 9e16b52a45..4f27a8944f 100755 Binary files a/script/testdata/vm_version_with_snapshot and b/script/testdata/vm_version_with_snapshot differ diff --git a/script/testdata/vm_version_with_snapshot.c b/script/testdata/vm_version_with_snapshot.c index 8861f4a4ed..bea91f5fbe 100644 --- a/script/testdata/vm_version_with_snapshot.c +++ b/script/testdata/vm_version_with_snapshot.c @@ -7,22 +7,19 @@ #define sprintf(...) #endif -void try_pause() { - syscall(2178, 0, 0, 0, 0, 0, 0); -} +void try_pause() { syscall(2178, 0, 0, 0, 0, 0, 0); } -int vm_version() { - return syscall(2041, 0, 0, 0, 0, 0, 0); -} +int vm_version() { return syscall(2041, 0, 0, 0, 0, 0, 0); } int main() { #ifdef DEBUG char message[2048]; #endif int ver; - for (int i=0; i<4096; i++) { + for (int i = 0; i < 4096; i++) { ver = vm_version(); - sprintf(message, "version = %d", ver); ckb_debug(message); + sprintf(message, "version = %d", ver); + ckb_debug(message); if (i > 16) { try_pause(); } diff --git a/verification/src/transaction_verifier.rs b/verification/src/transaction_verifier.rs index 918704fe4a..b6317cebaa 100644 --- a/verification/src/transaction_verifier.rs +++ b/verification/src/transaction_verifier.rs @@ -104,7 +104,10 @@ impl<'a> NonContextualTransactionVerifier<'a> { /// [`CapacityVerifier`](./struct.CapacityVerifier.html) /// [`ScriptVerifier`](./struct.ScriptVerifier.html) /// [`FeeCalculator`](./struct.FeeCalculator.html) -pub struct ContextualTransactionVerifier
{ +pub struct ContextualTransactionVerifier
+where + DL: Send + Sync + Clone + CellDataProvider + HeaderProvider + ExtensionProvider + 'static, +{ pub(crate) compatible: CompatibleVerifier, pub(crate) time_relative: TimeRelativeTransactionVerifier
, pub(crate) capacity: CapacityVerifier,