ckb_script/
types.rs

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