Skip to main content

pallet_domains/
lib.rs

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