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
25pub struct TimeRelativeTransactionVerifier<M> {
31 pub(crate) maturity: MaturityVerifier,
32 pub(crate) since: SinceVerifier<M>,
33}
34
35impl<DL: HeaderFieldsProvider> TimeRelativeTransactionVerifier<DL> {
36 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 pub fn verify(&self) -> Result<(), Error> {
55 self.maturity.verify()?;
56 self.since.verify()?;
57 Ok(())
58 }
59}
60
61pub 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 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 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
105pub 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 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 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 #[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 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
213pub 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 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
328pub 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
361pub 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
461pub struct CapacityVerifier {
463 resolved_transaction: Arc<ResolvedTransaction>,
464 dao_type_hash: Byte32,
465}
466
467impl CapacityVerifier {
468 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 pub fn verify(&self) -> Result<(), Error> {
479 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
541pub enum SinceMetric {
543 BlockNumber(u64),
545 EpochNumberWithFraction(EpochNumberWithFraction),
547 Timestamp(u64),
549}
550
551#[derive(Copy, Clone, Debug)]
555pub struct Since(pub u64);
556
557impl Since {
558 pub fn is_absolute(self) -> bool {
560 self.0 & LOCK_TYPE_FLAG == 0
561 }
562
563 #[inline]
565 pub fn is_relative(self) -> bool {
566 !self.is_absolute()
567 }
568
569 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 pub fn extract_metric(self) -> Option<SinceMetric> {
577 let value = self.0 & VALUE_MASK;
578 match self.0 & METRIC_TYPE_FLAG_MASK {
579 0x0000_0000_0000_0000 => Some(SinceMetric::BlockNumber(value)),
581 0x2000_0000_0000_0000 => Some(SinceMetric::EpochNumberWithFraction(
583 EpochNumberWithFraction::from_full_value_unchecked(value),
584 )),
585 0x4000_0000_0000_0000 => Some(SinceMetric::Timestamp(value * 1000)),
587 _ => None,
588 }
589 }
590}
591
592pub 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 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 let since: u64 = input.since().into();
739 if since == 0 {
740 continue;
741 }
742 let since = Since(since);
743 if !since.flags_is_valid() {
745 return Err((TransactionError::InvalidSince { index }).into());
746 }
747
748 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
779pub 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
811pub struct DaoScriptSizeVerifier<DL> {
814 resolved_transaction: Arc<ResolvedTransaction>,
815 consensus: Arc<Consensus>,
816 data_loader: DL,
817}
818
819impl<DL: CellDataProvider> DaoScriptSizeVerifier<DL> {
820 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 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 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 let input_data = match self.data_loader.load_cell_data(input_meta) {
857 Some(data) => data,
858 None => continue,
859 };
860
861 if input_data.into_iter().any(|b| b != 0) {
863 continue;
864 }
865
866 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 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}