pallet_domains/
lib.rs

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