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