pallet_domains/
lib.rs

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