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::{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 => value.checked_mul(1000).map(SinceMetric::Timestamp),
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                    let required_block_number = info
681                        .block_number
682                        .checked_add(block_number)
683                        .ok_or(TransactionError::InvalidSince { index })?;
684                    if self.tx_env.block_number(proposal_window) < required_block_number {
685                        return Err((TransactionError::Immature { index }).into());
686                    }
687                }
688                Some(SinceMetric::EpochNumberWithFraction(epoch_number_with_fraction)) => {
689                    if !epoch_number_with_fraction.is_well_formed_increment() {
690                        return Err((TransactionError::InvalidSince { index }).into());
691                    }
692                    let a = self.tx_env.epoch().to_rational();
693                    let b = info.block_epoch.to_rational()
694                        + 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                    // pass_median_time(current_block) starts with tip block, which is the
701                    // parent of current block.
702                    // pass_median_time(input_cell's block) starts with cell_block_number - 1,
703                    // which is the parent of input_cell's block
704                    let proposal_window = self.consensus.tx_proposal_window();
705                    let parent_hash = self.tx_env.parent_hash();
706                    let epoch_number = self.tx_env.epoch_number(proposal_window);
707                    let hardfork_switch = self.consensus.hardfork_switch();
708                    let base_timestamp = if hardfork_switch
709                        .ckb2021
710                        .is_block_ts_as_relative_since_start_enabled(epoch_number)
711                    {
712                        self.data_loader
713                            .get_header_fields(&info.block_hash)
714                            .expect("header exist")
715                            .timestamp
716                    } else {
717                        self.parent_median_time(&info.block_hash)
718                    };
719                    let current_median_time = self.block_median_time(&parent_hash);
720                    let required_timestamp = base_timestamp
721                        .checked_add(timestamp)
722                        .ok_or(TransactionError::InvalidSince { index })?;
723                    if current_median_time < required_timestamp {
724                        return Err((TransactionError::Immature { index }).into());
725                    }
726                }
727                None => {
728                    return Err((TransactionError::InvalidSince { index }).into());
729                }
730            }
731        }
732        Ok(())
733    }
734
735    pub fn verify(&self) -> Result<(), Error> {
736        for (index, (cell_meta, input)) in self
737            .rtx
738            .resolved_inputs
739            .iter()
740            .zip(self.rtx.transaction.inputs())
741            .enumerate()
742        {
743            // ignore empty since
744            let since: u64 = input.since().into();
745            if since == 0 {
746                continue;
747            }
748            let since = Since(since);
749            // check remain flags
750            if !since.flags_is_valid() {
751                return Err((TransactionError::InvalidSince { index }).into());
752            }
753
754            // verify time lock
755            self.verify_absolute_lock(index, since)?;
756            self.verify_relative_lock(index, since, cell_meta)?;
757        }
758        Ok(())
759    }
760}
761
762pub struct OutputsDataVerifier<'a> {
763    transaction: &'a TransactionView,
764}
765
766impl<'a> OutputsDataVerifier<'a> {
767    pub fn new(transaction: &'a TransactionView) -> Self {
768        Self { transaction }
769    }
770
771    pub fn verify(&self) -> Result<(), TransactionError> {
772        let outputs_len = self.transaction.outputs().len();
773        let outputs_data_len = self.transaction.outputs_data().len();
774
775        if outputs_len != outputs_data_len {
776            return Err(TransactionError::OutputsDataLengthMismatch {
777                outputs_len,
778                outputs_data_len,
779            });
780        }
781        Ok(())
782    }
783}
784
785// Verify that the ScriptHashType of transaction outputs
786// is within the range permitted by the current consensus rules.
787pub struct ScriptHashTypeVerifier<'a> {
788    transaction: &'a TransactionView,
789}
790
791impl<'a> ScriptHashTypeVerifier<'a> {
792    pub fn new(transaction: &'a TransactionView) -> Self {
793        Self { transaction }
794    }
795
796    pub fn verify(&self) -> Result<(), Error> {
797        for output in self.transaction.outputs() {
798            if let Ok(hash_type) = TryInto::<ScriptHashType>::try_into(output.lock().hash_type()) {
799                let val: u8 = hash_type.into();
800                if !ENABLED_SCRIPT_HASH_TYPE.contains(&val) {
801                    return Err(
802                        TransactionError::ScriptHashTypeNotPermitted { hash_type: val }.into(),
803                    );
804                }
805            } else {
806                return Err((TransactionError::InvalidScriptHashType {
807                    hash_type: output.lock().hash_type(),
808                })
809                .into());
810            }
811        }
812
813        Ok(())
814    }
815}
816
817/// Verifies that deposit cell and withdrawing cell in Nervos DAO use same sized lock scripts.
818/// It provides a temporary solution till Nervos DAO script can be properly upgraded.
819pub struct DaoScriptSizeVerifier<DL> {
820    resolved_transaction: Arc<ResolvedTransaction>,
821    consensus: Arc<Consensus>,
822    data_loader: DL,
823}
824
825impl<DL: CellDataProvider> DaoScriptSizeVerifier<DL> {
826    /// Create a new `DaoScriptSizeVerifier`
827    pub fn new(
828        resolved_transaction: Arc<ResolvedTransaction>,
829        consensus: Arc<Consensus>,
830        data_loader: DL,
831    ) -> Self {
832        DaoScriptSizeVerifier {
833            resolved_transaction,
834            consensus,
835            data_loader,
836        }
837    }
838
839    fn dao_type_hash(&self) -> Byte32 {
840        self.consensus.dao_type_hash()
841    }
842
843    /// Verifies that for all Nervos DAO transactions, withdrawing cells must use lock scripts
844    /// of the same size as corresponding deposit cells
845    pub fn verify(&self) -> Result<(), Error> {
846        let dao_type_hash = self.dao_type_hash();
847        for (i, (input_meta, cell_output)) in self
848            .resolved_transaction
849            .resolved_inputs
850            .iter()
851            .zip(self.resolved_transaction.transaction.outputs())
852            .enumerate()
853        {
854            // Both the input and output cell must use Nervos DAO as type script
855            if !(cell_uses_dao_type_script(&input_meta.cell_output, &dao_type_hash)
856                && cell_uses_dao_type_script(&cell_output, &dao_type_hash))
857            {
858                continue;
859            }
860
861            // A Nervos DAO deposit cell must have input data
862            let input_data = match self.data_loader.load_cell_data(input_meta) {
863                Some(data) => data,
864                None => continue,
865            };
866
867            // Only input data with full zeros are counted as deposit cell
868            if input_data.into_iter().any(|b| b != 0) {
869                continue;
870            }
871
872            // Only cells committed after the pre-defined block number in consensus is
873            // applied to this rule
874            if let Some(info) = &input_meta.transaction_info
875                && info.block_number
876                    < self
877                        .consensus
878                        .starting_block_limiting_dao_withdrawing_lock()
879            {
880                continue;
881            }
882
883            // Now we have a pair of DAO deposit and withdrawing cells, it is expected
884            // they have the lock scripts of the same size.
885            if input_meta.cell_output.lock().total_size() != cell_output.lock().total_size() {
886                return Err((TransactionError::DaoLockSizeMismatch { index: i }).into());
887            }
888        }
889        Ok(())
890    }
891}