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, Machine, RunMode, ScriptGroup, ScriptGroupType, ScriptVersion, SgData,
10 SyscallGenerator, TerminatedResult, TxData,
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};
24#[cfg(not(target_family = "wasm"))]
25use ckb_vm::machine::Pause as VMPause;
26use ckb_vm::{DefaultMachineRunner, Error as VMInternalError};
27use std::sync::Arc;
28#[cfg(not(target_family = "wasm"))]
29use tokio::sync::{
30 oneshot,
31 watch::{self, Receiver},
32};
33
34#[cfg(test)]
35mod tests;
36
37pub struct TransactionScriptsVerifier<
39 DL: CellDataProvider,
40 V = DebugPrinter,
41 M: DefaultMachineRunner = Machine,
42> {
43 tx_data: Arc<TxData<DL>>,
44 syscall_generator: SyscallGenerator<DL, V, <M as DefaultMachineRunner>::Inner>,
45 syscall_context: V,
46}
47
48impl<DL> TransactionScriptsVerifier<DL>
49where
50 DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static,
51{
52 pub fn new(
54 rtx: Arc<ResolvedTransaction>,
55 data_loader: DL,
56 consensus: Arc<Consensus>,
57 tx_env: Arc<TxVerifyEnv>,
58 ) -> Self {
59 let debug_printer: DebugPrinter = Arc::new(
60 #[allow(unused_variables)]
61 |hash: &Byte32, message: &str| {
62 #[cfg(feature = "logging")]
63 debug!("script group: {} DEBUG OUTPUT: {}", hash, message);
64 },
65 );
66
67 Self::new_with_debug_printer(rtx, data_loader, consensus, tx_env, debug_printer)
68 }
69
70 pub fn new_with_debug_printer(
72 rtx: Arc<ResolvedTransaction>,
73 data_loader: DL,
74 consensus: Arc<Consensus>,
75 tx_env: Arc<TxVerifyEnv>,
76 debug_printer: DebugPrinter,
77 ) -> Self {
78 Self::new_with_generator(
79 rtx,
80 data_loader,
81 consensus,
82 tx_env,
83 generate_ckb_syscalls,
84 debug_printer,
85 )
86 }
87}
88
89impl<DL, V, M> TransactionScriptsVerifier<DL, V, M>
90where
91 DL: CellDataProvider + HeaderProvider + ExtensionProvider + Clone,
92 V: Clone,
93 M: DefaultMachineRunner,
94{
95 pub fn new_with_generator(
106 rtx: Arc<ResolvedTransaction>,
107 data_loader: DL,
108 consensus: Arc<Consensus>,
109 tx_env: Arc<TxVerifyEnv>,
110 syscall_generator: SyscallGenerator<DL, V, <M as DefaultMachineRunner>::Inner>,
111 syscall_context: V,
112 ) -> TransactionScriptsVerifier<DL, V, M> {
113 let tx_data = Arc::new(TxData::new(rtx, data_loader, consensus, tx_env));
114
115 TransactionScriptsVerifier {
116 tx_data,
117 syscall_generator,
118 syscall_context,
119 }
120 }
121
122 #[inline]
129 #[allow(dead_code)]
130 fn hash(&self) -> Byte32 {
131 self.tx_data.tx_hash()
132 }
133
134 pub fn extract_script(&self, script: &Script) -> Result<Bytes, ScriptError> {
136 self.tx_data.extract_script(script)
137 }
138
139 pub fn select_version(&self, script: &Script) -> Result<ScriptVersion, ScriptError> {
141 self.tx_data.select_version(script)
142 }
143
144 pub fn groups(&self) -> impl Iterator<Item = (&'_ Byte32, &'_ ScriptGroup)> {
146 self.tx_data.groups()
147 }
148
149 pub fn groups_with_type(
151 &self,
152 ) -> impl Iterator<Item = (ScriptGroupType, &'_ Byte32, &'_ ScriptGroup)> {
153 self.tx_data.groups_with_type()
154 }
155
156 pub fn find_script_group(
158 &self,
159 script_group_type: ScriptGroupType,
160 script_hash: &Byte32,
161 ) -> Option<&ScriptGroup> {
162 self.tx_data
163 .find_script_group(script_group_type, script_hash)
164 }
165
166 pub fn verify(&self, max_cycles: Cycle) -> Result<Cycle, Error> {
181 let mut cycles: Cycle = 0;
182
183 for (_hash, group) in self.groups() {
185 let used_cycles = self
187 .verify_script_group(group, max_cycles - cycles)
188 .map_err(|e| {
189 #[cfg(feature = "logging")]
190 logging::on_script_error(_hash, &self.hash(), &e);
191 e.source(group)
192 })?;
193
194 cycles = wrapping_cycles_add(cycles, used_cycles, group)?;
195 }
196 Ok(cycles)
197 }
198
199 pub fn verify_single(
202 &self,
203 script_group_type: ScriptGroupType,
204 script_hash: &Byte32,
205 max_cycles: Cycle,
206 ) -> Result<Cycle, ScriptError> {
207 match self.find_script_group(script_group_type, script_hash) {
208 Some(group) => self.verify_script_group(group, max_cycles),
209 None => Err(ScriptError::ScriptNotFound(script_hash.clone())),
210 }
211 }
212
213 fn verify_script_group(
214 &self,
215 group: &ScriptGroup,
216 max_cycles: Cycle,
217 ) -> Result<Cycle, ScriptError> {
218 if group.script.code_hash() == TYPE_ID_CODE_HASH.into()
219 && Into::<u8>::into(group.script.hash_type()) == Into::<u8>::into(ScriptHashType::Type)
220 {
221 let verifier = TypeIdSystemScript {
222 rtx: &self.tx_data.rtx,
223 script_group: group,
224 max_cycles,
225 };
226 verifier.verify()
227 } else {
228 self.run(group, max_cycles)
229 }
230 }
231
232 pub fn create_scheduler(
234 &self,
235 script_group: &ScriptGroup,
236 ) -> Result<Scheduler<DL, V, M>, ScriptError> {
237 let sg_data = SgData::new(&self.tx_data, script_group)?;
238 Ok(Scheduler::new(
239 sg_data,
240 self.syscall_generator,
241 self.syscall_context.clone(),
242 ))
243 }
244
245 pub fn detailed_run(
248 &self,
249 script_group: &ScriptGroup,
250 max_cycles: Cycle,
251 ) -> Result<TerminatedResult, ScriptError> {
252 let mut scheduler = self.create_scheduler(script_group)?;
253 scheduler
254 .run(RunMode::LimitCycles(max_cycles))
255 .map_err(|err| self.map_vm_internal_error(err, max_cycles))
256 }
257
258 fn run(&self, script_group: &ScriptGroup, max_cycles: Cycle) -> Result<Cycle, ScriptError> {
259 let result = self.detailed_run(script_group, max_cycles)?;
260
261 if result.exit_code == 0 {
262 Ok(result.consumed_cycles)
263 } else {
264 Err(ScriptError::validation_failure(
265 &script_group.script,
266 result.exit_code,
267 ))
268 }
269 }
270
271 fn map_vm_internal_error(&self, error: VMInternalError, max_cycles: Cycle) -> ScriptError {
272 match error {
273 VMInternalError::CyclesExceeded => ScriptError::ExceededMaximumCycles(max_cycles),
274 VMInternalError::External(reason) if reason.eq("stopped") => ScriptError::Interrupts,
275 _ => ScriptError::VMInternalError(error),
276 }
277 }
278}
279
280#[cfg(not(target_family = "wasm"))]
281impl<DL, V, M> TransactionScriptsVerifier<DL, V, M>
282where
283 DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static,
284 V: Send + Clone + 'static,
285 M: DefaultMachineRunner + Send + 'static,
286{
287 pub async fn resumable_verify_with_signal(
291 &self,
292 limit_cycles: Cycle,
293 command_rx: &mut Receiver<ChunkCommand>,
294 ) -> Result<Cycle, Error> {
295 let mut cycles = 0;
296
297 let groups: Vec<_> = self.groups().collect();
298 for (_hash, group) in groups.iter() {
299 let remain_cycles = limit_cycles.checked_sub(cycles).ok_or_else(|| {
301 ScriptError::Other(format!("expect invalid cycles {limit_cycles} {cycles}"))
302 .source(group)
303 })?;
304
305 match self
306 .verify_group_with_signal(group, remain_cycles, command_rx)
307 .await
308 {
309 Ok(used_cycles) => {
310 cycles = wrapping_cycles_add(cycles, used_cycles, group)?;
311 }
312 Err(e) => {
313 #[cfg(feature = "logging")]
314 logging::on_script_error(_hash, &self.hash(), &e);
315 return Err(e.source(group).into());
316 }
317 }
318 }
319
320 Ok(cycles)
321 }
322
323 async fn verify_group_with_signal(
324 &self,
325 group: &ScriptGroup,
326 max_cycles: Cycle,
327 command_rx: &mut Receiver<ChunkCommand>,
328 ) -> Result<Cycle, ScriptError> {
329 if group.script.code_hash() == TYPE_ID_CODE_HASH.into()
330 && Into::<u8>::into(group.script.hash_type()) == Into::<u8>::into(ScriptHashType::Type)
331 {
332 let verifier = TypeIdSystemScript {
333 rtx: &self.tx_data.rtx,
334 script_group: group,
335 max_cycles,
336 };
337 verifier.verify()
338 } else {
339 self.chunk_run_with_signal(group, max_cycles, command_rx)
340 .await
341 }
342 }
343
344 async fn chunk_run_with_signal(
345 &self,
346 script_group: &ScriptGroup,
347 max_cycles: Cycle,
348 signal: &mut Receiver<ChunkCommand>,
349 ) -> Result<Cycle, ScriptError> {
350 let mut scheduler = self.create_scheduler(script_group)?;
351 let mut pause = VMPause::new();
352 let child_pause = pause.clone();
353 let (finish_tx, mut finish_rx) =
354 oneshot::channel::<Result<TerminatedResult, ckb_vm::Error>>();
355
356 let (child_tx, mut child_rx) = watch::channel(ChunkCommand::Resume);
360 let jh = tokio::spawn(async move {
361 child_rx.mark_changed();
362 loop {
363 let pause_cloned = child_pause.clone();
364 let _ = child_rx.changed().await;
365 match *child_rx.borrow() {
366 ChunkCommand::Stop => {
367 let exit = Err(ckb_vm::Error::External("stopped".into()));
368 let _ = finish_tx.send(exit);
369 return;
370 }
371 ChunkCommand::Suspend => {
372 continue;
373 }
374 ChunkCommand::Resume => {
375 let res = scheduler.run(RunMode::Pause(pause_cloned, max_cycles));
377 match res {
378 Ok(_) => {
379 let _ = finish_tx.send(res);
380 return;
381 }
382 Err(VMInternalError::Pause) => {
383 debug_assert!(
385 scheduler.consumed_cycles() <= max_cycles,
386 "Consumed cycles ({}) exceeded max_cycles ({})",
387 scheduler.consumed_cycles(),
388 max_cycles
389 );
390 }
391 _ => {
392 let _ = finish_tx.send(res);
393 return;
394 }
395 }
396 }
397 }
398 }
399 });
400
401 loop {
402 tokio::select! {
403 Ok(_) = signal.changed() => {
404 let command = signal.borrow().to_owned();
405 match command {
407 ChunkCommand::Suspend => {
408 pause.interrupt();
409 }
410 ChunkCommand::Stop => {
411 pause.interrupt();
412 let _ = child_tx.send(command);
413 }
414 ChunkCommand::Resume => {
415 pause.free();
416 let _ = child_tx.send(command);
417 }
418 }
419 }
420 Ok(res) = &mut finish_rx => {
421 let _ = jh.await;
422 match res {
423 Ok(TerminatedResult {
424 exit_code: 0,
425 consumed_cycles: cycles,
426 }) => {
427 return Ok(cycles);
428 }
429 Ok(TerminatedResult { exit_code, .. }) => {
430 return Err(ScriptError::validation_failure(
431 &script_group.script,
432 exit_code
433 ))},
434 Err(err) => {
435 return Err(self.map_vm_internal_error(err, max_cycles));
436 }
437 }
438
439 }
440 else => { break Err(ScriptError::validation_failure(&script_group.script, 0)) }
441 }
442 }
443 }
444}
445
446fn wrapping_cycles_add(
447 lhs: Cycle,
448 rhs: Cycle,
449 group: &ScriptGroup,
450) -> Result<Cycle, TransactionScriptError> {
451 lhs.checked_add(rhs)
452 .ok_or_else(|| ScriptError::CyclesOverflow(lhs, rhs).source(group))
453}
454
455#[cfg(feature = "logging")]
456mod logging {
457 use super::{Byte32, ScriptError, info};
458
459 pub fn on_script_error(group: &Byte32, tx: &Byte32, error: &ScriptError) {
460 info!(
461 "Error validating script group {} of transaction {}: {}",
462 group, tx, error
463 );
464 }
465}