pallet_domains/
lib.rs

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