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