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