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 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.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 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 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 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 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 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 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 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 }
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 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}