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