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};
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 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
54pub 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 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 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 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 #[inline]
146 #[allow(dead_code)]
147 fn hash(&self) -> Byte32 {
148 self.tx_data.tx_hash()
149 }
150
151 pub fn extract_script(&self, script: &Script) -> Result<Bytes, ScriptError> {
153 self.tx_data.extract_script(script)
154 }
155
156 pub fn select_version(&self, script: &Script) -> Result<ScriptVersion, ScriptError> {
158 self.tx_data.select_version(script)
159 }
160
161 pub fn groups(&self) -> impl Iterator<Item = (&'_ Byte32, &'_ ScriptGroup)> {
163 self.tx_data.groups()
164 }
165
166 pub fn groups_with_type(
168 &self,
169 ) -> impl Iterator<Item = (ScriptGroupType, &'_ Byte32, &'_ ScriptGroup)> {
170 self.tx_data.groups_with_type()
171 }
172
173 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 pub fn verify(&self, max_cycles: Cycle) -> Result<Cycle, Error> {
198 let mut cycles: Cycle = 0;
199
200 for (_hash, group) in self.groups() {
202 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 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 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 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 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 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 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.into()
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.into()
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(TerminatedResult {
485 exit_code,
486 consumed_cycles: cycles,
487 }) => {
488 if exit_code == 0 {
489 Ok(ChunkState::Completed(
490 cycles,
491 scheduler.consumed_cycles() - previous_cycles,
492 ))
493 } else {
494 Err(ScriptError::validation_failure(
495 &script_group.script,
496 exit_code,
497 ))
498 }
499 }
500 Err(error) => match error {
501 VMInternalError::CyclesExceeded | VMInternalError::Pause => {
502 let snapshot = scheduler
503 .suspend()
504 .map_err(|err| self.map_vm_internal_error(err, max_cycles))?;
505 Ok(ChunkState::suspended(snapshot))
506 }
507 _ => Err(self.map_vm_internal_error(error, max_cycles)),
508 },
509 }
510 }
511
512 pub fn create_scheduler(
514 &self,
515 script_group: &ScriptGroup,
516 ) -> Result<Scheduler<DL, V, M>, ScriptError> {
517 let sg_data = SgData::new(&self.tx_data, script_group)?;
518 Ok(Scheduler::new(
519 sg_data,
520 self.syscall_generator,
521 self.syscall_context.clone(),
522 ))
523 }
524
525 pub fn resume_scheduler(
527 &self,
528 script_group: &ScriptGroup,
529 state: &FullSuspendedState,
530 ) -> Result<Scheduler<DL, V, M>, ScriptError> {
531 let sg_data = SgData::new(&self.tx_data, script_group)?;
532 Ok(Scheduler::resume(
533 sg_data,
534 self.syscall_generator,
535 self.syscall_context.clone(),
536 state.clone(),
537 ))
538 }
539
540 pub fn detailed_run(
543 &self,
544 script_group: &ScriptGroup,
545 max_cycles: Cycle,
546 ) -> Result<TerminatedResult, ScriptError> {
547 let mut scheduler = self.create_scheduler(script_group)?;
548 scheduler
549 .run(RunMode::LimitCycles(max_cycles))
550 .map_err(|err| self.map_vm_internal_error(err, max_cycles))
551 }
552
553 fn run(&self, script_group: &ScriptGroup, max_cycles: Cycle) -> Result<Cycle, ScriptError> {
554 let result = self.detailed_run(script_group, max_cycles)?;
555
556 if result.exit_code == 0 {
557 Ok(result.consumed_cycles)
558 } else {
559 Err(ScriptError::validation_failure(
560 &script_group.script,
561 result.exit_code,
562 ))
563 }
564 }
565
566 fn map_vm_internal_error(&self, error: VMInternalError, max_cycles: Cycle) -> ScriptError {
567 match error {
568 VMInternalError::CyclesExceeded => ScriptError::ExceededMaximumCycles(max_cycles),
569 VMInternalError::External(reason) if reason.eq("stopped") => ScriptError::Interrupts,
570 _ => ScriptError::VMInternalError(error),
571 }
572 }
573}
574
575#[cfg(not(target_family = "wasm"))]
576impl<DL, V, M> TransactionScriptsVerifier<DL, V, M>
577where
578 DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static,
579 V: Send + Clone + 'static,
580 M: DefaultMachineRunner + Send + 'static,
581{
582 pub async fn resumable_verify_with_signal(
586 &self,
587 limit_cycles: Cycle,
588 command_rx: &mut Receiver<ChunkCommand>,
589 ) -> Result<Cycle, Error> {
590 let mut cycles = 0;
591
592 let groups: Vec<_> = self.groups().collect();
593 for (_hash, group) in groups.iter() {
594 let remain_cycles = limit_cycles.checked_sub(cycles).ok_or_else(|| {
596 ScriptError::Other(format!("expect invalid cycles {limit_cycles} {cycles}"))
597 .source(group)
598 })?;
599
600 match self
601 .verify_group_with_signal(group, remain_cycles, command_rx)
602 .await
603 {
604 Ok(used_cycles) => {
605 cycles = wrapping_cycles_add(cycles, used_cycles, group)?;
606 }
607 Err(e) => {
608 #[cfg(feature = "logging")]
609 logging::on_script_error(_hash, &self.hash(), &e);
610 return Err(e.source(group).into());
611 }
612 }
613 }
614
615 Ok(cycles)
616 }
617
618 async fn verify_group_with_signal(
619 &self,
620 group: &ScriptGroup,
621 max_cycles: Cycle,
622 command_rx: &mut Receiver<ChunkCommand>,
623 ) -> Result<Cycle, ScriptError> {
624 if group.script.code_hash() == TYPE_ID_CODE_HASH.into()
625 && Into::<u8>::into(group.script.hash_type()) == Into::<u8>::into(ScriptHashType::Type)
626 {
627 let verifier = TypeIdSystemScript {
628 rtx: &self.tx_data.rtx,
629 script_group: group,
630 max_cycles,
631 };
632 verifier.verify()
633 } else {
634 self.chunk_run_with_signal(group, max_cycles, command_rx)
635 .await
636 }
637 }
638
639 async fn chunk_run_with_signal(
640 &self,
641 script_group: &ScriptGroup,
642 max_cycles: Cycle,
643 signal: &mut Receiver<ChunkCommand>,
644 ) -> Result<Cycle, ScriptError> {
645 let mut scheduler = self.create_scheduler(script_group)?;
646 let mut pause = VMPause::new();
647 let child_pause = pause.clone();
648 let (finish_tx, mut finish_rx) =
649 oneshot::channel::<Result<TerminatedResult, ckb_vm::Error>>();
650
651 let (child_tx, mut child_rx) = watch::channel(ChunkCommand::Resume);
655 let jh = tokio::spawn(async move {
656 child_rx.mark_changed();
657 loop {
658 let pause_cloned = child_pause.clone();
659 let _ = child_rx.changed().await;
660 match *child_rx.borrow() {
661 ChunkCommand::Stop => {
662 let exit = Err(ckb_vm::Error::External("stopped".into()));
663 let _ = finish_tx.send(exit);
664 return;
665 }
666 ChunkCommand::Suspend => {
667 continue;
668 }
669 ChunkCommand::Resume => {
670 let res = scheduler.run(RunMode::Pause(pause_cloned, max_cycles));
672 match res {
673 Ok(_) => {
674 let _ = finish_tx.send(res);
675 return;
676 }
677 Err(VMInternalError::Pause) => {
678 debug_assert!(
680 scheduler.consumed_cycles() <= max_cycles,
681 "Consumed cycles ({}) exceeded max_cycles ({})",
682 scheduler.consumed_cycles(),
683 max_cycles
684 );
685 }
686 _ => {
687 let _ = finish_tx.send(res);
688 return;
689 }
690 }
691 }
692 }
693 }
694 });
695
696 loop {
697 tokio::select! {
698 Ok(_) = signal.changed() => {
699 let command = signal.borrow().to_owned();
700 match command {
702 ChunkCommand::Suspend => {
703 pause.interrupt();
704 }
705 ChunkCommand::Stop => {
706 pause.interrupt();
707 let _ = child_tx.send(command);
708 }
709 ChunkCommand::Resume => {
710 pause.free();
711 let _ = child_tx.send(command);
712 }
713 }
714 }
715 Ok(res) = &mut finish_rx => {
716 let _ = jh.await;
717 match res {
718 Ok(TerminatedResult {
719 exit_code: 0,
720 consumed_cycles: cycles,
721 }) => {
722 return Ok(cycles);
723 }
724 Ok(TerminatedResult { exit_code, .. }) => {
725 return Err(ScriptError::validation_failure(
726 &script_group.script,
727 exit_code
728 ))},
729 Err(err) => {
730 return Err(self.map_vm_internal_error(err, max_cycles));
731 }
732 }
733
734 }
735 else => { break Err(ScriptError::validation_failure(&script_group.script, 0)) }
736 }
737 }
738 }
739}
740
741fn wrapping_cycles_add(
742 lhs: Cycle,
743 rhs: Cycle,
744 group: &ScriptGroup,
745) -> Result<Cycle, TransactionScriptError> {
746 lhs.checked_add(rhs)
747 .ok_or_else(|| ScriptError::CyclesOverflow(lhs, rhs).source(group))
748}
749
750#[cfg(feature = "logging")]
751mod logging {
752 use super::{Byte32, ScriptError, info};
753
754 pub fn on_script_error(group: &Byte32, tx: &Byte32, error: &ScriptError) {
755 info!(
756 "Error validating script group {} of transaction {}: {}",
757 group, tx, error
758 );
759 }
760}