pallet_domains/
lib.rs

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