pallet_domains/
lib.rs

1//! Pallet Domains
2
3#![cfg_attr(not(feature = "std"), no_std)]
4#![feature(array_windows, variant_count)]
5
6#[cfg(feature = "runtime-benchmarks")]
7mod benchmarking;
8
9#[cfg(test)]
10mod tests;
11
12pub mod block_tree;
13pub mod bundle_storage_fund;
14pub mod domain_registry;
15pub mod extensions;
16mod nominator_position;
17pub mod runtime_registry;
18pub mod staking;
19mod staking_epoch;
20pub mod weights;
21
22extern crate alloc;
23
24use crate::block_tree::{Error as BlockTreeError, verify_execution_receipt};
25use crate::bundle_storage_fund::{charge_bundle_storage_fee, storage_fund_account};
26use crate::domain_registry::{DomainConfig, Error as DomainRegistryError};
27use crate::runtime_registry::into_complete_raw_genesis;
28use crate::staking::OperatorStatus;
29#[cfg(feature = "runtime-benchmarks")]
30pub use crate::staking::do_register_operator;
31use crate::staking_epoch::EpochTransitionResult;
32pub use crate::weights::WeightInfo;
33#[cfg(not(feature = "std"))]
34use alloc::boxed::Box;
35use alloc::collections::btree_map::BTreeMap;
36#[cfg(not(feature = "std"))]
37use alloc::vec::Vec;
38use domain_runtime_primitives::EthereumAccountId;
39use frame_support::dispatch::DispatchResult;
40use frame_support::ensure;
41use frame_support::pallet_prelude::{RuntimeDebug, StorageVersion};
42use frame_support::traits::fungible::{Inspect, InspectHold};
43use frame_support::traits::tokens::{Fortitude, Preservation};
44use frame_support::traits::{EnsureOrigin, Get, Randomness as RandomnessT, Time};
45use frame_support::weights::Weight;
46use frame_system::offchain::SubmitTransaction;
47use frame_system::pallet_prelude::*;
48pub use pallet::*;
49use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
50use scale_info::TypeInfo;
51use sp_consensus_subspace::WrappedPotOutput;
52use sp_consensus_subspace::consensus::is_proof_of_time_valid;
53use sp_core::H256;
54use sp_domains::bundle::{Bundle, BundleVersion, OpaqueBundle};
55use sp_domains::bundle_producer_election::BundleProducerElectionParams;
56use sp_domains::execution_receipt::{
57    ExecutionReceipt, ExecutionReceiptRef, ExecutionReceiptVersion, SealedSingletonReceipt,
58};
59use sp_domains::{
60    BundleAndExecutionReceiptVersion, DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT, DomainBundleLimit,
61    DomainId, DomainInstanceData, EMPTY_EXTRINSIC_ROOT, OperatorId, OperatorPublicKey,
62    OperatorSignature, ProofOfElection, RuntimeId,
63};
64use sp_domains_fraud_proof::fraud_proof::{
65    DomainRuntimeCodeAt, FraudProof, FraudProofVariant, InvalidBlockFeesProof,
66    InvalidDomainBlockHashProof, InvalidTransfersProof,
67};
68use sp_domains_fraud_proof::storage_proof::{self, BasicStorageProof, DomainRuntimeCodeProof};
69use sp_domains_fraud_proof::verification::{
70    verify_invalid_block_fees_fraud_proof, verify_invalid_bundles_fraud_proof,
71    verify_invalid_domain_block_hash_fraud_proof,
72    verify_invalid_domain_extrinsics_root_fraud_proof, verify_invalid_state_transition_fraud_proof,
73    verify_invalid_transfers_fraud_proof, verify_valid_bundle_fraud_proof,
74};
75use sp_runtime::traits::{BlockNumberProvider, CheckedSub, Hash, Header, One, Zero};
76use sp_runtime::transaction_validity::TransactionPriority;
77use sp_runtime::{RuntimeAppPublic, SaturatedConversion, Saturating};
78use sp_subspace_mmr::{ConsensusChainMmrLeafProof, MmrProofVerifier};
79pub use staking::OperatorConfig;
80use subspace_core_primitives::pot::PotOutput;
81use subspace_core_primitives::{BlockHash, SlotNumber, U256};
82use subspace_runtime_primitives::{Balance, CreateUnsigned, Moment, StorageFee};
83
84/// Maximum number of nominators to slash within a give operator at a time.
85pub const MAX_NOMINATORS_TO_SLASH: u32 = 10;
86
87pub(crate) type BalanceOf<T> = <T as Config>::Balance;
88
89pub(crate) type FungibleHoldId<T> =
90    <<T as Config>::Currency as InspectHold<<T as frame_system::Config>::AccountId>>::Reason;
91
92pub(crate) type NominatorId<T> = <T as frame_system::Config>::AccountId;
93
94pub trait HoldIdentifier<T: Config> {
95    fn staking_staked() -> FungibleHoldId<T>;
96    fn domain_instantiation_id() -> FungibleHoldId<T>;
97    fn storage_fund_withdrawal() -> FungibleHoldId<T>;
98}
99
100pub trait BlockSlot<T: frame_system::Config> {
101    // Return the future slot of the given `block_number`
102    fn future_slot(block_number: BlockNumberFor<T>) -> Option<sp_consensus_slots::Slot>;
103
104    // Return the latest block number whose slot is less than the given `to_check` slot
105    fn slot_produced_after(to_check: sp_consensus_slots::Slot) -> Option<BlockNumberFor<T>>;
106}
107
108pub type ExecutionReceiptOf<T> = ExecutionReceipt<
109    BlockNumberFor<T>,
110    <T as frame_system::Config>::Hash,
111    DomainBlockNumberFor<T>,
112    <T as Config>::DomainHash,
113    BalanceOf<T>,
114>;
115
116pub type ExecutionReceiptRefOf<'a, T> = ExecutionReceiptRef<
117    'a,
118    BlockNumberFor<T>,
119    <T as frame_system::Config>::Hash,
120    DomainBlockNumberFor<T>,
121    <T as Config>::DomainHash,
122    BalanceOf<T>,
123>;
124
125pub type OpaqueBundleOf<T> = OpaqueBundle<
126    BlockNumberFor<T>,
127    <T as frame_system::Config>::Hash,
128    <T as Config>::DomainHeader,
129    BalanceOf<T>,
130>;
131
132pub type SingletonReceiptOf<T> = SealedSingletonReceipt<
133    BlockNumberFor<T>,
134    <T as frame_system::Config>::Hash,
135    <T as Config>::DomainHeader,
136    BalanceOf<T>,
137>;
138
139pub type FraudProofFor<T> = FraudProof<
140    BlockNumberFor<T>,
141    <T as frame_system::Config>::Hash,
142    <T as Config>::DomainHeader,
143    <T as Config>::MmrHash,
144>;
145
146/// Parameters used to verify proof of election.
147#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
148pub(crate) struct ElectionVerificationParams<Balance> {
149    operators: BTreeMap<OperatorId, Balance>,
150    total_domain_stake: Balance,
151}
152
153pub type DomainBlockNumberFor<T> = <<T as Config>::DomainHeader as Header>::Number;
154pub type DomainHashingFor<T> = <<T as Config>::DomainHeader as Header>::Hashing;
155pub type ReceiptHashFor<T> = <<T as Config>::DomainHeader as Header>::Hash;
156
157pub type BlockTreeNodeFor<T> = crate::block_tree::BlockTreeNode<
158    BlockNumberFor<T>,
159    <T as frame_system::Config>::Hash,
160    DomainBlockNumberFor<T>,
161    <T as Config>::DomainHash,
162    BalanceOf<T>,
163>;
164
165/// Custom origin for validated unsigned extrinsics.
166#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
167pub enum RawOrigin {
168    ValidatedUnsigned,
169}
170
171/// Ensure the domain origin.
172pub struct EnsureDomainOrigin;
173impl<O: Into<Result<RawOrigin, O>> + From<RawOrigin>> EnsureOrigin<O> for EnsureDomainOrigin {
174    type Success = ();
175
176    fn try_origin(o: O) -> Result<Self::Success, O> {
177        o.into().map(|o| match o {
178            RawOrigin::ValidatedUnsigned => (),
179        })
180    }
181
182    #[cfg(feature = "runtime-benchmarks")]
183    fn try_successful_origin() -> Result<O, ()> {
184        Ok(O::from(RawOrigin::ValidatedUnsigned))
185    }
186}
187
188/// The current storage version.
189const STORAGE_VERSION: StorageVersion = StorageVersion::new(5);
190
191/// The number of bundle of a particular domain to be included in the block is probabilistic
192/// and based on the consensus chain slot probability and domain bundle slot probability, usually
193/// the value is 6 on average, smaller/bigger value with less probability, we hypocritically use
194/// 100 as the maximum number of bundle per block for benchmarking.
195const MAX_BUNDLE_PER_BLOCK: u32 = 100;
196
197pub(crate) type StateRootOf<T> = <<T as frame_system::Config>::Hashing as Hash>::Output;
198
199#[expect(clippy::useless_conversion, reason = "Macro-generated")]
200#[frame_support::pallet]
201mod pallet {
202    #[cfg(not(feature = "runtime-benchmarks"))]
203    use crate::DomainHashingFor;
204    #[cfg(not(feature = "runtime-benchmarks"))]
205    use crate::MAX_NOMINATORS_TO_SLASH;
206    use crate::block_tree::{
207        AcceptedReceiptType, BlockTreeNode, Error as BlockTreeError, ReceiptType,
208        execution_receipt_type, process_execution_receipt, prune_receipt,
209    };
210    use crate::bundle_storage_fund::Error as BundleStorageFundError;
211    #[cfg(not(feature = "runtime-benchmarks"))]
212    use crate::bundle_storage_fund::refund_storage_fee;
213    use crate::domain_registry::{
214        DomainConfigParams, DomainObject, Error as DomainRegistryError, do_instantiate_domain,
215        do_update_domain_allow_list,
216    };
217    use crate::runtime_registry::{
218        DomainRuntimeUpgradeEntry, Error as RuntimeRegistryError, ScheduledRuntimeUpgrade,
219        do_register_runtime, do_schedule_runtime_upgrade, do_upgrade_runtimes,
220        register_runtime_at_genesis,
221    };
222    #[cfg(not(feature = "runtime-benchmarks"))]
223    use crate::staking::do_reward_operators;
224    use crate::staking::{
225        Deposit, DomainEpoch, Error as StakingError, Operator, OperatorConfig, SharePrice,
226        StakingSummary, Withdrawal, do_deregister_operator, do_mark_invalid_bundle_authors,
227        do_mark_operators_as_slashed, do_nominate_operator, do_register_operator, do_unlock_funds,
228        do_unlock_nominator, do_unmark_invalid_bundle_authors, do_withdraw_stake,
229    };
230    #[cfg(not(feature = "runtime-benchmarks"))]
231    use crate::staking_epoch::do_slash_operator;
232    use crate::staking_epoch::{Error as StakingEpochError, do_finalize_domain_current_epoch};
233    use crate::storage_proof::InherentExtrinsicData;
234    use crate::weights::WeightInfo;
235    use crate::{
236        BalanceOf, BlockSlot, BlockTreeNodeFor, DomainBlockNumberFor, ElectionVerificationParams,
237        ExecutionReceiptOf, FraudProofFor, HoldIdentifier, MAX_BUNDLE_PER_BLOCK, NominatorId,
238        OpaqueBundleOf, RawOrigin, ReceiptHashFor, STORAGE_VERSION, SingletonReceiptOf,
239        StateRootOf,
240    };
241    #[cfg(not(feature = "std"))]
242    use alloc::string::String;
243    #[cfg(not(feature = "std"))]
244    use alloc::vec;
245    #[cfg(not(feature = "std"))]
246    use alloc::vec::Vec;
247    use domain_runtime_primitives::{EVMChainId, EthereumAccountId};
248    use frame_support::pallet_prelude::*;
249    use frame_support::traits::fungible::{Inspect, InspectHold, Mutate, MutateHold};
250    use frame_support::traits::tokens::Preservation;
251    use frame_support::traits::{Randomness as RandomnessT, Time};
252    use frame_support::weights::Weight;
253    use frame_support::{Identity, PalletError};
254    use frame_system::pallet_prelude::*;
255    use parity_scale_codec::FullCodec;
256    use sp_core::H256;
257    use sp_domains::bundle::BundleDigest;
258    use sp_domains::bundle_producer_election::ProofOfElectionError;
259    use sp_domains::{
260        BundleAndExecutionReceiptVersion, DomainBundleSubmitted, DomainId, DomainOwner,
261        DomainSudoCall, DomainsTransfersTracker, EpochIndex,
262        EvmDomainContractCreationAllowedByCall, GenesisDomain, OnChainRewards,
263        OnDomainInstantiated, OperatorAllowList, OperatorId, OperatorRewardSource, RuntimeId,
264        RuntimeObject, RuntimeType,
265    };
266    use sp_domains_fraud_proof::fraud_proof_runtime_interface::domain_runtime_call;
267    use sp_domains_fraud_proof::storage_proof::{self, FraudProofStorageKeyProvider};
268    use sp_domains_fraud_proof::{InvalidTransactionCode, StatelessDomainRuntimeCall};
269    use sp_runtime::Saturating;
270    use sp_runtime::traits::{
271        AtLeast32BitUnsigned, BlockNumberProvider, CheckEqual, CheckedAdd, Header as HeaderT,
272        MaybeDisplay, One, SimpleBitOps, Zero,
273    };
274    use sp_std::boxed::Box;
275    use sp_std::collections::btree_map::BTreeMap;
276    use sp_std::collections::btree_set::BTreeSet;
277    use sp_std::fmt::Debug;
278    use sp_subspace_mmr::MmrProofVerifier;
279    use subspace_core_primitives::{Randomness, U256};
280    use subspace_runtime_primitives::StorageFee;
281
282    #[pallet::config]
283    pub trait Config: frame_system::Config<Hash: Into<H256> + From<H256>> {
284        type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
285
286        /// Origin for domain call.
287        type DomainOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = ()>;
288
289        // TODO: `DomainHash` can be derived from `DomainHeader`, it is still needed just for
290        // converting `DomainHash` to/from `H256` without encode/decode, remove it once we found
291        // other ways to do this.
292        /// Domain block hash type.
293        type DomainHash: Parameter
294            + Member
295            + MaybeSerializeDeserialize
296            + Debug
297            + MaybeDisplay
298            + SimpleBitOps
299            + Ord
300            + Default
301            + Copy
302            + CheckEqual
303            + sp_std::hash::Hash
304            + AsRef<[u8]>
305            + AsMut<[u8]>
306            + MaxEncodedLen
307            + Into<H256>
308            + From<H256>;
309
310        // We need this explicit type since Currency::Balance does not provide From<u64>
311        type Balance: Parameter
312            + Member
313            + MaybeSerializeDeserialize
314            + AtLeast32BitUnsigned
315            + FullCodec
316            + Debug
317            + MaybeDisplay
318            + Default
319            + Copy
320            + MaxEncodedLen
321            + From<u64>;
322
323        /// The domain header type.
324        type DomainHeader: HeaderT<Hash = Self::DomainHash>;
325
326        /// Same with `pallet_subspace::Config::ConfirmationDepthK`.
327        #[pallet::constant]
328        type ConfirmationDepthK: Get<BlockNumberFor<Self>>;
329
330        /// Currency type used by the domains for staking and other currency related stuff.
331        type Currency: Inspect<Self::AccountId, Balance = Self::Balance>
332            + Mutate<Self::AccountId>
333            + InspectHold<Self::AccountId>
334            + MutateHold<Self::AccountId>;
335
336        /// Type representing the shares in the staking protocol.
337        type Share: Parameter
338            + Member
339            + MaybeSerializeDeserialize
340            + Debug
341            + AtLeast32BitUnsigned
342            + FullCodec
343            + Copy
344            + Default
345            + TypeInfo
346            + MaxEncodedLen
347            + IsType<BalanceOf<Self>>;
348
349        /// A variation of the Identifier used for holding the funds used for staking and domains.
350        type HoldIdentifier: HoldIdentifier<Self>;
351
352        /// The block tree pruning depth.
353        #[pallet::constant]
354        type BlockTreePruningDepth: Get<DomainBlockNumberFor<Self>>;
355
356        /// Consensus chain slot probability.
357        #[pallet::constant]
358        type ConsensusSlotProbability: Get<(u64, u64)>;
359
360        /// The maximum block size limit for all domain.
361        #[pallet::constant]
362        type MaxDomainBlockSize: Get<u32>;
363
364        /// The maximum block weight limit for all domain.
365        #[pallet::constant]
366        type MaxDomainBlockWeight: Get<Weight>;
367
368        /// The maximum domain name length limit for all domain.
369        #[pallet::constant]
370        type MaxDomainNameLength: Get<u32>;
371
372        /// The amount of fund to be locked up for the domain instance creator.
373        #[pallet::constant]
374        type DomainInstantiationDeposit: Get<BalanceOf<Self>>;
375
376        /// Weight information for extrinsics in this pallet.
377        type WeightInfo: WeightInfo;
378
379        /// Initial domain tx range value.
380        #[pallet::constant]
381        type InitialDomainTxRange: Get<u64>;
382
383        /// Domain tx range is adjusted after every DomainTxRangeAdjustmentInterval blocks.
384        #[pallet::constant]
385        type DomainTxRangeAdjustmentInterval: Get<u64>;
386
387        /// Minimum operator stake required to become operator of a domain.
388        #[pallet::constant]
389        type MinOperatorStake: Get<BalanceOf<Self>>;
390
391        /// Minimum nominator stake required to nominate and operator.
392        #[pallet::constant]
393        type MinNominatorStake: Get<BalanceOf<Self>>;
394
395        /// Minimum number of blocks after which any finalized withdrawals are released to nominators.
396        #[pallet::constant]
397        type StakeWithdrawalLockingPeriod: Get<DomainBlockNumberFor<Self>>;
398
399        /// Domain epoch transition interval
400        #[pallet::constant]
401        type StakeEpochDuration: Get<DomainBlockNumberFor<Self>>;
402
403        /// Treasury account.
404        #[pallet::constant]
405        type TreasuryAccount: Get<Self::AccountId>;
406
407        /// The maximum number of pending staking operation that can perform upon epoch transition.
408        #[pallet::constant]
409        type MaxPendingStakingOperation: Get<u32>;
410
411        /// Randomness source.
412        type Randomness: RandomnessT<Self::Hash, BlockNumberFor<Self>>;
413
414        /// The pallet-domains's pallet id.
415        #[pallet::constant]
416        type PalletId: Get<frame_support::PalletId>;
417
418        /// Storage fee interface used to deal with bundle storage fee
419        type StorageFee: StorageFee<BalanceOf<Self>>;
420
421        /// The block timestamp
422        type BlockTimestamp: Time;
423
424        /// The block slot
425        type BlockSlot: BlockSlot<Self>;
426
427        /// Transfers tracker.
428        type DomainsTransfersTracker: DomainsTransfersTracker<BalanceOf<Self>>;
429
430        /// Upper limit for total initial accounts domains
431        type MaxInitialDomainAccounts: Get<u32>;
432
433        /// Minimum balance for each initial domain account
434        type MinInitialDomainAccountBalance: Get<BalanceOf<Self>>;
435
436        /// How many block a bundle should still consider as valid after produced
437        #[pallet::constant]
438        type BundleLongevity: Get<u32>;
439
440        /// Post hook to notify accepted domain bundles in previous block.
441        type DomainBundleSubmitted: DomainBundleSubmitted;
442
443        /// A hook to call after a domain is instantiated
444        type OnDomainInstantiated: OnDomainInstantiated;
445
446        /// Hash type of MMR
447        type MmrHash: Parameter + Member + Default + Clone;
448
449        /// MMR proof verifier
450        type MmrProofVerifier: MmrProofVerifier<Self::MmrHash, BlockNumberFor<Self>, StateRootOf<Self>>;
451
452        /// Fraud proof storage key provider
453        type FraudProofStorageKeyProvider: FraudProofStorageKeyProvider<BlockNumberFor<Self>>;
454
455        /// Hook to handle chain rewards.
456        type OnChainRewards: OnChainRewards<BalanceOf<Self>>;
457
458        /// The max number of withdrawals per nominator that may exist at any time,
459        /// once this limit is reached, the nominator need to unlock the withdrawal
460        /// before requesting new withdrawal.
461        #[pallet::constant]
462        type WithdrawalLimit: Get<u32>;
463
464        /// Current bundle version accepted by the runtime.
465        #[pallet::constant]
466        type CurrentBundleAndExecutionReceiptVersion: Get<BundleAndExecutionReceiptVersion>;
467    }
468
469    #[pallet::pallet]
470    #[pallet::without_storage_info]
471    #[pallet::storage_version(STORAGE_VERSION)]
472    pub struct Pallet<T>(_);
473
474    /// Bundles submitted successfully in current block.
475    #[pallet::storage]
476    pub type SuccessfulBundles<T> = StorageMap<_, Identity, DomainId, Vec<H256>, ValueQuery>;
477
478    /// Stores the next runtime id.
479    #[pallet::storage]
480    pub(super) type NextRuntimeId<T> = StorageValue<_, RuntimeId, ValueQuery>;
481
482    /// Starting EVM chain ID for evm runtimes.
483    pub struct StartingEVMChainId;
484
485    impl Get<EVMChainId> for StartingEVMChainId {
486        fn get() -> EVMChainId {
487            // Starting EVM chainID for domains.
488            870
489        }
490    }
491
492    /// Stores the next evm chain id.
493    #[pallet::storage]
494    pub(super) type NextEVMChainId<T> = StorageValue<_, EVMChainId, ValueQuery, StartingEVMChainId>;
495
496    #[pallet::storage]
497    pub type RuntimeRegistry<T: Config> =
498        StorageMap<_, Identity, RuntimeId, RuntimeObject<BlockNumberFor<T>, T::Hash>, OptionQuery>;
499
500    #[pallet::storage]
501    pub(super) type ScheduledRuntimeUpgrades<T: Config> = StorageDoubleMap<
502        _,
503        Identity,
504        BlockNumberFor<T>,
505        Identity,
506        RuntimeId,
507        ScheduledRuntimeUpgrade<T::Hash>,
508        OptionQuery,
509    >;
510
511    #[pallet::storage]
512    pub(super) type NextOperatorId<T> = StorageValue<_, OperatorId, ValueQuery>;
513
514    #[pallet::storage]
515    pub(super) type OperatorIdOwner<T: Config> =
516        StorageMap<_, Identity, OperatorId, T::AccountId, OptionQuery>;
517
518    #[pallet::storage]
519    #[pallet::getter(fn domain_staking_summary)]
520    pub(super) type DomainStakingSummary<T: Config> =
521        StorageMap<_, Identity, DomainId, StakingSummary<OperatorId, BalanceOf<T>>, OptionQuery>;
522
523    /// List of all registered operators and their configuration.
524    #[pallet::storage]
525    pub(super) type Operators<T: Config> = StorageMap<
526        _,
527        Identity,
528        OperatorId,
529        Operator<BalanceOf<T>, T::Share, DomainBlockNumberFor<T>, ReceiptHashFor<T>>,
530        OptionQuery,
531    >;
532
533    /// The highest slot of the bundle submitted by an operator
534    #[pallet::storage]
535    pub(super) type OperatorHighestSlot<T: Config> =
536        StorageMap<_, Identity, OperatorId, u64, ValueQuery>;
537
538    /// The set of slot of the bundle submitted by an operator in the current block, cleared at the
539    /// next block initialization
540    #[pallet::storage]
541    pub(super) type OperatorBundleSlot<T: Config> =
542        StorageMap<_, Identity, OperatorId, BTreeSet<u64>, ValueQuery>;
543
544    /// Share price for the operator pool at the end of Domain epoch.
545    // TODO: currently unbounded storage.
546    #[pallet::storage]
547    pub type OperatorEpochSharePrice<T: Config> =
548        StorageDoubleMap<_, Identity, OperatorId, Identity, DomainEpoch, SharePrice, OptionQuery>;
549
550    /// List of all deposits for given Operator.
551    #[pallet::storage]
552    pub(super) type Deposits<T: Config> = StorageDoubleMap<
553        _,
554        Identity,
555        OperatorId,
556        Identity,
557        NominatorId<T>,
558        Deposit<T::Share, BalanceOf<T>>,
559        OptionQuery,
560    >;
561
562    /// List of all withdrawals for a given operator.
563    #[pallet::storage]
564    pub(super) type Withdrawals<T: Config> = StorageDoubleMap<
565        _,
566        Identity,
567        OperatorId,
568        Identity,
569        NominatorId<T>,
570        Withdrawal<BalanceOf<T>, T::Share, DomainBlockNumberFor<T>>,
571        OptionQuery,
572    >;
573
574    /// The amount of balance the nominator hold for a given operator
575    #[pallet::storage]
576    pub(super) type DepositOnHold<T: Config> =
577        StorageMap<_, Identity, (OperatorId, NominatorId<T>), BalanceOf<T>, ValueQuery>;
578
579    /// A list operators who were slashed during the current epoch associated with the domain.
580    /// When the epoch for a given domain is complete, operator total stake is moved to treasury and
581    /// then deleted.
582    #[pallet::storage]
583    pub(super) type PendingSlashes<T: Config> =
584        StorageMap<_, Identity, DomainId, BTreeSet<OperatorId>, OptionQuery>;
585
586    /// The pending staking operation count of the current epoch, it should not larger than
587    /// `MaxPendingStakingOperation` and will be resetted to 0 upon epoch transition.
588    #[pallet::storage]
589    pub(super) type PendingStakingOperationCount<T: Config> =
590        StorageMap<_, Identity, DomainId, u32, ValueQuery>;
591
592    /// Stores the next domain id.
593    #[pallet::storage]
594    #[pallet::getter(fn next_domain_id)]
595    pub(super) type NextDomainId<T> = StorageValue<_, DomainId, ValueQuery>;
596
597    /// The domain registry
598    #[pallet::storage]
599    pub(super) type DomainRegistry<T: Config> = StorageMap<
600        _,
601        Identity,
602        DomainId,
603        DomainObject<BlockNumberFor<T>, ReceiptHashFor<T>, T::AccountId, BalanceOf<T>>,
604        OptionQuery,
605    >;
606
607    /// The domain block tree, map (`domain_id`, `domain_block_number`) to the hash of ER,
608    /// which can be used get the block tree node in `BlockTreeNodes`
609    #[pallet::storage]
610    pub(super) type BlockTree<T: Config> = StorageDoubleMap<
611        _,
612        Identity,
613        DomainId,
614        Identity,
615        DomainBlockNumberFor<T>,
616        ReceiptHashFor<T>,
617        OptionQuery,
618    >;
619
620    /// Mapping of block tree node hash to the node, each node represent a domain block
621    #[pallet::storage]
622    pub(super) type BlockTreeNodes<T: Config> =
623        StorageMap<_, Identity, ReceiptHashFor<T>, BlockTreeNodeFor<T>, OptionQuery>;
624
625    /// The head receipt number of each domain
626    #[pallet::storage]
627    pub(super) type HeadReceiptNumber<T: Config> =
628        StorageMap<_, Identity, DomainId, DomainBlockNumberFor<T>, ValueQuery>;
629
630    /// The hash of the new head receipt added in the current consensus block
631    ///
632    /// Temporary storage only exist during block execution
633    #[pallet::storage]
634    pub(super) type NewAddedHeadReceipt<T: Config> =
635        StorageMap<_, Identity, DomainId, T::DomainHash, OptionQuery>;
636
637    /// Map of consensus block hashes.
638    ///
639    /// The consensus block hash used to verify ER, only store the consensus block hash for a domain
640    /// if that consensus block contains bundle of the domain, the hash will be pruned when the ER
641    /// that point to the consensus block is pruned.
642    #[pallet::storage]
643    #[pallet::getter(fn consensus_block_info)]
644    pub type ConsensusBlockHash<T: Config> =
645        StorageDoubleMap<_, Identity, DomainId, Identity, BlockNumberFor<T>, T::Hash, OptionQuery>;
646
647    /// A set of `BundleDigest` from all bundles that successfully submitted to the consensus block,
648    /// these bundles will be used to construct the domain block and `ExecutionInbox` is used to:
649    ///
650    /// 1. Ensure subsequent ERs of that domain block include all pre-validated extrinsic bundles
651    /// 2. Index the `InboxedBundleAuthor` and pruned its value when the corresponding `ExecutionInbox` is pruned
652    #[pallet::storage]
653    pub type ExecutionInbox<T: Config> = StorageNMap<
654        _,
655        (
656            NMapKey<Identity, DomainId>,
657            NMapKey<Identity, DomainBlockNumberFor<T>>,
658            NMapKey<Identity, BlockNumberFor<T>>,
659        ),
660        Vec<BundleDigest<T::DomainHash>>,
661        ValueQuery,
662    >;
663
664    /// A mapping of `bundle_header_hash` -> `bundle_author` for all the successfully submitted bundles of
665    /// the last `BlockTreePruningDepth` domain blocks. Used to verify the invalid bundle fraud proof and
666    /// slash malicious operator who have submitted invalid bundle.
667    #[pallet::storage]
668    pub(super) type InboxedBundleAuthor<T: Config> =
669        StorageMap<_, Identity, T::DomainHash, OperatorId, OptionQuery>;
670
671    /// The block number of the best domain block, increase by one when the first bundle of the domain is
672    /// successfully submitted to current consensus block, which mean a new domain block with this block
673    /// number will be produce. Used as a pointer in `ExecutionInbox` to identify the current under building
674    /// domain block, also used as a mapping of consensus block number to domain block number.
675    //
676    // NOTE: the `HeadDomainNumber` is lazily updated for the domain runtime upgrade block (which only include
677    // the runtime upgrade tx from the consensus chain and no any user submitted tx from the bundle), use
678    // `domain_best_number` for the actual best domain block
679    #[pallet::storage]
680    pub(super) type HeadDomainNumber<T: Config> =
681        StorageMap<_, Identity, DomainId, DomainBlockNumberFor<T>, ValueQuery>;
682
683    /// A temporary storage to hold any previous epoch details for a given domain
684    /// if the epoch transitioned in this block so that all the submitted bundles
685    /// within this block are verified.
686    /// TODO: The storage is cleared on block finalization that means this storage is already cleared when
687    /// verifying the `submit_bundle` extrinsic and not used at all
688    #[pallet::storage]
689    pub(super) type LastEpochStakingDistribution<T: Config> =
690        StorageMap<_, Identity, DomainId, ElectionVerificationParams<BalanceOf<T>>, OptionQuery>;
691
692    /// Storage to hold all the domain's latest confirmed block.
693    #[pallet::storage]
694    #[pallet::getter(fn latest_confirmed_domain_execution_receipt)]
695    pub type LatestConfirmedDomainExecutionReceipt<T: Config> =
696        StorageMap<_, Identity, DomainId, ExecutionReceiptOf<T>, OptionQuery>;
697
698    /// Storage to hold all the domain's genesis execution receipt.
699    #[pallet::storage]
700    #[pallet::getter(fn domain_genesis_block_execution_receipt)]
701    pub type DomainGenesisBlockExecutionReceipt<T: Config> =
702        StorageMap<_, Identity, DomainId, ExecutionReceiptOf<T>, OptionQuery>;
703
704    /// The latest ER submitted by the operator for a given domain. It is used to determine if the operator
705    /// has submitted bad ER and is pending to slash.
706    ///
707    /// The storage item of a given `(domain_id, operator_id)` will be pruned after either:
708    /// - All the ERs submitted by the operator for this domain are confirmed and pruned
709    /// - All the bad ERs submitted by the operator for this domain are pruned and the operator is slashed
710    #[pallet::storage]
711    #[pallet::getter(fn latest_submitted_er)]
712    pub(super) type LatestSubmittedER<T: Config> =
713        StorageMap<_, Identity, (DomainId, OperatorId), DomainBlockNumberFor<T>, ValueQuery>;
714
715    /// Storage for PermissionedActions for domain instantiation and other permissioned calls.
716    #[pallet::storage]
717    pub(super) type PermissionedActionAllowedBy<T: Config> =
718        StorageValue<_, sp_domains::PermissionedActionAllowedBy<T::AccountId>, OptionQuery>;
719
720    /// Accumulate treasury funds temporarily until the funds are above Existential deposit.
721    /// We do this to ensure minting small amounts into treasury would not fail.
722    #[pallet::storage]
723    pub(super) type AccumulatedTreasuryFunds<T> = StorageValue<_, BalanceOf<T>, ValueQuery>;
724
725    /// Storage used to keep track of which consensus block each domain runtime upgrade happens in.
726    #[pallet::storage]
727    pub(super) type DomainRuntimeUpgradeRecords<T: Config> = StorageMap<
728        _,
729        Identity,
730        RuntimeId,
731        BTreeMap<BlockNumberFor<T>, DomainRuntimeUpgradeEntry<T::Hash>>,
732        ValueQuery,
733    >;
734
735    /// Temporary storage to keep track of domain runtime upgrades which happened in the parent
736    /// block. Cleared in the current block's initialization.
737    #[pallet::storage]
738    pub type DomainRuntimeUpgrades<T> = StorageValue<_, Vec<RuntimeId>, ValueQuery>;
739
740    /// Temporary storage to hold the sudo calls meant for domains.
741    ///
742    /// Storage is cleared when there are any successful bundles in the next block.
743    /// Only one sudo call is allowed per domain per consensus block.
744    #[pallet::storage]
745    pub type DomainSudoCalls<T: Config> =
746        StorageMap<_, Identity, DomainId, DomainSudoCall, ValueQuery>;
747
748    /// Storage that hold a list of all frozen domains.
749    ///
750    /// A frozen domain does not accept the bundles but does accept a fraud proof.
751    #[pallet::storage]
752    pub type FrozenDomains<T> = StorageValue<_, BTreeSet<DomainId>, ValueQuery>;
753
754    /// Temporary storage to hold the "set contract creation allowed by" calls meant for EVM Domains.
755    ///
756    /// Storage is cleared when there are any successful bundles in the next block.
757    /// Only one of these calls is allowed per domain per consensus block.
758    #[pallet::storage]
759    pub type EvmDomainContractCreationAllowedByCalls<T: Config> =
760        StorageMap<_, Identity, DomainId, EvmDomainContractCreationAllowedByCall, ValueQuery>;
761
762    /// Storage for chain rewards specific to each domain.
763    /// These rewards to equally distributed to active operators during epoch migration.
764    #[pallet::storage]
765    pub type DomainChainRewards<T: Config> =
766        StorageMap<_, Identity, DomainId, BalanceOf<T>, ValueQuery>;
767
768    /// Storage for operators who are marked as invalid bundle authors in the current epoch.
769    /// Will be cleared once epoch is transitioned.
770    #[pallet::storage]
771    pub type InvalidBundleAuthors<T: Config> =
772        StorageMap<_, Identity, DomainId, BTreeSet<OperatorId>, ValueQuery>;
773
774    /// Storage that hold a previous versions of Bundle and Execution Receipt.
775    /// Unfortunately, it adds a new item for every runtime upgrade if the versions change between
776    /// runtime upgrades. If the versions does not change, then same version is set with higher block
777    /// number.
778    /// Pruning this storage is not quiet straight forward since each domain
779    /// may submit an ER with a gap as well and also introduces the loop to find the
780    /// correct block number.
781    #[pallet::storage]
782    pub type PreviousBundleAndExecutionReceiptVersions<T> =
783        StorageValue<_, BTreeMap<BlockNumberFor<T>, BundleAndExecutionReceiptVersion>, ValueQuery>;
784
785    #[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq)]
786    pub enum BundleError {
787        /// Can not find the operator for given operator id.
788        InvalidOperatorId,
789        /// Invalid signature on the bundle header.
790        BadBundleSignature,
791        /// Invalid vrf signature in the proof of election.
792        BadVrfSignature,
793        /// Can not find the domain for given domain id.
794        InvalidDomainId,
795        /// Operator is not allowed to produce bundles in current epoch.
796        BadOperator,
797        /// Failed to pass the threshold check.
798        ThresholdUnsatisfied,
799        /// Invalid Threshold.
800        InvalidThreshold,
801        /// An invalid execution receipt found in the bundle.
802        Receipt(BlockTreeError),
803        /// Bundle size exceed the max bundle size limit in the domain config
804        BundleTooLarge,
805        /// Bundle with an invalid extrinsic root
806        InvalidExtrinsicRoot,
807        /// Invalid proof of time in the proof of election
808        InvalidProofOfTime,
809        /// The bundle is built on a slot in the future
810        SlotInTheFuture,
811        /// The bundle is built on a slot in the past
812        SlotInThePast,
813        /// Bundle weight exceeds the max bundle weight limit
814        BundleTooHeavy,
815        /// The bundle slot is smaller then the highest slot from previous slot
816        /// thus potential equivocated bundle
817        SlotSmallerThanPreviousBlockBundle,
818        /// Equivocated bundle in current block
819        EquivocatedBundle,
820        /// Domain is frozen and cannot accept new bundles
821        DomainFrozen,
822        /// The operator's bundle storage fund unable to pay the storage fee
823        UnableToPayBundleStorageFee,
824        /// Unexpected receipt gap when validating `submit_bundle`
825        UnexpectedReceiptGap,
826        /// Expecting receipt gap when validating `submit_receipt`
827        ExpectingReceiptGap,
828        /// Failed to get missed domain runtime upgrade count
829        FailedToGetMissedUpgradeCount,
830        /// Bundle version mismatch
831        BundleVersionMismatch,
832        /// Execution receipt version mismatch
833        ExecutionVersionMismatch,
834        /// Execution receipt version missing
835        ExecutionVersionMissing,
836    }
837
838    #[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq)]
839    pub enum FraudProofError {
840        /// The targeted bad receipt not found which may already pruned by other
841        /// fraud proof or the fraud proof is submitted to the wrong fork.
842        BadReceiptNotFound,
843        /// The genesis receipt is unchallengeable.
844        ChallengingGenesisReceipt,
845        /// The descendants of the fraudulent ER is not pruned
846        DescendantsOfFraudulentERNotPruned,
847        /// Invalid fraud proof since block fees are not mismatched.
848        InvalidBlockFeesFraudProof,
849        /// Invalid fraud proof since transfers are not mismatched.
850        InvalidTransfersFraudProof,
851        /// Invalid domain block hash fraud proof.
852        InvalidDomainBlockHashFraudProof,
853        /// Invalid domain extrinsic fraud proof
854        InvalidExtrinsicRootFraudProof,
855        /// Invalid state transition fraud proof
856        InvalidStateTransitionFraudProof,
857        /// Parent receipt not found.
858        ParentReceiptNotFound,
859        /// Invalid bundles fraud proof
860        InvalidBundleFraudProof,
861        /// Bad/Invalid valid bundle fraud proof
862        BadValidBundleFraudProof,
863        /// Missing operator.
864        MissingOperator,
865        /// Unexpected fraud proof.
866        UnexpectedFraudProof,
867        /// The bad receipt already reported by a previous fraud proof
868        BadReceiptAlreadyReported,
869        /// Bad MMR proof, it may due to the proof is expired or it is generated against a different fork.
870        BadMmrProof,
871        /// Unexpected MMR proof
872        UnexpectedMmrProof,
873        /// Missing MMR proof
874        MissingMmrProof,
875        /// Domain runtime not found
876        RuntimeNotFound,
877        /// The domain runtime code proof is not provided
878        DomainRuntimeCodeProofNotFound,
879        /// The domain runtime code proof is unexpected
880        UnexpectedDomainRuntimeCodeProof,
881        /// The storage proof is invalid
882        StorageProof(storage_proof::VerificationError),
883    }
884
885    impl From<BundleError> for TransactionValidity {
886        fn from(e: BundleError) -> Self {
887            if BundleError::UnableToPayBundleStorageFee == e {
888                InvalidTransactionCode::BundleStorageFeePayment.into()
889            } else if let BundleError::Receipt(_) = e {
890                InvalidTransactionCode::ExecutionReceipt.into()
891            } else {
892                InvalidTransactionCode::Bundle.into()
893            }
894        }
895    }
896
897    impl From<storage_proof::VerificationError> for FraudProofError {
898        fn from(err: storage_proof::VerificationError) -> Self {
899            FraudProofError::StorageProof(err)
900        }
901    }
902
903    impl<T> From<FraudProofError> for Error<T> {
904        fn from(err: FraudProofError) -> Self {
905            Error::FraudProof(err)
906        }
907    }
908
909    impl<T> From<RuntimeRegistryError> for Error<T> {
910        fn from(err: RuntimeRegistryError) -> Self {
911            Error::RuntimeRegistry(err)
912        }
913    }
914
915    impl<T> From<StakingError> for Error<T> {
916        fn from(err: StakingError) -> Self {
917            Error::Staking(err)
918        }
919    }
920
921    impl<T> From<StakingEpochError> for Error<T> {
922        fn from(err: StakingEpochError) -> Self {
923            Error::StakingEpoch(err)
924        }
925    }
926
927    impl<T> From<DomainRegistryError> for Error<T> {
928        fn from(err: DomainRegistryError) -> Self {
929            Error::DomainRegistry(err)
930        }
931    }
932
933    impl<T> From<BlockTreeError> for Error<T> {
934        fn from(err: BlockTreeError) -> Self {
935            Error::BlockTree(err)
936        }
937    }
938
939    impl From<ProofOfElectionError> for BundleError {
940        fn from(err: ProofOfElectionError) -> Self {
941            match err {
942                ProofOfElectionError::BadVrfProof => Self::BadVrfSignature,
943                ProofOfElectionError::ThresholdUnsatisfied => Self::ThresholdUnsatisfied,
944                ProofOfElectionError::InvalidThreshold => Self::InvalidThreshold,
945            }
946        }
947    }
948
949    impl<T> From<BundleStorageFundError> for Error<T> {
950        fn from(err: BundleStorageFundError) -> Self {
951            Error::BundleStorageFund(err)
952        }
953    }
954
955    #[pallet::error]
956    pub enum Error<T> {
957        /// Invalid fraud proof.
958        FraudProof(FraudProofError),
959        /// Runtime registry specific errors
960        RuntimeRegistry(RuntimeRegistryError),
961        /// Staking related errors.
962        Staking(StakingError),
963        /// Staking epoch specific errors.
964        StakingEpoch(StakingEpochError),
965        /// Domain registry specific errors
966        DomainRegistry(DomainRegistryError),
967        /// Block tree specific errors
968        BlockTree(BlockTreeError),
969        /// Bundle storage fund specific errors
970        BundleStorageFund(BundleStorageFundError),
971        /// Permissioned action is not allowed by the caller.
972        PermissionedActionNotAllowed,
973        /// Domain Sudo call already exists.
974        DomainSudoCallExists,
975        /// Invalid Domain sudo call.
976        InvalidDomainSudoCall,
977        /// Domain must be frozen before execution receipt can be pruned.
978        DomainNotFrozen,
979        /// Domain is not a private EVM domain.
980        NotPrivateEvmDomain,
981        /// Account is not a Domain owner or root.
982        NotDomainOwnerOrRoot,
983        /// EVM Domain "set contract creation allowed by" call already exists.
984        EvmDomainContractCreationAllowedByCallExists,
985    }
986
987    /// Reason for slashing an operator
988    #[derive(Clone, Debug, PartialEq, Encode, Decode, TypeInfo)]
989    pub enum SlashedReason<DomainBlock, ReceiptHash> {
990        /// Operator produced bad bundle.
991        InvalidBundle(DomainBlock),
992        /// Operator submitted bad Execution receipt.
993        BadExecutionReceipt(ReceiptHash),
994    }
995
996    #[pallet::event]
997    #[pallet::generate_deposit(pub (super) fn deposit_event)]
998    pub enum Event<T: Config> {
999        /// A domain bundle was included.
1000        BundleStored {
1001            domain_id: DomainId,
1002            bundle_hash: H256,
1003            bundle_author: OperatorId,
1004        },
1005        DomainRuntimeCreated {
1006            runtime_id: RuntimeId,
1007            runtime_type: RuntimeType,
1008        },
1009        DomainRuntimeUpgradeScheduled {
1010            runtime_id: RuntimeId,
1011            scheduled_at: BlockNumberFor<T>,
1012        },
1013        DomainRuntimeUpgraded {
1014            runtime_id: RuntimeId,
1015        },
1016        OperatorRegistered {
1017            operator_id: OperatorId,
1018            domain_id: DomainId,
1019        },
1020        NominatedStakedUnlocked {
1021            operator_id: OperatorId,
1022            nominator_id: NominatorId<T>,
1023            unlocked_amount: BalanceOf<T>,
1024        },
1025        StorageFeeUnlocked {
1026            operator_id: OperatorId,
1027            nominator_id: NominatorId<T>,
1028            storage_fee: BalanceOf<T>,
1029        },
1030        OperatorNominated {
1031            operator_id: OperatorId,
1032            nominator_id: NominatorId<T>,
1033            amount: BalanceOf<T>,
1034        },
1035        DomainInstantiated {
1036            domain_id: DomainId,
1037        },
1038        OperatorSwitchedDomain {
1039            old_domain_id: DomainId,
1040            new_domain_id: DomainId,
1041        },
1042        OperatorDeregistered {
1043            operator_id: OperatorId,
1044        },
1045        NominatorUnlocked {
1046            operator_id: OperatorId,
1047            nominator_id: NominatorId<T>,
1048        },
1049        WithdrewStake {
1050            operator_id: OperatorId,
1051            nominator_id: NominatorId<T>,
1052        },
1053        PreferredOperator {
1054            operator_id: OperatorId,
1055            nominator_id: NominatorId<T>,
1056        },
1057        OperatorRewarded {
1058            source: OperatorRewardSource<BlockNumberFor<T>>,
1059            operator_id: OperatorId,
1060            reward: BalanceOf<T>,
1061        },
1062        OperatorTaxCollected {
1063            operator_id: OperatorId,
1064            tax: BalanceOf<T>,
1065        },
1066        DomainEpochCompleted {
1067            domain_id: DomainId,
1068            completed_epoch_index: EpochIndex,
1069        },
1070        ForceDomainEpochTransition {
1071            domain_id: DomainId,
1072            completed_epoch_index: EpochIndex,
1073        },
1074        FraudProofProcessed {
1075            domain_id: DomainId,
1076            new_head_receipt_number: Option<DomainBlockNumberFor<T>>,
1077        },
1078        DomainOperatorAllowListUpdated {
1079            domain_id: DomainId,
1080        },
1081        OperatorSlashed {
1082            operator_id: OperatorId,
1083            reason: SlashedReason<DomainBlockNumberFor<T>, ReceiptHashFor<T>>,
1084        },
1085        StorageFeeDeposited {
1086            operator_id: OperatorId,
1087            nominator_id: NominatorId<T>,
1088            amount: BalanceOf<T>,
1089        },
1090        DomainFrozen {
1091            domain_id: DomainId,
1092        },
1093        DomainUnfrozen {
1094            domain_id: DomainId,
1095        },
1096        PrunedExecutionReceipt {
1097            domain_id: DomainId,
1098            new_head_receipt_number: Option<DomainBlockNumberFor<T>>,
1099        },
1100    }
1101
1102    #[pallet::origin]
1103    pub type Origin = RawOrigin;
1104
1105    /// Per-domain state for tx range calculation.
1106    #[derive(Debug, Default, Decode, Encode, TypeInfo, PartialEq, Eq)]
1107    pub struct TxRangeState {
1108        /// Current tx range.
1109        pub tx_range: U256,
1110
1111        /// Blocks in the current adjustment interval.
1112        pub interval_blocks: u64,
1113
1114        /// Bundles in the current adjustment interval.
1115        pub interval_bundles: u64,
1116    }
1117
1118    impl TxRangeState {
1119        /// Called when a bundle is added to the current block.
1120        pub fn on_bundle(&mut self) {
1121            self.interval_bundles += 1;
1122        }
1123    }
1124
1125    #[pallet::storage]
1126    pub(super) type DomainTxRangeState<T: Config> =
1127        StorageMap<_, Identity, DomainId, TxRangeState, OptionQuery>;
1128
1129    #[pallet::call]
1130    impl<T: Config> Pallet<T> {
1131        #[pallet::call_index(0)]
1132        #[pallet::weight(Pallet::<T>::max_submit_bundle_weight())]
1133        pub fn submit_bundle(
1134            origin: OriginFor<T>,
1135            opaque_bundle: OpaqueBundleOf<T>,
1136        ) -> DispatchResultWithPostInfo {
1137            T::DomainOrigin::ensure_origin(origin)?;
1138
1139            log::trace!("Processing bundle: {opaque_bundle:?}");
1140
1141            let domain_id = opaque_bundle.domain_id();
1142            let bundle_hash = opaque_bundle.hash();
1143            let bundle_header_hash = opaque_bundle.sealed_header().pre_hash();
1144            let extrinsics_root = opaque_bundle.extrinsics_root();
1145            let operator_id = opaque_bundle.operator_id();
1146            let bundle_size = opaque_bundle.size();
1147            let slot_number = opaque_bundle.slot_number();
1148            let receipt = opaque_bundle.into_receipt();
1149            #[cfg_attr(feature = "runtime-benchmarks", allow(unused_variables))]
1150            let receipt_block_number = *receipt.domain_block_number();
1151
1152            #[cfg(not(feature = "runtime-benchmarks"))]
1153            let mut actual_weight = T::WeightInfo::submit_bundle();
1154            #[cfg(feature = "runtime-benchmarks")]
1155            let actual_weight = T::WeightInfo::submit_bundle();
1156
1157            match execution_receipt_type::<T>(domain_id, &receipt.as_execution_receipt_ref()) {
1158                ReceiptType::Rejected(rejected_receipt_type) => {
1159                    return Err(Error::<T>::BlockTree(rejected_receipt_type.into()).into());
1160                }
1161                // Add the exeuction receipt to the block tree
1162                ReceiptType::Accepted(accepted_receipt_type) => {
1163                    // Before adding the new head receipt to the block tree, try to prune any previous
1164                    // bad ER at the same domain block and slash the submitter.
1165                    //
1166                    // NOTE: Skip the following staking related operations when benchmarking the
1167                    // `submit_bundle` call, these operations will be benchmarked separately.
1168                    #[cfg(not(feature = "runtime-benchmarks"))]
1169                    if accepted_receipt_type == AcceptedReceiptType::NewHead
1170                        && let Some(BlockTreeNode {
1171                            execution_receipt,
1172                            operator_ids,
1173                        }) = prune_receipt::<T>(domain_id, receipt_block_number)
1174                            .map_err(Error::<T>::from)?
1175                    {
1176                        actual_weight = actual_weight.saturating_add(
1177                            T::WeightInfo::handle_bad_receipt(operator_ids.len() as u32),
1178                        );
1179
1180                        let bad_receipt_hash = execution_receipt.hash::<DomainHashingFor<T>>();
1181                        do_mark_operators_as_slashed::<T>(
1182                            operator_ids.into_iter(),
1183                            SlashedReason::BadExecutionReceipt(bad_receipt_hash),
1184                        )
1185                        .map_err(Error::<T>::from)?;
1186
1187                        do_unmark_invalid_bundle_authors::<T>(domain_id, &execution_receipt)
1188                            .map_err(Error::<T>::from)?;
1189                    }
1190
1191                    if accepted_receipt_type == AcceptedReceiptType::NewHead {
1192                        // when a new receipt is accepted and extending the chain,
1193                        // also mark the invalid bundle authors from this er
1194                        do_mark_invalid_bundle_authors::<T>(domain_id, &receipt)
1195                            .map_err(Error::<T>::Staking)?;
1196                    }
1197
1198                    #[cfg_attr(feature = "runtime-benchmarks", allow(unused_variables))]
1199                    let maybe_confirmed_domain_block_info = process_execution_receipt::<T>(
1200                        domain_id,
1201                        operator_id,
1202                        receipt,
1203                        accepted_receipt_type,
1204                    )
1205                    .map_err(Error::<T>::from)?;
1206
1207                    // If any domain block is confirmed, then we have a new head added
1208                    // so distribute the operator rewards and, if required, do epoch transition as well.
1209                    //
1210                    // NOTE: Skip the following staking related operations when benchmarking the
1211                    // `submit_bundle` call, these operations will be benchmarked separately.
1212                    #[cfg(not(feature = "runtime-benchmarks"))]
1213                    if let Some(confirmed_block_info) = maybe_confirmed_domain_block_info {
1214                        actual_weight =
1215                            actual_weight.saturating_add(T::WeightInfo::confirm_domain_block(
1216                                confirmed_block_info.operator_ids.len() as u32,
1217                                confirmed_block_info.invalid_bundle_authors.len() as u32,
1218                            ));
1219
1220                        refund_storage_fee::<T>(
1221                            confirmed_block_info.total_storage_fee,
1222                            confirmed_block_info.paid_bundle_storage_fees,
1223                        )
1224                        .map_err(Error::<T>::from)?;
1225
1226                        do_reward_operators::<T>(
1227                            domain_id,
1228                            OperatorRewardSource::Bundle {
1229                                at_block_number: confirmed_block_info.consensus_block_number,
1230                            },
1231                            confirmed_block_info.operator_ids.into_iter(),
1232                            confirmed_block_info.rewards,
1233                        )
1234                        .map_err(Error::<T>::from)?;
1235
1236                        do_mark_operators_as_slashed::<T>(
1237                            confirmed_block_info.invalid_bundle_authors.into_iter(),
1238                            SlashedReason::InvalidBundle(confirmed_block_info.domain_block_number),
1239                        )
1240                        .map_err(Error::<T>::from)?;
1241                    }
1242                }
1243            }
1244
1245            // `SuccessfulBundles` is empty means this is the first accepted bundle for this domain in this
1246            // consensus block, which also mean a domain block will be produced thus update `HeadDomainNumber`
1247            // to this domain block's block number.
1248            if SuccessfulBundles::<T>::get(domain_id).is_empty() {
1249                // Domain runtime upgrade is forced to happen, even if there is no bundle submitted for a given domain
1250                // it will still derive a domain block for the upgrade, so we need to increase the `HeadDomainNumber`
1251                // by the number of runtime upgrades that happened since the last block, to account for these blocks.
1252                //
1253                // NOTE: if a domain runtime upgrade happened in the current block it won't be accounted for in
1254                // `missed_upgrade` because `DomainRuntimeUpgradeRecords` is updated in the next block's initialization.
1255                let missed_upgrade =
1256                    Self::missed_domain_runtime_upgrade(domain_id).map_err(Error::<T>::from)?;
1257
1258                let next_number = HeadDomainNumber::<T>::get(domain_id)
1259                    .checked_add(&One::one())
1260                    .ok_or::<Error<T>>(BlockTreeError::MaxHeadDomainNumber.into())?
1261                    .checked_add(&missed_upgrade.into())
1262                    .ok_or::<Error<T>>(BlockTreeError::MaxHeadDomainNumber.into())?;
1263
1264                // Trigger an epoch transition, if needed, at the first bundle in the block
1265                #[cfg(not(feature = "runtime-benchmarks"))]
1266                if next_number % T::StakeEpochDuration::get() == Zero::zero() {
1267                    let epoch_transition_res = do_finalize_domain_current_epoch::<T>(domain_id)
1268                        .map_err(Error::<T>::from)?;
1269
1270                    Self::deposit_event(Event::DomainEpochCompleted {
1271                        domain_id,
1272                        completed_epoch_index: epoch_transition_res.completed_epoch_index,
1273                    });
1274
1275                    actual_weight = actual_weight
1276                        .saturating_add(Self::actual_epoch_transition_weight(epoch_transition_res));
1277                }
1278
1279                HeadDomainNumber::<T>::set(domain_id, next_number);
1280            }
1281
1282            // Put the `extrinsics_root` into the inbox of the current domain block being built
1283            let head_domain_number = HeadDomainNumber::<T>::get(domain_id);
1284            let consensus_block_number = frame_system::Pallet::<T>::current_block_number();
1285            ExecutionInbox::<T>::append(
1286                (domain_id, head_domain_number, consensus_block_number),
1287                BundleDigest {
1288                    header_hash: bundle_header_hash,
1289                    extrinsics_root,
1290                    size: bundle_size,
1291                },
1292            );
1293
1294            InboxedBundleAuthor::<T>::insert(bundle_header_hash, operator_id);
1295
1296            SuccessfulBundles::<T>::append(domain_id, bundle_hash);
1297
1298            OperatorBundleSlot::<T>::mutate(operator_id, |slot_set| slot_set.insert(slot_number));
1299
1300            // slash operators who are in pending slash
1301            #[cfg(not(feature = "runtime-benchmarks"))]
1302            {
1303                let slashed_nominator_count =
1304                    do_slash_operator::<T>(domain_id, MAX_NOMINATORS_TO_SLASH)
1305                        .map_err(Error::<T>::from)?;
1306                actual_weight = actual_weight
1307                    .saturating_add(T::WeightInfo::slash_operator(slashed_nominator_count));
1308            }
1309
1310            Self::deposit_event(Event::BundleStored {
1311                domain_id,
1312                bundle_hash,
1313                bundle_author: operator_id,
1314            });
1315
1316            // Ensure the returned weight not exceed the maximum weight in the `pallet::weight`
1317            Ok(Some(actual_weight.min(Self::max_submit_bundle_weight())).into())
1318        }
1319
1320        #[pallet::call_index(15)]
1321        #[pallet::weight((
1322            T::WeightInfo::submit_fraud_proof().saturating_add(
1323                T::WeightInfo::handle_bad_receipt(MAX_BUNDLE_PER_BLOCK)
1324            ),
1325            DispatchClass::Operational
1326        ))]
1327        pub fn submit_fraud_proof(
1328            origin: OriginFor<T>,
1329            fraud_proof: Box<FraudProofFor<T>>,
1330        ) -> DispatchResultWithPostInfo {
1331            T::DomainOrigin::ensure_origin(origin)?;
1332
1333            log::trace!("Processing fraud proof: {fraud_proof:?}");
1334
1335            #[cfg(not(feature = "runtime-benchmarks"))]
1336            let mut actual_weight = T::WeightInfo::submit_fraud_proof();
1337            #[cfg(feature = "runtime-benchmarks")]
1338            let actual_weight = T::WeightInfo::submit_fraud_proof();
1339
1340            let domain_id = fraud_proof.domain_id();
1341            let bad_receipt_hash = fraud_proof.targeted_bad_receipt_hash();
1342            let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
1343            let bad_receipt_number = *BlockTreeNodes::<T>::get(bad_receipt_hash)
1344                .ok_or::<Error<T>>(FraudProofError::BadReceiptNotFound.into())?
1345                .execution_receipt
1346                .domain_block_number();
1347            // The `head_receipt_number` must greater than or equal to any existing receipt, including
1348            // the bad receipt, otherwise the fraud proof should be rejected due to `BadReceiptNotFound`,
1349            // double check here to make it more robust.
1350            ensure!(
1351                head_receipt_number >= bad_receipt_number,
1352                Error::<T>::from(FraudProofError::BadReceiptNotFound),
1353            );
1354
1355            // Prune the bad ER and slash the submitter, the descendants of the bad ER (i.e. all ERs in
1356            // `[bad_receipt_number + 1..head_receipt_number]` ) and the corresponding submitter will be
1357            // pruned/slashed lazily as the domain progressed.
1358            //
1359            // NOTE: Skip the following staking related operations when benchmarking the
1360            // `submit_fraud_proof` call, these operations will be benchmarked separately.
1361            #[cfg(not(feature = "runtime-benchmarks"))]
1362            {
1363                let BlockTreeNode {
1364                    execution_receipt,
1365                    operator_ids,
1366                } = prune_receipt::<T>(domain_id, bad_receipt_number)
1367                    .map_err(Error::<T>::from)?
1368                    .ok_or::<Error<T>>(FraudProofError::BadReceiptNotFound.into())?;
1369
1370                actual_weight = actual_weight.saturating_add(T::WeightInfo::handle_bad_receipt(
1371                    (operator_ids.len() as u32).min(MAX_BUNDLE_PER_BLOCK),
1372                ));
1373
1374                do_mark_operators_as_slashed::<T>(
1375                    operator_ids.into_iter(),
1376                    SlashedReason::BadExecutionReceipt(bad_receipt_hash),
1377                )
1378                .map_err(Error::<T>::from)?;
1379
1380                // unmark any operators who are incorrectly marked as invalid bundle authors.
1381                do_unmark_invalid_bundle_authors::<T>(domain_id, &execution_receipt)
1382                    .map_err(Error::<T>::Staking)?;
1383            }
1384
1385            // Update the head receipt number to `bad_receipt_number - 1`
1386            let new_head_receipt_number = bad_receipt_number.saturating_sub(One::one());
1387            HeadReceiptNumber::<T>::insert(domain_id, new_head_receipt_number);
1388
1389            Self::deposit_event(Event::FraudProofProcessed {
1390                domain_id,
1391                new_head_receipt_number: Some(new_head_receipt_number),
1392            });
1393
1394            Ok(Some(actual_weight).into())
1395        }
1396
1397        #[pallet::call_index(2)]
1398        #[pallet::weight(T::WeightInfo::register_domain_runtime())]
1399        pub fn register_domain_runtime(
1400            origin: OriginFor<T>,
1401            runtime_name: String,
1402            runtime_type: RuntimeType,
1403            // TODO: we can use `RawGenesis` as argument directly to avoid decoding but the in tool like
1404            // `polkadot.js` it will require the user to provide each field of the struct type and not
1405            // support uploading file, which is bad UX.
1406            raw_genesis_storage: Vec<u8>,
1407        ) -> DispatchResult {
1408            ensure_root(origin)?;
1409
1410            let block_number = frame_system::Pallet::<T>::current_block_number();
1411            let runtime_id = do_register_runtime::<T>(
1412                runtime_name,
1413                runtime_type,
1414                raw_genesis_storage,
1415                block_number,
1416            )
1417            .map_err(Error::<T>::from)?;
1418
1419            Self::deposit_event(Event::DomainRuntimeCreated {
1420                runtime_id,
1421                runtime_type,
1422            });
1423
1424            Ok(())
1425        }
1426
1427        #[pallet::call_index(3)]
1428        #[pallet::weight(T::WeightInfo::upgrade_domain_runtime())]
1429        pub fn upgrade_domain_runtime(
1430            origin: OriginFor<T>,
1431            runtime_id: RuntimeId,
1432            raw_genesis_storage: Vec<u8>,
1433        ) -> DispatchResult {
1434            ensure_root(origin)?;
1435
1436            let block_number = frame_system::Pallet::<T>::current_block_number();
1437            let scheduled_at =
1438                do_schedule_runtime_upgrade::<T>(runtime_id, raw_genesis_storage, block_number)
1439                    .map_err(Error::<T>::from)?;
1440
1441            Self::deposit_event(Event::DomainRuntimeUpgradeScheduled {
1442                runtime_id,
1443                scheduled_at,
1444            });
1445
1446            Ok(())
1447        }
1448
1449        #[pallet::call_index(4)]
1450        #[pallet::weight(T::WeightInfo::register_operator())]
1451        pub fn register_operator(
1452            origin: OriginFor<T>,
1453            domain_id: DomainId,
1454            amount: BalanceOf<T>,
1455            config: OperatorConfig<BalanceOf<T>>,
1456        ) -> DispatchResult {
1457            let owner = ensure_signed(origin)?;
1458
1459            let (operator_id, current_epoch_index) =
1460                do_register_operator::<T>(owner, domain_id, amount, config)
1461                    .map_err(Error::<T>::from)?;
1462
1463            Self::deposit_event(Event::OperatorRegistered {
1464                operator_id,
1465                domain_id,
1466            });
1467
1468            // if the domain's current epoch is 0,
1469            // then do an epoch transition so that operator can start producing bundles
1470            if current_epoch_index.is_zero() {
1471                do_finalize_domain_current_epoch::<T>(domain_id).map_err(Error::<T>::from)?;
1472            }
1473
1474            Ok(())
1475        }
1476
1477        #[pallet::call_index(5)]
1478        #[pallet::weight(T::WeightInfo::nominate_operator())]
1479        pub fn nominate_operator(
1480            origin: OriginFor<T>,
1481            operator_id: OperatorId,
1482            amount: BalanceOf<T>,
1483        ) -> DispatchResult {
1484            let nominator_id = ensure_signed(origin)?;
1485
1486            do_nominate_operator::<T>(operator_id, nominator_id.clone(), amount)
1487                .map_err(Error::<T>::from)?;
1488
1489            Ok(())
1490        }
1491
1492        #[pallet::call_index(6)]
1493        #[pallet::weight(T::WeightInfo::instantiate_domain())]
1494        pub fn instantiate_domain(
1495            origin: OriginFor<T>,
1496            domain_config_params: DomainConfigParams<T::AccountId, BalanceOf<T>>,
1497        ) -> DispatchResult {
1498            let who = ensure_signed(origin)?;
1499            ensure!(
1500                PermissionedActionAllowedBy::<T>::get()
1501                    .map(|allowed_by| allowed_by.is_allowed(&who))
1502                    .unwrap_or_default(),
1503                Error::<T>::PermissionedActionNotAllowed
1504            );
1505
1506            let created_at = frame_system::Pallet::<T>::current_block_number();
1507
1508            let domain_id = do_instantiate_domain::<T>(domain_config_params, who, created_at)
1509                .map_err(Error::<T>::from)?;
1510
1511            Self::deposit_event(Event::DomainInstantiated { domain_id });
1512
1513            Ok(())
1514        }
1515
1516        #[pallet::call_index(8)]
1517        #[pallet::weight(T::WeightInfo::deregister_operator())]
1518        pub fn deregister_operator(
1519            origin: OriginFor<T>,
1520            operator_id: OperatorId,
1521        ) -> DispatchResult {
1522            let who = ensure_signed(origin)?;
1523
1524            do_deregister_operator::<T>(who, operator_id).map_err(Error::<T>::from)?;
1525
1526            Self::deposit_event(Event::OperatorDeregistered { operator_id });
1527
1528            Ok(())
1529        }
1530
1531        #[pallet::call_index(9)]
1532        #[pallet::weight(T::WeightInfo::withdraw_stake())]
1533        pub fn withdraw_stake(
1534            origin: OriginFor<T>,
1535            operator_id: OperatorId,
1536            to_withdraw: T::Share,
1537        ) -> DispatchResult {
1538            let who = ensure_signed(origin)?;
1539
1540            do_withdraw_stake::<T>(operator_id, who.clone(), to_withdraw)
1541                .map_err(Error::<T>::from)?;
1542
1543            Self::deposit_event(Event::WithdrewStake {
1544                operator_id,
1545                nominator_id: who,
1546            });
1547
1548            Ok(())
1549        }
1550
1551        /// Unlocks the first withdrawal given the unlocking period is complete.
1552        /// Even if the rest of the withdrawals are out of the unlocking period, the nominator
1553        /// should call this extrinsic to unlock each withdrawal
1554        #[pallet::call_index(10)]
1555        #[pallet::weight(T::WeightInfo::unlock_funds(T::WithdrawalLimit::get()))]
1556        pub fn unlock_funds(
1557            origin: OriginFor<T>,
1558            operator_id: OperatorId,
1559        ) -> DispatchResultWithPostInfo {
1560            let nominator_id = ensure_signed(origin)?;
1561            let withdrawal_count = do_unlock_funds::<T>(operator_id, nominator_id.clone())
1562                .map_err(crate::pallet::Error::<T>::from)?;
1563
1564            Ok(Some(T::WeightInfo::unlock_funds(
1565                withdrawal_count.min(T::WithdrawalLimit::get()),
1566            ))
1567            .into())
1568        }
1569
1570        /// Unlocks the nominator under given operator given the unlocking period is complete.
1571        /// A nominator can initiate their unlock given operator is already deregistered.
1572        #[pallet::call_index(11)]
1573        #[pallet::weight(T::WeightInfo::unlock_nominator())]
1574        pub fn unlock_nominator(origin: OriginFor<T>, operator_id: OperatorId) -> DispatchResult {
1575            let nominator = ensure_signed(origin)?;
1576
1577            do_unlock_nominator::<T>(operator_id, nominator.clone())
1578                .map_err(crate::pallet::Error::<T>::from)?;
1579
1580            Self::deposit_event(Event::NominatorUnlocked {
1581                operator_id,
1582                nominator_id: nominator,
1583            });
1584
1585            Ok(())
1586        }
1587
1588        /// Extrinsic to update domain's operator allow list.
1589        /// Note:
1590        /// - If the previous allowed list is set to specific operators and new allow list is set
1591        ///   to `Anyone`, then domain will become permissioned to open for all operators.
1592        /// - If the previous allowed list is set to `Anyone` or specific operators and the new
1593        ///   allow list is set to specific operators, then all the registered not allowed operators
1594        ///   will continue to operate until they de-register themselves.
1595        #[pallet::call_index(12)]
1596        #[pallet::weight(T::WeightInfo::update_domain_operator_allow_list())]
1597        pub fn update_domain_operator_allow_list(
1598            origin: OriginFor<T>,
1599            domain_id: DomainId,
1600            operator_allow_list: OperatorAllowList<T::AccountId>,
1601        ) -> DispatchResult {
1602            let who = ensure_signed(origin)?;
1603            do_update_domain_allow_list::<T>(who, domain_id, operator_allow_list)
1604                .map_err(Error::<T>::from)?;
1605            Self::deposit_event(crate::pallet::Event::DomainOperatorAllowListUpdated { domain_id });
1606            Ok(())
1607        }
1608
1609        /// Force staking epoch transition for a given domain
1610        #[pallet::call_index(13)]
1611        #[pallet::weight(Pallet::<T>::max_staking_epoch_transition())]
1612        pub fn force_staking_epoch_transition(
1613            origin: OriginFor<T>,
1614            domain_id: DomainId,
1615        ) -> DispatchResultWithPostInfo {
1616            ensure_root(origin)?;
1617
1618            let epoch_transition_res =
1619                do_finalize_domain_current_epoch::<T>(domain_id).map_err(Error::<T>::from)?;
1620
1621            Self::deposit_event(Event::ForceDomainEpochTransition {
1622                domain_id,
1623                completed_epoch_index: epoch_transition_res.completed_epoch_index,
1624            });
1625
1626            // Ensure the returned weight not exceed the maximum weight in the `pallet::weight`
1627            let actual_weight = Self::actual_epoch_transition_weight(epoch_transition_res)
1628                .min(Self::max_staking_epoch_transition());
1629
1630            Ok(Some(actual_weight).into())
1631        }
1632
1633        /// Update permissioned action allowed by storage by Sudo.
1634        #[pallet::call_index(14)]
1635        #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(0, 1))]
1636        pub fn set_permissioned_action_allowed_by(
1637            origin: OriginFor<T>,
1638            permissioned_action_allowed_by: sp_domains::PermissionedActionAllowedBy<T::AccountId>,
1639        ) -> DispatchResult {
1640            ensure_root(origin)?;
1641            PermissionedActionAllowedBy::<T>::put(permissioned_action_allowed_by);
1642            Ok(())
1643        }
1644
1645        /// Submit a domain sudo call.
1646        #[pallet::call_index(16)]
1647        #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(3, 1))]
1648        pub fn send_domain_sudo_call(
1649            origin: OriginFor<T>,
1650            domain_id: DomainId,
1651            call: Vec<u8>,
1652        ) -> DispatchResult {
1653            ensure_root(origin)?;
1654            ensure!(
1655                DomainSudoCalls::<T>::get(domain_id).maybe_call.is_none(),
1656                Error::<T>::DomainSudoCallExists
1657            );
1658
1659            let domain_runtime = Self::domain_runtime_code(domain_id).ok_or(
1660                Error::<T>::DomainRegistry(DomainRegistryError::DomainNotFound),
1661            )?;
1662            ensure!(
1663                domain_runtime_call(
1664                    domain_runtime,
1665                    StatelessDomainRuntimeCall::IsValidDomainSudoCall(call.clone()),
1666                )
1667                .unwrap_or(false),
1668                Error::<T>::InvalidDomainSudoCall
1669            );
1670
1671            DomainSudoCalls::<T>::set(
1672                domain_id,
1673                DomainSudoCall {
1674                    maybe_call: Some(call),
1675                },
1676            );
1677            Ok(())
1678        }
1679
1680        /// Freezes a given domain.
1681        /// A frozen domain does not accept new bundles but accepts fraud proofs.
1682        #[pallet::call_index(17)]
1683        #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(0, 1))]
1684        pub fn freeze_domain(origin: OriginFor<T>, domain_id: DomainId) -> DispatchResult {
1685            ensure_root(origin)?;
1686            FrozenDomains::<T>::mutate(|frozen_domains| frozen_domains.insert(domain_id));
1687            Self::deposit_event(Event::DomainFrozen { domain_id });
1688            Ok(())
1689        }
1690
1691        /// Unfreezes a frozen domain.
1692        #[pallet::call_index(18)]
1693        #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(0, 1))]
1694        pub fn unfreeze_domain(origin: OriginFor<T>, domain_id: DomainId) -> DispatchResult {
1695            ensure_root(origin)?;
1696            FrozenDomains::<T>::mutate(|frozen_domains| frozen_domains.remove(&domain_id));
1697            Self::deposit_event(Event::DomainUnfrozen { domain_id });
1698            Ok(())
1699        }
1700
1701        /// Prunes a given execution receipt for given frozen domain.
1702        /// This call assumes the execution receipt to be bad and implicitly trusts Sudo
1703        /// to do the necessary validation of the ER before dispatching this call.
1704        #[pallet::call_index(19)]
1705        #[pallet::weight(Pallet::<T>::max_prune_domain_execution_receipt())]
1706        pub fn prune_domain_execution_receipt(
1707            origin: OriginFor<T>,
1708            domain_id: DomainId,
1709            bad_receipt_hash: ReceiptHashFor<T>,
1710        ) -> DispatchResultWithPostInfo {
1711            ensure_root(origin)?;
1712            ensure!(
1713                FrozenDomains::<T>::get().contains(&domain_id),
1714                Error::<T>::DomainNotFrozen
1715            );
1716
1717            let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
1718            let bad_receipt_number = *BlockTreeNodes::<T>::get(bad_receipt_hash)
1719                .ok_or::<Error<T>>(FraudProofError::BadReceiptNotFound.into())?
1720                .execution_receipt
1721                .domain_block_number();
1722            // The `head_receipt_number` must greater than or equal to any existing receipt, including
1723            // the bad receipt.
1724            ensure!(
1725                head_receipt_number >= bad_receipt_number,
1726                Error::<T>::from(FraudProofError::BadReceiptNotFound),
1727            );
1728
1729            let mut actual_weight = T::DbWeight::get().reads(3);
1730
1731            // prune the bad ER
1732            let BlockTreeNode {
1733                execution_receipt,
1734                operator_ids,
1735            } = prune_receipt::<T>(domain_id, bad_receipt_number)
1736                .map_err(Error::<T>::from)?
1737                .ok_or::<Error<T>>(FraudProofError::BadReceiptNotFound.into())?;
1738
1739            actual_weight = actual_weight.saturating_add(T::WeightInfo::handle_bad_receipt(
1740                (operator_ids.len() as u32).min(MAX_BUNDLE_PER_BLOCK),
1741            ));
1742
1743            do_mark_operators_as_slashed::<T>(
1744                operator_ids.into_iter(),
1745                SlashedReason::BadExecutionReceipt(bad_receipt_hash),
1746            )
1747            .map_err(Error::<T>::from)?;
1748
1749            // unmark any operators who are incorrectly marked as invalid bundle authors.
1750            do_unmark_invalid_bundle_authors::<T>(domain_id, &execution_receipt)
1751                .map_err(Error::<T>::Staking)?;
1752
1753            // Update the head receipt number to `bad_receipt_number - 1`
1754            let new_head_receipt_number = bad_receipt_number.saturating_sub(One::one());
1755            HeadReceiptNumber::<T>::insert(domain_id, new_head_receipt_number);
1756            actual_weight = actual_weight.saturating_add(T::DbWeight::get().reads_writes(0, 1));
1757
1758            Self::deposit_event(Event::PrunedExecutionReceipt {
1759                domain_id,
1760                new_head_receipt_number: Some(new_head_receipt_number),
1761            });
1762
1763            Ok(Some(actual_weight).into())
1764        }
1765
1766        /// Transfer funds from treasury to given account
1767        #[pallet::call_index(20)]
1768        #[pallet::weight(T::WeightInfo::transfer_treasury_funds())]
1769        pub fn transfer_treasury_funds(
1770            origin: OriginFor<T>,
1771            account_id: T::AccountId,
1772            balance: BalanceOf<T>,
1773        ) -> DispatchResult {
1774            ensure_root(origin)?;
1775            T::Currency::transfer(
1776                &T::TreasuryAccount::get(),
1777                &account_id,
1778                balance,
1779                Preservation::Preserve,
1780            )?;
1781            Ok(())
1782        }
1783
1784        #[pallet::call_index(21)]
1785        #[pallet::weight(Pallet::<T>::max_submit_receipt_weight())]
1786        pub fn submit_receipt(
1787            origin: OriginFor<T>,
1788            singleton_receipt: SingletonReceiptOf<T>,
1789        ) -> DispatchResultWithPostInfo {
1790            T::DomainOrigin::ensure_origin(origin)?;
1791
1792            let domain_id = singleton_receipt.domain_id();
1793            let operator_id = singleton_receipt.operator_id();
1794            let receipt = singleton_receipt.into_receipt();
1795
1796            #[cfg(not(feature = "runtime-benchmarks"))]
1797            let mut actual_weight = T::WeightInfo::submit_receipt();
1798            #[cfg(feature = "runtime-benchmarks")]
1799            let actual_weight = T::WeightInfo::submit_receipt();
1800
1801            match execution_receipt_type::<T>(domain_id, &receipt.as_execution_receipt_ref()) {
1802                ReceiptType::Rejected(rejected_receipt_type) => {
1803                    return Err(Error::<T>::BlockTree(rejected_receipt_type.into()).into());
1804                }
1805                // Add the exeuctione receipt to the block tree
1806                ReceiptType::Accepted(accepted_receipt_type) => {
1807                    // Before adding the new head receipt to the block tree, try to prune any previous
1808                    // bad ER at the same domain block and slash the submitter.
1809                    //
1810                    // NOTE: Skip the following staking related operations when benchmarking the
1811                    // `submit_receipt` call, these operations will be benchmarked separately.
1812                    #[cfg(not(feature = "runtime-benchmarks"))]
1813                    if accepted_receipt_type == AcceptedReceiptType::NewHead
1814                        && let Some(BlockTreeNode {
1815                            execution_receipt,
1816                            operator_ids,
1817                        }) = prune_receipt::<T>(domain_id, *receipt.domain_block_number())
1818                            .map_err(Error::<T>::from)?
1819                    {
1820                        actual_weight = actual_weight.saturating_add(
1821                            T::WeightInfo::handle_bad_receipt(operator_ids.len() as u32),
1822                        );
1823
1824                        let bad_receipt_hash = execution_receipt.hash::<DomainHashingFor<T>>();
1825                        do_mark_operators_as_slashed::<T>(
1826                            operator_ids.into_iter(),
1827                            SlashedReason::BadExecutionReceipt(bad_receipt_hash),
1828                        )
1829                        .map_err(Error::<T>::from)?;
1830
1831                        do_unmark_invalid_bundle_authors::<T>(domain_id, &execution_receipt)
1832                            .map_err(Error::<T>::from)?;
1833                    }
1834
1835                    if accepted_receipt_type == AcceptedReceiptType::NewHead {
1836                        // when a new receipt is accepted and extending the chain,
1837                        // also mark the invalid bundle authors from this er
1838                        do_mark_invalid_bundle_authors::<T>(domain_id, &receipt)
1839                            .map_err(Error::<T>::Staking)?;
1840                    }
1841
1842                    #[cfg_attr(feature = "runtime-benchmarks", allow(unused_variables))]
1843                    let maybe_confirmed_domain_block_info = process_execution_receipt::<T>(
1844                        domain_id,
1845                        operator_id,
1846                        receipt,
1847                        accepted_receipt_type,
1848                    )
1849                    .map_err(Error::<T>::from)?;
1850
1851                    // NOTE: Skip the following staking related operations when benchmarking the
1852                    // `submit_receipt` call, these operations will be benchmarked separately.
1853                    #[cfg(not(feature = "runtime-benchmarks"))]
1854                    if let Some(confirmed_block_info) = maybe_confirmed_domain_block_info {
1855                        actual_weight =
1856                            actual_weight.saturating_add(T::WeightInfo::confirm_domain_block(
1857                                confirmed_block_info.operator_ids.len() as u32,
1858                                confirmed_block_info.invalid_bundle_authors.len() as u32,
1859                            ));
1860
1861                        refund_storage_fee::<T>(
1862                            confirmed_block_info.total_storage_fee,
1863                            confirmed_block_info.paid_bundle_storage_fees,
1864                        )
1865                        .map_err(Error::<T>::from)?;
1866
1867                        do_reward_operators::<T>(
1868                            domain_id,
1869                            OperatorRewardSource::Bundle {
1870                                at_block_number: confirmed_block_info.consensus_block_number,
1871                            },
1872                            confirmed_block_info.operator_ids.into_iter(),
1873                            confirmed_block_info.rewards,
1874                        )
1875                        .map_err(Error::<T>::from)?;
1876
1877                        do_mark_operators_as_slashed::<T>(
1878                            confirmed_block_info.invalid_bundle_authors.into_iter(),
1879                            SlashedReason::InvalidBundle(confirmed_block_info.domain_block_number),
1880                        )
1881                        .map_err(Error::<T>::from)?;
1882                    }
1883                }
1884            }
1885
1886            // slash operator who are in pending slash
1887            #[cfg(not(feature = "runtime-benchmarks"))]
1888            {
1889                let slashed_nominator_count =
1890                    do_slash_operator::<T>(domain_id, MAX_NOMINATORS_TO_SLASH)
1891                        .map_err(Error::<T>::from)?;
1892                actual_weight = actual_weight
1893                    .saturating_add(T::WeightInfo::slash_operator(slashed_nominator_count));
1894            }
1895
1896            // Ensure the returned weight not exceed the maximum weight in the `pallet::weight`
1897            Ok(Some(actual_weight.min(Self::max_submit_receipt_weight())).into())
1898        }
1899
1900        /// Submit an EVM domain "set contract creation allowed by" call as domain owner or root.
1901        #[pallet::call_index(22)]
1902        #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(3, 1))]
1903        pub fn send_evm_domain_set_contract_creation_allowed_by_call(
1904            origin: OriginFor<T>,
1905            domain_id: DomainId,
1906            contract_creation_allowed_by: sp_domains::PermissionedActionAllowedBy<
1907                EthereumAccountId,
1908            >,
1909        ) -> DispatchResult {
1910            let signer = ensure_signed_or_root(origin)?;
1911
1912            ensure!(
1913                Pallet::<T>::is_private_evm_domain(domain_id),
1914                Error::<T>::NotPrivateEvmDomain,
1915            );
1916            if let Some(non_root_signer) = signer {
1917                ensure!(
1918                    Pallet::<T>::is_domain_owner(domain_id, non_root_signer),
1919                    Error::<T>::NotDomainOwnerOrRoot,
1920                );
1921            }
1922            ensure!(
1923                EvmDomainContractCreationAllowedByCalls::<T>::get(domain_id)
1924                    .maybe_call
1925                    .is_none(),
1926                Error::<T>::EvmDomainContractCreationAllowedByCallExists,
1927            );
1928
1929            EvmDomainContractCreationAllowedByCalls::<T>::set(
1930                domain_id,
1931                EvmDomainContractCreationAllowedByCall {
1932                    maybe_call: Some(contract_creation_allowed_by),
1933                },
1934            );
1935
1936            Ok(())
1937        }
1938    }
1939
1940    #[pallet::genesis_config]
1941    pub struct GenesisConfig<T: Config> {
1942        pub permissioned_action_allowed_by:
1943            Option<sp_domains::PermissionedActionAllowedBy<T::AccountId>>,
1944        pub genesis_domains: Vec<GenesisDomain<T::AccountId, BalanceOf<T>>>,
1945    }
1946
1947    impl<T: Config> Default for GenesisConfig<T> {
1948        fn default() -> Self {
1949            GenesisConfig {
1950                permissioned_action_allowed_by: None,
1951                genesis_domains: vec![],
1952            }
1953        }
1954    }
1955
1956    #[pallet::genesis_build]
1957    impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
1958        fn build(&self) {
1959            if let Some(permissioned_action_allowed_by) =
1960                self.permissioned_action_allowed_by.as_ref().cloned()
1961            {
1962                PermissionedActionAllowedBy::<T>::put(permissioned_action_allowed_by)
1963            }
1964
1965            self.genesis_domains
1966                .clone()
1967                .into_iter()
1968                .for_each(|genesis_domain| {
1969                    // Register the genesis domain runtime
1970                    let runtime_id = register_runtime_at_genesis::<T>(
1971                        genesis_domain.runtime_name,
1972                        genesis_domain.runtime_type,
1973                        genesis_domain.runtime_version,
1974                        genesis_domain.raw_genesis_storage,
1975                        Zero::zero(),
1976                    )
1977                    .expect("Genesis runtime registration must always succeed");
1978
1979                    // Instantiate the genesis domain
1980                    let domain_config_params = DomainConfigParams {
1981                        domain_name: genesis_domain.domain_name,
1982                        runtime_id,
1983                        maybe_bundle_limit: None,
1984                        bundle_slot_probability: genesis_domain.bundle_slot_probability,
1985                        operator_allow_list: genesis_domain.operator_allow_list,
1986                        initial_balances: genesis_domain.initial_balances,
1987                        domain_runtime_config: genesis_domain.domain_runtime_config,
1988                    };
1989                    let domain_owner = genesis_domain.owner_account_id;
1990                    let domain_id = do_instantiate_domain::<T>(
1991                        domain_config_params,
1992                        domain_owner.clone(),
1993                        Zero::zero(),
1994                    )
1995                    .expect("Genesis domain instantiation must always succeed");
1996
1997                    // Register domain_owner as the genesis operator.
1998                    let operator_config = OperatorConfig {
1999                        signing_key: genesis_domain.signing_key.clone(),
2000                        minimum_nominator_stake: genesis_domain.minimum_nominator_stake,
2001                        nomination_tax: genesis_domain.nomination_tax,
2002                    };
2003                    let operator_stake = T::MinOperatorStake::get();
2004                    do_register_operator::<T>(
2005                        domain_owner,
2006                        domain_id,
2007                        operator_stake,
2008                        operator_config,
2009                    )
2010                    .expect("Genesis operator registration must succeed");
2011
2012                    do_finalize_domain_current_epoch::<T>(domain_id)
2013                        .expect("Genesis epoch must succeed");
2014                });
2015        }
2016    }
2017
2018    /// Combined fraud proof data for the InvalidInherentExtrinsic fraud proof
2019    #[pallet::storage]
2020    pub type BlockInherentExtrinsicData<T> = StorageValue<_, InherentExtrinsicData>;
2021
2022    #[pallet::hooks]
2023    // TODO: proper benchmark
2024    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
2025        fn on_initialize(block_number: BlockNumberFor<T>) -> Weight {
2026            let parent_number = block_number - One::one();
2027            let parent_hash = frame_system::Pallet::<T>::block_hash(parent_number);
2028
2029            // Record any previous domain runtime upgrades in `DomainRuntimeUpgradeRecords`
2030            for runtime_id in DomainRuntimeUpgrades::<T>::take() {
2031                let reference_count = RuntimeRegistry::<T>::get(runtime_id)
2032                    .expect("Runtime object must be present since domain is insantiated; qed")
2033                    .instance_count;
2034                if !reference_count.is_zero() {
2035                    DomainRuntimeUpgradeRecords::<T>::mutate(runtime_id, |upgrade_record| {
2036                        upgrade_record.insert(
2037                            parent_number,
2038                            DomainRuntimeUpgradeEntry {
2039                                at_hash: parent_hash,
2040                                reference_count,
2041                            },
2042                        )
2043                    });
2044                }
2045            }
2046            // Set DomainRuntimeUpgrades to an empty list. (If there are no runtime upgrades
2047            // scheduled in the current block, we can generate a proof the list is empty.)
2048            DomainRuntimeUpgrades::<T>::set(Vec::new());
2049            // Do the domain runtime upgrades scheduled in the current block, and record them in
2050            // DomainRuntimeUpgrades
2051            do_upgrade_runtimes::<T>(block_number);
2052
2053            // Store the hash of the parent consensus block for domains that have bundles submitted
2054            // in that consensus block
2055            for (domain_id, _) in SuccessfulBundles::<T>::drain() {
2056                ConsensusBlockHash::<T>::insert(domain_id, parent_number, parent_hash);
2057                T::DomainBundleSubmitted::domain_bundle_submitted(domain_id);
2058
2059                // And clear the domain inherents which have been submitted.
2060                DomainSudoCalls::<T>::mutate(domain_id, |sudo_call| {
2061                    sudo_call.clear();
2062                });
2063                EvmDomainContractCreationAllowedByCalls::<T>::mutate(
2064                    domain_id,
2065                    |evm_contract_call| {
2066                        evm_contract_call.clear();
2067                    },
2068                );
2069            }
2070
2071            for (operator_id, slot_set) in OperatorBundleSlot::<T>::drain() {
2072                // NOTE: `OperatorBundleSlot` uses `BTreeSet` so `last` will return the maximum
2073                // value in the set
2074                if let Some(highest_slot) = slot_set.last() {
2075                    OperatorHighestSlot::<T>::insert(operator_id, highest_slot);
2076                }
2077            }
2078
2079            BlockInherentExtrinsicData::<T>::kill();
2080
2081            Weight::zero()
2082        }
2083
2084        fn on_finalize(_: BlockNumberFor<T>) {
2085            // If this consensus block will derive any domain block, gather the necessary storage
2086            // for potential fraud proof usage
2087            if SuccessfulBundles::<T>::iter_keys().count() > 0
2088                || !DomainRuntimeUpgrades::<T>::get().is_empty()
2089            {
2090                let extrinsics_shuffling_seed = Randomness::from(
2091                    Into::<H256>::into(Self::extrinsics_shuffling_seed_value()).to_fixed_bytes(),
2092                );
2093
2094                // There are no actual conversions here, but the trait bounds required to prove that
2095                // (and debug-print the error in expect()) are very verbose.
2096                let timestamp = Self::timestamp_value();
2097
2098                // The value returned by the consensus_chain_byte_fee() runtime API
2099                let consensus_transaction_byte_fee = Self::consensus_transaction_byte_fee_value();
2100
2101                let inherent_extrinsic_data = InherentExtrinsicData {
2102                    extrinsics_shuffling_seed,
2103                    timestamp,
2104                    consensus_transaction_byte_fee,
2105                };
2106
2107                BlockInherentExtrinsicData::<T>::set(Some(inherent_extrinsic_data));
2108            }
2109
2110            let _ = LastEpochStakingDistribution::<T>::clear(u32::MAX, None);
2111            let _ = NewAddedHeadReceipt::<T>::clear(u32::MAX, None);
2112        }
2113    }
2114}
2115
2116impl<T: Config> Pallet<T> {
2117    fn log_bundle_error(err: &BundleError, domain_id: DomainId, operator_id: OperatorId) {
2118        match err {
2119            // These errors are common due to networking delay or chain re-org,
2120            // using a lower log level to avoid the noise.
2121            BundleError::Receipt(BlockTreeError::InFutureReceipt)
2122            | BundleError::Receipt(BlockTreeError::StaleReceipt)
2123            | BundleError::Receipt(BlockTreeError::NewBranchReceipt)
2124            | BundleError::Receipt(BlockTreeError::UnavailableConsensusBlockHash)
2125            | BundleError::Receipt(BlockTreeError::BuiltOnUnknownConsensusBlock)
2126            | BundleError::SlotInThePast
2127            | BundleError::SlotInTheFuture
2128            | BundleError::InvalidProofOfTime
2129            | BundleError::SlotSmallerThanPreviousBlockBundle
2130            | BundleError::ExpectingReceiptGap
2131            | BundleError::UnexpectedReceiptGap => {
2132                log::debug!(
2133                    "Bad bundle/receipt, domain {domain_id:?}, operator {operator_id:?}, error: {err:?}",
2134                );
2135            }
2136            _ => {
2137                log::warn!(
2138                    "Bad bundle/receipt, domain {domain_id:?}, operator {operator_id:?}, error: {err:?}",
2139                );
2140            }
2141        }
2142    }
2143
2144    pub fn successful_bundles(domain_id: DomainId) -> Vec<H256> {
2145        SuccessfulBundles::<T>::get(domain_id)
2146    }
2147
2148    pub fn domain_runtime_code(domain_id: DomainId) -> Option<Vec<u8>> {
2149        RuntimeRegistry::<T>::get(Self::runtime_id(domain_id)?)
2150            .and_then(|mut runtime_object| runtime_object.raw_genesis.take_runtime_code())
2151    }
2152
2153    pub fn domain_best_number(domain_id: DomainId) -> Result<DomainBlockNumberFor<T>, BundleError> {
2154        // The missed domain runtime upgrades will derive domain blocks thus should be accounted
2155        // into the domain best number
2156        let missed_upgrade = Self::missed_domain_runtime_upgrade(domain_id)
2157            .map_err(|_| BundleError::FailedToGetMissedUpgradeCount)?;
2158
2159        Ok(HeadDomainNumber::<T>::get(domain_id) + missed_upgrade.into())
2160    }
2161
2162    /// Returns the runtime ID for the supplied `domain_id`, if that domain exists.
2163    pub fn runtime_id(domain_id: DomainId) -> Option<RuntimeId> {
2164        DomainRegistry::<T>::get(domain_id)
2165            .map(|domain_object| domain_object.domain_config.runtime_id)
2166    }
2167
2168    /// Returns the list of runtime upgrades in the current block.
2169    pub fn runtime_upgrades() -> Vec<RuntimeId> {
2170        DomainRuntimeUpgrades::<T>::get()
2171    }
2172
2173    pub fn domain_instance_data(
2174        domain_id: DomainId,
2175    ) -> Option<(DomainInstanceData, BlockNumberFor<T>)> {
2176        let domain_obj = DomainRegistry::<T>::get(domain_id)?;
2177        let runtime_object = RuntimeRegistry::<T>::get(domain_obj.domain_config.runtime_id)?;
2178        let runtime_type = runtime_object.runtime_type;
2179        let total_issuance = domain_obj.domain_config.total_issuance()?;
2180        let raw_genesis = into_complete_raw_genesis::<T>(
2181            runtime_object,
2182            domain_id,
2183            &domain_obj.domain_runtime_info,
2184            total_issuance,
2185            domain_obj.domain_config.initial_balances,
2186        )
2187        .ok()?;
2188        Some((
2189            DomainInstanceData {
2190                runtime_type,
2191                raw_genesis,
2192            },
2193            domain_obj.created_at,
2194        ))
2195    }
2196
2197    /// Returns the tx range for the domain.
2198    pub fn domain_tx_range(domain_id: DomainId) -> U256 {
2199        DomainTxRangeState::<T>::try_get(domain_id)
2200            .map(|state| state.tx_range)
2201            .ok()
2202            .unwrap_or_else(Self::initial_tx_range)
2203    }
2204
2205    pub fn bundle_producer_election_params(
2206        domain_id: DomainId,
2207    ) -> Option<BundleProducerElectionParams<BalanceOf<T>>> {
2208        match (
2209            DomainRegistry::<T>::get(domain_id),
2210            DomainStakingSummary::<T>::get(domain_id),
2211        ) {
2212            (Some(domain_object), Some(stake_summary)) => Some(BundleProducerElectionParams {
2213                total_domain_stake: stake_summary.current_total_stake,
2214                bundle_slot_probability: domain_object.domain_config.bundle_slot_probability,
2215            }),
2216            _ => None,
2217        }
2218    }
2219
2220    pub fn operator(operator_id: OperatorId) -> Option<(OperatorPublicKey, BalanceOf<T>)> {
2221        Operators::<T>::get(operator_id)
2222            .map(|operator| (operator.signing_key, operator.current_total_stake))
2223    }
2224
2225    fn check_extrinsics_root(opaque_bundle: &OpaqueBundleOf<T>) -> Result<(), BundleError> {
2226        let expected_extrinsics_root = <T::DomainHeader as Header>::Hashing::ordered_trie_root(
2227            opaque_bundle
2228                .extrinsics()
2229                .iter()
2230                .map(|xt| xt.encode())
2231                .collect(),
2232            sp_core::storage::StateVersion::V1,
2233        );
2234        ensure!(
2235            expected_extrinsics_root == opaque_bundle.extrinsics_root(),
2236            BundleError::InvalidExtrinsicRoot
2237        );
2238        Ok(())
2239    }
2240
2241    fn check_slot_and_proof_of_time(
2242        slot_number: u64,
2243        proof_of_time: PotOutput,
2244        pre_dispatch: bool,
2245    ) -> Result<(), BundleError> {
2246        // NOTE: the `current_block_number` from `frame_system` is initialized during `validate_unsigned` thus
2247        // it is the same value in both `validate_unsigned` and `pre_dispatch`
2248        let current_block_number = frame_system::Pallet::<T>::current_block_number();
2249
2250        // Check if the slot is in future
2251        //
2252        // NOTE: during `validate_unsigned` this is implicitly checked within `is_proof_of_time_valid` since we
2253        // are using quick verification which will return `false` if the `proof-of-time` is not seem by the node
2254        // before.
2255        if pre_dispatch && let Some(future_slot) = T::BlockSlot::future_slot(current_block_number) {
2256            ensure!(slot_number <= *future_slot, BundleError::SlotInTheFuture)
2257        }
2258
2259        // Check if the bundle is built too long time ago and beyond `T::BundleLongevity` number of consensus blocks.
2260        let produced_after_block_number =
2261            match T::BlockSlot::slot_produced_after(slot_number.into()) {
2262                Some(n) => n,
2263                None => {
2264                    // There is no slot for the genesis block, if the current block is less than `BundleLongevity`
2265                    // than we assume the slot is produced after the genesis block.
2266                    if current_block_number > T::BundleLongevity::get().into() {
2267                        return Err(BundleError::SlotInThePast);
2268                    } else {
2269                        Zero::zero()
2270                    }
2271                }
2272            };
2273        let produced_after_block_hash = if produced_after_block_number == current_block_number {
2274            // The hash of the current block is only available in the next block thus use the parent hash here
2275            frame_system::Pallet::<T>::parent_hash()
2276        } else {
2277            frame_system::Pallet::<T>::block_hash(produced_after_block_number)
2278        };
2279        if let Some(last_eligible_block) =
2280            current_block_number.checked_sub(&T::BundleLongevity::get().into())
2281        {
2282            ensure!(
2283                produced_after_block_number >= last_eligible_block,
2284                BundleError::SlotInThePast
2285            );
2286        }
2287
2288        if !is_proof_of_time_valid(
2289            BlockHash::try_from(produced_after_block_hash.as_ref())
2290                .expect("Must be able to convert to block hash type"),
2291            SlotNumber::from(slot_number),
2292            WrappedPotOutput::from(proof_of_time),
2293            // Quick verification when entering transaction pool, but not when constructing the block
2294            !pre_dispatch,
2295        ) {
2296            return Err(BundleError::InvalidProofOfTime);
2297        }
2298
2299        Ok(())
2300    }
2301
2302    fn validate_bundle(
2303        opaque_bundle: &OpaqueBundleOf<T>,
2304        domain_config: &DomainConfig<T::AccountId, BalanceOf<T>>,
2305    ) -> Result<(), BundleError> {
2306        ensure!(
2307            opaque_bundle.body_size() <= domain_config.max_bundle_size,
2308            BundleError::BundleTooLarge
2309        );
2310
2311        ensure!(
2312            opaque_bundle
2313                .estimated_weight()
2314                .all_lte(domain_config.max_bundle_weight),
2315            BundleError::BundleTooHeavy
2316        );
2317
2318        Self::check_extrinsics_root(opaque_bundle)?;
2319
2320        Ok(())
2321    }
2322
2323    fn validate_eligibility(
2324        to_sign: &[u8],
2325        signature: &OperatorSignature,
2326        proof_of_election: &ProofOfElection,
2327        domain_config: &DomainConfig<T::AccountId, BalanceOf<T>>,
2328        pre_dispatch: bool,
2329    ) -> Result<(), BundleError> {
2330        let domain_id = proof_of_election.domain_id;
2331        let operator_id = proof_of_election.operator_id;
2332        let slot_number = proof_of_election.slot_number;
2333
2334        ensure!(
2335            !FrozenDomains::<T>::get().contains(&domain_id),
2336            BundleError::DomainFrozen
2337        );
2338
2339        let operator = Operators::<T>::get(operator_id).ok_or(BundleError::InvalidOperatorId)?;
2340
2341        let operator_status = operator.status::<T>(operator_id);
2342        ensure!(
2343            *operator_status != OperatorStatus::Slashed
2344                && *operator_status != OperatorStatus::PendingSlash
2345                && !matches!(operator_status, OperatorStatus::InvalidBundle(_)),
2346            BundleError::BadOperator
2347        );
2348
2349        if !operator.signing_key.verify(&to_sign, signature) {
2350            return Err(BundleError::BadBundleSignature);
2351        }
2352
2353        // Ensure this is no equivocated bundle that reuse `ProofOfElection` from the previous block
2354        ensure!(
2355            slot_number
2356                > Self::operator_highest_slot_from_previous_block(operator_id, pre_dispatch),
2357            BundleError::SlotSmallerThanPreviousBlockBundle,
2358        );
2359
2360        // Ensure there is no equivocated/duplicated bundle in the same block
2361        ensure!(
2362            !OperatorBundleSlot::<T>::get(operator_id).contains(&slot_number),
2363            BundleError::EquivocatedBundle,
2364        );
2365
2366        let (operator_stake, total_domain_stake) =
2367            Self::fetch_operator_stake_info(domain_id, &operator_id)?;
2368
2369        Self::check_slot_and_proof_of_time(
2370            slot_number,
2371            proof_of_election.proof_of_time,
2372            pre_dispatch,
2373        )?;
2374
2375        sp_domains::bundle_producer_election::check_proof_of_election(
2376            &operator.signing_key,
2377            domain_config.bundle_slot_probability,
2378            proof_of_election,
2379            operator_stake.saturated_into(),
2380            total_domain_stake.saturated_into(),
2381        )?;
2382
2383        Ok(())
2384    }
2385
2386    /// Verifies if the submitted ER version matches with the version
2387    /// defined at the block number ER is derived from.
2388    fn check_execution_receipt_version(
2389        er_derived_consensus_number: BlockNumberFor<T>,
2390        receipt_version: ExecutionReceiptVersion,
2391    ) -> Result<(), BundleError> {
2392        let expected_execution_receipt_version =
2393            Self::bundle_and_execution_receipt_version_for_consensus_number(
2394                er_derived_consensus_number,
2395                PreviousBundleAndExecutionReceiptVersions::<T>::get(),
2396                T::CurrentBundleAndExecutionReceiptVersion::get(),
2397            )
2398            .ok_or(BundleError::ExecutionVersionMissing)?
2399            .execution_receipt_version;
2400        match (receipt_version, expected_execution_receipt_version) {
2401            (ExecutionReceiptVersion::V0, ExecutionReceiptVersion::V0) => Ok(()),
2402        }
2403    }
2404
2405    fn validate_submit_bundle(
2406        opaque_bundle: &OpaqueBundleOf<T>,
2407        pre_dispatch: bool,
2408    ) -> Result<(), BundleError> {
2409        let current_bundle_version =
2410            T::CurrentBundleAndExecutionReceiptVersion::get().bundle_version;
2411
2412        // bundle version check
2413        match (current_bundle_version, opaque_bundle) {
2414            (BundleVersion::V0, Bundle::V0(_)) => Ok::<(), BundleError>(()),
2415        }?;
2416
2417        let domain_id = opaque_bundle.domain_id();
2418        let operator_id = opaque_bundle.operator_id();
2419        let sealed_header = opaque_bundle.sealed_header();
2420
2421        let receipt = sealed_header.receipt();
2422        Self::check_execution_receipt_version(
2423            *receipt.consensus_block_number(),
2424            receipt.version(),
2425        )?;
2426
2427        // Ensure the receipt gap is <= 1 so that the bundle will only be acceptted if its receipt is
2428        // derived from the latest domain block, and the stale bundle (that verified against an old
2429        // domain block) produced by a lagging honest operator will be rejected.
2430        ensure!(
2431            Self::receipt_gap(domain_id)? <= One::one(),
2432            BundleError::UnexpectedReceiptGap,
2433        );
2434
2435        let domain_config = &DomainRegistry::<T>::get(domain_id)
2436            .ok_or(BundleError::InvalidDomainId)?
2437            .domain_config;
2438
2439        Self::validate_bundle(opaque_bundle, domain_config)?;
2440
2441        Self::validate_eligibility(
2442            sealed_header.pre_hash().as_ref(),
2443            sealed_header.signature(),
2444            sealed_header.proof_of_election(),
2445            domain_config,
2446            pre_dispatch,
2447        )?;
2448
2449        verify_execution_receipt::<T>(domain_id, &receipt).map_err(BundleError::Receipt)?;
2450
2451        charge_bundle_storage_fee::<T>(operator_id, opaque_bundle.size())
2452            .map_err(|_| BundleError::UnableToPayBundleStorageFee)?;
2453
2454        Ok(())
2455    }
2456
2457    fn validate_singleton_receipt(
2458        sealed_singleton_receipt: &SingletonReceiptOf<T>,
2459        pre_dispatch: bool,
2460    ) -> Result<(), BundleError> {
2461        let domain_id = sealed_singleton_receipt.domain_id();
2462        let operator_id = sealed_singleton_receipt.operator_id();
2463
2464        // Singleton receipt is only allowed when there is a receipt gap
2465        ensure!(
2466            Self::receipt_gap(domain_id)? > One::one(),
2467            BundleError::ExpectingReceiptGap,
2468        );
2469
2470        Self::check_execution_receipt_version(
2471            *sealed_singleton_receipt
2472                .singleton_receipt
2473                .receipt
2474                .consensus_block_number(),
2475            sealed_singleton_receipt.singleton_receipt.receipt.version(),
2476        )?;
2477
2478        let domain_config = DomainRegistry::<T>::get(domain_id)
2479            .ok_or(BundleError::InvalidDomainId)?
2480            .domain_config;
2481        Self::validate_eligibility(
2482            sealed_singleton_receipt.pre_hash().as_ref(),
2483            &sealed_singleton_receipt.signature,
2484            &sealed_singleton_receipt.singleton_receipt.proof_of_election,
2485            &domain_config,
2486            pre_dispatch,
2487        )?;
2488
2489        verify_execution_receipt::<T>(
2490            domain_id,
2491            &sealed_singleton_receipt
2492                .singleton_receipt
2493                .receipt
2494                .as_execution_receipt_ref(),
2495        )
2496        .map_err(BundleError::Receipt)?;
2497
2498        charge_bundle_storage_fee::<T>(operator_id, sealed_singleton_receipt.size())
2499            .map_err(|_| BundleError::UnableToPayBundleStorageFee)?;
2500
2501        Ok(())
2502    }
2503
2504    fn validate_fraud_proof(
2505        fraud_proof: &FraudProofFor<T>,
2506    ) -> Result<(DomainId, TransactionPriority), FraudProofError> {
2507        let domain_id = fraud_proof.domain_id();
2508        let bad_receipt_hash = fraud_proof.targeted_bad_receipt_hash();
2509        let bad_receipt = BlockTreeNodes::<T>::get(bad_receipt_hash)
2510            .ok_or(FraudProofError::BadReceiptNotFound)?
2511            .execution_receipt;
2512        let bad_receipt_domain_block_number = *bad_receipt.domain_block_number();
2513
2514        ensure!(
2515            !bad_receipt_domain_block_number.is_zero(),
2516            FraudProofError::ChallengingGenesisReceipt
2517        );
2518
2519        ensure!(
2520            !Self::is_bad_er_pending_to_prune(domain_id, bad_receipt_domain_block_number),
2521            FraudProofError::BadReceiptAlreadyReported,
2522        );
2523
2524        ensure!(
2525            !fraud_proof.is_unexpected_domain_runtime_code_proof(),
2526            FraudProofError::UnexpectedDomainRuntimeCodeProof,
2527        );
2528
2529        ensure!(
2530            !fraud_proof.is_unexpected_mmr_proof(),
2531            FraudProofError::UnexpectedMmrProof,
2532        );
2533
2534        let maybe_state_root = match &fraud_proof.maybe_mmr_proof {
2535            Some(mmr_proof) => Some(Self::verify_mmr_proof_and_extract_state_root(
2536                mmr_proof.clone(),
2537                *bad_receipt.consensus_block_number(),
2538            )?),
2539            None => None,
2540        };
2541
2542        match &fraud_proof.proof {
2543            FraudProofVariant::InvalidBlockFees(InvalidBlockFeesProof { storage_proof }) => {
2544                let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2545                    domain_id,
2546                    &bad_receipt,
2547                    fraud_proof.maybe_domain_runtime_code_proof.clone(),
2548                )?;
2549
2550                verify_invalid_block_fees_fraud_proof::<
2551                    T::Block,
2552                    DomainBlockNumberFor<T>,
2553                    T::DomainHash,
2554                    BalanceOf<T>,
2555                    DomainHashingFor<T>,
2556                >(bad_receipt, storage_proof, domain_runtime_code)
2557                .map_err(|err| {
2558                    log::error!("Block fees proof verification failed: {err:?}");
2559                    FraudProofError::InvalidBlockFeesFraudProof
2560                })?;
2561            }
2562            FraudProofVariant::InvalidTransfers(InvalidTransfersProof { storage_proof }) => {
2563                let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2564                    domain_id,
2565                    &bad_receipt,
2566                    fraud_proof.maybe_domain_runtime_code_proof.clone(),
2567                )?;
2568
2569                verify_invalid_transfers_fraud_proof::<
2570                    T::Block,
2571                    DomainBlockNumberFor<T>,
2572                    T::DomainHash,
2573                    BalanceOf<T>,
2574                    DomainHashingFor<T>,
2575                >(bad_receipt, storage_proof, domain_runtime_code)
2576                .map_err(|err| {
2577                    log::error!("Domain transfers proof verification failed: {err:?}");
2578                    FraudProofError::InvalidTransfersFraudProof
2579                })?;
2580            }
2581            FraudProofVariant::InvalidDomainBlockHash(InvalidDomainBlockHashProof {
2582                digest_storage_proof,
2583            }) => {
2584                let parent_receipt =
2585                    BlockTreeNodes::<T>::get(*bad_receipt.parent_domain_block_receipt_hash())
2586                        .ok_or(FraudProofError::ParentReceiptNotFound)?
2587                        .execution_receipt;
2588                verify_invalid_domain_block_hash_fraud_proof::<
2589                    T::Block,
2590                    BalanceOf<T>,
2591                    T::DomainHeader,
2592                >(
2593                    bad_receipt,
2594                    digest_storage_proof.clone(),
2595                    *parent_receipt.domain_block_hash(),
2596                )
2597                .map_err(|err| {
2598                    log::error!("Invalid Domain block hash proof verification failed: {err:?}");
2599                    FraudProofError::InvalidDomainBlockHashFraudProof
2600                })?;
2601            }
2602            FraudProofVariant::InvalidExtrinsicsRoot(proof) => {
2603                let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2604                    domain_id,
2605                    &bad_receipt,
2606                    fraud_proof.maybe_domain_runtime_code_proof.clone(),
2607                )?;
2608                let runtime_id =
2609                    Self::runtime_id(domain_id).ok_or(FraudProofError::RuntimeNotFound)?;
2610                let state_root = maybe_state_root.ok_or(FraudProofError::MissingMmrProof)?;
2611
2612                verify_invalid_domain_extrinsics_root_fraud_proof::<
2613                    T::Block,
2614                    BalanceOf<T>,
2615                    T::DomainHeader,
2616                    T::Hashing,
2617                    T::FraudProofStorageKeyProvider,
2618                >(
2619                    bad_receipt,
2620                    proof,
2621                    domain_id,
2622                    runtime_id,
2623                    state_root,
2624                    domain_runtime_code,
2625                )
2626                .map_err(|err| {
2627                    log::error!("Invalid Domain extrinsic root proof verification failed: {err:?}");
2628                    FraudProofError::InvalidExtrinsicRootFraudProof
2629                })?;
2630            }
2631            FraudProofVariant::InvalidStateTransition(proof) => {
2632                let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2633                    domain_id,
2634                    &bad_receipt,
2635                    fraud_proof.maybe_domain_runtime_code_proof.clone(),
2636                )?;
2637                let bad_receipt_parent =
2638                    BlockTreeNodes::<T>::get(*bad_receipt.parent_domain_block_receipt_hash())
2639                        .ok_or(FraudProofError::ParentReceiptNotFound)?
2640                        .execution_receipt;
2641
2642                verify_invalid_state_transition_fraud_proof::<
2643                    T::Block,
2644                    T::DomainHeader,
2645                    BalanceOf<T>,
2646                >(bad_receipt, bad_receipt_parent, proof, domain_runtime_code)
2647                .map_err(|err| {
2648                    log::error!("Invalid State transition proof verification failed: {err:?}");
2649                    FraudProofError::InvalidStateTransitionFraudProof
2650                })?;
2651            }
2652            FraudProofVariant::InvalidBundles(proof) => {
2653                let state_root = maybe_state_root.ok_or(FraudProofError::MissingMmrProof)?;
2654                let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2655                    domain_id,
2656                    &bad_receipt,
2657                    fraud_proof.maybe_domain_runtime_code_proof.clone(),
2658                )?;
2659
2660                let bad_receipt_parent =
2661                    BlockTreeNodes::<T>::get(*bad_receipt.parent_domain_block_receipt_hash())
2662                        .ok_or(FraudProofError::ParentReceiptNotFound)?
2663                        .execution_receipt;
2664
2665                verify_invalid_bundles_fraud_proof::<
2666                    T::Block,
2667                    T::DomainHeader,
2668                    T::MmrHash,
2669                    BalanceOf<T>,
2670                    T::FraudProofStorageKeyProvider,
2671                    T::MmrProofVerifier,
2672                >(
2673                    bad_receipt,
2674                    bad_receipt_parent,
2675                    proof,
2676                    domain_id,
2677                    state_root,
2678                    domain_runtime_code,
2679                )
2680                .map_err(|err| {
2681                    log::error!("Invalid Bundle proof verification failed: {err:?}");
2682                    FraudProofError::InvalidBundleFraudProof
2683                })?;
2684            }
2685            FraudProofVariant::ValidBundle(proof) => {
2686                let state_root = maybe_state_root.ok_or(FraudProofError::MissingMmrProof)?;
2687                let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2688                    domain_id,
2689                    &bad_receipt,
2690                    fraud_proof.maybe_domain_runtime_code_proof.clone(),
2691                )?;
2692
2693                verify_valid_bundle_fraud_proof::<
2694                    T::Block,
2695                    T::DomainHeader,
2696                    BalanceOf<T>,
2697                    T::FraudProofStorageKeyProvider,
2698                >(
2699                    bad_receipt,
2700                    proof,
2701                    domain_id,
2702                    state_root,
2703                    domain_runtime_code,
2704                )
2705                .map_err(|err| {
2706                    log::error!("Valid bundle proof verification failed: {err:?}");
2707                    FraudProofError::BadValidBundleFraudProof
2708                })?
2709            }
2710            #[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
2711            FraudProofVariant::Dummy => {
2712                // Almost every fraud proof (except `InvalidDomainBlockHash` fraud proof) need to call
2713                // `get_domain_runtime_code_for_receipt` thus we include this part in the benchmark of
2714                // the dummy fraud proof.
2715                //
2716                // NOTE: the dummy fraud proof's `maybe_domain_runtime_code_proof` is `None` thus
2717                // this proof's verification is not included in the benchmark here.
2718                let _ = Self::get_domain_runtime_code_for_receipt(
2719                    domain_id,
2720                    &bad_receipt,
2721                    fraud_proof.maybe_domain_runtime_code_proof.clone(),
2722                )?;
2723            }
2724        }
2725
2726        // The priority of fraud proof is determined by how many blocks left before the bad ER
2727        // is confirmed, the less the more emergency it is, thus give a higher priority.
2728        let block_before_bad_er_confirm = bad_receipt_domain_block_number.saturating_sub(
2729            Self::latest_confirmed_domain_block_number(fraud_proof.domain_id()),
2730        );
2731        let priority =
2732            TransactionPriority::MAX - block_before_bad_er_confirm.saturated_into::<u64>();
2733
2734        // Use the domain id as tag thus the consensus node only accept one fraud proof for a
2735        // specific domain at a time
2736        let tag = fraud_proof.domain_id();
2737
2738        Ok((tag, priority))
2739    }
2740
2741    /// Return operators specific election verification params for Proof of Election verification.
2742    /// If there was an epoch transition in this block for this domain,
2743    ///     then return the parameters from previous epoch stored in LastEpochStakingDistribution
2744    /// Else, return those details from the Domain's stake summary for this epoch.
2745    fn fetch_operator_stake_info(
2746        domain_id: DomainId,
2747        operator_id: &OperatorId,
2748    ) -> Result<(BalanceOf<T>, BalanceOf<T>), BundleError> {
2749        if let Some(pending_election_params) = LastEpochStakingDistribution::<T>::get(domain_id)
2750            && let Some(operator_stake) = pending_election_params.operators.get(operator_id)
2751        {
2752            return Ok((*operator_stake, pending_election_params.total_domain_stake));
2753        }
2754        let domain_stake_summary =
2755            DomainStakingSummary::<T>::get(domain_id).ok_or(BundleError::InvalidDomainId)?;
2756        let operator_stake = domain_stake_summary
2757            .current_operators
2758            .get(operator_id)
2759            .ok_or(BundleError::BadOperator)?;
2760        Ok((*operator_stake, domain_stake_summary.current_total_stake))
2761    }
2762
2763    /// Calculates the initial tx range.
2764    fn initial_tx_range() -> U256 {
2765        U256::MAX / T::InitialDomainTxRange::get()
2766    }
2767
2768    /// Returns the best execution chain number.
2769    pub fn head_receipt_number(domain_id: DomainId) -> DomainBlockNumberFor<T> {
2770        HeadReceiptNumber::<T>::get(domain_id)
2771    }
2772
2773    /// Returns the block number of the oldest existing unconfirmed execution receipt, return `None`
2774    /// means there is no unconfirmed ER exist or submitted yet.
2775    pub fn oldest_unconfirmed_receipt_number(
2776        domain_id: DomainId,
2777    ) -> Option<DomainBlockNumberFor<T>> {
2778        let oldest_nonconfirmed_er_number =
2779            Self::latest_confirmed_domain_block_number(domain_id).saturating_add(One::one());
2780        let is_er_exist = BlockTree::<T>::get(domain_id, oldest_nonconfirmed_er_number).is_some();
2781        let is_pending_to_prune =
2782            Self::is_bad_er_pending_to_prune(domain_id, oldest_nonconfirmed_er_number);
2783
2784        if is_er_exist && !is_pending_to_prune {
2785            Some(oldest_nonconfirmed_er_number)
2786        } else {
2787            // The `oldest_nonconfirmed_er_number` ER may not exist if
2788            // - The domain just started and no ER submitted yet
2789            // - The oldest ER just pruned by fraud proof and no new ER submitted yet
2790            // - When using consensus block to derive the challenge period forward (unimplemented yet)
2791            None
2792        }
2793    }
2794
2795    /// Returns the latest confirmed domain block number for a given domain
2796    /// Zero block is always a default confirmed block.
2797    pub fn latest_confirmed_domain_block_number(domain_id: DomainId) -> DomainBlockNumberFor<T> {
2798        LatestConfirmedDomainExecutionReceipt::<T>::get(domain_id)
2799            .map(|er| *er.domain_block_number())
2800            .unwrap_or_default()
2801    }
2802
2803    pub fn latest_confirmed_domain_block(
2804        domain_id: DomainId,
2805    ) -> Option<(DomainBlockNumberFor<T>, T::DomainHash)> {
2806        LatestConfirmedDomainExecutionReceipt::<T>::get(domain_id)
2807            .map(|er| (*er.domain_block_number(), *er.domain_block_hash()))
2808    }
2809
2810    /// Returns the domain bundle limit of the given domain
2811    pub fn domain_bundle_limit(
2812        domain_id: DomainId,
2813    ) -> Result<Option<DomainBundleLimit>, DomainRegistryError> {
2814        let domain_config = match DomainRegistry::<T>::get(domain_id) {
2815            None => return Ok(None),
2816            Some(domain_obj) => domain_obj.domain_config,
2817        };
2818
2819        Ok(Some(DomainBundleLimit {
2820            max_bundle_size: domain_config.max_bundle_size,
2821            max_bundle_weight: domain_config.max_bundle_weight,
2822        }))
2823    }
2824
2825    /// Returns if there are any ERs in the challenge period that have non empty extrinsics.
2826    /// Note that Genesis ER is also considered special and hence non empty
2827    pub fn non_empty_er_exists(domain_id: DomainId) -> bool {
2828        if BlockTree::<T>::contains_key(domain_id, DomainBlockNumberFor::<T>::zero()) {
2829            return true;
2830        }
2831
2832        // Start from the oldest non-confirmed ER to the head domain number
2833        let mut to_check =
2834            Self::latest_confirmed_domain_block_number(domain_id).saturating_add(One::one());
2835
2836        // NOTE: we use the `HeadDomainNumber` here instead of the `domain_best_number`, which include the
2837        // missed domain runtime upgrade block, because we don't want to trigger empty bundle production
2838        // for confirming these blocks since they only include runtime upgrade extrinsic and no any user
2839        // submitted extrinsic.
2840        let head_number = HeadDomainNumber::<T>::get(domain_id);
2841
2842        while to_check <= head_number {
2843            if !ExecutionInbox::<T>::iter_prefix_values((domain_id, to_check)).all(|digests| {
2844                digests
2845                    .iter()
2846                    .all(|digest| digest.extrinsics_root == EMPTY_EXTRINSIC_ROOT.into())
2847            }) {
2848                return true;
2849            }
2850
2851            to_check = to_check.saturating_add(One::one())
2852        }
2853
2854        false
2855    }
2856
2857    /// The external function used to access the extrinsics shuffling seed stored in
2858    /// `BlockInherentExtrinsicData`.
2859    pub fn extrinsics_shuffling_seed() -> T::Hash {
2860        // Fall back to recalculating if it hasn't been stored yet.
2861        BlockInherentExtrinsicData::<T>::get()
2862            .map(|data| H256::from(*data.extrinsics_shuffling_seed).into())
2863            .unwrap_or_else(|| Self::extrinsics_shuffling_seed_value())
2864    }
2865
2866    /// The internal function used to calculate the extrinsics shuffling seed for storage into
2867    /// `BlockInherentExtrinsicData`.
2868    fn extrinsics_shuffling_seed_value() -> T::Hash {
2869        let subject = DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT;
2870        let (randomness, _) = T::Randomness::random(subject);
2871        randomness
2872    }
2873
2874    /// The external function used to access the timestamp stored in
2875    /// `BlockInherentExtrinsicData`.
2876    pub fn timestamp() -> Moment {
2877        // Fall back to recalculating if it hasn't been stored yet.
2878        BlockInherentExtrinsicData::<T>::get()
2879            .map(|data| data.timestamp)
2880            .unwrap_or_else(|| Self::timestamp_value())
2881    }
2882
2883    /// The internal function used to access the timestamp for storage into
2884    /// `BlockInherentExtrinsicData`.
2885    fn timestamp_value() -> Moment {
2886        // There are no actual conversions here, but the trait bounds required to prove that
2887        // (and debug-print the error in expect()) are very verbose.
2888        T::BlockTimestamp::now()
2889            .try_into()
2890            .map_err(|_| ())
2891            .expect("Moment is the same type in both pallets; qed")
2892    }
2893
2894    /// The external function used to access the consensus transaction byte fee stored in
2895    /// `BlockInherentExtrinsicData`.
2896    /// This value is returned by the consensus_chain_byte_fee() runtime API
2897    pub fn consensus_transaction_byte_fee() -> Balance {
2898        // Fall back to recalculating if it hasn't been stored yet.
2899        BlockInherentExtrinsicData::<T>::get()
2900            .map(|data| data.consensus_transaction_byte_fee)
2901            .unwrap_or_else(|| Self::consensus_transaction_byte_fee_value())
2902    }
2903
2904    /// The internal function used to calculate the consensus transaction byte fee for storage into
2905    /// `BlockInherentExtrinsicData`.
2906    fn consensus_transaction_byte_fee_value() -> Balance {
2907        // There are no actual conversions here, but the trait bounds required to prove that
2908        // (and debug-print the error in expect()) are very verbose.
2909        let transaction_byte_fee: Balance = T::StorageFee::transaction_byte_fee()
2910            .try_into()
2911            .map_err(|_| ())
2912            .expect("Balance is the same type in both pallets; qed");
2913
2914        sp_domains::DOMAIN_STORAGE_FEE_MULTIPLIER * transaction_byte_fee
2915    }
2916
2917    pub fn execution_receipt(receipt_hash: ReceiptHashFor<T>) -> Option<ExecutionReceiptOf<T>> {
2918        BlockTreeNodes::<T>::get(receipt_hash).map(|db| db.execution_receipt)
2919    }
2920
2921    /// Returns the correct bundle and er version based on
2922    /// the consensus block number at which execution receipt was derived.
2923    pub(crate) fn bundle_and_execution_receipt_version_for_consensus_number<BEV>(
2924        er_derived_number: BlockNumberFor<T>,
2925        previous_versions: BTreeMap<BlockNumberFor<T>, BEV>,
2926        current_version: BEV,
2927    ) -> Option<BEV>
2928    where
2929        BEV: Copy + Clone,
2930    {
2931        // short circuit if the er version should be the latest version
2932        match previous_versions.last_key_value() {
2933            // if there are no versions, all ERs should be the latest version.
2934            None => {
2935                return Some(current_version);
2936            }
2937            Some((number, version)) => {
2938                // if er derived number is greater than the last stored version,
2939                // then er version should be the latest version.
2940                if er_derived_number > *number {
2941                    return Some(current_version);
2942                }
2943
2944                // if the er derived number is equal to the last stored version,
2945                // then the er version should be the previous er version
2946                if er_derived_number == *number {
2947                    return Some(*version);
2948                }
2949            }
2950        }
2951
2952        // if we are here, it means the er version should be the version before a previous upgrade.
2953        // loop through to find the correct version.
2954        for (upgraded_number, version) in previous_versions.into_iter() {
2955            if er_derived_number <= upgraded_number {
2956                return Some(version);
2957            }
2958        }
2959
2960        // should not reach here since above loop always finds the oldest version
2961        None
2962    }
2963
2964    pub fn receipt_hash(
2965        domain_id: DomainId,
2966        domain_number: DomainBlockNumberFor<T>,
2967    ) -> Option<ReceiptHashFor<T>> {
2968        BlockTree::<T>::get(domain_id, domain_number)
2969    }
2970
2971    pub fn confirmed_domain_block_storage_key(domain_id: DomainId) -> Vec<u8> {
2972        LatestConfirmedDomainExecutionReceipt::<T>::hashed_key_for(domain_id)
2973    }
2974
2975    pub fn is_bad_er_pending_to_prune(
2976        domain_id: DomainId,
2977        receipt_number: DomainBlockNumberFor<T>,
2978    ) -> bool {
2979        // The genesis receipt is always valid
2980        if receipt_number.is_zero() {
2981            return false;
2982        }
2983
2984        let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
2985
2986        // If `receipt_number` is greater than the current `head_receipt_number` meaning it is a
2987        // bad ER and the `head_receipt_number` is previously reverted by a fraud proof
2988        head_receipt_number < receipt_number
2989    }
2990
2991    pub fn is_operator_pending_to_slash(domain_id: DomainId, operator_id: OperatorId) -> bool {
2992        let latest_submitted_er = LatestSubmittedER::<T>::get((domain_id, operator_id));
2993
2994        // The genesis receipt is always valid
2995        if latest_submitted_er.is_zero() {
2996            return false;
2997        }
2998
2999        let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
3000
3001        // If the operator have submitted an ER greater than the current `head_receipt_number`
3002        // meaning the ER is a bad ER and the `head_receipt_number` is previously reverted by
3003        // a fraud proof
3004        head_receipt_number < latest_submitted_er
3005    }
3006
3007    pub fn max_submit_bundle_weight() -> Weight {
3008        T::WeightInfo::submit_bundle()
3009            .saturating_add(
3010                // NOTE: within `submit_bundle`, only one of (or none) `handle_bad_receipt` and
3011                // `confirm_domain_block` can happen, thus we use the `max` of them
3012                //
3013                // We use `MAX_BUNDLE_PER_BLOCK` number to assume the number of slashed operators.
3014                // We do not expect so many operators to be slashed but nontheless, if it did happen
3015                // we will limit the weight to 100 operators.
3016                T::WeightInfo::handle_bad_receipt(MAX_BUNDLE_PER_BLOCK).max(
3017                    T::WeightInfo::confirm_domain_block(MAX_BUNDLE_PER_BLOCK, MAX_BUNDLE_PER_BLOCK),
3018                ),
3019            )
3020            .saturating_add(Self::max_staking_epoch_transition())
3021            .saturating_add(T::WeightInfo::slash_operator(MAX_NOMINATORS_TO_SLASH))
3022    }
3023
3024    pub fn max_submit_receipt_weight() -> Weight {
3025        T::WeightInfo::submit_bundle()
3026            .saturating_add(
3027                // NOTE: within `submit_bundle`, only one of (or none) `handle_bad_receipt` and
3028                // `confirm_domain_block` can happen, thus we use the `max` of them
3029                //
3030                // We use `MAX_BUNDLE_PER_BLOCK` number to assume the number of slashed operators.
3031                // We do not expect so many operators to be slashed but nontheless, if it did happen
3032                // we will limit the weight to 100 operators.
3033                T::WeightInfo::handle_bad_receipt(MAX_BUNDLE_PER_BLOCK).max(
3034                    T::WeightInfo::confirm_domain_block(MAX_BUNDLE_PER_BLOCK, MAX_BUNDLE_PER_BLOCK),
3035                ),
3036            )
3037            .saturating_add(T::WeightInfo::slash_operator(MAX_NOMINATORS_TO_SLASH))
3038    }
3039
3040    pub fn max_staking_epoch_transition() -> Weight {
3041        T::WeightInfo::operator_reward_tax_and_restake(MAX_BUNDLE_PER_BLOCK).saturating_add(
3042            T::WeightInfo::finalize_domain_epoch_staking(T::MaxPendingStakingOperation::get()),
3043        )
3044    }
3045
3046    pub fn max_prune_domain_execution_receipt() -> Weight {
3047        T::WeightInfo::handle_bad_receipt(MAX_BUNDLE_PER_BLOCK)
3048            .saturating_add(T::DbWeight::get().reads_writes(3, 1))
3049    }
3050
3051    fn actual_epoch_transition_weight(epoch_transition_res: EpochTransitionResult) -> Weight {
3052        let EpochTransitionResult {
3053            rewarded_operator_count,
3054            finalized_operator_count,
3055            completed_epoch_index: _,
3056        } = epoch_transition_res;
3057
3058        T::WeightInfo::operator_reward_tax_and_restake(rewarded_operator_count).saturating_add(
3059            T::WeightInfo::finalize_domain_epoch_staking(finalized_operator_count),
3060        )
3061    }
3062
3063    /// Reward the active operators of this domain epoch.
3064    pub fn reward_domain_operators(domain_id: DomainId, rewards: BalanceOf<T>) {
3065        DomainChainRewards::<T>::mutate(domain_id, |current_rewards| {
3066            current_rewards.saturating_add(rewards)
3067        });
3068    }
3069
3070    pub fn storage_fund_account_balance(operator_id: OperatorId) -> BalanceOf<T> {
3071        let storage_fund_acc = storage_fund_account::<T>(operator_id);
3072        T::Currency::reducible_balance(&storage_fund_acc, Preservation::Preserve, Fortitude::Polite)
3073    }
3074
3075    // Get the highest slot of the bundle submitted by a given operator from the previous block
3076    //
3077    // Return 0 if the operator not submit any bundle before
3078    pub fn operator_highest_slot_from_previous_block(
3079        operator_id: OperatorId,
3080        pre_dispatch: bool,
3081    ) -> u64 {
3082        if pre_dispatch {
3083            OperatorHighestSlot::<T>::get(operator_id)
3084        } else {
3085            // The `OperatorBundleSlot` is lazily move to `OperatorHighestSlot` in the `on_initialize` hook
3086            // so when validating tx in the pool we should check `OperatorBundleSlot` first (which is from the
3087            // parent block) then `OperatorHighestSlot`
3088            //
3089            // NOTE: `OperatorBundleSlot` use `BTreeSet` so `last` will return the maximum value in the set
3090            *OperatorBundleSlot::<T>::get(operator_id)
3091                .last()
3092                .unwrap_or(&OperatorHighestSlot::<T>::get(operator_id))
3093        }
3094    }
3095
3096    // Get the domain runtime code that used to derive `receipt`, if the runtime code still present in
3097    // the state then get it from the state otherwise from the `maybe_domain_runtime_code_at` proof.
3098    pub fn get_domain_runtime_code_for_receipt(
3099        domain_id: DomainId,
3100        receipt: &ExecutionReceiptOf<T>,
3101        maybe_domain_runtime_code_at: Option<
3102            DomainRuntimeCodeAt<BlockNumberFor<T>, T::Hash, T::MmrHash>,
3103        >,
3104    ) -> Result<Vec<u8>, FraudProofError> {
3105        let runtime_id = Self::runtime_id(domain_id).ok_or(FraudProofError::RuntimeNotFound)?;
3106        let current_runtime_obj =
3107            RuntimeRegistry::<T>::get(runtime_id).ok_or(FraudProofError::RuntimeNotFound)?;
3108
3109        // NOTE: domain runtime code is taking affect in the next block, so to get the domain runtime code
3110        // that used to derive `receipt` we need to use runtime code at `parent_receipt.consensus_block_number`
3111        let at = {
3112            let parent_receipt =
3113                BlockTreeNodes::<T>::get(*receipt.parent_domain_block_receipt_hash())
3114                    .ok_or(FraudProofError::ParentReceiptNotFound)?
3115                    .execution_receipt;
3116            *parent_receipt.consensus_block_number()
3117        };
3118
3119        let is_domain_runtime_upgraded = current_runtime_obj.updated_at >= at;
3120
3121        let mut runtime_obj = match (is_domain_runtime_upgraded, maybe_domain_runtime_code_at) {
3122            //  The domain runtime is upgraded since `at`, the domain runtime code in `at` is not available
3123            // so `domain_runtime_code_proof` must be provided
3124            (true, None) => return Err(FraudProofError::DomainRuntimeCodeProofNotFound),
3125            (true, Some(domain_runtime_code_at)) => {
3126                let DomainRuntimeCodeAt {
3127                    mmr_proof,
3128                    domain_runtime_code_proof,
3129                } = domain_runtime_code_at;
3130
3131                let state_root = Self::verify_mmr_proof_and_extract_state_root(mmr_proof, at)?;
3132
3133                <DomainRuntimeCodeProof as BasicStorageProof<T::Block>>::verify::<
3134                    T::FraudProofStorageKeyProvider,
3135                >(domain_runtime_code_proof, runtime_id, &state_root)?
3136            }
3137            // Domain runtime code in `at` is available in the state so `domain_runtime_code_proof`
3138            // is unexpected
3139            (false, Some(_)) => return Err(FraudProofError::UnexpectedDomainRuntimeCodeProof),
3140            (false, None) => current_runtime_obj,
3141        };
3142        let code = runtime_obj
3143            .raw_genesis
3144            .take_runtime_code()
3145            .ok_or(storage_proof::VerificationError::RuntimeCodeNotFound)?;
3146        Ok(code)
3147    }
3148
3149    pub fn is_domain_runtime_upgraded_since(
3150        domain_id: DomainId,
3151        at: BlockNumberFor<T>,
3152    ) -> Option<bool> {
3153        Self::runtime_id(domain_id)
3154            .and_then(RuntimeRegistry::<T>::get)
3155            .map(|runtime_obj| runtime_obj.updated_at >= at)
3156    }
3157
3158    pub fn verify_mmr_proof_and_extract_state_root(
3159        mmr_leaf_proof: ConsensusChainMmrLeafProof<BlockNumberFor<T>, T::Hash, T::MmrHash>,
3160        expected_block_number: BlockNumberFor<T>,
3161    ) -> Result<T::Hash, FraudProofError> {
3162        let leaf_data = T::MmrProofVerifier::verify_proof_and_extract_leaf(mmr_leaf_proof)
3163            .ok_or(FraudProofError::BadMmrProof)?;
3164
3165        // Ensure it is a proof of the exact block that we expected
3166        if expected_block_number != leaf_data.block_number() {
3167            return Err(FraudProofError::UnexpectedMmrProof);
3168        }
3169
3170        Ok(leaf_data.state_root())
3171    }
3172
3173    // Return the number of domain runtime upgrade happened since `last_domain_block_number`
3174    fn missed_domain_runtime_upgrade(domain_id: DomainId) -> Result<u32, BlockTreeError> {
3175        let runtime_id = Self::runtime_id(domain_id).ok_or(BlockTreeError::RuntimeNotFound)?;
3176        let last_domain_block_number = HeadDomainNumber::<T>::get(domain_id);
3177
3178        // The consensus block number that derive the last domain block
3179        let last_block_at =
3180            ExecutionInbox::<T>::iter_key_prefix((domain_id, last_domain_block_number))
3181                .next()
3182                // If there is no `ExecutionInbox` exist for the `last_domain_block_number` it means
3183                // there is no bundle submitted for the domain since it is instantiated, in this case,
3184                // we use the `domain_obj.created_at` (which derive the genesis block).
3185                .or(DomainRegistry::<T>::get(domain_id).map(|domain_obj| domain_obj.created_at))
3186                .ok_or(BlockTreeError::LastBlockNotFound)?;
3187
3188        Ok(DomainRuntimeUpgradeRecords::<T>::get(runtime_id)
3189            .into_keys()
3190            .rev()
3191            .take_while(|upgraded_at| *upgraded_at > last_block_at)
3192            .count() as u32)
3193    }
3194
3195    /// Returns true if the Domain is registered.
3196    pub fn is_domain_registered(domain_id: DomainId) -> bool {
3197        DomainStakingSummary::<T>::contains_key(domain_id)
3198    }
3199
3200    /// Returns domain's sudo call, if any.
3201    pub fn domain_sudo_call(domain_id: DomainId) -> Option<Vec<u8>> {
3202        DomainSudoCalls::<T>::get(domain_id).maybe_call
3203    }
3204
3205    // The gap between `domain_best_number` and `HeadReceiptNumber` represent the number
3206    // of receipt to be submitted
3207    pub fn receipt_gap(domain_id: DomainId) -> Result<DomainBlockNumberFor<T>, BundleError> {
3208        let domain_best_number = Self::domain_best_number(domain_id)?;
3209        let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
3210
3211        Ok(domain_best_number.saturating_sub(head_receipt_number))
3212    }
3213
3214    /// Returns true if this is an EVM domain.
3215    pub fn is_evm_domain(domain_id: DomainId) -> bool {
3216        if let Some(domain_obj) = DomainRegistry::<T>::get(domain_id) {
3217            domain_obj.domain_runtime_info.is_evm_domain()
3218        } else {
3219            false
3220        }
3221    }
3222
3223    /// Returns true if this is a private EVM domain.
3224    pub fn is_private_evm_domain(domain_id: DomainId) -> bool {
3225        if let Some(domain_obj) = DomainRegistry::<T>::get(domain_id) {
3226            domain_obj.domain_runtime_info.is_private_evm_domain()
3227        } else {
3228            false
3229        }
3230    }
3231
3232    /// Returns EVM domain's "set contract creation allowed by" call, if any.
3233    pub fn evm_domain_contract_creation_allowed_by_call(
3234        domain_id: DomainId,
3235    ) -> Option<sp_domains::PermissionedActionAllowedBy<EthereumAccountId>> {
3236        EvmDomainContractCreationAllowedByCalls::<T>::get(domain_id).maybe_call
3237    }
3238
3239    /// Updates `previous_versions` with the latest bundle and execution receipt version, and
3240    /// returns the updated map.
3241    #[must_use = "set PreviousBundleAndExecutionReceiptVersions to the value returned by this function"]
3242    pub(crate) fn calculate_previous_bundle_and_execution_receipt_versions<BEV>(
3243        block_number: BlockNumberFor<T>,
3244        mut previous_versions: BTreeMap<BlockNumberFor<T>, BEV>,
3245        current_version: BEV,
3246    ) -> BTreeMap<BlockNumberFor<T>, BEV>
3247    where
3248        BEV: PartialEq,
3249    {
3250        // First version change, just add it to the map (no replacements possible)
3251        if previous_versions.is_empty() {
3252            previous_versions.insert(block_number, current_version);
3253        } else {
3254            // if there is a previous version stored, and
3255            // the last previous version matches the current version,
3256            // then we can mark that version with the latest upgraded block number.
3257            let (prev_number, prev_version) = previous_versions
3258                .pop_last()
3259                .expect("at least one version is available due to check above");
3260
3261            // versions matched, so insert the version with latest block number.
3262            if prev_version == current_version {
3263                previous_versions.insert(block_number, current_version);
3264            } else {
3265                // versions did not match, so keep the last previous version at its original block
3266                // number, and add the current version at the latest block number.
3267                previous_versions.insert(prev_number, prev_version);
3268                previous_versions.insert(block_number, current_version);
3269            }
3270        }
3271
3272        previous_versions
3273    }
3274
3275    /// Returns the current bundle and execution receipt versions.
3276    ///
3277    /// When there is a substrate upgrade at block #x, and if the client
3278    /// uses any runtime apis at that particular block, the new runtime is used
3279    /// instead of previous runtime even though previous runtime was used for execution.
3280    /// This is an unfortunate side-effect of substrate pulling the runtime from :code: key
3281    /// which was replaced with new one in that block #x.
3282    /// Since we store the version before runtime upgrade, if there exists a key in the stored version
3283    /// for that specific block, we return the that version instead of currently defined version on new runtime.
3284    pub fn current_bundle_and_execution_receipt_version() -> BundleAndExecutionReceiptVersion {
3285        let block_number = frame_system::Pallet::<T>::block_number();
3286        let versions = PreviousBundleAndExecutionReceiptVersions::<T>::get();
3287        match versions.get(&block_number) {
3288            // no upgrade happened at this number, so safe to return the current version
3289            None => T::CurrentBundleAndExecutionReceiptVersion::get(),
3290            // upgrade did happen at this block, so return the version stored at this number.
3291            Some(version) => *version,
3292        }
3293    }
3294
3295    /// Returns the complete nominator position for a given operator and account at the current block.
3296    ///
3297    /// This calculates the total position including:
3298    /// - Current stake value (converted from shares using instant share price including rewards)
3299    /// - Total storage fee deposits (known + pending)
3300    /// - Pending deposits (not yet converted to shares)
3301    /// - Pending withdrawals (with unlock timing)
3302    ///
3303    /// Note: Operator accounts are also nominator accounts, so this call will return the position
3304    /// for the operator account.
3305    ///
3306    /// Returns None if no position exists for the given operator and account at the current block.
3307    pub fn nominator_position(
3308        operator_id: OperatorId,
3309        nominator_account: T::AccountId,
3310    ) -> Option<sp_domains::NominatorPosition<BalanceOf<T>, DomainBlockNumberFor<T>, T::Share>>
3311    {
3312        nominator_position::nominator_position::<T>(operator_id, nominator_account)
3313    }
3314}
3315
3316impl<T: Config> subspace_runtime_primitives::OnSetCode<BlockNumberFor<T>> for Pallet<T> {
3317    /// Store the Bundle and Er versions before runtime is upgraded along with the
3318    /// Consensus number at which runtime is upgraded.
3319    fn set_code(block_number: BlockNumberFor<T>) -> DispatchResult {
3320        PreviousBundleAndExecutionReceiptVersions::<T>::set(
3321            Self::calculate_previous_bundle_and_execution_receipt_versions(
3322                block_number,
3323                PreviousBundleAndExecutionReceiptVersions::<T>::get(),
3324                T::CurrentBundleAndExecutionReceiptVersion::get(),
3325            ),
3326        );
3327
3328        Ok(())
3329    }
3330}
3331
3332impl<T: Config> sp_domains::DomainOwner<T::AccountId> for Pallet<T> {
3333    fn is_domain_owner(domain_id: DomainId, acc: T::AccountId) -> bool {
3334        if let Some(domain_obj) = DomainRegistry::<T>::get(domain_id) {
3335            domain_obj.owner_account_id == acc
3336        } else {
3337            false
3338        }
3339    }
3340}
3341
3342impl<T> Pallet<T>
3343where
3344    T: Config + CreateUnsigned<Call<T>>,
3345{
3346    /// Submits an unsigned extrinsic [`Call::submit_bundle`].
3347    pub fn submit_bundle_unsigned(opaque_bundle: OpaqueBundleOf<T>) {
3348        let slot = opaque_bundle.slot_number();
3349        let extrinsics_count = opaque_bundle.body_length();
3350
3351        let call = Call::submit_bundle { opaque_bundle };
3352        let ext = T::create_unsigned(call.into());
3353
3354        match SubmitTransaction::<T, Call<T>>::submit_transaction(ext) {
3355            Ok(()) => {
3356                log::info!("Submitted bundle from slot {slot}, extrinsics: {extrinsics_count}",);
3357            }
3358            Err(()) => {
3359                log::error!("Error submitting bundle");
3360            }
3361        }
3362    }
3363
3364    /// Submits an unsigned extrinsic [`Call::submit_receipt`].
3365    pub fn submit_receipt_unsigned(singleton_receipt: SingletonReceiptOf<T>) {
3366        let slot = singleton_receipt.slot_number();
3367        let domain_block_number = *singleton_receipt.receipt().domain_block_number();
3368
3369        let call = Call::submit_receipt { singleton_receipt };
3370        let ext = T::create_unsigned(call.into());
3371        match SubmitTransaction::<T, Call<T>>::submit_transaction(ext) {
3372            Ok(()) => {
3373                log::info!(
3374                    "Submitted singleton receipt from slot {slot}, domain_block_number: {domain_block_number:?}",
3375                );
3376            }
3377            Err(()) => {
3378                log::error!("Error submitting singleton receipt");
3379            }
3380        }
3381    }
3382
3383    /// Submits an unsigned extrinsic [`Call::submit_fraud_proof`].
3384    pub fn submit_fraud_proof_unsigned(fraud_proof: FraudProofFor<T>) {
3385        let call = Call::submit_fraud_proof {
3386            fraud_proof: Box::new(fraud_proof),
3387        };
3388
3389        let ext = T::create_unsigned(call.into());
3390        match SubmitTransaction::<T, Call<T>>::submit_transaction(ext) {
3391            Ok(()) => {
3392                log::info!("Submitted fraud proof");
3393            }
3394            Err(()) => {
3395                log::error!("Error submitting fraud proof");
3396            }
3397        }
3398    }
3399}
3400
3401/// Calculates the new tx range based on the bundles produced during the interval.
3402pub fn calculate_tx_range(
3403    cur_tx_range: U256,
3404    actual_bundle_count: u64,
3405    expected_bundle_count: u64,
3406) -> U256 {
3407    if actual_bundle_count == 0 || expected_bundle_count == 0 {
3408        return cur_tx_range;
3409    }
3410
3411    let Some(new_tx_range) = U256::from(actual_bundle_count)
3412        .saturating_mul(&cur_tx_range)
3413        .checked_div(&U256::from(expected_bundle_count))
3414    else {
3415        return cur_tx_range;
3416    };
3417
3418    let upper_bound = cur_tx_range.saturating_mul(&U256::from(4_u64));
3419    let Some(lower_bound) = cur_tx_range.checked_div(&U256::from(4_u64)) else {
3420        return cur_tx_range;
3421    };
3422    new_tx_range.clamp(lower_bound, upper_bound)
3423}