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