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