pallet_domains/
lib.rs

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