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