Skip to main content

ckb_script/
types.rs

1//! Common type definitions for ckb-script package.
2
3use crate::{error::ScriptError, verify_env::TxVerifyEnv};
4use ckb_chain_spec::consensus::Consensus;
5use ckb_types::{
6    core::{
7        Cycle, ScriptHashType,
8        cell::{CellMeta, ResolvedTransaction},
9    },
10    packed::{Byte32, CellOutput, OutPoint, Script},
11    prelude::*,
12};
13use ckb_vm::{
14    ISA_B, ISA_IMC, ISA_MOP, Syscalls,
15    machine::{VERSION0, VERSION1, VERSION2},
16};
17use serde::{Deserialize, Serialize};
18use std::collections::{BTreeMap, HashMap};
19use std::fmt;
20use std::sync::{
21    Arc, Mutex, RwLock,
22    atomic::{AtomicU64, Ordering},
23};
24
25use ckb_traits::CellDataProvider;
26use ckb_vm::snapshot2::Snapshot2Context;
27
28use ckb_vm::{
29    DefaultMachineRunner, SupportMachine, bytes::Bytes, machine::Pause, snapshot2::DataSource,
30};
31
32/// The type of CKB-VM ISA.
33pub type VmIsa = u8;
34/// /// The type of CKB-VM version.
35pub type VmVersion = u32;
36
37/// The default machine type when asm feature is enabled. Note that ckb-script now functions
38/// solely based on ckb_vm::DefaultMachineRunner trait. The type provided here is only for
39/// default implementations.
40#[cfg(has_asm)]
41pub type Machine = ckb_vm::machine::asm::AsmMachine;
42/// The default machine type when neither asm feature nor flatmemory feature is not enabled
43#[cfg(all(not(has_asm), not(feature = "flatmemory")))]
44pub type Machine = ckb_vm::TraceMachine<
45    ckb_vm::DefaultCoreMachine<u64, ckb_vm::WXorXMemory<ckb_vm::SparseMemory<u64>>>,
46>;
47/// The default machine type when asm feature is not enabled, but flatmemory is enabled
48#[cfg(all(not(has_asm), feature = "flatmemory"))]
49pub type Machine = ckb_vm::TraceMachine<
50    ckb_vm::DefaultCoreMachine<u64, ckb_vm::WXorXMemory<ckb_vm::FlatMemory<u64>>>,
51>;
52
53/// Debug printer function type
54pub type DebugPrinter = Arc<dyn Fn(&Byte32, &str) + Send + Sync>;
55/// Syscall generator function type
56pub type SyscallGenerator<DL, V, M> =
57    fn(&VmId, &SgData<DL>, &VmContext<DL>, &V) -> Vec<Box<dyn Syscalls<M>>>;
58
59/// The version of CKB Script Verifier.
60#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
61pub enum ScriptVersion {
62    /// CKB VM 0 with Syscall version 1.
63    V0 = 0,
64    /// CKB VM 1 with Syscall version 1 and version 2.
65    V1 = 1,
66    /// CKB VM 2 with Syscall version 1, version 2 and version 3.
67    V2 = 2,
68}
69
70impl ScriptVersion {
71    /// Returns the latest version.
72    pub const fn latest() -> Self {
73        Self::V2
74    }
75
76    /// Returns the ISA set of CKB VM in current script version.
77    pub fn vm_isa(self) -> VmIsa {
78        match self {
79            Self::V0 => ISA_IMC,
80            Self::V1 => ISA_IMC | ISA_B | ISA_MOP,
81            Self::V2 => ISA_IMC | ISA_B | ISA_MOP,
82        }
83    }
84
85    /// Returns the version of CKB VM in current script version.
86    pub fn vm_version(self) -> VmVersion {
87        match self {
88            Self::V0 => VERSION0,
89            Self::V1 => VERSION1,
90            Self::V2 => VERSION2,
91        }
92    }
93
94    /// Returns the specific data script hash type.
95    ///
96    /// Returns:
97    /// - `ScriptHashType::Data` for version 0;
98    /// - `ScriptHashType::Data1` for version 1;
99    pub fn data_hash_type(self) -> ScriptHashType {
100        match self {
101            Self::V0 => ScriptHashType::Data,
102            Self::V1 => ScriptHashType::Data1,
103            Self::V2 => ScriptHashType::Data2,
104        }
105    }
106
107    /// Creates a CKB VM core machine without cycles limit.
108    ///
109    /// In fact, there is still a limit of `max_cycles` which is set to `2^64-1`.
110    pub fn init_core_machine_without_limit(self) -> <Machine as DefaultMachineRunner>::Inner {
111        self.init_core_machine(u64::MAX)
112    }
113
114    /// Creates a CKB VM core machine.
115    pub fn init_core_machine(self, max_cycles: Cycle) -> <Machine as DefaultMachineRunner>::Inner {
116        let isa = self.vm_isa();
117        let version = self.vm_version();
118        <<Machine as DefaultMachineRunner>::Inner as SupportMachine>::new(isa, version, max_cycles)
119    }
120}
121
122/// A script group is defined as scripts that share the same hash.
123///
124/// A script group will only be executed once per transaction, the
125/// script itself should check against all inputs/outputs in its group
126/// if needed.
127#[derive(Clone, Debug)]
128pub struct ScriptGroup {
129    /// The script.
130    ///
131    /// A script group is a group of input and output cells that share the same script.
132    pub script: Script,
133    /// The script group type.
134    pub group_type: ScriptGroupType,
135    /// Indices of input cells.
136    pub input_indices: Vec<usize>,
137    /// Indices of output cells.
138    pub output_indices: Vec<usize>,
139}
140
141/// The methods included here are defected in a way: all construction
142/// methods here create ScriptGroup without any `input_indices` or
143/// `output_indices` filled. One has to manually fill them later(or forgot
144/// about this).
145/// As a result, we are marking them as crate-only methods for now. This
146/// forces users to one of the following 2 solutions:
147/// * Call `groups()` on `TxData` so they can fetch `ScriptGroup` data with
148///   all correct data filled.
149/// * Manually construct the struct where they have to think what shall be
150///   used for `input_indices` and `output_indices`.
151impl ScriptGroup {
152    /// Creates a new script group struct.
153    pub(crate) fn new(script: &Script, group_type: ScriptGroupType) -> Self {
154        Self {
155            group_type,
156            script: script.to_owned(),
157            input_indices: vec![],
158            output_indices: vec![],
159        }
160    }
161
162    /// Creates a lock script group.
163    pub(crate) fn from_lock_script(script: &Script) -> Self {
164        Self::new(script, ScriptGroupType::Lock)
165    }
166
167    /// Creates a type script group.
168    pub(crate) fn from_type_script(script: &Script) -> Self {
169        Self::new(script, ScriptGroupType::Type)
170    }
171}
172
173/// The script group type.
174///
175/// A cell can have a lock script and an optional type script. Even they reference the same script,
176/// lock script and type script will not be grouped together.
177#[derive(Copy, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Debug)]
178#[serde(rename_all = "snake_case")]
179pub enum ScriptGroupType {
180    /// Lock script group.
181    Lock,
182    /// Type script group.
183    Type,
184}
185
186impl fmt::Display for ScriptGroupType {
187    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
188        match self {
189            ScriptGroupType::Lock => write!(f, "Lock"),
190            ScriptGroupType::Type => write!(f, "Type"),
191        }
192    }
193}
194
195/// Struct specifies which script has verified so far.
196#[derive(Clone)]
197pub struct TransactionState {
198    /// current suspended script index
199    pub current: usize,
200    /// current consumed cycle
201    pub current_cycles: Cycle,
202    /// limit cycles
203    pub limit_cycles: Cycle,
204}
205
206impl TransactionState {
207    /// Creates a new TransactionState struct
208    pub fn new(current: usize, current_cycles: Cycle, limit_cycles: Cycle) -> Self {
209        TransactionState {
210            current,
211            current_cycles,
212            limit_cycles,
213        }
214    }
215
216    /// Return next limit cycles according to max_cycles and step_cycles
217    pub fn next_limit_cycles(&self, step_cycles: Cycle, max_cycles: Cycle) -> (Cycle, bool) {
218        let remain = max_cycles - self.current_cycles;
219        let next_limit = self.limit_cycles + step_cycles;
220
221        if next_limit < remain {
222            (next_limit, false)
223        } else {
224            (remain, true)
225        }
226    }
227}
228
229/// Enum represent resumable verify result
230#[allow(clippy::large_enum_variant)]
231#[derive(Debug)]
232pub enum VerifyResult {
233    /// Completed total cycles
234    Completed(Cycle),
235    /// Suspended state
236    Suspended(TransactionState),
237}
238
239impl std::fmt::Debug for TransactionState {
240    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> std::fmt::Result {
241        f.debug_struct("TransactionState")
242            .field("current", &self.current)
243            .field("current_cycles", &self.current_cycles)
244            .field("limit_cycles", &self.limit_cycles)
245            .finish()
246    }
247}
248
249/// ChunkCommand is used to control the verification process to suspend or resume
250#[derive(Eq, PartialEq, Clone, Debug)]
251pub enum ChunkCommand {
252    /// Suspend the verification process
253    Suspend,
254    /// Resume the verification process
255    Resume,
256    /// Stop the verification process
257    Stop,
258}
259
260/// VM id type
261pub type VmId = u64;
262/// The first VM booted always have 0 as the ID
263pub const FIRST_VM_ID: VmId = 0;
264
265/// File descriptor
266#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
267pub struct Fd(pub u64);
268
269/// The first FD to be used
270pub const FIRST_FD_SLOT: u64 = 2;
271
272impl Fd {
273    /// Creates a new pipe with 2 fds, also return the next available fd slot
274    pub fn create(slot: u64) -> (Fd, Fd, u64) {
275        (Fd(slot), Fd(slot + 1), slot + 2)
276    }
277
278    /// Finds the other file descriptor of a pipe
279    pub fn other_fd(&self) -> Fd {
280        Fd(self.0 ^ 0x1)
281    }
282
283    /// Tests if current fd is used for reading from a pipe
284    pub fn is_read(&self) -> bool {
285        self.0.is_multiple_of(2)
286    }
287
288    /// Tests if current fd is used for writing to a pipe
289    pub fn is_write(&self) -> bool {
290        self.0 % 2 == 1
291    }
292}
293
294/// VM is in waiting-to-read state.
295#[derive(Clone, Debug, PartialEq, Eq, Hash)]
296pub struct ReadState {
297    /// FD to read from
298    pub fd: Fd,
299    /// Length to read
300    pub length: u64,
301    /// VM address to read data into
302    pub buffer_addr: u64,
303    /// Length address to keep final read length
304    pub length_addr: u64,
305}
306
307/// VM is in waiting-to-write state.
308#[derive(Clone, Debug, PartialEq, Eq, Hash)]
309pub struct WriteState {
310    /// FD to write to
311    pub fd: Fd,
312    /// Bytes that have already been written
313    pub consumed: u64,
314    /// Length to write
315    pub length: u64,
316    /// VM address to write data from
317    pub buffer_addr: u64,
318    /// Length of address to keep final written length
319    pub length_addr: u64,
320}
321
322/// VM State.
323#[derive(Clone, Debug, PartialEq, Eq, Hash)]
324pub enum VmState {
325    /// Runnable.
326    Runnable,
327    /// Terminated.
328    Terminated,
329    /// Wait.
330    Wait {
331        /// Target vm id.
332        target_vm_id: VmId,
333        /// Exit code addr.
334        exit_code_addr: u64,
335    },
336    /// WaitForWrite.
337    WaitForWrite(WriteState),
338    /// WaitForRead.
339    WaitForRead(ReadState),
340}
341
342/// Used to specify the location of script data.
343#[derive(Clone, Debug)]
344pub struct DataLocation {
345    /// A pointer to the data.
346    pub data_piece_id: DataPieceId,
347    /// Data offset.
348    pub offset: u64,
349    /// Data length.
350    pub length: u64,
351}
352
353/// Arguments for exec syscall
354#[derive(Clone, Debug)]
355pub struct ExecV2Args {
356    /// Data location for the program to invoke
357    pub location: DataLocation,
358    /// Argc
359    pub argc: u64,
360    /// Argv
361    pub argv: u64,
362}
363
364/// Arguments for spawn syscall
365#[derive(Clone, Debug)]
366pub struct SpawnArgs {
367    /// Data location for the program to spawn
368    pub location: DataLocation,
369    /// Argc
370    pub argc: u64,
371    /// Argv
372    pub argv: u64,
373    /// File descriptors to pass to spawned child process
374    pub fds: Vec<Fd>,
375    /// VM address to keep pid for spawned child process
376    pub process_id_addr: u64,
377}
378
379/// Arguments for wait syscall
380#[derive(Clone, Debug)]
381pub struct WaitArgs {
382    /// VM ID to wait for termination
383    pub target_id: VmId,
384    /// VM address to keep exit code for the waited process
385    pub exit_code_addr: u64,
386}
387
388/// Arguments for pipe syscall
389#[derive(Clone, Debug)]
390pub struct PipeArgs {
391    /// VM address to keep the first created file descriptor
392    pub fd1_addr: u64,
393    /// VM address to keep the second created file descriptor
394    pub fd2_addr: u64,
395}
396
397/// Arguments shared by read, write and inherited fd syscalls
398#[derive(Clone, Debug)]
399pub struct FdArgs {
400    /// For each and write syscalls, this contains the file descriptor to use
401    pub fd: Fd,
402    /// Length to read or length to write for read/write syscalls. Inherited
403    /// fd syscall will ignore this field.
404    pub length: u64,
405    /// VM address to keep returned data
406    pub buffer_addr: u64,
407    /// VM address for a input / output length buffer.
408    /// For read / write syscalls, this contains the actual data length.
409    /// For inherited fd syscall, this contains the number of file descriptors.
410    pub length_addr: u64,
411}
412
413/// Inter-process message, this is now used for implementing syscalls, but might
414/// be expanded for more usages later.
415#[derive(Clone, Debug)]
416pub enum Message {
417    /// Exec syscall
418    ExecV2(VmId, ExecV2Args),
419    /// Spawn syscall
420    Spawn(VmId, SpawnArgs),
421    /// Wait syscall
422    Wait(VmId, WaitArgs),
423    /// Pipe syscall
424    Pipe(VmId, PipeArgs),
425    /// Read syscall
426    FdRead(VmId, FdArgs),
427    /// Write syscall
428    FdWrite(VmId, FdArgs),
429    /// Inherited FD syscall
430    InheritedFileDescriptor(VmId, FdArgs),
431    /// Close syscall
432    Close(VmId, Fd),
433}
434
435/// A pointer to the data that is part of the transaction.
436#[derive(Clone, Debug, PartialEq, Eq, Hash)]
437pub enum DataPieceId {
438    /// The nth input cell data.
439    Input(u32),
440    /// The nth output data.
441    Output(u32),
442    /// The nth cell dep cell data.
443    CellDep(u32),
444    /// The nth group input cell data.
445    GroupInput(u32),
446    /// The nth group output data.
447    GroupOutput(u32),
448    /// The nth witness.
449    Witness(u32),
450    /// The nth witness group input.
451    WitnessGroupInput(u32),
452    /// The nth witness group output.
453    WitnessGroupOutput(u32),
454}
455
456impl TryFrom<(u64, u64, u64)> for DataPieceId {
457    type Error = String;
458
459    fn try_from(value: (u64, u64, u64)) -> Result<Self, Self::Error> {
460        let (source, index, place) = value;
461        let index: u32 =
462            u32::try_from(index).map_err(|e| format!("Error casting index to u32: {}", e))?;
463        match (source, place) {
464            (1, 0) => Ok(DataPieceId::Input(index)),
465            (2, 0) => Ok(DataPieceId::Output(index)),
466            (3, 0) => Ok(DataPieceId::CellDep(index)),
467            (0x0100000000000001, 0) => Ok(DataPieceId::GroupInput(index)),
468            (0x0100000000000002, 0) => Ok(DataPieceId::GroupOutput(index)),
469            (1, 1) => Ok(DataPieceId::Witness(index)),
470            (2, 1) => Ok(DataPieceId::Witness(index)),
471            (0x0100000000000001, 1) => Ok(DataPieceId::WitnessGroupInput(index)),
472            (0x0100000000000002, 1) => Ok(DataPieceId::WitnessGroupOutput(index)),
473            _ => Err(format!("Invalid source value: {:#x}", source)),
474        }
475    }
476}
477
478/// A cell that is either loaded, or not yet loaded.
479#[derive(Debug, PartialEq, Eq, Clone)]
480pub enum DataGuard {
481    /// Un-loaded out point
482    NotLoaded(OutPoint),
483    /// Loaded data
484    Loaded(Bytes),
485}
486
487/// LazyData wrapper make sure not-loaded data will be loaded only after one access
488#[derive(Debug, Clone)]
489pub struct LazyData(Arc<RwLock<DataGuard>>);
490
491impl LazyData {
492    fn from_cell_meta(cell_meta: &CellMeta) -> LazyData {
493        match &cell_meta.mem_cell_data {
494            Some(data) => LazyData(Arc::new(RwLock::new(DataGuard::Loaded(data.to_owned())))),
495            None => LazyData(Arc::new(RwLock::new(DataGuard::NotLoaded(
496                cell_meta.out_point.clone(),
497            )))),
498        }
499    }
500
501    fn access<DL: CellDataProvider>(&self, data_loader: &DL) -> Result<Bytes, ScriptError> {
502        let guard = self
503            .0
504            .read()
505            .map_err(|_| ScriptError::Other("RwLock poisoned".into()))?
506            .to_owned();
507        match guard {
508            DataGuard::NotLoaded(out_point) => {
509                let data = data_loader
510                    .get_cell_data(&out_point)
511                    .ok_or(ScriptError::Other("cell data not found".into()))?;
512                let mut write_guard = self
513                    .0
514                    .write()
515                    .map_err(|_| ScriptError::Other("RwLock poisoned".into()))?;
516                *write_guard = DataGuard::Loaded(data.clone());
517                Ok(data)
518            }
519            DataGuard::Loaded(bytes) => Ok(bytes),
520        }
521    }
522}
523
524/// A tri-state enum for representing binary lookup results.
525#[derive(Debug, Clone)]
526pub enum Binaries {
527    /// A unique cell is found for the requested binary.
528    Unique(Byte32, usize, LazyData),
529    /// Multiple cells are found for the requested binary, but all
530    /// the cells contains the same content(hence binary lookup still
531    /// succeeds).
532    Duplicate(Byte32, usize, LazyData),
533    /// Multiple cells are found for the requested binary, and they
534    /// differ so there is no way to tell which binary shall be used.
535    Multiple,
536}
537
538impl Binaries {
539    fn new(data_hash: Byte32, dep_index: usize, data: LazyData) -> Self {
540        Self::Unique(data_hash, dep_index, data)
541    }
542
543    fn merge(&mut self, data_hash: &Byte32) {
544        match self {
545            Self::Unique(hash, dep_index, data) | Self::Duplicate(hash, dep_index, data) => {
546                if hash != data_hash {
547                    *self = Self::Multiple;
548                } else {
549                    *self = Self::Duplicate(hash.to_owned(), *dep_index, data.to_owned());
550                }
551            }
552            Self::Multiple => {}
553        }
554    }
555}
556
557/// Immutable context data at transaction level
558#[derive(Clone, Debug)]
559pub struct TxData<DL> {
560    /// ResolvedTransaction.
561    pub rtx: Arc<ResolvedTransaction>,
562
563    /// Passed & derived information.
564    pub info: Arc<TxInfo<DL>>,
565}
566
567/// Information that is either passed as the context of the transaction,
568/// or can be derived from the transaction.
569#[derive(Clone, Debug)]
570pub struct TxInfo<DL> {
571    /// Data loader.
572    pub data_loader: DL,
573    /// Chain consensus parameters
574    pub consensus: Arc<Consensus>,
575    /// Transaction verification environment
576    pub tx_env: Arc<TxVerifyEnv>,
577
578    /// Potential binaries in current transaction indexed by data hash
579    pub binaries_by_data_hash: HashMap<Byte32, (usize, LazyData)>,
580    /// Potential binaries in current transaction indexed by type script hash
581    pub binaries_by_type_hash: HashMap<Byte32, Binaries>,
582    /// Lock script groups, orders here are important
583    pub lock_groups: BTreeMap<Byte32, ScriptGroup>,
584    /// Type script groups, orders here are important
585    pub type_groups: BTreeMap<Byte32, ScriptGroup>,
586    /// Output cells in current transaction reorganized in CellMeta format
587    pub outputs: Vec<CellMeta>,
588}
589
590impl<DL> TxData<DL>
591where
592    DL: CellDataProvider,
593{
594    /// Creates a new TxData structure
595    pub fn new(
596        rtx: Arc<ResolvedTransaction>,
597        data_loader: DL,
598        consensus: Arc<Consensus>,
599        tx_env: Arc<TxVerifyEnv>,
600    ) -> Self {
601        let tx_hash = rtx.transaction.hash();
602        let resolved_cell_deps = &rtx.resolved_cell_deps;
603        let resolved_inputs = &rtx.resolved_inputs;
604        let outputs = rtx
605            .transaction
606            .outputs_with_data_iter()
607            .enumerate()
608            .map(|(index, (cell_output, data))| {
609                let out_point = OutPoint::new_builder()
610                    .tx_hash(tx_hash.clone())
611                    .index(index)
612                    .build();
613                let data_hash = CellOutput::calc_data_hash(&data);
614                CellMeta {
615                    cell_output,
616                    out_point,
617                    transaction_info: None,
618                    data_bytes: data.len() as u64,
619                    mem_cell_data: Some(data),
620                    mem_cell_data_hash: Some(data_hash),
621                }
622            })
623            .collect();
624
625        let mut binaries_by_data_hash: HashMap<Byte32, (usize, LazyData)> = HashMap::default();
626        let mut binaries_by_type_hash: HashMap<Byte32, Binaries> = HashMap::default();
627        for (i, cell_meta) in resolved_cell_deps.iter().enumerate() {
628            let data_hash = data_loader
629                .load_cell_data_hash(cell_meta)
630                .expect("cell data hash");
631            let lazy = LazyData::from_cell_meta(cell_meta);
632            binaries_by_data_hash.insert(data_hash.to_owned(), (i, lazy.to_owned()));
633
634            if let Some(t) = &cell_meta.cell_output.type_().to_opt() {
635                binaries_by_type_hash
636                    .entry(t.calc_script_hash())
637                    .and_modify(|bin| bin.merge(&data_hash))
638                    .or_insert_with(|| Binaries::new(data_hash.to_owned(), i, lazy.to_owned()));
639            }
640        }
641
642        let mut lock_groups = BTreeMap::default();
643        let mut type_groups = BTreeMap::default();
644        for (i, cell_meta) in resolved_inputs.iter().enumerate() {
645            // here we are only pre-processing the data, verify method validates
646            // each input has correct script setup.
647            let output = &cell_meta.cell_output;
648            let lock_group_entry = lock_groups
649                .entry(output.calc_lock_hash())
650                .or_insert_with(|| ScriptGroup::from_lock_script(&output.lock()));
651            lock_group_entry.input_indices.push(i);
652            if let Some(t) = &output.type_().to_opt() {
653                let type_group_entry = type_groups
654                    .entry(t.calc_script_hash())
655                    .or_insert_with(|| ScriptGroup::from_type_script(t));
656                type_group_entry.input_indices.push(i);
657            }
658        }
659        for (i, output) in rtx.transaction.outputs().into_iter().enumerate() {
660            if let Some(t) = &output.type_().to_opt() {
661                let type_group_entry = type_groups
662                    .entry(t.calc_script_hash())
663                    .or_insert_with(|| ScriptGroup::from_type_script(t));
664                type_group_entry.output_indices.push(i);
665            }
666        }
667
668        Self {
669            rtx,
670            info: Arc::new(TxInfo {
671                data_loader,
672                consensus,
673                tx_env,
674                binaries_by_data_hash,
675                binaries_by_type_hash,
676                lock_groups,
677                type_groups,
678                outputs,
679            }),
680        }
681    }
682
683    #[inline]
684    /// Extracts actual script binary either in dep cells.
685    pub fn extract_script(&self, script: &Script) -> Result<Bytes, ScriptError> {
686        self.info.extract_script(script)
687    }
688}
689
690impl<DL> TxInfo<DL>
691where
692    DL: CellDataProvider,
693{
694    #[inline]
695    /// Extracts actual script binary either in dep cells.
696    pub fn extract_script(&self, script: &Script) -> Result<Bytes, ScriptError> {
697        let (lazy, _) = self.extract_script_and_dep_index(script)?;
698        lazy.access(&self.data_loader)
699    }
700}
701
702impl<DL> TxData<DL> {
703    #[inline]
704    /// Calculates transaction hash
705    pub fn tx_hash(&self) -> Byte32 {
706        self.rtx.transaction.hash()
707    }
708
709    #[inline]
710    /// Extracts the index of the script binary in dep cells
711    pub fn extract_referenced_dep_index(&self, script: &Script) -> Result<usize, ScriptError> {
712        self.info.extract_referenced_dep_index(script)
713    }
714
715    #[inline]
716    /// Finds the script group from cell deps.
717    pub fn find_script_group(
718        &self,
719        script_group_type: ScriptGroupType,
720        script_hash: &Byte32,
721    ) -> Option<&ScriptGroup> {
722        self.info.find_script_group(script_group_type, script_hash)
723    }
724
725    #[inline]
726    /// Returns the version of the machine based on the script and the consensus rules.
727    pub fn select_version(&self, script: &Script) -> Result<ScriptVersion, ScriptError> {
728        self.info.select_version(script)
729    }
730
731    #[inline]
732    /// Returns all script groups.
733    pub fn groups(&self) -> impl Iterator<Item = (&'_ Byte32, &'_ ScriptGroup)> {
734        self.info.groups()
735    }
736
737    #[inline]
738    /// Returns all script groups with type.
739    pub fn groups_with_type(
740        &self,
741    ) -> impl Iterator<Item = (ScriptGroupType, &'_ Byte32, &'_ ScriptGroup)> {
742        self.info.groups_with_type()
743    }
744}
745
746impl<DL> TxInfo<DL> {
747    #[inline]
748    /// Extracts the index of the script binary in dep cells
749    pub fn extract_referenced_dep_index(&self, script: &Script) -> Result<usize, ScriptError> {
750        let (_, dep_index) = self.extract_script_and_dep_index(script)?;
751        Ok(*dep_index)
752    }
753
754    fn extract_script_and_dep_index(
755        &self,
756        script: &Script,
757    ) -> Result<(&LazyData, &usize), ScriptError> {
758        let script_hash_type = ScriptHashType::try_from(script.hash_type())
759            .map_err(|err| ScriptError::InvalidScriptHashType(err.to_string()))?;
760        match script_hash_type {
761            ScriptHashType::Data | ScriptHashType::Data1 | ScriptHashType::Data2 => {
762                if let Some((dep_index, lazy)) = self.binaries_by_data_hash.get(&script.code_hash())
763                {
764                    Ok((lazy, dep_index))
765                } else {
766                    Err(ScriptError::ScriptNotFound(script.code_hash()))
767                }
768            }
769            ScriptHashType::Type => {
770                if let Some(ref bin) = self.binaries_by_type_hash.get(&script.code_hash()) {
771                    match bin {
772                        Binaries::Unique(_, dep_index, lazy) => Ok((lazy, dep_index)),
773                        Binaries::Duplicate(_, dep_index, lazy) => Ok((lazy, dep_index)),
774                        Binaries::Multiple => Err(ScriptError::MultipleMatches),
775                    }
776                } else {
777                    Err(ScriptError::ScriptNotFound(script.code_hash()))
778                }
779            }
780            hash_type => {
781                return Err(ScriptError::InvalidScriptHashType(format!(
782                    "The ScriptHashType/{:?} has not been activated, and is not permitted for use.",
783                    hash_type
784                )));
785            }
786        }
787    }
788
789    /// Finds the script group from cell deps.
790    pub fn find_script_group(
791        &self,
792        script_group_type: ScriptGroupType,
793        script_hash: &Byte32,
794    ) -> Option<&ScriptGroup> {
795        match script_group_type {
796            ScriptGroupType::Lock => self.lock_groups.get(script_hash),
797            ScriptGroupType::Type => self.type_groups.get(script_hash),
798        }
799    }
800
801    fn is_vm_version_1_and_syscalls_2_enabled(&self) -> bool {
802        // If the proposal window is allowed to prejudge on the vm version,
803        // it will cause proposal tx to start a new vm in the blocks before hardfork,
804        // destroying the assumption that the transaction execution only uses the old vm
805        // before hardfork, leading to unexpected network splits.
806        let epoch_number = self.tx_env.epoch_number_without_proposal_window();
807        let hardfork_switch = self.consensus.hardfork_switch();
808        hardfork_switch
809            .ckb2021
810            .is_vm_version_1_and_syscalls_2_enabled(epoch_number)
811    }
812
813    fn is_vm_version_2_and_syscalls_3_enabled(&self) -> bool {
814        // If the proposal window is allowed to prejudge on the vm version,
815        // it will cause proposal tx to start a new vm in the blocks before hardfork,
816        // destroying the assumption that the transaction execution only uses the old vm
817        // before hardfork, leading to unexpected network splits.
818        let epoch_number = self.tx_env.epoch_number_without_proposal_window();
819        let hardfork_switch = self.consensus.hardfork_switch();
820        hardfork_switch
821            .ckb2023
822            .is_vm_version_2_and_syscalls_3_enabled(epoch_number)
823    }
824
825    /// Returns the version of the machine based on the script and the consensus rules.
826    pub fn select_version(&self, script: &Script) -> Result<ScriptVersion, ScriptError> {
827        let is_vm_version_2_and_syscalls_3_enabled = self.is_vm_version_2_and_syscalls_3_enabled();
828        let is_vm_version_1_and_syscalls_2_enabled = self.is_vm_version_1_and_syscalls_2_enabled();
829        let script_hash_type = ScriptHashType::try_from(script.hash_type())
830            .map_err(|err| ScriptError::InvalidScriptHashType(err.to_string()))?;
831        match script_hash_type {
832            ScriptHashType::Data => Ok(ScriptVersion::V0),
833            ScriptHashType::Data1 => {
834                if is_vm_version_1_and_syscalls_2_enabled {
835                    Ok(ScriptVersion::V1)
836                } else {
837                    Err(ScriptError::InvalidVmVersion(1))
838                }
839            }
840            ScriptHashType::Data2 => {
841                if is_vm_version_2_and_syscalls_3_enabled {
842                    Ok(ScriptVersion::V2)
843                } else {
844                    Err(ScriptError::InvalidVmVersion(2))
845                }
846            }
847            ScriptHashType::Type => {
848                if is_vm_version_2_and_syscalls_3_enabled {
849                    Ok(ScriptVersion::V2)
850                } else if is_vm_version_1_and_syscalls_2_enabled {
851                    Ok(ScriptVersion::V1)
852                } else {
853                    Ok(ScriptVersion::V0)
854                }
855            }
856            hash_type => {
857                return Err(ScriptError::InvalidScriptHashType(format!(
858                    "The ScriptHashType/{:?} has not been activated, and is not permitted for use.",
859                    hash_type
860                )));
861            }
862        }
863    }
864
865    /// Returns all script groups.
866    pub fn groups(&self) -> impl Iterator<Item = (&'_ Byte32, &'_ ScriptGroup)> {
867        self.lock_groups.iter().chain(self.type_groups.iter())
868    }
869
870    /// Returns all script groups with type.
871    pub fn groups_with_type(
872        &self,
873    ) -> impl Iterator<Item = (ScriptGroupType, &'_ Byte32, &'_ ScriptGroup)> {
874        self.lock_groups
875            .iter()
876            .map(|(hash, group)| (ScriptGroupType::Lock, hash, group))
877            .chain(
878                self.type_groups
879                    .iter()
880                    .map(|(hash, group)| (ScriptGroupType::Type, hash, group)),
881            )
882    }
883}
884
885/// Immutable context data at script group level
886#[derive(Clone, Debug)]
887pub struct SgData<DL> {
888    /// ResolvedTransaction.
889    pub rtx: Arc<ResolvedTransaction>,
890
891    /// Passed & derived information at transaction level.
892    pub tx_info: Arc<TxInfo<DL>>,
893
894    /// Passed & derived information at script group level.
895    pub sg_info: Arc<SgInfo>,
896}
897
898/// Script group level derived information.
899#[derive(Clone, Debug)]
900pub struct SgInfo {
901    /// Currently executed script version
902    pub script_version: ScriptVersion,
903    /// Currently executed script group
904    pub script_group: ScriptGroup,
905    /// Currently executed script hash
906    pub script_hash: Byte32,
907    /// DataPieceId for the root program
908    pub program_data_piece_id: DataPieceId,
909}
910
911impl<DL> SgData<DL> {
912    /// Creates a new SgData structure from TxData, and script group information
913    pub fn new(tx_data: &TxData<DL>, script_group: &ScriptGroup) -> Result<Self, ScriptError> {
914        let script_hash = script_group.script.calc_script_hash();
915        let script_version = tx_data.select_version(&script_group.script)?;
916        let dep_index = tx_data
917            .extract_referenced_dep_index(&script_group.script)?
918            .try_into()
919            .map_err(|_| ScriptError::Other("u32 overflow".to_string()))?;
920        Ok(Self {
921            rtx: Arc::clone(&tx_data.rtx),
922            tx_info: Arc::clone(&tx_data.info),
923            sg_info: Arc::new(SgInfo {
924                script_version,
925                script_hash,
926                script_group: script_group.clone(),
927                program_data_piece_id: DataPieceId::CellDep(dep_index),
928            }),
929        })
930    }
931
932    /// Shortcut to data loader
933    pub fn data_loader(&self) -> &DL {
934        &self.tx_info.data_loader
935    }
936
937    /// Shortcut to group input indices
938    pub fn group_inputs(&self) -> &[usize] {
939        &self.sg_info.script_group.input_indices
940    }
941
942    /// Shortcut to group output indices
943    pub fn group_outputs(&self) -> &[usize] {
944        &self.sg_info.script_group.output_indices
945    }
946
947    /// Shortcut to all outputs
948    pub fn outputs(&self) -> &[CellMeta] {
949        &self.tx_info.outputs
950    }
951}
952
953impl<DL> DataSource<DataPieceId> for SgData<DL>
954where
955    DL: CellDataProvider,
956{
957    fn load_data(&self, id: &DataPieceId, offset: u64, length: u64) -> Option<(Bytes, u64)> {
958        match id {
959            DataPieceId::Input(i) => self
960                .rtx
961                .resolved_inputs
962                .get(*i as usize)
963                .and_then(|cell| self.data_loader().load_cell_data(cell)),
964            DataPieceId::Output(i) => self
965                .rtx
966                .transaction
967                .outputs_data()
968                .get(*i as usize)
969                .map(|data| data.raw_data()),
970            DataPieceId::CellDep(i) => self
971                .rtx
972                .resolved_cell_deps
973                .get(*i as usize)
974                .and_then(|cell| self.data_loader().load_cell_data(cell)),
975            DataPieceId::GroupInput(i) => self
976                .sg_info
977                .script_group
978                .input_indices
979                .get(*i as usize)
980                .and_then(|gi| self.rtx.resolved_inputs.get(*gi))
981                .and_then(|cell| self.data_loader().load_cell_data(cell)),
982            DataPieceId::GroupOutput(i) => self
983                .sg_info
984                .script_group
985                .output_indices
986                .get(*i as usize)
987                .and_then(|gi| self.rtx.transaction.outputs_data().get(*gi))
988                .map(|data| data.raw_data()),
989            DataPieceId::Witness(i) => self
990                .rtx
991                .transaction
992                .witnesses()
993                .get(*i as usize)
994                .map(|data| data.raw_data()),
995            DataPieceId::WitnessGroupInput(i) => self
996                .sg_info
997                .script_group
998                .input_indices
999                .get(*i as usize)
1000                .and_then(|gi| self.rtx.transaction.witnesses().get(*gi))
1001                .map(|data| data.raw_data()),
1002            DataPieceId::WitnessGroupOutput(i) => self
1003                .sg_info
1004                .script_group
1005                .output_indices
1006                .get(*i as usize)
1007                .and_then(|gi| self.rtx.transaction.witnesses().get(*gi))
1008                .map(|data| data.raw_data()),
1009        }
1010        .map(|data| {
1011            let offset = std::cmp::min(offset as usize, data.len());
1012            let full_length = data.len() - offset;
1013            let real_length = if length > 0 {
1014                std::cmp::min(full_length, length as usize)
1015            } else {
1016                full_length
1017            };
1018            (data.slice(offset..offset + real_length), full_length as u64)
1019        })
1020    }
1021}
1022
1023/// When the vm is initialized, arguments are loaded onto the stack.
1024/// This enum specifies how to locate these arguments.
1025#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1026pub enum VmArgs {
1027    /// Represents reading arguments from other vm.
1028    Reader {
1029        /// An identifier for the virtual machine/process.
1030        vm_id: u64,
1031        /// The number of arguments provided.
1032        argc: u64,
1033        /// The pointer of the actual arguments.
1034        argv: u64,
1035    },
1036    /// Represents reading arguments from a vector.
1037    Vector(Vec<Bytes>),
1038}
1039
1040/// Mutable data at virtual machine level
1041#[derive(Clone)]
1042pub struct VmContext<DL>
1043where
1044    DL: CellDataProvider,
1045{
1046    /// Cycles executed before current VM starts
1047    pub base_cycles: Arc<AtomicU64>,
1048    /// A mutable reference to scheduler's message box
1049    pub message_box: Arc<Mutex<Vec<Message>>>,
1050    /// A snapshot for COW usage
1051    pub snapshot2_context: Arc<Mutex<Snapshot2Context<DataPieceId, SgData<DL>>>>,
1052}
1053
1054impl<DL> VmContext<DL>
1055where
1056    DL: CellDataProvider + Clone,
1057{
1058    /// Creates a new VM context. It is by design that parameters to this function
1059    /// are references. It is a reminder that the inputs are designed to be shared
1060    /// among different entities.
1061    pub fn new(sg_data: &SgData<DL>, message_box: &Arc<Mutex<Vec<Message>>>) -> Self {
1062        Self {
1063            base_cycles: Arc::new(AtomicU64::new(0)),
1064            message_box: Arc::clone(message_box),
1065            snapshot2_context: Arc::new(Mutex::new(Snapshot2Context::new(sg_data.clone()))),
1066        }
1067    }
1068
1069    /// Sets current base cycles
1070    pub fn set_base_cycles(&mut self, base_cycles: u64) {
1071        self.base_cycles.store(base_cycles, Ordering::Release);
1072    }
1073}
1074
1075/// The scheduler's running mode.
1076#[derive(Clone)]
1077pub enum RunMode {
1078    /// Continues running until cycles are exhausted.
1079    LimitCycles(Cycle),
1080    /// Continues running until a Pause signal is received or cycles are exhausted.
1081    Pause(Pause, Cycle),
1082}
1083
1084/// Terminated result
1085#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
1086pub struct TerminatedResult {
1087    /// Root VM exit code
1088    pub exit_code: i8,
1089    /// Total consumed cycles by all VMs in current scheduler,
1090    /// up to this execution point.
1091    pub consumed_cycles: Cycle,
1092}
1093
1094/// Single iteration result
1095#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
1096pub struct IterationResult {
1097    /// VM ID that gets executed
1098    pub executed_vm: VmId,
1099    /// Terminated status
1100    pub terminated_status: Option<TerminatedResult>,
1101}