Skip to main content

ckb_verification/
transaction_verifier.rs

1use crate::cache::Completed;
2use crate::error::TransactionErrorSource;
3use crate::{TransactionError, TxVerifyEnv};
4use ckb_chain_spec::consensus::Consensus;
5use ckb_constant::consensus::ENABLED_SCRIPT_HASH_TYPE;
6use ckb_dao::DaoCalculator;
7use ckb_dao_utils::DaoError;
8use ckb_error::Error;
9#[cfg(not(target_family = "wasm"))]
10use ckb_script::ChunkCommand;
11use ckb_script::{ScriptError, TransactionScriptsVerifier};
12use ckb_traits::{
13    CellDataProvider, EpochProvider, ExtensionProvider, HeaderFieldsProvider, HeaderProvider,
14};
15use ckb_types::{
16    core::{
17        Capacity, Cycle, EpochNumberWithFraction, ScriptHashType, TransactionView, Version,
18        cell::{CellMeta, ResolvedTransaction},
19    },
20    packed::{Byte32, CellOutput},
21};
22use std::collections::HashSet;
23use std::sync::Arc;
24
25/// The time-related TX verification
26///
27/// Contains:
28/// [`MaturityVerifier`](./struct.MaturityVerifier.html)
29/// [`SinceVerifier`](./struct.SinceVerifier.html)
30pub struct TimeRelativeTransactionVerifier<M> {
31    pub(crate) maturity: MaturityVerifier,
32    pub(crate) since: SinceVerifier<M>,
33}
34
35impl<DL: HeaderFieldsProvider> TimeRelativeTransactionVerifier<DL> {
36    /// Creates a new TimeRelativeTransactionVerifier
37    pub fn new(
38        rtx: Arc<ResolvedTransaction>,
39        consensus: Arc<Consensus>,
40        data_loader: DL,
41        tx_env: Arc<TxVerifyEnv>,
42    ) -> Self {
43        TimeRelativeTransactionVerifier {
44            maturity: MaturityVerifier::new(
45                Arc::clone(&rtx),
46                tx_env.epoch(),
47                consensus.cellbase_maturity(),
48            ),
49            since: SinceVerifier::new(rtx, consensus, data_loader, tx_env),
50        }
51    }
52
53    /// Perform time-related verification
54    pub fn verify(&self) -> Result<(), Error> {
55        self.maturity.verify()?;
56        self.since.verify()?;
57        Ok(())
58    }
59}
60
61/// Context-independent verification checks for transaction
62///
63/// Basic checks that don't depend on any context
64/// Contains:
65/// - Check for version
66/// - Check for size
67/// - Check inputs and output empty
68/// - Check for duplicate deps
69/// - Check for whether outputs match data
70/// - Check whether output lock hash type within enabled range
71pub struct NonContextualTransactionVerifier<'a> {
72    pub(crate) version: VersionVerifier<'a>,
73    pub(crate) size: SizeVerifier<'a>,
74    pub(crate) empty: EmptyVerifier<'a>,
75    pub(crate) duplicate_deps: DuplicateDepsVerifier<'a>,
76    pub(crate) outputs_data_verifier: OutputsDataVerifier<'a>,
77    pub(crate) script_hash_type: ScriptHashTypeVerifier<'a>,
78}
79
80impl<'a> NonContextualTransactionVerifier<'a> {
81    /// Creates a new NonContextualTransactionVerifier
82    pub fn new(tx: &'a TransactionView, consensus: &'a Consensus) -> Self {
83        NonContextualTransactionVerifier {
84            version: VersionVerifier::new(tx, consensus.tx_version()),
85            size: SizeVerifier::new(tx, consensus.max_block_bytes()),
86            empty: EmptyVerifier::new(tx),
87            duplicate_deps: DuplicateDepsVerifier::new(tx),
88            outputs_data_verifier: OutputsDataVerifier::new(tx),
89            script_hash_type: ScriptHashTypeVerifier::new(tx),
90        }
91    }
92
93    /// Perform context-independent verification
94    pub fn verify(&self) -> Result<(), Error> {
95        self.version.verify()?;
96        self.size.verify()?;
97        self.empty.verify()?;
98        self.duplicate_deps.verify()?;
99        self.outputs_data_verifier.verify()?;
100        self.script_hash_type.verify()?;
101        Ok(())
102    }
103}
104
105/// Script verification that can reuse cycles from a prior successful verification.
106struct CachedScriptVerifier<DL>
107where
108    DL: Send + Sync + Clone + CellDataProvider + HeaderProvider + ExtensionProvider + 'static,
109{
110    inner: ScriptVerifier<DL>,
111    cached_cycles: Option<Cycle>,
112}
113
114impl<DL> CachedScriptVerifier<DL>
115where
116    DL: CellDataProvider + HeaderProvider + ExtensionProvider + Send + Sync + Clone + 'static,
117{
118    fn new(
119        rtx: Arc<ResolvedTransaction>,
120        data_loader: DL,
121        consensus: Arc<Consensus>,
122        tx_env: Arc<TxVerifyEnv>,
123        cached_cycles: Option<Cycle>,
124    ) -> Self {
125        Self {
126            inner: TransactionScriptsVerifier::new(rtx, data_loader, consensus, tx_env),
127            cached_cycles,
128        }
129    }
130
131    fn verify(&self, max_cycles: Cycle) -> Result<Cycle, Error> {
132        match self.cached_cycles {
133            Some(cycles) if cycles <= max_cycles => Ok(cycles),
134            Some(_) => Err(ScriptError::ExceededMaximumCycles(max_cycles)
135                .unknown_source()
136                .into()),
137            None => self.inner.verify(max_cycles),
138        }
139    }
140
141    #[cfg(not(target_family = "wasm"))]
142    async fn resumable_verify_with_signal(
143        &self,
144        max_cycles: Cycle,
145        command_rx: &mut tokio::sync::watch::Receiver<ChunkCommand>,
146    ) -> Result<Cycle, Error> {
147        match self.cached_cycles {
148            Some(cycles) if cycles <= max_cycles => Ok(cycles),
149            Some(_) => Err(ScriptError::ExceededMaximumCycles(max_cycles)
150                .unknown_source()
151                .into()),
152            None => {
153                self.inner
154                    .resumable_verify_with_signal(max_cycles, command_rx)
155                    .await
156            }
157        }
158    }
159}
160
161/// Context-dependent verification checks for transaction
162///
163/// Contains:
164/// [`TimeRelativeTransactionVerifier`](./struct.TimeRelativeTransactionVerifier.html)
165/// [`CapacityVerifier`](./struct.CapacityVerifier.html)
166/// [`ScriptVerifier`](./struct.ScriptVerifier.html)
167/// [`FeeCalculator`](./struct.FeeCalculator.html)
168pub struct ContextualTransactionVerifier<DL>
169where
170    DL: Send + Sync + Clone + CellDataProvider + HeaderProvider + ExtensionProvider + 'static,
171{
172    pub(crate) time_relative: TimeRelativeTransactionVerifier<DL>,
173    pub(crate) capacity: CapacityVerifier,
174    script: CachedScriptVerifier<DL>,
175    pub(crate) fee_calculator: FeeCalculator<DL>,
176}
177
178impl<DL> ContextualTransactionVerifier<DL>
179where
180    DL: CellDataProvider
181        + HeaderProvider
182        + ExtensionProvider
183        + HeaderFieldsProvider
184        + EpochProvider
185        + Send
186        + Sync
187        + Clone
188        + 'static,
189{
190    /// Creates a new ContextualTransactionVerifier
191    pub fn new(
192        rtx: Arc<ResolvedTransaction>,
193        consensus: Arc<Consensus>,
194        data_loader: DL,
195        tx_env: Arc<TxVerifyEnv>,
196    ) -> Self {
197        Self::new_with_cached_script_cycles(rtx, consensus, data_loader, tx_env, None)
198    }
199
200    /// Creates a new ContextualTransactionVerifier with optional cached script cycles.
201    pub fn new_with_cached_script_cycles(
202        rtx: Arc<ResolvedTransaction>,
203        consensus: Arc<Consensus>,
204        data_loader: DL,
205        tx_env: Arc<TxVerifyEnv>,
206        cached_script_cycles: Option<Cycle>,
207    ) -> Self {
208        ContextualTransactionVerifier {
209            time_relative: TimeRelativeTransactionVerifier::new(
210                Arc::clone(&rtx),
211                Arc::clone(&consensus),
212                data_loader.clone(),
213                Arc::clone(&tx_env),
214            ),
215            script: CachedScriptVerifier::new(
216                Arc::clone(&rtx),
217                data_loader.clone(),
218                Arc::clone(&consensus),
219                Arc::clone(&tx_env),
220                cached_script_cycles,
221            ),
222            capacity: CapacityVerifier::new(Arc::clone(&rtx), consensus.dao_type_hash()),
223            fee_calculator: FeeCalculator::new(rtx, consensus, data_loader),
224        }
225    }
226
227    /// Perform context-dependent verification.
228    ///
229    /// skip script verify will result in the return value cycle always is zero
230    pub fn verify(&self, max_cycles: Cycle, skip_script_verify: bool) -> Result<Completed, Error> {
231        self.time_relative.verify()?;
232        self.capacity.verify()?;
233        let cycles = if skip_script_verify {
234            0
235        } else {
236            self.script.verify(max_cycles)?
237        };
238        let fee = self.fee_calculator.transaction_fee()?;
239        Ok(Completed { cycles, fee })
240    }
241
242    /// Perform context-dependent verification with command
243    /// The verification will be interrupted when receiving a Suspend command
244    #[cfg(not(target_family = "wasm"))]
245    pub async fn verify_with_pause(
246        &self,
247        max_cycles: Cycle,
248        command_rx: &mut tokio::sync::watch::Receiver<ChunkCommand>,
249    ) -> Result<Completed, Error> {
250        self.time_relative.verify()?;
251        self.capacity.verify()?;
252        let fee = self.fee_calculator.transaction_fee()?;
253        let cycles = self
254            .script
255            .resumable_verify_with_signal(max_cycles, command_rx)
256            .await?;
257        Ok(Completed { cycles, fee })
258    }
259}
260
261// /// Full tx verification checks
262// ///
263// /// Contains:
264// /// [`NonContextualTransactionVerifier`](./struct.NonContextualTransactionVerifier.html)
265// /// [`ContextualTransactionVerifier`](./struct.ContextualTransactionVerifier.html)
266// pub struct TransactionVerifier<'a, DL> {
267//     pub(crate) non_contextual: NonContextualTransactionVerifier<'a>,
268//     pub(crate) contextual: ContextualTransactionVerifier<'a, DL>,
269// }
270
271// impl<'a, DL: HeaderProvider + CellDataProvider + EpochProvider + Send + Sync + Clone + 'static>
272//     TransactionVerifier<'a, DL>
273// {
274//     /// Creates a new TransactionVerifier
275//     pub fn new(
276//         rtx: Arc<ResolvedTransaction>,
277//         consensus: &'a Consensus,
278//         data_loader: DL,
279//         tx_env: &'a TxVerifyEnv,
280//     ) -> Self {
281//         TransactionVerifier {
282//             non_contextual: NonContextualTransactionVerifier::new(&rtx.transaction, consensus),
283//             contextual: ContextualTransactionVerifier::new(rtx, consensus, data_loader, tx_env),
284//         }
285//     }
286
287//     /// Perform all tx verification
288//     pub fn verify(&self, max_cycles: Cycle) -> Result<Completed, Error> {
289//         self.non_contextual.verify()?;
290//         self.contextual.verify(max_cycles, false)
291//     }
292// }
293
294pub struct FeeCalculator<DL> {
295    transaction: Arc<ResolvedTransaction>,
296    consensus: Arc<Consensus>,
297    data_loader: DL,
298}
299
300impl<DL: CellDataProvider + HeaderProvider + ExtensionProvider + EpochProvider> FeeCalculator<DL> {
301    fn new(
302        transaction: Arc<ResolvedTransaction>,
303        consensus: Arc<Consensus>,
304        data_loader: DL,
305    ) -> Self {
306        Self {
307            transaction,
308            consensus,
309            data_loader,
310        }
311    }
312
313    fn transaction_fee(&self) -> Result<Capacity, DaoError> {
314        // skip tx fee calculation for cellbase
315        if self.transaction.is_cellbase() {
316            Ok(Capacity::zero())
317        } else {
318            DaoCalculator::new(self.consensus.as_ref(), &self.data_loader)
319                .transaction_fee(&self.transaction)
320        }
321    }
322}
323
324pub struct VersionVerifier<'a> {
325    transaction: &'a TransactionView,
326    tx_version: Version,
327}
328
329impl<'a> VersionVerifier<'a> {
330    pub fn new(transaction: &'a TransactionView, tx_version: Version) -> Self {
331        VersionVerifier {
332            transaction,
333            tx_version,
334        }
335    }
336
337    pub fn verify(&self) -> Result<(), Error> {
338        if self.transaction.version() != self.tx_version {
339            return Err((TransactionError::MismatchedVersion {
340                expected: self.tx_version,
341                actual: self.transaction.version(),
342            })
343            .into());
344        }
345        Ok(())
346    }
347}
348
349pub struct SizeVerifier<'a> {
350    transaction: &'a TransactionView,
351    block_bytes_limit: u64,
352}
353
354impl<'a> SizeVerifier<'a> {
355    pub fn new(transaction: &'a TransactionView, block_bytes_limit: u64) -> Self {
356        SizeVerifier {
357            transaction,
358            block_bytes_limit,
359        }
360    }
361
362    pub fn verify(&self) -> Result<(), Error> {
363        let size = self.transaction.data().serialized_size_in_block() as u64;
364        if size <= self.block_bytes_limit {
365            Ok(())
366        } else {
367            Err(TransactionError::ExceededMaximumBlockBytes {
368                actual: size,
369                limit: self.block_bytes_limit,
370            }
371            .into())
372        }
373    }
374}
375
376/// Perform rules verification describe in CKB script, also check cycles limit
377///
378/// See:
379/// - [ckb-vm](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0003-ckb-vm/0003-ckb-vm.md)
380/// - [vm-cycle-limits](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0014-vm-cycle-limits/0014-vm-cycle-limits.md)
381pub type ScriptVerifier<DL> = TransactionScriptsVerifier<DL>;
382
383pub struct EmptyVerifier<'a> {
384    transaction: &'a TransactionView,
385}
386
387impl<'a> EmptyVerifier<'a> {
388    pub fn new(transaction: &'a TransactionView) -> Self {
389        EmptyVerifier { transaction }
390    }
391
392    pub fn verify(&self) -> Result<(), Error> {
393        if self.transaction.inputs().is_empty() {
394            Err(TransactionError::Empty {
395                inner: TransactionErrorSource::Inputs,
396            }
397            .into())
398        } else if self.transaction.outputs().is_empty() && !self.transaction.is_cellbase() {
399            Err(TransactionError::Empty {
400                inner: TransactionErrorSource::Outputs,
401            }
402            .into())
403        } else {
404            Ok(())
405        }
406    }
407}
408
409/// MaturityVerifier
410///
411/// If input or dep prev is cellbase, check that it's matured
412pub struct MaturityVerifier {
413    transaction: Arc<ResolvedTransaction>,
414    epoch: EpochNumberWithFraction,
415    cellbase_maturity: EpochNumberWithFraction,
416}
417
418impl MaturityVerifier {
419    pub fn new(
420        transaction: Arc<ResolvedTransaction>,
421        epoch: EpochNumberWithFraction,
422        cellbase_maturity: EpochNumberWithFraction,
423    ) -> Self {
424        MaturityVerifier {
425            transaction,
426            epoch,
427            cellbase_maturity,
428        }
429    }
430
431    pub fn verify(&self) -> Result<(), Error> {
432        let cellbase_immature = |meta: &CellMeta| -> bool {
433            meta.transaction_info
434                .as_ref()
435                .map(|info| {
436                    info.block_number > 0 && info.is_cellbase() && {
437                        let threshold =
438                            self.cellbase_maturity.to_rational() + info.block_epoch.to_rational();
439                        let current = self.epoch.to_rational();
440                        current < threshold
441                    }
442                })
443                .unwrap_or(false)
444        };
445
446        if let Some(index) = self
447            .transaction
448            .resolved_inputs
449            .iter()
450            .position(cellbase_immature)
451        {
452            return Err(TransactionError::CellbaseImmaturity {
453                inner: TransactionErrorSource::Inputs,
454                index,
455            }
456            .into());
457        }
458
459        if let Some(index) = self
460            .transaction
461            .resolved_cell_deps
462            .iter()
463            .position(cellbase_immature)
464        {
465            return Err(TransactionError::CellbaseImmaturity {
466                inner: TransactionErrorSource::CellDeps,
467                index,
468            }
469            .into());
470        }
471
472        Ok(())
473    }
474}
475
476pub struct DuplicateDepsVerifier<'a> {
477    transaction: &'a TransactionView,
478}
479
480impl<'a> DuplicateDepsVerifier<'a> {
481    pub fn new(transaction: &'a TransactionView) -> Self {
482        DuplicateDepsVerifier { transaction }
483    }
484
485    pub fn verify(&self) -> Result<(), Error> {
486        let transaction = self.transaction;
487        let mut seen_cells = HashSet::with_capacity(self.transaction.cell_deps().len());
488        let mut seen_headers = HashSet::with_capacity(self.transaction.header_deps().len());
489
490        if let Some(dep) = transaction
491            .cell_deps_iter()
492            .find_map(|dep| seen_cells.replace(dep))
493        {
494            return Err(TransactionError::DuplicateCellDeps {
495                out_point: dep.out_point(),
496            }
497            .into());
498        }
499        if let Some(hash) = transaction
500            .header_deps_iter()
501            .find_map(|hash| seen_headers.replace(hash))
502        {
503            return Err(TransactionError::DuplicateHeaderDeps { hash }.into());
504        }
505        Ok(())
506    }
507}
508
509/// Perform inputs and outputs `capacity` field related verification
510pub struct CapacityVerifier {
511    resolved_transaction: Arc<ResolvedTransaction>,
512    dao_type_hash: Byte32,
513}
514
515impl CapacityVerifier {
516    /// Create a new `CapacityVerifier`
517    pub fn new(resolved_transaction: Arc<ResolvedTransaction>, dao_type_hash: Byte32) -> Self {
518        CapacityVerifier {
519            resolved_transaction,
520            dao_type_hash,
521        }
522    }
523
524    /// Verify sum of inputs capacity should be greater than or equal to sum of outputs capacity
525    /// Verify outputs capacity should be greater than or equal to its occupied capacity
526    pub fn verify(&self) -> Result<(), Error> {
527        // skip OutputsSumOverflow verification for resolved cellbase and DAO
528        // withdraw transactions.
529        // cellbase's outputs are verified by RewardVerifier
530        // DAO withdraw transaction is verified via the type script of DAO cells
531        if !(self.resolved_transaction.is_cellbase() || self.valid_dao_withdraw_transaction()) {
532            let inputs_sum = self.resolved_transaction.inputs_capacity()?;
533            let outputs_sum = self.resolved_transaction.outputs_capacity()?;
534
535            if inputs_sum < outputs_sum {
536                return Err((TransactionError::OutputsSumOverflow {
537                    inputs_sum,
538                    outputs_sum,
539                })
540                .into());
541            }
542        }
543
544        for (index, (output, data)) in self
545            .resolved_transaction
546            .transaction
547            .outputs_with_data_iter()
548            .enumerate()
549        {
550            let data_occupied_capacity = Capacity::bytes(data.len())?;
551            if output.is_lack_of_capacity(data_occupied_capacity)? {
552                return Err((TransactionError::InsufficientCellCapacity {
553                    index,
554                    inner: TransactionErrorSource::Outputs,
555                    capacity: output.capacity().into(),
556                    occupied_capacity: output.occupied_capacity(data_occupied_capacity)?,
557                })
558                .into());
559            }
560        }
561
562        Ok(())
563    }
564
565    fn valid_dao_withdraw_transaction(&self) -> bool {
566        self.resolved_transaction
567            .resolved_inputs
568            .iter()
569            .any(|cell_meta| cell_uses_dao_type_script(&cell_meta.cell_output, &self.dao_type_hash))
570    }
571}
572
573fn cell_uses_dao_type_script(cell_output: &CellOutput, dao_type_hash: &Byte32) -> bool {
574    cell_output
575        .type_()
576        .to_opt()
577        .map(|t| {
578            Into::<u8>::into(t.hash_type()) == Into::<u8>::into(ScriptHashType::Type)
579                && &t.code_hash() == dao_type_hash
580        })
581        .unwrap_or(false)
582}
583
584const LOCK_TYPE_FLAG: u64 = 1 << 63;
585const METRIC_TYPE_FLAG_MASK: u64 = 0x6000_0000_0000_0000;
586const VALUE_MASK: u64 = 0x00ff_ffff_ffff_ffff;
587const REMAIN_FLAGS_BITS: u64 = 0x1f00_0000_0000_0000;
588
589/// Metric represent value
590pub enum SinceMetric {
591    /// The metric_flag is 00, `value` can be explained as a block number or a relative block number.
592    BlockNumber(u64),
593    /// The metric_flag is 01, value can be explained as an absolute epoch or relative epoch.
594    EpochNumberWithFraction(EpochNumberWithFraction),
595    /// The metric_flag is 10, value can be explained as a block timestamp(unix time) or a relative
596    Timestamp(u64),
597}
598
599/// The struct define wrapper for (unsigned 64-bit integer) tx field since
600///
601/// See [tx-since](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0017-tx-valid-since/0017-tx-valid-since.md)
602#[derive(Copy, Clone, Debug)]
603pub struct Since(pub u64);
604
605impl Since {
606    /// Whether since represented absolute form
607    pub fn is_absolute(self) -> bool {
608        self.0 & LOCK_TYPE_FLAG == 0
609    }
610
611    /// Whether since represented relative form
612    #[inline]
613    pub fn is_relative(self) -> bool {
614        !self.is_absolute()
615    }
616
617    /// Whether since flag is valid
618    pub fn flags_is_valid(self) -> bool {
619        (self.0 & REMAIN_FLAGS_BITS == 0)
620            && ((self.0 & METRIC_TYPE_FLAG_MASK) != METRIC_TYPE_FLAG_MASK)
621    }
622
623    /// Extracts a `SinceMetric` from an unsigned 64-bit integer since
624    pub fn extract_metric(self) -> Option<SinceMetric> {
625        let value = self.0 & VALUE_MASK;
626        match self.0 & METRIC_TYPE_FLAG_MASK {
627            //0b0000_0000
628            0x0000_0000_0000_0000 => Some(SinceMetric::BlockNumber(value)),
629            //0b0010_0000
630            0x2000_0000_0000_0000 => Some(SinceMetric::EpochNumberWithFraction(
631                EpochNumberWithFraction::from_full_value_unchecked(value),
632            )),
633            //0b0100_0000
634            0x4000_0000_0000_0000 => value.checked_mul(1000).map(SinceMetric::Timestamp),
635            _ => None,
636        }
637    }
638}
639
640/// SinceVerifier
641///
642/// Rules detail see:
643/// [tx-since-specification](https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0017-tx-valid-since/0017-tx-valid-since.md#detailed-specification
644pub struct SinceVerifier<DL> {
645    rtx: Arc<ResolvedTransaction>,
646    consensus: Arc<Consensus>,
647    data_loader: DL,
648    tx_env: Arc<TxVerifyEnv>,
649}
650
651impl<DL: HeaderFieldsProvider> SinceVerifier<DL> {
652    pub fn new(
653        rtx: Arc<ResolvedTransaction>,
654        consensus: Arc<Consensus>,
655        data_loader: DL,
656        tx_env: Arc<TxVerifyEnv>,
657    ) -> Self {
658        SinceVerifier {
659            rtx,
660            consensus,
661            data_loader,
662            tx_env,
663        }
664    }
665
666    fn parent_median_time(&self, block_hash: &Byte32) -> u64 {
667        let header_fields = self
668            .data_loader
669            .get_header_fields(block_hash)
670            .expect("parent block exist");
671        self.block_median_time(&header_fields.parent_hash)
672    }
673
674    fn block_median_time(&self, block_hash: &Byte32) -> u64 {
675        let median_block_count = self.consensus.median_time_block_count();
676        self.data_loader
677            .block_median_time(block_hash, median_block_count)
678    }
679
680    fn verify_absolute_lock(&self, index: usize, since: Since) -> Result<(), Error> {
681        if since.is_absolute() {
682            match since.extract_metric() {
683                Some(SinceMetric::BlockNumber(block_number)) => {
684                    let proposal_window = self.consensus.tx_proposal_window();
685                    if self.tx_env.block_number(proposal_window) < block_number {
686                        return Err((TransactionError::Immature { index }).into());
687                    }
688                }
689                Some(SinceMetric::EpochNumberWithFraction(epoch_number_with_fraction)) => {
690                    if !epoch_number_with_fraction.is_well_formed_increment() {
691                        return Err((TransactionError::InvalidSince { index }).into());
692                    }
693                    let a = self.tx_env.epoch().to_rational();
694                    let b = epoch_number_with_fraction.normalize().to_rational();
695                    if a < b {
696                        return Err((TransactionError::Immature { index }).into());
697                    }
698                }
699                Some(SinceMetric::Timestamp(timestamp)) => {
700                    let parent_hash = self.tx_env.parent_hash();
701                    let tip_timestamp = self.block_median_time(&parent_hash);
702                    if tip_timestamp < timestamp {
703                        return Err((TransactionError::Immature { index }).into());
704                    }
705                }
706                None => {
707                    return Err((TransactionError::InvalidSince { index }).into());
708                }
709            }
710        }
711        Ok(())
712    }
713
714    fn verify_relative_lock(
715        &self,
716        index: usize,
717        since: Since,
718        cell_meta: &CellMeta,
719    ) -> Result<(), Error> {
720        if since.is_relative() {
721            let info = match cell_meta.transaction_info {
722                Some(ref transaction_info) => Ok(transaction_info),
723                None => Err(TransactionError::Immature { index }),
724            }?;
725            match since.extract_metric() {
726                Some(SinceMetric::BlockNumber(block_number)) => {
727                    let proposal_window = self.consensus.tx_proposal_window();
728                    let required_block_number = info
729                        .block_number
730                        .checked_add(block_number)
731                        .ok_or(TransactionError::InvalidSince { index })?;
732                    if self.tx_env.block_number(proposal_window) < required_block_number {
733                        return Err((TransactionError::Immature { index }).into());
734                    }
735                }
736                Some(SinceMetric::EpochNumberWithFraction(epoch_number_with_fraction)) => {
737                    if !epoch_number_with_fraction.is_well_formed_increment() {
738                        return Err((TransactionError::InvalidSince { index }).into());
739                    }
740                    let a = self.tx_env.epoch().to_rational();
741                    let b = info.block_epoch.to_rational()
742                        + epoch_number_with_fraction.normalize().to_rational();
743                    if a < b {
744                        return Err((TransactionError::Immature { index }).into());
745                    }
746                }
747                Some(SinceMetric::Timestamp(timestamp)) => {
748                    // pass_median_time(current_block) starts with tip block, which is the
749                    // parent of current block.
750                    // pass_median_time(input_cell's block) starts with cell_block_number - 1,
751                    // which is the parent of input_cell's block
752                    let proposal_window = self.consensus.tx_proposal_window();
753                    let parent_hash = self.tx_env.parent_hash();
754                    let epoch_number = self.tx_env.epoch_number(proposal_window);
755                    let hardfork_switch = self.consensus.hardfork_switch();
756                    let base_timestamp = if hardfork_switch
757                        .ckb2021
758                        .is_block_ts_as_relative_since_start_enabled(epoch_number)
759                    {
760                        self.data_loader
761                            .get_header_fields(&info.block_hash)
762                            .expect("header exist")
763                            .timestamp
764                    } else {
765                        self.parent_median_time(&info.block_hash)
766                    };
767                    let current_median_time = self.block_median_time(&parent_hash);
768                    let required_timestamp = base_timestamp
769                        .checked_add(timestamp)
770                        .ok_or(TransactionError::InvalidSince { index })?;
771                    if current_median_time < required_timestamp {
772                        return Err((TransactionError::Immature { index }).into());
773                    }
774                }
775                None => {
776                    return Err((TransactionError::InvalidSince { index }).into());
777                }
778            }
779        }
780        Ok(())
781    }
782
783    pub fn verify(&self) -> Result<(), Error> {
784        for (index, (cell_meta, input)) in self
785            .rtx
786            .resolved_inputs
787            .iter()
788            .zip(self.rtx.transaction.inputs())
789            .enumerate()
790        {
791            // ignore empty since
792            let since: u64 = input.since().into();
793            if since == 0 {
794                continue;
795            }
796            let since = Since(since);
797            // check remain flags
798            if !since.flags_is_valid() {
799                return Err((TransactionError::InvalidSince { index }).into());
800            }
801
802            // verify time lock
803            self.verify_absolute_lock(index, since)?;
804            self.verify_relative_lock(index, since, cell_meta)?;
805        }
806        Ok(())
807    }
808}
809
810pub struct OutputsDataVerifier<'a> {
811    transaction: &'a TransactionView,
812}
813
814impl<'a> OutputsDataVerifier<'a> {
815    pub fn new(transaction: &'a TransactionView) -> Self {
816        Self { transaction }
817    }
818
819    pub fn verify(&self) -> Result<(), TransactionError> {
820        let outputs_len = self.transaction.outputs().len();
821        let outputs_data_len = self.transaction.outputs_data().len();
822
823        if outputs_len != outputs_data_len {
824            return Err(TransactionError::OutputsDataLengthMismatch {
825                outputs_len,
826                outputs_data_len,
827            });
828        }
829        Ok(())
830    }
831}
832
833// Verify that the ScriptHashType of transaction outputs
834// is within the range permitted by the current consensus rules.
835pub struct ScriptHashTypeVerifier<'a> {
836    transaction: &'a TransactionView,
837}
838
839impl<'a> ScriptHashTypeVerifier<'a> {
840    pub fn new(transaction: &'a TransactionView) -> Self {
841        Self { transaction }
842    }
843
844    pub fn verify(&self) -> Result<(), Error> {
845        for output in self.transaction.outputs() {
846            if let Ok(hash_type) = TryInto::<ScriptHashType>::try_into(output.lock().hash_type()) {
847                let val: u8 = hash_type.into();
848                if !ENABLED_SCRIPT_HASH_TYPE.contains(&val) {
849                    return Err(
850                        TransactionError::ScriptHashTypeNotPermitted { hash_type: val }.into(),
851                    );
852                }
853            } else {
854                return Err((TransactionError::InvalidScriptHashType {
855                    hash_type: output.lock().hash_type(),
856                })
857                .into());
858            }
859        }
860
861        Ok(())
862    }
863}
864
865/// Verifies that deposit cell and withdrawing cell in Nervos DAO use same sized lock scripts.
866/// It provides a temporary solution till Nervos DAO script can be properly upgraded.
867pub struct DaoScriptSizeVerifier<DL> {
868    resolved_transaction: Arc<ResolvedTransaction>,
869    consensus: Arc<Consensus>,
870    data_loader: DL,
871}
872
873impl<DL: CellDataProvider> DaoScriptSizeVerifier<DL> {
874    /// Create a new `DaoScriptSizeVerifier`
875    pub fn new(
876        resolved_transaction: Arc<ResolvedTransaction>,
877        consensus: Arc<Consensus>,
878        data_loader: DL,
879    ) -> Self {
880        DaoScriptSizeVerifier {
881            resolved_transaction,
882            consensus,
883            data_loader,
884        }
885    }
886
887    fn dao_type_hash(&self) -> Byte32 {
888        self.consensus.dao_type_hash()
889    }
890
891    /// Verifies that for all Nervos DAO transactions, withdrawing cells must use lock scripts
892    /// of the same size as corresponding deposit cells
893    pub fn verify(&self) -> Result<(), Error> {
894        let dao_type_hash = self.dao_type_hash();
895
896        for (i, (input_meta, cell_output)) in self
897            .resolved_transaction
898            .resolved_inputs
899            .iter()
900            .zip(self.resolved_transaction.transaction.outputs())
901            .enumerate()
902        {
903            // Both the input and output cell must use Nervos DAO as type script
904            if !(cell_uses_dao_type_script(&input_meta.cell_output, &dao_type_hash)
905                && cell_uses_dao_type_script(&cell_output, &dao_type_hash))
906            {
907                continue;
908            }
909
910            // A Nervos DAO deposit cell must have input data
911            let input_data = match self.data_loader.load_cell_data(input_meta) {
912                Some(data) => data,
913                None => continue,
914            };
915
916            // Only input data with full zeros are counted as deposit cell
917            if input_data.into_iter().any(|b| b != 0) {
918                continue;
919            }
920
921            // Only cells committed after the pre-defined block number in consensus is
922            // applied to this rule
923            if let Some(info) = &input_meta.transaction_info
924                && info.block_number
925                    < self
926                        .consensus
927                        .starting_block_limiting_dao_withdrawing_lock()
928            {
929                continue;
930            }
931
932            // Now we have a pair of DAO deposit and withdrawing cells, it is expected
933            // they have the lock scripts of the same size.
934            if input_meta.cell_output.lock().total_size() != cell_output.lock().total_size() {
935                return Err((TransactionError::DaoLockSizeMismatch { index: i }).into());
936            }
937        }
938        self.verify_output_data(&dao_type_hash)?;
939        Ok(())
940    }
941
942    fn verify_output_data(&self, dao_type_hash: &Byte32) -> Result<(), Error> {
943        let transaction = &self.resolved_transaction.transaction;
944        let outputs = transaction.outputs();
945        let outputs_data = transaction.outputs_data();
946
947        for (index, output) in outputs.into_iter().enumerate() {
948            if !cell_uses_dao_type_script(&output, dao_type_hash) {
949                continue;
950            }
951
952            let Some(output_data) = outputs_data.get(index).map(|data| data.raw_data()) else {
953                continue;
954            };
955
956            if output_data.iter().all(|b| *b == 0) {
957                continue;
958            }
959
960            if !self.same_index_input_is_dao_deposit(index, dao_type_hash) {
961                return Err((TransactionError::DaoOutputDataMismatch { index }).into());
962            }
963        }
964
965        Ok(())
966    }
967
968    fn same_index_input_is_dao_deposit(&self, index: usize, dao_type_hash: &Byte32) -> bool {
969        let Some(input_meta) = self.resolved_transaction.resolved_inputs.get(index) else {
970            return false;
971        };
972
973        if !cell_uses_dao_type_script(&input_meta.cell_output, dao_type_hash) {
974            return false;
975        }
976
977        self.data_loader
978            .load_cell_data(input_meta)
979            .map(|input_data| input_data.iter().all(|b| *b == 0))
980            .unwrap_or(false)
981    }
982}