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 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
55pub 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 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 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 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 #[inline]
147 #[allow(dead_code)]
148 fn hash(&self) -> Byte32 {
149 self.tx_data.tx_hash()
150 }
151
152 pub fn extract_script(&self, script: &Script) -> Result<Bytes, ScriptError> {
154 self.tx_data.extract_script(script)
155 }
156
157 pub fn select_version(&self, script: &Script) -> Result<ScriptVersion, ScriptError> {
159 self.tx_data.select_version(script)
160 }
161
162 pub fn groups(&self) -> impl Iterator<Item = (&'_ Byte32, &'_ ScriptGroup)> {
164 self.tx_data.groups()
165 }
166
167 pub fn groups_with_type(
169 &self,
170 ) -> impl Iterator<Item = (ScriptGroupType, &'_ Byte32, &'_ ScriptGroup)> {
171 self.tx_data.groups_with_type()
172 }
173
174 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 pub fn verify(&self, max_cycles: Cycle) -> Result<Cycle, Error> {
199 let mut cycles: Cycle = 0;
200
201 for (_hash, group) in self.groups() {
203 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}