pallet_domains/
lib.rs

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