1use crate::cache::Completed;
2use crate::error::TransactionErrorSource;
3use crate::{TransactionError, TxVerifyEnv};
4use ckb_chain_spec::consensus::Consensus;
5use ckb_constant::consensus::ENABLED_SCRIPT_HASH_TYPE;
6use ckb_dao::DaoCalculator;
7use ckb_dao_utils::DaoError;
8use ckb_error::Error;
9#[cfg(not(target_family = "wasm"))]
10use ckb_script::ChunkCommand;
11use ckb_script::{TransactionScriptsVerifier, TransactionState};
12use ckb_traits::{
13 CellDataProvider, EpochProvider, ExtensionProvider, HeaderFieldsProvider, HeaderProvider,
14};
15use ckb_types::{
16 core::{
17 Capacity, Cycle, EpochNumberWithFraction, ScriptHashType, TransactionView, Version,
18 cell::{CellMeta, ResolvedTransaction},
19 },
20 packed::{Byte32, CellOutput},
21 prelude::*,
22};
23use std::collections::HashSet;
24use std::sync::Arc;
25
26pub struct TimeRelativeTransactionVerifier<M> {
32 pub(crate) maturity: MaturityVerifier,
33 pub(crate) since: SinceVerifier<M>,
34}
35
36impl<DL: HeaderFieldsProvider> TimeRelativeTransactionVerifier<DL> {
37 pub fn new(
39 rtx: Arc<ResolvedTransaction>,
40 consensus: Arc<Consensus>,
41 data_loader: DL,
42 tx_env: Arc<TxVerifyEnv>,
43 ) -> Self {
44 TimeRelativeTransactionVerifier {
45 maturity: MaturityVerifier::new(
46 Arc::clone(&rtx),
47 tx_env.epoch(),
48 consensus.cellbase_maturity(),
49 ),
50 since: SinceVerifier::new(rtx, consensus, data_loader, tx_env),
51 }
52 }
53
54 pub fn verify(&self) -> Result<(), Error> {
56 self.maturity.verify()?;
57 self.since.verify()?;
58 Ok(())
59 }
60}
61
62pub struct NonContextualTransactionVerifier<'a> {
73 pub(crate) version: VersionVerifier<'a>,
74 pub(crate) size: SizeVerifier<'a>,
75 pub(crate) empty: EmptyVerifier<'a>,
76 pub(crate) duplicate_deps: DuplicateDepsVerifier<'a>,
77 pub(crate) outputs_data_verifier: OutputsDataVerifier<'a>,
78 pub(crate) script_hash_type: ScriptHashTypeVerifier<'a>,
79}
80
81impl<'a> NonContextualTransactionVerifier<'a> {
82 pub fn new(tx: &'a TransactionView, consensus: &'a Consensus) -> Self {
84 NonContextualTransactionVerifier {
85 version: VersionVerifier::new(tx, consensus.tx_version()),
86 size: SizeVerifier::new(tx, consensus.max_block_bytes()),
87 empty: EmptyVerifier::new(tx),
88 duplicate_deps: DuplicateDepsVerifier::new(tx),
89 outputs_data_verifier: OutputsDataVerifier::new(tx),
90 script_hash_type: ScriptHashTypeVerifier::new(tx),
91 }
92 }
93
94 pub fn verify(&self) -> Result<(), Error> {
96 self.version.verify()?;
97 self.size.verify()?;
98 self.empty.verify()?;
99 self.duplicate_deps.verify()?;
100 self.outputs_data_verifier.verify()?;
101 self.script_hash_type.verify()?;
102 Ok(())
103 }
104}
105
106pub struct ContextualTransactionVerifier<DL>
114where
115 DL: Send + Sync + Clone + CellDataProvider + HeaderProvider + ExtensionProvider + 'static,
116{
117 pub(crate) time_relative: TimeRelativeTransactionVerifier<DL>,
118 pub(crate) capacity: CapacityVerifier,
119 pub(crate) script: ScriptVerifier<DL>,
120 pub(crate) fee_calculator: FeeCalculator<DL>,
121}
122
123impl<DL> ContextualTransactionVerifier<DL>
124where
125 DL: CellDataProvider
126 + HeaderProvider
127 + ExtensionProvider
128 + HeaderFieldsProvider
129 + EpochProvider
130 + Send
131 + Sync
132 + Clone
133 + 'static,
134{
135 pub fn new(
137 rtx: Arc<ResolvedTransaction>,
138 consensus: Arc<Consensus>,
139 data_loader: DL,
140 tx_env: Arc<TxVerifyEnv>,
141 ) -> Self {
142 ContextualTransactionVerifier {
143 time_relative: TimeRelativeTransactionVerifier::new(
144 Arc::clone(&rtx),
145 Arc::clone(&consensus),
146 data_loader.clone(),
147 Arc::clone(&tx_env),
148 ),
149 script: TransactionScriptsVerifier::new(
150 Arc::clone(&rtx),
151 data_loader.clone(),
152 Arc::clone(&consensus),
153 Arc::clone(&tx_env),
154 ),
155 capacity: CapacityVerifier::new(Arc::clone(&rtx), consensus.dao_type_hash()),
156 fee_calculator: FeeCalculator::new(rtx, consensus, data_loader),
157 }
158 }
159
160 pub fn verify(&self, max_cycles: Cycle, skip_script_verify: bool) -> Result<Completed, Error> {
164 self.time_relative.verify()?;
165 self.capacity.verify()?;
166 let cycles = if skip_script_verify {
167 0
168 } else {
169 self.script.verify(max_cycles)?
170 };
171 let fee = self.fee_calculator.transaction_fee()?;
172 Ok(Completed { cycles, fee })
173 }
174
175 #[cfg(not(target_family = "wasm"))]
178 pub async fn verify_with_pause(
179 &self,
180 max_cycles: Cycle,
181 command_rx: &mut tokio::sync::watch::Receiver<ChunkCommand>,
182 ) -> Result<Completed, Error> {
183 self.time_relative.verify()?;
184 self.capacity.verify()?;
185 let fee = self.fee_calculator.transaction_fee()?;
186 let cycles = self
187 .script
188 .resumable_verify_with_signal(max_cycles, command_rx)
189 .await?;
190 Ok(Completed { cycles, fee })
191 }
192
193 pub fn complete(
197 &self,
198 max_cycles: Cycle,
199 skip_script_verify: bool,
200 state: &TransactionState,
201 ) -> Result<Completed, Error> {
202 self.time_relative.verify()?;
203 self.capacity.verify()?;
204 let cycles = if skip_script_verify {
205 0
206 } else {
207 self.script.complete(state, max_cycles)?
208 };
209 let fee = self.fee_calculator.transaction_fee()?;
210 Ok(Completed { cycles, fee })
211 }
212}
213
214pub struct FeeCalculator<DL> {
248 transaction: Arc<ResolvedTransaction>,
249 consensus: Arc<Consensus>,
250 data_loader: DL,
251}
252
253impl<DL: CellDataProvider + HeaderProvider + ExtensionProvider + EpochProvider> FeeCalculator<DL> {
254 fn new(
255 transaction: Arc<ResolvedTransaction>,
256 consensus: Arc<Consensus>,
257 data_loader: DL,
258 ) -> Self {
259 Self {
260 transaction,
261 consensus,
262 data_loader,
263 }
264 }
265
266 fn transaction_fee(&self) -> Result<Capacity, DaoError> {
267 if self.transaction.is_cellbase() {
269 Ok(Capacity::zero())
270 } else {
271 DaoCalculator::new(self.consensus.as_ref(), &self.data_loader)
272 .transaction_fee(&self.transaction)
273 }
274 }
275}
276
277pub struct VersionVerifier<'a> {
278 transaction: &'a TransactionView,
279 tx_version: Version,
280}
281
282impl<'a> VersionVerifier<'a> {
283 pub fn new(transaction: &'a TransactionView, tx_version: Version) -> Self {
284 VersionVerifier {
285 transaction,
286 tx_version,
287 }
288 }
289
290 pub fn verify(&self) -> Result<(), Error> {
291 if self.transaction.version() != self.tx_version {
292 return Err((TransactionError::MismatchedVersion {
293 expected: self.tx_version,
294 actual: self.transaction.version(),
295 })
296 .into());
297 }
298 Ok(())
299 }
300}
301
302pub struct SizeVerifier<'a> {
303 transaction: &'a TransactionView,
304 block_bytes_limit: u64,
305}
306
307impl<'a> SizeVerifier<'a> {
308 pub fn new(transaction: &'a TransactionView, block_bytes_limit: u64) -> Self {
309 SizeVerifier {
310 transaction,
311 block_bytes_limit,
312 }
313 }
314
315 pub fn verify(&self) -> Result<(), Error> {
316 let size = self.transaction.data().serialized_size_in_block() as u64;
317 if size <= self.block_bytes_limit {
318 Ok(())
319 } else {
320 Err(TransactionError::ExceededMaximumBlockBytes {
321 actual: size,
322 limit: self.block_bytes_limit,
323 }
324 .into())
325 }
326 }
327}
328
329pub type ScriptVerifier<DL> = TransactionScriptsVerifier<DL>;
335
336pub struct EmptyVerifier<'a> {
337 transaction: &'a TransactionView,
338}
339
340impl<'a> EmptyVerifier<'a> {
341 pub fn new(transaction: &'a TransactionView) -> Self {
342 EmptyVerifier { transaction }
343 }
344
345 pub fn verify(&self) -> Result<(), Error> {
346 if self.transaction.inputs().is_empty() {
347 Err(TransactionError::Empty {
348 inner: TransactionErrorSource::Inputs,
349 }
350 .into())
351 } else if self.transaction.outputs().is_empty() && !self.transaction.is_cellbase() {
352 Err(TransactionError::Empty {
353 inner: TransactionErrorSource::Outputs,
354 }
355 .into())
356 } else {
357 Ok(())
358 }
359 }
360}
361
362pub struct MaturityVerifier {
366 transaction: Arc<ResolvedTransaction>,
367 epoch: EpochNumberWithFraction,
368 cellbase_maturity: EpochNumberWithFraction,
369}
370
371impl MaturityVerifier {
372 pub fn new(
373 transaction: Arc<ResolvedTransaction>,
374 epoch: EpochNumberWithFraction,
375 cellbase_maturity: EpochNumberWithFraction,
376 ) -> Self {
377 MaturityVerifier {
378 transaction,
379 epoch,
380 cellbase_maturity,
381 }
382 }
383
384 pub fn verify(&self) -> Result<(), Error> {
385 let cellbase_immature = |meta: &CellMeta| -> bool {
386 meta.transaction_info
387 .as_ref()
388 .map(|info| {
389 info.block_number > 0 && info.is_cellbase() && {
390 let threshold =
391 self.cellbase_maturity.to_rational() + info.block_epoch.to_rational();
392 let current = self.epoch.to_rational();
393 current < threshold
394 }
395 })
396 .unwrap_or(false)
397 };
398
399 if let Some(index) = self
400 .transaction
401 .resolved_inputs
402 .iter()
403 .position(cellbase_immature)
404 {
405 return Err(TransactionError::CellbaseImmaturity {
406 inner: TransactionErrorSource::Inputs,
407 index,
408 }
409 .into());
410 }
411
412 if let Some(index) = self
413 .transaction
414 .resolved_cell_deps
415 .iter()
416 .position(cellbase_immature)
417 {
418 return Err(TransactionError::CellbaseImmaturity {
419 inner: TransactionErrorSource::CellDeps,
420 index,
421 }
422 .into());
423 }
424
425 Ok(())
426 }
427}
428
429pub struct DuplicateDepsVerifier<'a> {
430 transaction: &'a TransactionView,
431}
432
433impl<'a> DuplicateDepsVerifier<'a> {
434 pub fn new(transaction: &'a TransactionView) -> Self {
435 DuplicateDepsVerifier { transaction }
436 }
437
438 pub fn verify(&self) -> Result<(), Error> {
439 let transaction = self.transaction;
440 let mut seen_cells = HashSet::with_capacity(self.transaction.cell_deps().len());
441 let mut seen_headers = HashSet::with_capacity(self.transaction.header_deps().len());
442
443 if let Some(dep) = transaction
444 .cell_deps_iter()
445 .find_map(|dep| seen_cells.replace(dep))
446 {
447 return Err(TransactionError::DuplicateCellDeps {
448 out_point: dep.out_point(),
449 }
450 .into());
451 }
452 if let Some(hash) = transaction
453 .header_deps_iter()
454 .find_map(|hash| seen_headers.replace(hash))
455 {
456 return Err(TransactionError::DuplicateHeaderDeps { hash }.into());
457 }
458 Ok(())
459 }
460}
461
462pub struct CapacityVerifier {
464 resolved_transaction: Arc<ResolvedTransaction>,
465 dao_type_hash: Byte32,
466}
467
468impl CapacityVerifier {
469 pub fn new(resolved_transaction: Arc<ResolvedTransaction>, dao_type_hash: Byte32) -> Self {
471 CapacityVerifier {
472 resolved_transaction,
473 dao_type_hash,
474 }
475 }
476
477 pub fn verify(&self) -> Result<(), Error> {
480 if !(self.resolved_transaction.is_cellbase() || self.valid_dao_withdraw_transaction()) {
485 let inputs_sum = self.resolved_transaction.inputs_capacity()?;
486 let outputs_sum = self.resolved_transaction.outputs_capacity()?;
487
488 if inputs_sum < outputs_sum {
489 return Err((TransactionError::OutputsSumOverflow {
490 inputs_sum,
491 outputs_sum,
492 })
493 .into());
494 }
495 }
496
497 for (index, (output, data)) in self
498 .resolved_transaction
499 .transaction
500 .outputs_with_data_iter()
501 .enumerate()
502 {
503 let data_occupied_capacity = Capacity::bytes(data.len())?;
504 if output.is_lack_of_capacity(data_occupied_capacity)? {
505 return Err((TransactionError::InsufficientCellCapacity {
506 index,
507 inner: TransactionErrorSource::Outputs,
508 capacity: output.capacity().unpack(),
509 occupied_capacity: output.occupied_capacity(data_occupied_capacity)?,
510 })
511 .into());
512 }
513 }
514
515 Ok(())
516 }
517
518 fn valid_dao_withdraw_transaction(&self) -> bool {
519 self.resolved_transaction
520 .resolved_inputs
521 .iter()
522 .any(|cell_meta| cell_uses_dao_type_script(&cell_meta.cell_output, &self.dao_type_hash))
523 }
524}
525
526fn cell_uses_dao_type_script(cell_output: &CellOutput, dao_type_hash: &Byte32) -> bool {
527 cell_output
528 .type_()
529 .to_opt()
530 .map(|t| {
531 Into::<u8>::into(t.hash_type()) == Into::<u8>::into(ScriptHashType::Type)
532 && &t.code_hash() == dao_type_hash
533 })
534 .unwrap_or(false)
535}
536
537const LOCK_TYPE_FLAG: u64 = 1 << 63;
538const METRIC_TYPE_FLAG_MASK: u64 = 0x6000_0000_0000_0000;
539const VALUE_MASK: u64 = 0x00ff_ffff_ffff_ffff;
540const REMAIN_FLAGS_BITS: u64 = 0x1f00_0000_0000_0000;
541
542pub enum SinceMetric {
544 BlockNumber(u64),
546 EpochNumberWithFraction(EpochNumberWithFraction),
548 Timestamp(u64),
550}
551
552#[derive(Copy, Clone, Debug)]
556pub struct Since(pub u64);
557
558impl Since {
559 pub fn is_absolute(self) -> bool {
561 self.0 & LOCK_TYPE_FLAG == 0
562 }
563
564 #[inline]
566 pub fn is_relative(self) -> bool {
567 !self.is_absolute()
568 }
569
570 pub fn flags_is_valid(self) -> bool {
572 (self.0 & REMAIN_FLAGS_BITS == 0)
573 && ((self.0 & METRIC_TYPE_FLAG_MASK) != METRIC_TYPE_FLAG_MASK)
574 }
575
576 pub fn extract_metric(self) -> Option<SinceMetric> {
578 let value = self.0 & VALUE_MASK;
579 match self.0 & METRIC_TYPE_FLAG_MASK {
580 0x0000_0000_0000_0000 => Some(SinceMetric::BlockNumber(value)),
582 0x2000_0000_0000_0000 => Some(SinceMetric::EpochNumberWithFraction(
584 EpochNumberWithFraction::from_full_value_unchecked(value),
585 )),
586 0x4000_0000_0000_0000 => Some(SinceMetric::Timestamp(value * 1000)),
588 _ => None,
589 }
590 }
591}
592
593pub struct SinceVerifier<DL> {
598 rtx: Arc<ResolvedTransaction>,
599 consensus: Arc<Consensus>,
600 data_loader: DL,
601 tx_env: Arc<TxVerifyEnv>,
602}
603
604impl<DL: HeaderFieldsProvider> SinceVerifier<DL> {
605 pub fn new(
606 rtx: Arc<ResolvedTransaction>,
607 consensus: Arc<Consensus>,
608 data_loader: DL,
609 tx_env: Arc<TxVerifyEnv>,
610 ) -> Self {
611 SinceVerifier {
612 rtx,
613 consensus,
614 data_loader,
615 tx_env,
616 }
617 }
618
619 fn parent_median_time(&self, block_hash: &Byte32) -> u64 {
620 let header_fields = self
621 .data_loader
622 .get_header_fields(block_hash)
623 .expect("parent block exist");
624 self.block_median_time(&header_fields.parent_hash)
625 }
626
627 fn block_median_time(&self, block_hash: &Byte32) -> u64 {
628 let median_block_count = self.consensus.median_time_block_count();
629 self.data_loader
630 .block_median_time(block_hash, median_block_count)
631 }
632
633 fn verify_absolute_lock(&self, index: usize, since: Since) -> Result<(), Error> {
634 if since.is_absolute() {
635 match since.extract_metric() {
636 Some(SinceMetric::BlockNumber(block_number)) => {
637 let proposal_window = self.consensus.tx_proposal_window();
638 if self.tx_env.block_number(proposal_window) < block_number {
639 return Err((TransactionError::Immature { index }).into());
640 }
641 }
642 Some(SinceMetric::EpochNumberWithFraction(epoch_number_with_fraction)) => {
643 if !epoch_number_with_fraction.is_well_formed_increment() {
644 return Err((TransactionError::InvalidSince { index }).into());
645 }
646 let a = self.tx_env.epoch().to_rational();
647 let b = epoch_number_with_fraction.normalize().to_rational();
648 if a < b {
649 return Err((TransactionError::Immature { index }).into());
650 }
651 }
652 Some(SinceMetric::Timestamp(timestamp)) => {
653 let parent_hash = self.tx_env.parent_hash();
654 let tip_timestamp = self.block_median_time(&parent_hash);
655 if tip_timestamp < timestamp {
656 return Err((TransactionError::Immature { index }).into());
657 }
658 }
659 None => {
660 return Err((TransactionError::InvalidSince { index }).into());
661 }
662 }
663 }
664 Ok(())
665 }
666
667 fn verify_relative_lock(
668 &self,
669 index: usize,
670 since: Since,
671 cell_meta: &CellMeta,
672 ) -> Result<(), Error> {
673 if since.is_relative() {
674 let info = match cell_meta.transaction_info {
675 Some(ref transaction_info) => Ok(transaction_info),
676 None => Err(TransactionError::Immature { index }),
677 }?;
678 match since.extract_metric() {
679 Some(SinceMetric::BlockNumber(block_number)) => {
680 let proposal_window = self.consensus.tx_proposal_window();
681 if self.tx_env.block_number(proposal_window) < info.block_number + block_number
682 {
683 return Err((TransactionError::Immature { index }).into());
684 }
685 }
686 Some(SinceMetric::EpochNumberWithFraction(epoch_number_with_fraction)) => {
687 if !epoch_number_with_fraction.is_well_formed_increment() {
688 return Err((TransactionError::InvalidSince { index }).into());
689 }
690 let a = self.tx_env.epoch().to_rational();
691 let b = info.block_epoch.to_rational()
692 + epoch_number_with_fraction.normalize().to_rational();
693 if a < b {
694 return Err((TransactionError::Immature { index }).into());
695 }
696 }
697 Some(SinceMetric::Timestamp(timestamp)) => {
698 let proposal_window = self.consensus.tx_proposal_window();
703 let parent_hash = self.tx_env.parent_hash();
704 let epoch_number = self.tx_env.epoch_number(proposal_window);
705 let hardfork_switch = self.consensus.hardfork_switch();
706 let base_timestamp = if hardfork_switch
707 .ckb2021
708 .is_block_ts_as_relative_since_start_enabled(epoch_number)
709 {
710 self.data_loader
711 .get_header_fields(&info.block_hash)
712 .expect("header exist")
713 .timestamp
714 } else {
715 self.parent_median_time(&info.block_hash)
716 };
717 let current_median_time = self.block_median_time(&parent_hash);
718 if current_median_time < base_timestamp + timestamp {
719 return Err((TransactionError::Immature { index }).into());
720 }
721 }
722 None => {
723 return Err((TransactionError::InvalidSince { index }).into());
724 }
725 }
726 }
727 Ok(())
728 }
729
730 pub fn verify(&self) -> Result<(), Error> {
731 for (index, (cell_meta, input)) in self
732 .rtx
733 .resolved_inputs
734 .iter()
735 .zip(self.rtx.transaction.inputs())
736 .enumerate()
737 {
738 let since: u64 = input.since().unpack();
740 if since == 0 {
741 continue;
742 }
743 let since = Since(since);
744 if !since.flags_is_valid() {
746 return Err((TransactionError::InvalidSince { index }).into());
747 }
748
749 self.verify_absolute_lock(index, since)?;
751 self.verify_relative_lock(index, since, cell_meta)?;
752 }
753 Ok(())
754 }
755}
756
757pub struct OutputsDataVerifier<'a> {
758 transaction: &'a TransactionView,
759}
760
761impl<'a> OutputsDataVerifier<'a> {
762 pub fn new(transaction: &'a TransactionView) -> Self {
763 Self { transaction }
764 }
765
766 pub fn verify(&self) -> Result<(), TransactionError> {
767 let outputs_len = self.transaction.outputs().len();
768 let outputs_data_len = self.transaction.outputs_data().len();
769
770 if outputs_len != outputs_data_len {
771 return Err(TransactionError::OutputsDataLengthMismatch {
772 outputs_len,
773 outputs_data_len,
774 });
775 }
776 Ok(())
777 }
778}
779
780pub struct ScriptHashTypeVerifier<'a> {
783 transaction: &'a TransactionView,
784}
785
786impl<'a> ScriptHashTypeVerifier<'a> {
787 pub fn new(transaction: &'a TransactionView) -> Self {
788 Self { transaction }
789 }
790
791 pub fn verify(&self) -> Result<(), Error> {
792 for output in self.transaction.outputs() {
793 if let Ok(hash_type) = TryInto::<ScriptHashType>::try_into(output.lock().hash_type()) {
794 let val: u8 = hash_type.into();
795 if !ENABLED_SCRIPT_HASH_TYPE.contains(&val) {
796 return Err(
797 TransactionError::ScriptHashTypeNotPermitted { hash_type: val }.into(),
798 );
799 }
800 } else {
801 return Err((TransactionError::InvalidScriptHashType {
802 hash_type: output.lock().hash_type(),
803 })
804 .into());
805 }
806 }
807
808 Ok(())
809 }
810}
811
812pub struct DaoScriptSizeVerifier<DL> {
815 resolved_transaction: Arc<ResolvedTransaction>,
816 consensus: Arc<Consensus>,
817 data_loader: DL,
818}
819
820impl<DL: CellDataProvider> DaoScriptSizeVerifier<DL> {
821 pub fn new(
823 resolved_transaction: Arc<ResolvedTransaction>,
824 consensus: Arc<Consensus>,
825 data_loader: DL,
826 ) -> Self {
827 DaoScriptSizeVerifier {
828 resolved_transaction,
829 consensus,
830 data_loader,
831 }
832 }
833
834 fn dao_type_hash(&self) -> Byte32 {
835 self.consensus.dao_type_hash()
836 }
837
838 pub fn verify(&self) -> Result<(), Error> {
841 let dao_type_hash = self.dao_type_hash();
842 for (i, (input_meta, cell_output)) in self
843 .resolved_transaction
844 .resolved_inputs
845 .iter()
846 .zip(self.resolved_transaction.transaction.outputs())
847 .enumerate()
848 {
849 if !(cell_uses_dao_type_script(&input_meta.cell_output, &dao_type_hash)
851 && cell_uses_dao_type_script(&cell_output, &dao_type_hash))
852 {
853 continue;
854 }
855
856 let input_data = match self.data_loader.load_cell_data(input_meta) {
858 Some(data) => data,
859 None => continue,
860 };
861
862 if input_data.into_iter().any(|b| b != 0) {
864 continue;
865 }
866
867 if let Some(info) = &input_meta.transaction_info {
870 if info.block_number
871 < self
872 .consensus
873 .starting_block_limiting_dao_withdrawing_lock()
874 {
875 continue;
876 }
877 }
878
879 if input_meta.cell_output.lock().total_size() != cell_output.lock().total_size() {
882 return Err((TransactionError::DaoLockSizeMismatch { index: i }).into());
883 }
884 }
885 Ok(())
886 }
887}