use crate::cost_model::transferred_byte_cycles;
use crate::syscalls::utils::load_c_string;
use crate::syscalls::{
Source, SourceEntry, INDEX_OUT_OF_BOUND, SLICE_OUT_OF_BOUND, SPAWN,
SPAWN_EXCEEDED_MAX_CONTENT_LENGTH, SPAWN_EXCEEDED_MAX_PEAK_MEMORY, SPAWN_EXTRA_CYCLES_BASE,
SPAWN_EXTRA_CYCLES_PER_MEMORY_PAGE, SPAWN_MAX_CONTENT_LENGTH, SPAWN_MAX_MEMORY,
SPAWN_MAX_PEAK_MEMORY, SPAWN_MEMORY_PAGE_SIZE, SPAWN_WRONG_MEMORY_LIMIT, WRONG_FORMAT,
};
use crate::types::{
set_vm_max_cycles, CoreMachineType, Machine, MachineContext, ResumableMachine, SpawnData,
};
use crate::TransactionScriptsSyscallsGenerator;
use crate::{ScriptGroup, ScriptVersion};
use 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,
};
use std::sync::{Arc, Mutex};
pub struct Spawn<DL> {
script_group: ScriptGroup,
script_version: ScriptVersion,
syscalls_generator: TransactionScriptsSyscallsGenerator<DL>,
peak_memory: u64,
cycles_base: u64,
context: Arc<Mutex<MachineContext>>,
}
impl<DL: CellDataProvider + Clone + HeaderProvider + Send + Sync + 'static> Spawn<DL> {
pub fn new(
script_group: ScriptGroup,
script_version: ScriptVersion,
syscalls_generator: TransactionScriptsSyscallsGenerator<DL>,
peak_memory: u64,
cycles_base: u64,
context: Arc<Mutex<MachineContext>>,
) -> Self {
Self {
script_group,
script_version,
syscalls_generator,
peak_memory,
cycles_base,
context,
}
}
fn data_loader(&self) -> &DL {
&self.syscalls_generator.data_loader
}
fn outputs(&self) -> &Vec<CellMeta> {
&self.syscalls_generator.outputs
}
#[inline]
fn resolved_inputs(&self) -> &Vec<CellMeta> {
&self.syscalls_generator.rtx.resolved_inputs
}
#[inline]
fn resolved_cell_deps(&self) -> &Vec<CellMeta> {
&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<Mac, DL> Syscalls<Mac> for Spawn<DL>
where
Mac: SupportMachine,
DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static,
{
fn initialize(&mut self, _machine: &mut Mac) -> Result<(), VMError> {
Ok(())
}
fn ecall(&mut self, machine: &mut Mac) -> Result<bool, VMError> {
if machine.registers()[A7].to_u64() != SPAWN {
return Ok(false);
}
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;
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);
let memory_limit = machine
.memory_mut()
.load64(&Mac::REG::from_u64(memory_limit_addr))?
.to_u64();
let cycles_limit = machine.max_cycles() - machine.cycles();
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();
if content_length > SPAWN_MAX_CONTENT_LENGTH {
machine.set_register(A0, Mac::REG::from_u8(SPAWN_EXCEEDED_MAX_CONTENT_LENGTH));
return Ok(true);
}
if memory_limit > SPAWN_MAX_MEMORY || memory_limit == 0 {
machine.set_register(A0, Mac::REG::from_u8(SPAWN_WRONG_MEMORY_LIMIT));
return Ok(true);
}
if self.peak_memory + memory_limit > SPAWN_MAX_PEAK_MEMORY {
machine.set_register(A0, Mac::REG::from_u8(SPAWN_EXCEEDED_MAX_PEAK_MEMORY));
return Ok(true);
}
let spawn_data = SpawnData {
callee_peak_memory: self.peak_memory + memory_limit,
callee_memory_limit: memory_limit,
content: Arc::new(Mutex::new(Vec::<u8>::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,
)?;
let program = {
let cell = self.fetch_cell(source, index as usize);
if let Err(err) = cell {
machine.set_register(A0, Mac::REG::from_u8(err));
return Ok(true);
}
let cell = cell.unwrap();
let data = self.data_loader().load_cell_data(cell).ok_or_else(|| {
VMError::Unexpected(format!(
"Unexpected load_cell_data failed {}",
cell.out_point,
))
})?;
let size = data.len();
if offset >= size {
machine.set_register(A0, Mac::REG::from_u8(SLICE_OUT_OF_BOUND));
return Ok(true);
};
if length == 0 {
data.slice(offset..size)
} else {
let end = offset.checked_add(length).ok_or(VMError::MemOutOfBound)?;
if end > size {
machine.set_register(A0, Mac::REG::from_u8(SLICE_OUT_OF_BOUND));
return Ok(true);
}
data.slice(offset..end)
}
};
let mut addr = argv_addr.to_u64();
let mut argv_vec = Vec::new();
for _ in 0..argc {
let target_addr = machine
.memory_mut()
.load64(&Mac::REG::from_u64(addr))?
.to_u64();
let cstr = load_c_string(machine, target_addr)?;
argv_vec.push(cstr);
addr += 8;
}
let extra_cycles =
SPAWN_EXTRA_CYCLES_BASE + memory_limit * SPAWN_EXTRA_CYCLES_PER_MEMORY_PAGE;
machine_child.machine.add_cycles_no_checking(extra_cycles)?;
match machine_child.load_program(&program, &argv_vec) {
Ok(size) => {
machine_child
.machine
.add_cycles_no_checking(transferred_byte_cycles(size))?;
}
Err(_) => {
machine.add_cycles_no_checking(extra_cycles)?;
machine.set_register(A0, Mac::REG::from_u8(WRONG_FORMAT));
return Ok(true);
}
}
match machine_child.run() {
Ok(data) => {
update_caller_machine(machine, data, machine_child.machine.cycles(), &spawn_data)?;
Ok(true)
}
Err(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(VMError::CyclesExceeded)
}
Err(err) => Err(err),
}
}
}
pub fn build_child_machine<
DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static,
>(
script_group: &ScriptGroup,
script_version: ScriptVersion,
syscalls_generator: &TransactionScriptsSyscallsGenerator<DL>,
cycles_limit: u64,
spawn_data: &SpawnData,
context: &Arc<Mutex<MachineContext>>,
) -> Result<Machine, VMError> {
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 machine_builder =
DefaultMachineBuilder::new(machine_core).instruction_cycle_func(Box::new(estimate_cycles));
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)
}
pub fn update_caller_machine<Mac: SupportMachine>(
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(())
}