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