ckb_script/
verify.rs

1#[cfg(not(target_family = "wasm"))]
2use crate::ChunkCommand;
3use crate::scheduler::Scheduler;
4use crate::{
5    error::{ScriptError, TransactionScriptError},
6    syscalls::generator::generate_ckb_syscalls,
7    type_id::TypeIdSystemScript,
8    types::{
9        DebugPrinter, FullSuspendedState, Machine, RunMode, ScriptGroup, ScriptGroupType,
10        ScriptVersion, SgData, SyscallGenerator, TerminatedResult, TransactionState, TxData,
11        VerifyResult,
12    },
13    verify_env::TxVerifyEnv,
14};
15use ckb_chain_spec::consensus::{Consensus, TYPE_ID_CODE_HASH};
16use ckb_error::Error;
17#[cfg(feature = "logging")]
18use ckb_logger::{debug, info};
19use ckb_traits::{CellDataProvider, ExtensionProvider, HeaderProvider};
20use ckb_types::{
21    bytes::Bytes,
22    core::{Cycle, ScriptHashType, cell::ResolvedTransaction},
23    packed::{Byte32, Script},
24    prelude::*,
25};
26#[cfg(not(target_family = "wasm"))]
27use ckb_vm::machine::Pause as VMPause;
28use ckb_vm::{DefaultMachineRunner, Error as VMInternalError};
29use std::sync::Arc;
30#[cfg(not(target_family = "wasm"))]
31use tokio::sync::{
32    oneshot,
33    watch::{self, Receiver},
34};
35
36#[cfg(test)]
37mod tests;
38
39pub enum ChunkState {
40    Suspended(Option<FullSuspendedState>),
41    // (total_cycles, consumed_cycles in last chunk)
42    Completed(Cycle, Cycle),
43}
44
45impl ChunkState {
46    pub fn suspended(state: FullSuspendedState) -> Self {
47        ChunkState::Suspended(Some(state))
48    }
49
50    pub fn suspended_type_id() -> Self {
51        ChunkState::Suspended(None)
52    }
53}
54
55/// This struct leverages CKB VM to verify transaction inputs.
56pub struct TransactionScriptsVerifier<
57    DL: CellDataProvider,
58    V = DebugPrinter,
59    M: DefaultMachineRunner = Machine,
60> {
61    tx_data: Arc<TxData<DL>>,
62    syscall_generator: SyscallGenerator<DL, V, <M as DefaultMachineRunner>::Inner>,
63    syscall_context: V,
64}
65
66impl<DL> TransactionScriptsVerifier<DL>
67where
68    DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static,
69{
70    /// Create a script verifier using default CKB syscalls and a default debug printer
71    pub fn new(
72        rtx: Arc<ResolvedTransaction>,
73        data_loader: DL,
74        consensus: Arc<Consensus>,
75        tx_env: Arc<TxVerifyEnv>,
76    ) -> Self {
77        let debug_printer: DebugPrinter = Arc::new(
78            #[allow(unused_variables)]
79            |hash: &Byte32, message: &str| {
80                #[cfg(feature = "logging")]
81                debug!("script group: {} DEBUG OUTPUT: {}", hash, message);
82            },
83        );
84
85        Self::new_with_debug_printer(rtx, data_loader, consensus, tx_env, debug_printer)
86    }
87
88    /// Create a script verifier using default CKB syscalls and a custom debug printer
89    pub fn new_with_debug_printer(
90        rtx: Arc<ResolvedTransaction>,
91        data_loader: DL,
92        consensus: Arc<Consensus>,
93        tx_env: Arc<TxVerifyEnv>,
94        debug_printer: DebugPrinter,
95    ) -> Self {
96        Self::new_with_generator(
97            rtx,
98            data_loader,
99            consensus,
100            tx_env,
101            generate_ckb_syscalls,
102            debug_printer,
103        )
104    }
105}
106
107impl<DL, V, M> TransactionScriptsVerifier<DL, V, M>
108where
109    DL: CellDataProvider + HeaderProvider + ExtensionProvider + Clone,
110    V: Clone,
111    M: DefaultMachineRunner,
112{
113    /// Creates a script verifier for the transaction.
114    ///
115    /// ## Params
116    ///
117    /// * `rtx` - transaction which cell out points have been resolved.
118    /// * `data_loader` - used to load cell data.
119    /// * `consensus` - consensus parameters.
120    /// * `tx_env` - enviroment for verifying transaction, such as committed block, etc.
121    /// * `syscall_generator` - a syscall generator for current verifier
122    /// * `syscall_context` - context for syscall generator
123    pub fn new_with_generator(
124        rtx: Arc<ResolvedTransaction>,
125        data_loader: DL,
126        consensus: Arc<Consensus>,
127        tx_env: Arc<TxVerifyEnv>,
128        syscall_generator: SyscallGenerator<DL, V, <M as DefaultMachineRunner>::Inner>,
129        syscall_context: V,
130    ) -> TransactionScriptsVerifier<DL, V, M> {
131        let tx_data = Arc::new(TxData::new(rtx, data_loader, consensus, tx_env));
132
133        TransactionScriptsVerifier {
134            tx_data,
135            syscall_generator,
136            syscall_context,
137        }
138    }
139
140    //////////////////////////////////////////////////////////////////
141    // Functions below have been moved from verifier struct to TxData,
142    // however we still preserve all the public APIs by delegating
143    // them to TxData.
144    //////////////////////////////////////////////////////////////////
145
146    #[inline]
147    #[allow(dead_code)]
148    fn hash(&self) -> Byte32 {
149        self.tx_data.tx_hash()
150    }
151
152    /// Extracts actual script binary either in dep cells.
153    pub fn extract_script(&self, script: &Script) -> Result<Bytes, ScriptError> {
154        self.tx_data.extract_script(script)
155    }
156
157    /// Returns the version of the machine based on the script and the consensus rules.
158    pub fn select_version(&self, script: &Script) -> Result<ScriptVersion, ScriptError> {
159        self.tx_data.select_version(script)
160    }
161
162    /// Returns all script groups.
163    pub fn groups(&self) -> impl Iterator<Item = (&'_ Byte32, &'_ ScriptGroup)> {
164        self.tx_data.groups()
165    }
166
167    /// Returns all script groups with type.
168    pub fn groups_with_type(
169        &self,
170    ) -> impl Iterator<Item = (ScriptGroupType, &'_ Byte32, &'_ ScriptGroup)> {
171        self.tx_data.groups_with_type()
172    }
173
174    /// Finds the script group from cell deps.
175    pub fn find_script_group(
176        &self,
177        script_group_type: ScriptGroupType,
178        script_hash: &Byte32,
179    ) -> Option<&ScriptGroup> {
180        self.tx_data
181            .find_script_group(script_group_type, script_hash)
182    }
183
184    //////////////////////////////////////////////////////////////////
185    // This marks the end of delegated functions.
186    //////////////////////////////////////////////////////////////////
187
188    /// Verifies the transaction by running scripts.
189    ///
190    /// ## Params
191    ///
192    /// * `max_cycles` - Maximum allowed cycles to run the scripts. The verification quits early
193    ///   when the consumed cycles exceed the limit.
194    ///
195    /// ## Returns
196    ///
197    /// It returns the total consumed cycles on success, Otherwise it returns the verification error.
198    pub fn verify(&self, max_cycles: Cycle) -> Result<Cycle, Error> {
199        let mut cycles: Cycle = 0;
200
201        // Now run each script group
202        for (_hash, group) in self.groups() {
203            // max_cycles must reduce by each group exec
204            let used_cycles = self
205                .verify_script_group(group, max_cycles - cycles)
206                .map_err(|e| {
207                    #[cfg(feature = "logging")]
208                    logging::on_script_error(_hash, &self.hash(), &e);
209                    e.source(group)
210                })?;
211
212            cycles = wrapping_cycles_add(cycles, used_cycles, group)?;
213        }
214        Ok(cycles)
215    }
216
217    /// Performing a resumable verification on the transaction scripts.
218    ///
219    /// ## Params
220    ///
221    /// * `limit_cycles` - Maximum allowed cycles to run the scripts. The verification quits early
222    ///   when the consumed cycles exceed the limit.
223    ///
224    /// ## Returns
225    ///
226    /// It returns the total consumed cycles if verification completed,
227    /// If verify is suspended, a state will returned.
228    pub fn resumable_verify(&self, limit_cycles: Cycle) -> Result<VerifyResult, Error> {
229        let mut cycles = 0;
230        let mut current_consumed_cycles = 0;
231
232        let groups: Vec<_> = self.groups().collect();
233        for (idx, (_hash, group)) in groups.iter().enumerate() {
234            // vm should early return invalid cycles
235            let remain_cycles = limit_cycles
236                .checked_sub(current_consumed_cycles)
237                .ok_or_else(|| {
238                    ScriptError::Other(format!("expect invalid cycles {limit_cycles} {cycles}"))
239                        .source(group)
240                })?;
241
242            match self.verify_group_with_chunk(group, remain_cycles, &None) {
243                Ok(ChunkState::Completed(used_cycles, consumed_cycles)) => {
244                    current_consumed_cycles =
245                        wrapping_cycles_add(current_consumed_cycles, consumed_cycles, group)?;
246                    cycles = wrapping_cycles_add(cycles, used_cycles, group)?;
247                }
248                Ok(ChunkState::Suspended(state)) => {
249                    let current = idx;
250                    let state = TransactionState::new(state, current, cycles, remain_cycles);
251                    return Ok(VerifyResult::Suspended(state));
252                }
253                Err(e) => {
254                    #[cfg(feature = "logging")]
255                    logging::on_script_error(_hash, &self.hash(), &e);
256                    return Err(e.source(group).into());
257                }
258            }
259        }
260
261        Ok(VerifyResult::Completed(cycles))
262    }
263
264    /// Resuming an suspended verify from vm state
265    ///
266    /// ## Params
267    ///
268    /// * `state` - vm state.
269    ///
270    /// * `limit_cycles` - Maximum allowed cycles to run the scripts. The verification quits early
271    ///   when the consumed cycles exceed the limit.
272    ///
273    /// ## Returns
274    ///
275    /// It returns the total consumed cycles if verification completed,
276    /// If verify is suspended, a borrowed state will returned.
277    pub fn resume_from_state(
278        &self,
279        state: &TransactionState,
280        limit_cycles: Cycle,
281    ) -> Result<VerifyResult, Error> {
282        let TransactionState {
283            current,
284            state,
285            current_cycles,
286            ..
287        } = state;
288
289        let mut current_used = 0;
290        let mut cycles = *current_cycles;
291
292        let (_hash, current_group) = self.groups().nth(*current).ok_or_else(|| {
293            ScriptError::Other(format!("snapshot group missing {current:?}")).unknown_source()
294        })?;
295
296        let resumed_script_result =
297            self.verify_group_with_chunk(current_group, limit_cycles, state);
298
299        match resumed_script_result {
300            Ok(ChunkState::Completed(used_cycles, consumed_cycles)) => {
301                current_used = wrapping_cycles_add(current_used, consumed_cycles, current_group)?;
302                cycles = wrapping_cycles_add(cycles, used_cycles, current_group)?;
303            }
304            Ok(ChunkState::Suspended(state)) => {
305                let state = TransactionState::new(state, *current, cycles, limit_cycles);
306                return Ok(VerifyResult::Suspended(state));
307            }
308            Err(e) => {
309                #[cfg(feature = "logging")]
310                logging::on_script_error(_hash, &self.hash(), &e);
311                return Err(e.source(current_group).into());
312            }
313        }
314
315        for (idx, (_hash, group)) in self.groups().enumerate().skip(current + 1) {
316            let remain_cycles = limit_cycles.checked_sub(current_used).ok_or_else(|| {
317                ScriptError::Other(format!(
318                    "expect invalid cycles {limit_cycles} {current_used} {cycles}"
319                ))
320                .source(group)
321            })?;
322
323            match self.verify_group_with_chunk(group, remain_cycles, &None) {
324                Ok(ChunkState::Completed(_, consumed_cycles)) => {
325                    current_used = wrapping_cycles_add(current_used, consumed_cycles, group)?;
326                    cycles = wrapping_cycles_add(cycles, consumed_cycles, group)?;
327                }
328                Ok(ChunkState::Suspended(state)) => {
329                    let current = idx;
330                    let state = TransactionState::new(state, current, cycles, remain_cycles);
331                    return Ok(VerifyResult::Suspended(state));
332                }
333                Err(e) => {
334                    #[cfg(feature = "logging")]
335                    logging::on_script_error(_hash, &self.hash(), &e);
336                    return Err(e.source(group).into());
337                }
338            }
339        }
340
341        Ok(VerifyResult::Completed(cycles))
342    }
343
344    /// Complete an suspended verify
345    ///
346    /// ## Params
347    ///
348    /// * `snap` - Captured transaction verification state.
349    ///
350    /// * `max_cycles` - Maximum allowed cycles to run the scripts. The verification quits early
351    ///   when the consumed cycles exceed the limit.
352    ///
353    /// ## Returns
354    ///
355    /// It returns the total consumed cycles on completed, Otherwise it returns the verification error.
356    pub fn complete(&self, snap: &TransactionState, max_cycles: Cycle) -> Result<Cycle, Error> {
357        let mut cycles = snap.current_cycles;
358
359        let (_hash, current_group) = self.groups().nth(snap.current).ok_or_else(|| {
360            ScriptError::Other(format!("snapshot group missing {:?}", snap.current))
361                .unknown_source()
362        })?;
363
364        if max_cycles < cycles {
365            return Err(ScriptError::ExceededMaximumCycles(max_cycles)
366                .source(current_group)
367                .into());
368        }
369
370        // continue snapshot current script
371        // max_cycles - cycles checked
372        match self.verify_group_with_chunk(current_group, max_cycles - cycles, &snap.state) {
373            Ok(ChunkState::Completed(used_cycles, _consumed_cycles)) => {
374                cycles = wrapping_cycles_add(cycles, used_cycles, current_group)?;
375            }
376            Ok(ChunkState::Suspended(_)) => {
377                return Err(ScriptError::ExceededMaximumCycles(max_cycles)
378                    .source(current_group)
379                    .into());
380            }
381            Err(e) => {
382                #[cfg(feature = "logging")]
383                logging::on_script_error(_hash, &self.hash(), &e);
384                return Err(e.source(current_group).into());
385            }
386        }
387
388        for (_hash, group) in self.groups().skip(snap.current + 1) {
389            let remain_cycles = max_cycles.checked_sub(cycles).ok_or_else(|| {
390                ScriptError::Other(format!("expect invalid cycles {max_cycles} {cycles}"))
391                    .source(group)
392            })?;
393
394            match self.verify_group_with_chunk(group, remain_cycles, &None) {
395                Ok(ChunkState::Completed(used_cycles, _consumed_cycles)) => {
396                    cycles = wrapping_cycles_add(cycles, used_cycles, current_group)?;
397                }
398                Ok(ChunkState::Suspended(_)) => {
399                    return Err(ScriptError::ExceededMaximumCycles(max_cycles)
400                        .source(group)
401                        .into());
402                }
403                Err(e) => {
404                    #[cfg(feature = "logging")]
405                    logging::on_script_error(_hash, &self.hash(), &e);
406                    return Err(e.source(group).into());
407                }
408            }
409        }
410
411        Ok(cycles)
412    }
413
414    /// Runs a single script in current transaction, while this is not useful for
415    /// CKB itself, it can be very helpful when building a CKB debugger.
416    pub fn verify_single(
417        &self,
418        script_group_type: ScriptGroupType,
419        script_hash: &Byte32,
420        max_cycles: Cycle,
421    ) -> Result<Cycle, ScriptError> {
422        match self.find_script_group(script_group_type, script_hash) {
423            Some(group) => self.verify_script_group(group, max_cycles),
424            None => Err(ScriptError::ScriptNotFound(script_hash.clone())),
425        }
426    }
427
428    fn verify_script_group(
429        &self,
430        group: &ScriptGroup,
431        max_cycles: Cycle,
432    ) -> Result<Cycle, ScriptError> {
433        if group.script.code_hash() == TYPE_ID_CODE_HASH.pack()
434            && Into::<u8>::into(group.script.hash_type()) == Into::<u8>::into(ScriptHashType::Type)
435        {
436            let verifier = TypeIdSystemScript {
437                rtx: &self.tx_data.rtx,
438                script_group: group,
439                max_cycles,
440            };
441            verifier.verify()
442        } else {
443            self.run(group, max_cycles)
444        }
445    }
446
447    fn verify_group_with_chunk(
448        &self,
449        group: &ScriptGroup,
450        max_cycles: Cycle,
451        state: &Option<FullSuspendedState>,
452    ) -> Result<ChunkState, ScriptError> {
453        if group.script.code_hash() == TYPE_ID_CODE_HASH.pack()
454            && Into::<u8>::into(group.script.hash_type()) == Into::<u8>::into(ScriptHashType::Type)
455        {
456            let verifier = TypeIdSystemScript {
457                rtx: &self.tx_data.rtx,
458                script_group: group,
459                max_cycles,
460            };
461            match verifier.verify() {
462                Ok(cycles) => Ok(ChunkState::Completed(cycles, cycles)),
463                Err(ScriptError::ExceededMaximumCycles(_)) => Ok(ChunkState::suspended_type_id()),
464                Err(e) => Err(e),
465            }
466        } else {
467            self.chunk_run(group, max_cycles, state)
468        }
469    }
470
471    fn chunk_run(
472        &self,
473        script_group: &ScriptGroup,
474        max_cycles: Cycle,
475        state: &Option<FullSuspendedState>,
476    ) -> Result<ChunkState, ScriptError> {
477        let mut scheduler = if let Some(state) = state {
478            self.resume_scheduler(script_group, state)
479        } else {
480            self.create_scheduler(script_group)
481        }?;
482        let previous_cycles = scheduler.consumed_cycles();
483        let res = scheduler.run(RunMode::LimitCycles(max_cycles));
484        match res {
485            Ok(TerminatedResult {
486                exit_code,
487                consumed_cycles: cycles,
488            }) => {
489                if exit_code == 0 {
490                    Ok(ChunkState::Completed(
491                        cycles,
492                        scheduler.consumed_cycles() - previous_cycles,
493                    ))
494                } else {
495                    Err(ScriptError::validation_failure(
496                        &script_group.script,
497                        exit_code,
498                    ))
499                }
500            }
501            Err(error) => match error {
502                VMInternalError::CyclesExceeded | VMInternalError::Pause => {
503                    let snapshot = scheduler
504                        .suspend()
505                        .map_err(|err| self.map_vm_internal_error(err, max_cycles))?;
506                    Ok(ChunkState::suspended(snapshot))
507                }
508                _ => Err(self.map_vm_internal_error(error, max_cycles)),
509            },
510        }
511    }
512
513    /// Create a scheduler to manage virtual machine instances.
514    pub fn create_scheduler(
515        &self,
516        script_group: &ScriptGroup,
517    ) -> Result<Scheduler<DL, V, M>, ScriptError> {
518        let sg_data = SgData::new(&self.tx_data, script_group)?;
519        Ok(Scheduler::new(
520            sg_data,
521            self.syscall_generator,
522            self.syscall_context.clone(),
523        ))
524    }
525
526    /// Resumes a scheduler from a previous state.
527    pub fn resume_scheduler(
528        &self,
529        script_group: &ScriptGroup,
530        state: &FullSuspendedState,
531    ) -> Result<Scheduler<DL, V, M>, ScriptError> {
532        let sg_data = SgData::new(&self.tx_data, script_group)?;
533        Ok(Scheduler::resume(
534            sg_data,
535            self.syscall_generator,
536            self.syscall_context.clone(),
537            state.clone(),
538        ))
539    }
540
541    /// Runs a single program, then returns the exit code together with the entire
542    /// machine to the caller for more inspections.
543    pub fn detailed_run(
544        &self,
545        script_group: &ScriptGroup,
546        max_cycles: Cycle,
547    ) -> Result<TerminatedResult, ScriptError> {
548        let mut scheduler = self.create_scheduler(script_group)?;
549        scheduler
550            .run(RunMode::LimitCycles(max_cycles))
551            .map_err(|err| self.map_vm_internal_error(err, max_cycles))
552    }
553
554    fn run(&self, script_group: &ScriptGroup, max_cycles: Cycle) -> Result<Cycle, ScriptError> {
555        let result = self.detailed_run(script_group, max_cycles)?;
556
557        if result.exit_code == 0 {
558            Ok(result.consumed_cycles)
559        } else {
560            Err(ScriptError::validation_failure(
561                &script_group.script,
562                result.exit_code,
563            ))
564        }
565    }
566
567    fn map_vm_internal_error(&self, error: VMInternalError, max_cycles: Cycle) -> ScriptError {
568        match error {
569            VMInternalError::CyclesExceeded => ScriptError::ExceededMaximumCycles(max_cycles),
570            VMInternalError::External(reason) if reason.eq("stopped") => ScriptError::Interrupts,
571            _ => ScriptError::VMInternalError(error),
572        }
573    }
574}
575
576#[cfg(not(target_family = "wasm"))]
577impl<DL, V, M> TransactionScriptsVerifier<DL, V, M>
578where
579    DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static,
580    V: Send + Clone + 'static,
581    M: DefaultMachineRunner + Send + 'static,
582{
583    /// Performing a resumable verification on the transaction scripts with signal channel,
584    /// if `Suspend` comes from `command_rx`, the process will be hang up until `Resume` comes,
585    /// otherwise, it will return until the verification is completed.
586    pub async fn resumable_verify_with_signal(
587        &self,
588        limit_cycles: Cycle,
589        command_rx: &mut Receiver<ChunkCommand>,
590    ) -> Result<Cycle, Error> {
591        let mut cycles = 0;
592
593        let groups: Vec<_> = self.groups().collect();
594        for (_hash, group) in groups.iter() {
595            // vm should early return invalid cycles
596            let remain_cycles = limit_cycles.checked_sub(cycles).ok_or_else(|| {
597                ScriptError::Other(format!("expect invalid cycles {limit_cycles} {cycles}"))
598                    .source(group)
599            })?;
600
601            match self
602                .verify_group_with_signal(group, remain_cycles, command_rx)
603                .await
604            {
605                Ok(used_cycles) => {
606                    cycles = wrapping_cycles_add(cycles, used_cycles, group)?;
607                }
608                Err(e) => {
609                    #[cfg(feature = "logging")]
610                    logging::on_script_error(_hash, &self.hash(), &e);
611                    return Err(e.source(group).into());
612                }
613            }
614        }
615
616        Ok(cycles)
617    }
618
619    async fn verify_group_with_signal(
620        &self,
621        group: &ScriptGroup,
622        max_cycles: Cycle,
623        command_rx: &mut Receiver<ChunkCommand>,
624    ) -> Result<Cycle, ScriptError> {
625        if group.script.code_hash() == TYPE_ID_CODE_HASH.pack()
626            && Into::<u8>::into(group.script.hash_type()) == Into::<u8>::into(ScriptHashType::Type)
627        {
628            let verifier = TypeIdSystemScript {
629                rtx: &self.tx_data.rtx,
630                script_group: group,
631                max_cycles,
632            };
633            verifier.verify()
634        } else {
635            self.chunk_run_with_signal(group, max_cycles, command_rx)
636                .await
637        }
638    }
639
640    async fn chunk_run_with_signal(
641        &self,
642        script_group: &ScriptGroup,
643        max_cycles: Cycle,
644        signal: &mut Receiver<ChunkCommand>,
645    ) -> Result<Cycle, ScriptError> {
646        let mut scheduler = self.create_scheduler(script_group)?;
647        let mut pause = VMPause::new();
648        let child_pause = pause.clone();
649        let (finish_tx, mut finish_rx) =
650            oneshot::channel::<Result<TerminatedResult, ckb_vm::Error>>();
651
652        // send initial `Resume` command to child
653        // it's maybe useful to set initial command to `signal.borrow().to_owned()`
654        // so that we can control the initial state of child, which is useful for testing purpose
655        let (child_tx, mut child_rx) = watch::channel(ChunkCommand::Resume);
656        let jh = tokio::spawn(async move {
657            child_rx.mark_changed();
658            loop {
659                let pause_cloned = child_pause.clone();
660                let _ = child_rx.changed().await;
661                match *child_rx.borrow() {
662                    ChunkCommand::Stop => {
663                        let exit = Err(ckb_vm::Error::External("stopped".into()));
664                        let _ = finish_tx.send(exit);
665                        return;
666                    }
667                    ChunkCommand::Suspend => {
668                        continue;
669                    }
670                    ChunkCommand::Resume => {
671                        //info!("[verify-test] run_vms_child: resume");
672                        let res = scheduler.run(RunMode::Pause(pause_cloned, max_cycles));
673                        match res {
674                            Ok(_) => {
675                                let _ = finish_tx.send(res);
676                                return;
677                            }
678                            Err(VMInternalError::Pause) => {
679                                // continue to wait for
680                                debug_assert!(
681                                    scheduler.consumed_cycles() <= max_cycles,
682                                    "Consumed cycles ({}) exceeded max_cycles ({})",
683                                    scheduler.consumed_cycles(),
684                                    max_cycles
685                                );
686                            }
687                            _ => {
688                                let _ = finish_tx.send(res);
689                                return;
690                            }
691                        }
692                    }
693                }
694            }
695        });
696
697        loop {
698            tokio::select! {
699                Ok(_) = signal.changed() => {
700                    let command = signal.borrow().to_owned();
701                    //info!("[verify-test] run_vms_with_signal: {:?}", command);
702                    match command {
703                        ChunkCommand::Suspend => {
704                            pause.interrupt();
705                        }
706                        ChunkCommand::Stop => {
707                            pause.interrupt();
708                            let _ = child_tx.send(command);
709                        }
710                        ChunkCommand::Resume => {
711                            pause.free();
712                            let _ = child_tx.send(command);
713                        }
714                    }
715                }
716                Ok(res) = &mut finish_rx => {
717                    let _ = jh.await;
718                    match res {
719                        Ok(TerminatedResult {
720                            exit_code: 0,
721                            consumed_cycles: cycles,
722                        }) => {
723                            return Ok(cycles);
724                        }
725                        Ok(TerminatedResult { exit_code, .. }) => {
726                            return Err(ScriptError::validation_failure(
727                                &script_group.script,
728                                exit_code
729                            ))},
730                        Err(err) => {
731                            return Err(self.map_vm_internal_error(err, max_cycles));
732                        }
733                    }
734
735                }
736                else => { break Err(ScriptError::validation_failure(&script_group.script, 0)) }
737            }
738        }
739    }
740}
741
742fn wrapping_cycles_add(
743    lhs: Cycle,
744    rhs: Cycle,
745    group: &ScriptGroup,
746) -> Result<Cycle, TransactionScriptError> {
747    lhs.checked_add(rhs)
748        .ok_or_else(|| ScriptError::CyclesOverflow(lhs, rhs).source(group))
749}
750
751#[cfg(feature = "logging")]
752mod logging {
753    use super::{Byte32, ScriptError, info};
754
755    pub fn on_script_error(group: &Byte32, tx: &Byte32, error: &ScriptError) {
756        info!(
757            "Error validating script group {} of transaction {}: {}",
758            group, tx, error
759        );
760    }
761}