1#![cfg_attr(not(feature = "std"), no_std)]
4#![feature(array_windows, let_chains, variant_count)]
5
6#[cfg(feature = "runtime-benchmarks")]
7mod benchmarking;
8
9#[cfg(test)]
10mod tests;
11
12pub mod block_tree;
13pub mod bundle_storage_fund;
14pub mod domain_registry;
15pub mod extensions;
16pub mod migrations;
17pub mod runtime_registry;
18mod staking;
19mod staking_epoch;
20pub mod weights;
21
22extern crate alloc;
23
24use crate::block_tree::{verify_execution_receipt, Error as BlockTreeError};
25use crate::bundle_storage_fund::{charge_bundle_storage_fee, storage_fund_account};
26use crate::domain_registry::{DomainConfig, Error as DomainRegistryError};
27use crate::runtime_registry::into_complete_raw_genesis;
28#[cfg(feature = "runtime-benchmarks")]
29pub use crate::staking::do_register_operator;
30use crate::staking::{do_reward_operators, OperatorStatus};
31use crate::staking_epoch::EpochTransitionResult;
32use crate::weights::WeightInfo;
33#[cfg(not(feature = "std"))]
34use alloc::boxed::Box;
35use alloc::collections::btree_map::BTreeMap;
36#[cfg(not(feature = "std"))]
37use alloc::vec::Vec;
38use core::marker::PhantomData;
39use domain_runtime_primitives::EthereumAccountId;
40use frame_support::ensure;
41use frame_support::pallet_prelude::{RuntimeDebug, StorageVersion};
42use frame_support::traits::fungible::{Inspect, InspectHold};
43use frame_support::traits::tokens::{Fortitude, Preservation};
44use frame_support::traits::{EnsureOrigin, Get, Randomness as RandomnessT, Time};
45use frame_support::weights::Weight;
46use frame_system::offchain::SubmitTransaction;
47use frame_system::pallet_prelude::*;
48pub use pallet::*;
49use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
50use scale_info::TypeInfo;
51use sp_consensus_subspace::consensus::is_proof_of_time_valid;
52use sp_consensus_subspace::WrappedPotOutput;
53use sp_core::H256;
54use sp_domains::bundle_producer_election::BundleProducerElectionParams;
55use sp_domains::{
56 ChainId, DomainBundleLimit, DomainId, DomainInstanceData, ExecutionReceipt, OpaqueBundle,
57 OperatorId, OperatorPublicKey, OperatorRewardSource, OperatorSignature, ProofOfElection,
58 RuntimeId, SealedSingletonReceipt, DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT,
59 EMPTY_EXTRINSIC_ROOT,
60};
61use sp_domains_fraud_proof::fraud_proof::{
62 DomainRuntimeCodeAt, FraudProof, FraudProofVariant, InvalidBlockFeesProof,
63 InvalidDomainBlockHashProof, InvalidTransfersProof,
64};
65use sp_domains_fraud_proof::storage_proof::{self, BasicStorageProof, DomainRuntimeCodeProof};
66use sp_domains_fraud_proof::verification::{
67 verify_invalid_block_fees_fraud_proof, verify_invalid_bundles_fraud_proof,
68 verify_invalid_domain_block_hash_fraud_proof,
69 verify_invalid_domain_extrinsics_root_fraud_proof, verify_invalid_state_transition_fraud_proof,
70 verify_invalid_transfers_fraud_proof, verify_valid_bundle_fraud_proof,
71};
72use sp_runtime::traits::{BlockNumberProvider, CheckedSub, Hash, Header, One, Zero};
73use sp_runtime::transaction_validity::TransactionPriority;
74use sp_runtime::{RuntimeAppPublic, SaturatedConversion, Saturating};
75use sp_subspace_mmr::{ConsensusChainMmrLeafProof, MmrProofVerifier};
76pub use staking::OperatorConfig;
77use subspace_core_primitives::pot::PotOutput;
78use subspace_core_primitives::{BlockHash, SlotNumber, U256};
79use subspace_runtime_primitives::{Balance, CreateUnsigned, Moment, StorageFee};
80
81pub const MAX_NOMINATORS_TO_SLASH: u32 = 10;
83
84pub(crate) type BalanceOf<T> = <T as Config>::Balance;
85
86pub(crate) type FungibleHoldId<T> =
87 <<T as Config>::Currency as InspectHold<<T as frame_system::Config>::AccountId>>::Reason;
88
89pub(crate) type NominatorId<T> = <T as frame_system::Config>::AccountId;
90
91pub trait HoldIdentifier<T: Config> {
92 fn staking_staked() -> FungibleHoldId<T>;
93 fn domain_instantiation_id() -> FungibleHoldId<T>;
94 fn storage_fund_withdrawal() -> FungibleHoldId<T>;
95}
96
97pub trait BlockSlot<T: frame_system::Config> {
98 fn future_slot(block_number: BlockNumberFor<T>) -> Option<sp_consensus_slots::Slot>;
100
101 fn slot_produced_after(to_check: sp_consensus_slots::Slot) -> Option<BlockNumberFor<T>>;
103}
104
105pub type ExecutionReceiptOf<T> = ExecutionReceipt<
106 BlockNumberFor<T>,
107 <T as frame_system::Config>::Hash,
108 DomainBlockNumberFor<T>,
109 <T as Config>::DomainHash,
110 BalanceOf<T>,
111>;
112
113pub type OpaqueBundleOf<T> = OpaqueBundle<
114 BlockNumberFor<T>,
115 <T as frame_system::Config>::Hash,
116 <T as Config>::DomainHeader,
117 BalanceOf<T>,
118>;
119
120pub type SingletonReceiptOf<T> = SealedSingletonReceipt<
121 BlockNumberFor<T>,
122 <T as frame_system::Config>::Hash,
123 <T as Config>::DomainHeader,
124 BalanceOf<T>,
125>;
126
127pub type FraudProofFor<T> = FraudProof<
128 BlockNumberFor<T>,
129 <T as frame_system::Config>::Hash,
130 <T as Config>::DomainHeader,
131 <T as Config>::MmrHash,
132>;
133
134#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
136pub(crate) struct ElectionVerificationParams<Balance> {
137 operators: BTreeMap<OperatorId, Balance>,
138 total_domain_stake: Balance,
139}
140
141pub type DomainBlockNumberFor<T> = <<T as Config>::DomainHeader as Header>::Number;
142pub type DomainHashingFor<T> = <<T as Config>::DomainHeader as Header>::Hashing;
143pub type ReceiptHashFor<T> = <<T as Config>::DomainHeader as Header>::Hash;
144
145pub type BlockTreeNodeFor<T> = crate::block_tree::BlockTreeNode<
146 BlockNumberFor<T>,
147 <T as frame_system::Config>::Hash,
148 DomainBlockNumberFor<T>,
149 <T as Config>::DomainHash,
150 BalanceOf<T>,
151>;
152
153#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
155pub enum RawOrigin {
156 ValidatedUnsigned,
157}
158
159pub struct EnsureDomainOrigin;
161impl<O: Into<Result<RawOrigin, O>> + From<RawOrigin>> EnsureOrigin<O> for EnsureDomainOrigin {
162 type Success = ();
163
164 fn try_origin(o: O) -> Result<Self::Success, O> {
165 o.into().map(|o| match o {
166 RawOrigin::ValidatedUnsigned => (),
167 })
168 }
169
170 #[cfg(feature = "runtime-benchmarks")]
171 fn try_successful_origin() -> Result<O, ()> {
172 Ok(O::from(RawOrigin::ValidatedUnsigned))
173 }
174}
175
176#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
178pub struct DomainsSkipBalanceChecks<T>(PhantomData<T>);
179
180impl<T: Config> sp_domains::SkipBalanceChecks for DomainsSkipBalanceChecks<T> {
181 fn should_skip_balance_check(chain_id: ChainId) -> bool {
182 match chain_id {
183 ChainId::Consensus => false,
184 ChainId::Domain(domain_id) => SkipBalanceChecks::<T>::get().contains(&domain_id),
185 }
186 }
187}
188
189const STORAGE_VERSION: StorageVersion = StorageVersion::new(5);
191
192const MAX_BUNDLE_PER_BLOCK: u32 = 100;
197
198pub(crate) type StateRootOf<T> = <<T as frame_system::Config>::Hashing as Hash>::Output;
199
200#[expect(clippy::useless_conversion, reason = "Macro-generated")]
201#[frame_support::pallet]
202mod pallet {
203 #[cfg(not(feature = "runtime-benchmarks"))]
204 use crate::block_tree::AcceptedReceiptType;
205 use crate::block_tree::{
206 execution_receipt_type, process_execution_receipt, prune_receipt, Error as BlockTreeError,
207 ReceiptType,
208 };
209 #[cfg(not(feature = "runtime-benchmarks"))]
210 use crate::bundle_storage_fund::refund_storage_fee;
211 use crate::bundle_storage_fund::Error as BundleStorageFundError;
212 use crate::domain_registry::{
213 do_instantiate_domain, do_update_domain_allow_list, DomainConfigParams, DomainObject,
214 Error as DomainRegistryError,
215 };
216 use crate::runtime_registry::{
217 do_register_runtime, do_schedule_runtime_upgrade, do_upgrade_runtimes,
218 register_runtime_at_genesis, DomainRuntimeUpgradeEntry, Error as RuntimeRegistryError,
219 ScheduledRuntimeUpgrade,
220 };
221 #[cfg(not(feature = "runtime-benchmarks"))]
222 use crate::staking::do_reward_operators;
223 use crate::staking::{
224 do_deregister_operator, do_mark_operators_as_slashed, do_nominate_operator,
225 do_register_operator, do_unlock_funds, do_unlock_nominator, do_withdraw_stake, Deposit,
226 DomainEpoch, Error as StakingError, Operator, OperatorConfig, SharePrice, StakingSummary,
227 WithdrawStake, Withdrawal,
228 };
229 #[cfg(not(feature = "runtime-benchmarks"))]
230 use crate::staking_epoch::do_slash_operator;
231 use crate::staking_epoch::{do_finalize_domain_current_epoch, Error as StakingEpochError};
232 use crate::storage_proof::InherentExtrinsicData;
233 use crate::weights::WeightInfo;
234 #[cfg(not(feature = "runtime-benchmarks"))]
235 use crate::DomainHashingFor;
236 #[cfg(not(feature = "runtime-benchmarks"))]
237 use crate::MAX_NOMINATORS_TO_SLASH;
238 use crate::{
239 BalanceOf, BlockSlot, BlockTreeNodeFor, DomainBlockNumberFor, ElectionVerificationParams,
240 ExecutionReceiptOf, FraudProofFor, HoldIdentifier, NominatorId, OpaqueBundleOf, RawOrigin,
241 ReceiptHashFor, SingletonReceiptOf, StateRootOf, MAX_BUNDLE_PER_BLOCK, STORAGE_VERSION,
242 };
243 #[cfg(not(feature = "std"))]
244 use alloc::string::String;
245 #[cfg(not(feature = "std"))]
246 use alloc::vec;
247 #[cfg(not(feature = "std"))]
248 use alloc::vec::Vec;
249 use domain_runtime_primitives::{EVMChainId, EthereumAccountId};
250 use frame_support::pallet_prelude::*;
251 use frame_support::traits::fungible::{Inspect, InspectHold, Mutate, MutateHold};
252 use frame_support::traits::tokens::Preservation;
253 use frame_support::traits::{Randomness as RandomnessT, Time};
254 use frame_support::weights::Weight;
255 use frame_support::{Identity, PalletError};
256 use frame_system::pallet_prelude::*;
257 use parity_scale_codec::FullCodec;
258 use sp_core::H256;
259 use sp_domains::bundle_producer_election::ProofOfElectionError;
260 use sp_domains::{
261 BundleDigest, DomainBundleSubmitted, DomainId, DomainOwner, DomainSudoCall,
262 DomainsTransfersTracker, EpochIndex, EvmDomainContractCreationAllowedByCall, GenesisDomain,
263 OnChainRewards, OnDomainInstantiated, OperatorAllowList, OperatorId, OperatorRewardSource,
264 RuntimeId, RuntimeObject, RuntimeType,
265 };
266 use sp_domains_fraud_proof::fraud_proof_runtime_interface::domain_runtime_call;
267 use sp_domains_fraud_proof::storage_proof::{self, FraudProofStorageKeyProvider};
268 use sp_domains_fraud_proof::{InvalidTransactionCode, StatelessDomainRuntimeCall};
269 use sp_runtime::traits::{
270 AtLeast32BitUnsigned, BlockNumberProvider, CheckEqual, CheckedAdd, Header as HeaderT,
271 MaybeDisplay, One, SimpleBitOps, Zero,
272 };
273 use sp_runtime::Saturating;
274 use sp_std::boxed::Box;
275 use sp_std::collections::btree_map::BTreeMap;
276 use sp_std::collections::btree_set::BTreeSet;
277 use sp_std::fmt::Debug;
278 use sp_subspace_mmr::MmrProofVerifier;
279 use subspace_core_primitives::{Randomness, U256};
280 use subspace_runtime_primitives::StorageFee;
281
282 #[pallet::config]
283 pub trait Config: frame_system::Config<Hash: Into<H256> + From<H256>> {
284 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
285
286 type DomainOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = ()>;
288
289 type DomainHash: Parameter
294 + Member
295 + MaybeSerializeDeserialize
296 + Debug
297 + MaybeDisplay
298 + SimpleBitOps
299 + Ord
300 + Default
301 + Copy
302 + CheckEqual
303 + sp_std::hash::Hash
304 + AsRef<[u8]>
305 + AsMut<[u8]>
306 + MaxEncodedLen
307 + Into<H256>
308 + From<H256>;
309
310 type Balance: Parameter
312 + Member
313 + MaybeSerializeDeserialize
314 + AtLeast32BitUnsigned
315 + FullCodec
316 + Debug
317 + MaybeDisplay
318 + Default
319 + Copy
320 + MaxEncodedLen
321 + From<u64>;
322
323 type DomainHeader: HeaderT<Hash = Self::DomainHash>;
325
326 #[pallet::constant]
328 type ConfirmationDepthK: Get<BlockNumberFor<Self>>;
329
330 type Currency: Inspect<Self::AccountId, Balance = Self::Balance>
332 + Mutate<Self::AccountId>
333 + InspectHold<Self::AccountId>
334 + MutateHold<Self::AccountId>;
335
336 type Share: Parameter
338 + Member
339 + MaybeSerializeDeserialize
340 + Debug
341 + AtLeast32BitUnsigned
342 + FullCodec
343 + Copy
344 + Default
345 + TypeInfo
346 + MaxEncodedLen
347 + IsType<BalanceOf<Self>>;
348
349 type HoldIdentifier: HoldIdentifier<Self>;
351
352 #[pallet::constant]
354 type BlockTreePruningDepth: Get<DomainBlockNumberFor<Self>>;
355
356 #[pallet::constant]
358 type ConsensusSlotProbability: Get<(u64, u64)>;
359
360 #[pallet::constant]
362 type MaxDomainBlockSize: Get<u32>;
363
364 #[pallet::constant]
366 type MaxDomainBlockWeight: Get<Weight>;
367
368 #[pallet::constant]
370 type MaxDomainNameLength: Get<u32>;
371
372 #[pallet::constant]
374 type DomainInstantiationDeposit: Get<BalanceOf<Self>>;
375
376 type WeightInfo: WeightInfo;
378
379 #[pallet::constant]
381 type InitialDomainTxRange: Get<u64>;
382
383 #[pallet::constant]
385 type DomainTxRangeAdjustmentInterval: Get<u64>;
386
387 #[pallet::constant]
389 type MinOperatorStake: Get<BalanceOf<Self>>;
390
391 #[pallet::constant]
393 type MinNominatorStake: Get<BalanceOf<Self>>;
394
395 #[pallet::constant]
397 type StakeWithdrawalLockingPeriod: Get<DomainBlockNumberFor<Self>>;
398
399 #[pallet::constant]
401 type StakeEpochDuration: Get<DomainBlockNumberFor<Self>>;
402
403 #[pallet::constant]
405 type TreasuryAccount: Get<Self::AccountId>;
406
407 #[pallet::constant]
409 type MaxPendingStakingOperation: Get<u32>;
410
411 type Randomness: RandomnessT<Self::Hash, BlockNumberFor<Self>>;
413
414 #[pallet::constant]
416 type PalletId: Get<frame_support::PalletId>;
417
418 type StorageFee: StorageFee<BalanceOf<Self>>;
420
421 type BlockTimestamp: Time;
423
424 type BlockSlot: BlockSlot<Self>;
426
427 type DomainsTransfersTracker: DomainsTransfersTracker<BalanceOf<Self>>;
429
430 type MaxInitialDomainAccounts: Get<u32>;
432
433 type MinInitialDomainAccountBalance: Get<BalanceOf<Self>>;
435
436 #[pallet::constant]
438 type BundleLongevity: Get<u32>;
439
440 type DomainBundleSubmitted: DomainBundleSubmitted;
442
443 type OnDomainInstantiated: OnDomainInstantiated;
445
446 type MmrHash: Parameter + Member + Default + Clone;
448
449 type MmrProofVerifier: MmrProofVerifier<
451 Self::MmrHash,
452 BlockNumberFor<Self>,
453 StateRootOf<Self>,
454 >;
455
456 type FraudProofStorageKeyProvider: FraudProofStorageKeyProvider<BlockNumberFor<Self>>;
458
459 type OnChainRewards: OnChainRewards<BalanceOf<Self>>;
461
462 #[pallet::constant]
466 type WithdrawalLimit: Get<u32>;
467 }
468
469 #[pallet::pallet]
470 #[pallet::without_storage_info]
471 #[pallet::storage_version(STORAGE_VERSION)]
472 pub struct Pallet<T>(_);
473
474 #[pallet::storage]
476 pub type SuccessfulBundles<T> = StorageMap<_, Identity, DomainId, Vec<H256>, ValueQuery>;
477
478 #[pallet::storage]
480 pub(super) type NextRuntimeId<T> = StorageValue<_, RuntimeId, ValueQuery>;
481
482 pub struct StartingEVMChainId;
484
485 impl Get<EVMChainId> for StartingEVMChainId {
486 fn get() -> EVMChainId {
487 490000
490 }
491 }
492
493 #[pallet::storage]
495 pub(super) type NextEVMChainId<T> = StorageValue<_, EVMChainId, ValueQuery, StartingEVMChainId>;
496
497 #[pallet::storage]
498 pub type RuntimeRegistry<T: Config> =
499 StorageMap<_, Identity, RuntimeId, RuntimeObject<BlockNumberFor<T>, T::Hash>, OptionQuery>;
500
501 #[pallet::storage]
502 pub(super) type ScheduledRuntimeUpgrades<T: Config> = StorageDoubleMap<
503 _,
504 Identity,
505 BlockNumberFor<T>,
506 Identity,
507 RuntimeId,
508 ScheduledRuntimeUpgrade<T::Hash>,
509 OptionQuery,
510 >;
511
512 #[pallet::storage]
513 pub(super) type NextOperatorId<T> = StorageValue<_, OperatorId, ValueQuery>;
514
515 #[pallet::storage]
516 pub(super) type OperatorIdOwner<T: Config> =
517 StorageMap<_, Identity, OperatorId, T::AccountId, OptionQuery>;
518
519 #[pallet::storage]
520 #[pallet::getter(fn domain_staking_summary)]
521 pub(super) type DomainStakingSummary<T: Config> =
522 StorageMap<_, Identity, DomainId, StakingSummary<OperatorId, BalanceOf<T>>, OptionQuery>;
523
524 #[pallet::storage]
526 pub(super) type Operators<T: Config> = StorageMap<
527 _,
528 Identity,
529 OperatorId,
530 Operator<BalanceOf<T>, T::Share, DomainBlockNumberFor<T>>,
531 OptionQuery,
532 >;
533
534 #[pallet::storage]
536 pub(super) type OperatorHighestSlot<T: Config> =
537 StorageMap<_, Identity, OperatorId, u64, ValueQuery>;
538
539 #[pallet::storage]
542 pub(super) type OperatorBundleSlot<T: Config> =
543 StorageMap<_, Identity, OperatorId, BTreeSet<u64>, ValueQuery>;
544
545 #[pallet::storage]
548 pub type OperatorEpochSharePrice<T: Config> =
549 StorageDoubleMap<_, Identity, OperatorId, Identity, DomainEpoch, SharePrice, OptionQuery>;
550
551 #[pallet::storage]
553 pub(super) type Deposits<T: Config> = StorageDoubleMap<
554 _,
555 Identity,
556 OperatorId,
557 Identity,
558 NominatorId<T>,
559 Deposit<T::Share, BalanceOf<T>>,
560 OptionQuery,
561 >;
562
563 #[pallet::storage]
565 pub(super) type Withdrawals<T: Config> = StorageDoubleMap<
566 _,
567 Identity,
568 OperatorId,
569 Identity,
570 NominatorId<T>,
571 Withdrawal<BalanceOf<T>, T::Share, DomainBlockNumberFor<T>>,
572 OptionQuery,
573 >;
574
575 #[pallet::storage]
577 pub(super) type DepositOnHold<T: Config> =
578 StorageMap<_, Identity, (OperatorId, NominatorId<T>), BalanceOf<T>, ValueQuery>;
579
580 #[pallet::storage]
587 pub(super) type NominatorCount<T: Config> =
588 StorageMap<_, Identity, OperatorId, u32, ValueQuery>;
589
590 #[pallet::storage]
594 pub(super) type PendingSlashes<T: Config> =
595 StorageMap<_, Identity, DomainId, BTreeSet<OperatorId>, OptionQuery>;
596
597 #[pallet::storage]
600 pub(super) type PendingStakingOperationCount<T: Config> =
601 StorageMap<_, Identity, DomainId, u32, ValueQuery>;
602
603 #[pallet::storage]
605 #[pallet::getter(fn next_domain_id)]
606 pub(super) type NextDomainId<T> = StorageValue<_, DomainId, ValueQuery>;
607
608 #[pallet::storage]
610 pub(super) type DomainRegistry<T: Config> = StorageMap<
611 _,
612 Identity,
613 DomainId,
614 DomainObject<BlockNumberFor<T>, ReceiptHashFor<T>, T::AccountId, BalanceOf<T>>,
615 OptionQuery,
616 >;
617
618 #[pallet::storage]
621 pub(super) type BlockTree<T: Config> = StorageDoubleMap<
622 _,
623 Identity,
624 DomainId,
625 Identity,
626 DomainBlockNumberFor<T>,
627 ReceiptHashFor<T>,
628 OptionQuery,
629 >;
630
631 #[pallet::storage]
633 pub(super) type BlockTreeNodes<T: Config> =
634 StorageMap<_, Identity, ReceiptHashFor<T>, BlockTreeNodeFor<T>, OptionQuery>;
635
636 #[pallet::storage]
638 pub(super) type HeadReceiptNumber<T: Config> =
639 StorageMap<_, Identity, DomainId, DomainBlockNumberFor<T>, ValueQuery>;
640
641 #[pallet::storage]
645 pub(super) type NewAddedHeadReceipt<T: Config> =
646 StorageMap<_, Identity, DomainId, T::DomainHash, OptionQuery>;
647
648 #[pallet::storage]
654 #[pallet::getter(fn consensus_block_info)]
655 pub type ConsensusBlockHash<T: Config> =
656 StorageDoubleMap<_, Identity, DomainId, Identity, BlockNumberFor<T>, T::Hash, OptionQuery>;
657
658 #[pallet::storage]
664 pub type ExecutionInbox<T: Config> = StorageNMap<
665 _,
666 (
667 NMapKey<Identity, DomainId>,
668 NMapKey<Identity, DomainBlockNumberFor<T>>,
669 NMapKey<Identity, BlockNumberFor<T>>,
670 ),
671 Vec<BundleDigest<T::DomainHash>>,
672 ValueQuery,
673 >;
674
675 #[pallet::storage]
679 pub(super) type InboxedBundleAuthor<T: Config> =
680 StorageMap<_, Identity, T::DomainHash, OperatorId, OptionQuery>;
681
682 #[pallet::storage]
691 pub(super) type HeadDomainNumber<T: Config> =
692 StorageMap<_, Identity, DomainId, DomainBlockNumberFor<T>, ValueQuery>;
693
694 #[pallet::storage]
700 pub(super) type LastEpochStakingDistribution<T: Config> =
701 StorageMap<_, Identity, DomainId, ElectionVerificationParams<BalanceOf<T>>, OptionQuery>;
702
703 #[pallet::storage]
705 #[pallet::getter(fn latest_confirmed_domain_execution_receipt)]
706 pub type LatestConfirmedDomainExecutionReceipt<T: Config> =
707 StorageMap<_, Identity, DomainId, ExecutionReceiptOf<T>, OptionQuery>;
708
709 #[pallet::storage]
711 #[pallet::getter(fn domain_genesis_block_execution_receipt)]
712 pub type DomainGenesisBlockExecutionReceipt<T: Config> =
713 StorageMap<_, Identity, DomainId, ExecutionReceiptOf<T>, OptionQuery>;
714
715 #[pallet::storage]
722 #[pallet::getter(fn latest_submitted_er)]
723 pub(super) type LatestSubmittedER<T: Config> =
724 StorageMap<_, Identity, (DomainId, OperatorId), DomainBlockNumberFor<T>, ValueQuery>;
725
726 #[pallet::storage]
728 pub(super) type PermissionedActionAllowedBy<T: Config> =
729 StorageValue<_, sp_domains::PermissionedActionAllowedBy<T::AccountId>, OptionQuery>;
730
731 #[pallet::storage]
734 pub(super) type AccumulatedTreasuryFunds<T> = StorageValue<_, BalanceOf<T>, ValueQuery>;
735
736 #[pallet::storage]
738 pub(super) type DomainRuntimeUpgradeRecords<T: Config> = StorageMap<
739 _,
740 Identity,
741 RuntimeId,
742 BTreeMap<BlockNumberFor<T>, DomainRuntimeUpgradeEntry<T::Hash>>,
743 ValueQuery,
744 >;
745
746 #[pallet::storage]
749 pub type DomainRuntimeUpgrades<T> = StorageValue<_, Vec<RuntimeId>, ValueQuery>;
750
751 #[pallet::storage]
756 pub type DomainSudoCalls<T: Config> =
757 StorageMap<_, Identity, DomainId, DomainSudoCall, ValueQuery>;
758
759 #[pallet::storage]
763 pub type FrozenDomains<T> = StorageValue<_, BTreeSet<DomainId>, ValueQuery>;
764
765 #[pallet::storage]
770 pub type EvmDomainContractCreationAllowedByCalls<T: Config> =
771 StorageMap<_, Identity, DomainId, EvmDomainContractCreationAllowedByCall, ValueQuery>;
772
773 #[pallet::storage]
776 pub type SkipBalanceChecks<T> = StorageValue<_, BTreeSet<DomainId>, ValueQuery>;
777
778 #[pallet::storage]
783 pub type AllowedDefaultSharePriceEpoch<T> = StorageValue<_, DomainEpoch, OptionQuery>;
784
785 #[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq)]
786 pub enum BundleError {
787 InvalidOperatorId,
789 BadBundleSignature,
791 BadVrfSignature,
793 InvalidDomainId,
795 BadOperator,
797 ThresholdUnsatisfied,
799 Receipt(BlockTreeError),
801 BundleTooLarge,
803 InvalidExtrinsicRoot,
805 InvalidProofOfTime,
807 SlotInTheFuture,
809 SlotInThePast,
811 BundleTooHeavy,
813 SlotSmallerThanPreviousBlockBundle,
816 EquivocatedBundle,
818 DomainFrozen,
820 UnableToPayBundleStorageFee,
822 UnexpectedReceiptGap,
824 ExpectingReceiptGap,
826 FailedToGetMissedUpgradeCount,
828 }
829
830 #[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq)]
831 pub enum FraudProofError {
832 BadReceiptNotFound,
835 ChallengingGenesisReceipt,
837 DescendantsOfFraudulentERNotPruned,
839 InvalidBlockFeesFraudProof,
841 InvalidTransfersFraudProof,
843 InvalidDomainBlockHashFraudProof,
845 InvalidExtrinsicRootFraudProof,
847 InvalidStateTransitionFraudProof,
849 ParentReceiptNotFound,
851 InvalidBundleFraudProof,
853 BadValidBundleFraudProof,
855 MissingOperator,
857 UnexpectedFraudProof,
859 BadReceiptAlreadyReported,
861 BadMmrProof,
863 UnexpectedMmrProof,
865 MissingMmrProof,
867 RuntimeNotFound,
869 DomainRuntimeCodeProofNotFound,
871 UnexpectedDomainRuntimeCodeProof,
873 StorageProof(storage_proof::VerificationError),
875 }
876
877 impl From<BundleError> for TransactionValidity {
878 fn from(e: BundleError) -> Self {
879 if BundleError::UnableToPayBundleStorageFee == e {
880 InvalidTransactionCode::BundleStorageFeePayment.into()
881 } else if let BundleError::Receipt(_) = e {
882 InvalidTransactionCode::ExecutionReceipt.into()
883 } else {
884 InvalidTransactionCode::Bundle.into()
885 }
886 }
887 }
888
889 impl From<storage_proof::VerificationError> for FraudProofError {
890 fn from(err: storage_proof::VerificationError) -> Self {
891 FraudProofError::StorageProof(err)
892 }
893 }
894
895 impl<T> From<FraudProofError> for Error<T> {
896 fn from(err: FraudProofError) -> Self {
897 Error::FraudProof(err)
898 }
899 }
900
901 impl<T> From<RuntimeRegistryError> for Error<T> {
902 fn from(err: RuntimeRegistryError) -> Self {
903 Error::RuntimeRegistry(err)
904 }
905 }
906
907 impl<T> From<StakingError> for Error<T> {
908 fn from(err: StakingError) -> Self {
909 Error::Staking(err)
910 }
911 }
912
913 impl<T> From<StakingEpochError> for Error<T> {
914 fn from(err: StakingEpochError) -> Self {
915 Error::StakingEpoch(err)
916 }
917 }
918
919 impl<T> From<DomainRegistryError> for Error<T> {
920 fn from(err: DomainRegistryError) -> Self {
921 Error::DomainRegistry(err)
922 }
923 }
924
925 impl<T> From<BlockTreeError> for Error<T> {
926 fn from(err: BlockTreeError) -> Self {
927 Error::BlockTree(err)
928 }
929 }
930
931 impl From<ProofOfElectionError> for BundleError {
932 fn from(err: ProofOfElectionError) -> Self {
933 match err {
934 ProofOfElectionError::BadVrfProof => Self::BadVrfSignature,
935 ProofOfElectionError::ThresholdUnsatisfied => Self::ThresholdUnsatisfied,
936 }
937 }
938 }
939
940 impl<T> From<BundleStorageFundError> for Error<T> {
941 fn from(err: BundleStorageFundError) -> Self {
942 Error::BundleStorageFund(err)
943 }
944 }
945
946 #[pallet::error]
947 pub enum Error<T> {
948 FraudProof(FraudProofError),
950 RuntimeRegistry(RuntimeRegistryError),
952 Staking(StakingError),
954 StakingEpoch(StakingEpochError),
956 DomainRegistry(DomainRegistryError),
958 BlockTree(BlockTreeError),
960 BundleStorageFund(BundleStorageFundError),
962 PermissionedActionNotAllowed,
964 DomainSudoCallExists,
966 InvalidDomainSudoCall,
968 DomainNotFrozen,
970 NotPrivateEvmDomain,
972 NotDomainOwnerOrRoot,
974 EvmDomainContractCreationAllowedByCallExists,
976 }
977
978 #[derive(Clone, Debug, PartialEq, Encode, Decode, TypeInfo)]
980 pub enum SlashedReason<DomainBlock, ReceiptHash> {
981 InvalidBundle(DomainBlock),
983 BadExecutionReceipt(ReceiptHash),
985 }
986
987 #[pallet::event]
988 #[pallet::generate_deposit(pub (super) fn deposit_event)]
989 pub enum Event<T: Config> {
990 BundleStored {
992 domain_id: DomainId,
993 bundle_hash: H256,
994 bundle_author: OperatorId,
995 },
996 DomainRuntimeCreated {
997 runtime_id: RuntimeId,
998 runtime_type: RuntimeType,
999 },
1000 DomainRuntimeUpgradeScheduled {
1001 runtime_id: RuntimeId,
1002 scheduled_at: BlockNumberFor<T>,
1003 },
1004 DomainRuntimeUpgraded {
1005 runtime_id: RuntimeId,
1006 },
1007 OperatorRegistered {
1008 operator_id: OperatorId,
1009 domain_id: DomainId,
1010 },
1011 NominatedStakedUnlocked {
1012 operator_id: OperatorId,
1013 nominator_id: NominatorId<T>,
1014 unlocked_amount: BalanceOf<T>,
1015 },
1016 StorageFeeUnlocked {
1017 operator_id: OperatorId,
1018 nominator_id: NominatorId<T>,
1019 storage_fee: BalanceOf<T>,
1020 },
1021 OperatorNominated {
1022 operator_id: OperatorId,
1023 nominator_id: NominatorId<T>,
1024 amount: BalanceOf<T>,
1025 },
1026 DomainInstantiated {
1027 domain_id: DomainId,
1028 },
1029 OperatorSwitchedDomain {
1030 old_domain_id: DomainId,
1031 new_domain_id: DomainId,
1032 },
1033 OperatorDeregistered {
1034 operator_id: OperatorId,
1035 },
1036 NominatorUnlocked {
1037 operator_id: OperatorId,
1038 nominator_id: NominatorId<T>,
1039 },
1040 WithdrewStake {
1041 operator_id: OperatorId,
1042 nominator_id: NominatorId<T>,
1043 },
1044 PreferredOperator {
1045 operator_id: OperatorId,
1046 nominator_id: NominatorId<T>,
1047 },
1048 OperatorRewarded {
1049 source: OperatorRewardSource<BlockNumberFor<T>>,
1050 operator_id: OperatorId,
1051 reward: BalanceOf<T>,
1052 },
1053 OperatorTaxCollected {
1054 operator_id: OperatorId,
1055 tax: BalanceOf<T>,
1056 },
1057 DomainEpochCompleted {
1058 domain_id: DomainId,
1059 completed_epoch_index: EpochIndex,
1060 },
1061 ForceDomainEpochTransition {
1062 domain_id: DomainId,
1063 completed_epoch_index: EpochIndex,
1064 },
1065 FraudProofProcessed {
1066 domain_id: DomainId,
1067 new_head_receipt_number: Option<DomainBlockNumberFor<T>>,
1068 },
1069 DomainOperatorAllowListUpdated {
1070 domain_id: DomainId,
1071 },
1072 OperatorSlashed {
1073 operator_id: OperatorId,
1074 reason: SlashedReason<DomainBlockNumberFor<T>, ReceiptHashFor<T>>,
1075 },
1076 StorageFeeDeposited {
1077 operator_id: OperatorId,
1078 nominator_id: NominatorId<T>,
1079 amount: BalanceOf<T>,
1080 },
1081 DomainFrozen {
1082 domain_id: DomainId,
1083 },
1084 DomainUnfrozen {
1085 domain_id: DomainId,
1086 },
1087 PrunedExecutionReceipt {
1088 domain_id: DomainId,
1089 new_head_receipt_number: Option<DomainBlockNumberFor<T>>,
1090 },
1091 }
1092
1093 #[pallet::origin]
1094 pub type Origin = RawOrigin;
1095
1096 #[derive(Debug, Default, Decode, Encode, TypeInfo, PartialEq, Eq)]
1098 pub struct TxRangeState {
1099 pub tx_range: U256,
1101
1102 pub interval_blocks: u64,
1104
1105 pub interval_bundles: u64,
1107 }
1108
1109 impl TxRangeState {
1110 pub fn on_bundle(&mut self) {
1112 self.interval_bundles += 1;
1113 }
1114 }
1115
1116 #[pallet::storage]
1117 pub(super) type DomainTxRangeState<T: Config> =
1118 StorageMap<_, Identity, DomainId, TxRangeState, OptionQuery>;
1119
1120 #[pallet::call]
1121 impl<T: Config> Pallet<T> {
1122 #[pallet::call_index(0)]
1123 #[pallet::weight(Pallet::<T>::max_submit_bundle_weight())]
1124 pub fn submit_bundle(
1125 origin: OriginFor<T>,
1126 opaque_bundle: OpaqueBundleOf<T>,
1127 ) -> DispatchResultWithPostInfo {
1128 T::DomainOrigin::ensure_origin(origin)?;
1129
1130 log::trace!(target: "runtime::domains", "Processing bundle: {opaque_bundle:?}");
1131
1132 let domain_id = opaque_bundle.domain_id();
1133 let bundle_hash = opaque_bundle.hash();
1134 let bundle_header_hash = opaque_bundle.sealed_header.pre_hash();
1135 let extrinsics_root = opaque_bundle.extrinsics_root();
1136 let operator_id = opaque_bundle.operator_id();
1137 let bundle_size = opaque_bundle.size();
1138 let slot_number = opaque_bundle.slot_number();
1139 let receipt = opaque_bundle.into_receipt();
1140 #[cfg_attr(feature = "runtime-benchmarks", allow(unused_variables))]
1141 let receipt_block_number = receipt.domain_block_number;
1142
1143 #[cfg(not(feature = "runtime-benchmarks"))]
1144 let mut actual_weight = T::WeightInfo::submit_bundle();
1145 #[cfg(feature = "runtime-benchmarks")]
1146 let actual_weight = T::WeightInfo::submit_bundle();
1147
1148 match execution_receipt_type::<T>(domain_id, &receipt) {
1149 ReceiptType::Rejected(rejected_receipt_type) => {
1150 return Err(Error::<T>::BlockTree(rejected_receipt_type.into()).into());
1151 }
1152 ReceiptType::Accepted(accepted_receipt_type) => {
1154 #[cfg(not(feature = "runtime-benchmarks"))]
1160 if accepted_receipt_type == AcceptedReceiptType::NewHead {
1161 if let Some(block_tree_node) =
1162 prune_receipt::<T>(domain_id, receipt_block_number)
1163 .map_err(Error::<T>::from)?
1164 {
1165 actual_weight =
1166 actual_weight.saturating_add(T::WeightInfo::handle_bad_receipt(
1167 block_tree_node.operator_ids.len() as u32,
1168 ));
1169
1170 let bad_receipt_hash = block_tree_node
1171 .execution_receipt
1172 .hash::<DomainHashingFor<T>>();
1173 do_mark_operators_as_slashed::<T>(
1174 block_tree_node.operator_ids.into_iter(),
1175 SlashedReason::BadExecutionReceipt(bad_receipt_hash),
1176 )
1177 .map_err(Error::<T>::from)?;
1178 }
1179 }
1180
1181 #[cfg_attr(feature = "runtime-benchmarks", allow(unused_variables))]
1182 let maybe_confirmed_domain_block_info = process_execution_receipt::<T>(
1183 domain_id,
1184 operator_id,
1185 receipt,
1186 accepted_receipt_type,
1187 )
1188 .map_err(Error::<T>::from)?;
1189
1190 #[cfg(not(feature = "runtime-benchmarks"))]
1196 if let Some(confirmed_block_info) = maybe_confirmed_domain_block_info {
1197 actual_weight =
1198 actual_weight.saturating_add(T::WeightInfo::confirm_domain_block(
1199 confirmed_block_info.operator_ids.len() as u32,
1200 confirmed_block_info.invalid_bundle_authors.len() as u32,
1201 ));
1202
1203 refund_storage_fee::<T>(
1204 confirmed_block_info.total_storage_fee,
1205 confirmed_block_info.paid_bundle_storage_fees,
1206 )
1207 .map_err(Error::<T>::from)?;
1208
1209 do_reward_operators::<T>(
1210 domain_id,
1211 OperatorRewardSource::Bundle {
1212 at_block_number: confirmed_block_info.consensus_block_number,
1213 },
1214 confirmed_block_info.operator_ids.into_iter(),
1215 confirmed_block_info.rewards,
1216 )
1217 .map_err(Error::<T>::from)?;
1218
1219 do_mark_operators_as_slashed::<T>(
1220 confirmed_block_info.invalid_bundle_authors.into_iter(),
1221 SlashedReason::InvalidBundle(confirmed_block_info.domain_block_number),
1222 )
1223 .map_err(Error::<T>::from)?;
1224 }
1225 }
1226 }
1227
1228 if SuccessfulBundles::<T>::get(domain_id).is_empty() {
1232 let missed_upgrade =
1239 Self::missed_domain_runtime_upgrade(domain_id).map_err(Error::<T>::from)?;
1240
1241 let next_number = HeadDomainNumber::<T>::get(domain_id)
1242 .checked_add(&One::one())
1243 .ok_or::<Error<T>>(BlockTreeError::MaxHeadDomainNumber.into())?
1244 .checked_add(&missed_upgrade.into())
1245 .ok_or::<Error<T>>(BlockTreeError::MaxHeadDomainNumber.into())?;
1246
1247 #[cfg(not(feature = "runtime-benchmarks"))]
1249 if next_number % T::StakeEpochDuration::get() == Zero::zero() {
1250 let epoch_transition_res = do_finalize_domain_current_epoch::<T>(domain_id)
1251 .map_err(Error::<T>::from)?;
1252
1253 Self::deposit_event(Event::DomainEpochCompleted {
1254 domain_id,
1255 completed_epoch_index: epoch_transition_res.completed_epoch_index,
1256 });
1257
1258 actual_weight = actual_weight
1259 .saturating_add(Self::actual_epoch_transition_weight(epoch_transition_res));
1260 }
1261
1262 HeadDomainNumber::<T>::set(domain_id, next_number);
1263 }
1264
1265 let head_domain_number = HeadDomainNumber::<T>::get(domain_id);
1267 let consensus_block_number = frame_system::Pallet::<T>::current_block_number();
1268 ExecutionInbox::<T>::append(
1269 (domain_id, head_domain_number, consensus_block_number),
1270 BundleDigest {
1271 header_hash: bundle_header_hash,
1272 extrinsics_root,
1273 size: bundle_size,
1274 },
1275 );
1276
1277 InboxedBundleAuthor::<T>::insert(bundle_header_hash, operator_id);
1278
1279 SuccessfulBundles::<T>::append(domain_id, bundle_hash);
1280
1281 OperatorBundleSlot::<T>::mutate(operator_id, |slot_set| slot_set.insert(slot_number));
1282
1283 #[cfg(not(feature = "runtime-benchmarks"))]
1285 {
1286 let slashed_nominator_count =
1287 do_slash_operator::<T>(domain_id, MAX_NOMINATORS_TO_SLASH)
1288 .map_err(Error::<T>::from)?;
1289 actual_weight = actual_weight
1290 .saturating_add(T::WeightInfo::slash_operator(slashed_nominator_count));
1291 }
1292
1293 Self::deposit_event(Event::BundleStored {
1294 domain_id,
1295 bundle_hash,
1296 bundle_author: operator_id,
1297 });
1298
1299 Ok(Some(actual_weight.min(Self::max_submit_bundle_weight())).into())
1301 }
1302
1303 #[pallet::call_index(15)]
1304 #[pallet::weight((
1305 T::WeightInfo::submit_fraud_proof().saturating_add(
1306 T::WeightInfo::handle_bad_receipt(MAX_BUNDLE_PER_BLOCK)
1307 ),
1308 DispatchClass::Operational
1309 ))]
1310 pub fn submit_fraud_proof(
1311 origin: OriginFor<T>,
1312 fraud_proof: Box<FraudProofFor<T>>,
1313 ) -> DispatchResultWithPostInfo {
1314 T::DomainOrigin::ensure_origin(origin)?;
1315
1316 log::trace!(target: "runtime::domains", "Processing fraud proof: {fraud_proof:?}");
1317
1318 #[cfg(not(feature = "runtime-benchmarks"))]
1319 let mut actual_weight = T::WeightInfo::submit_fraud_proof();
1320 #[cfg(feature = "runtime-benchmarks")]
1321 let actual_weight = T::WeightInfo::submit_fraud_proof();
1322
1323 let domain_id = fraud_proof.domain_id();
1324 let bad_receipt_hash = fraud_proof.targeted_bad_receipt_hash();
1325 let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
1326 let bad_receipt_number = BlockTreeNodes::<T>::get(bad_receipt_hash)
1327 .ok_or::<Error<T>>(FraudProofError::BadReceiptNotFound.into())?
1328 .execution_receipt
1329 .domain_block_number;
1330 ensure!(
1334 head_receipt_number >= bad_receipt_number,
1335 Error::<T>::from(FraudProofError::BadReceiptNotFound),
1336 );
1337
1338 #[cfg(not(feature = "runtime-benchmarks"))]
1345 {
1346 let block_tree_node = prune_receipt::<T>(domain_id, bad_receipt_number)
1347 .map_err(Error::<T>::from)?
1348 .ok_or::<Error<T>>(FraudProofError::BadReceiptNotFound.into())?;
1349
1350 actual_weight = actual_weight.saturating_add(T::WeightInfo::handle_bad_receipt(
1351 (block_tree_node.operator_ids.len() as u32).min(MAX_BUNDLE_PER_BLOCK),
1352 ));
1353
1354 do_mark_operators_as_slashed::<T>(
1355 block_tree_node.operator_ids.into_iter(),
1356 SlashedReason::BadExecutionReceipt(bad_receipt_hash),
1357 )
1358 .map_err(Error::<T>::from)?;
1359 }
1360
1361 let new_head_receipt_number = bad_receipt_number.saturating_sub(One::one());
1363 HeadReceiptNumber::<T>::insert(domain_id, new_head_receipt_number);
1364
1365 Self::deposit_event(Event::FraudProofProcessed {
1366 domain_id,
1367 new_head_receipt_number: Some(new_head_receipt_number),
1368 });
1369
1370 Ok(Some(actual_weight).into())
1371 }
1372
1373 #[pallet::call_index(2)]
1374 #[pallet::weight(T::WeightInfo::register_domain_runtime())]
1375 pub fn register_domain_runtime(
1376 origin: OriginFor<T>,
1377 runtime_name: String,
1378 runtime_type: RuntimeType,
1379 raw_genesis_storage: Vec<u8>,
1383 ) -> DispatchResult {
1384 ensure_root(origin)?;
1385
1386 let block_number = frame_system::Pallet::<T>::current_block_number();
1387 let runtime_id = do_register_runtime::<T>(
1388 runtime_name,
1389 runtime_type,
1390 raw_genesis_storage,
1391 block_number,
1392 )
1393 .map_err(Error::<T>::from)?;
1394
1395 Self::deposit_event(Event::DomainRuntimeCreated {
1396 runtime_id,
1397 runtime_type,
1398 });
1399
1400 Ok(())
1401 }
1402
1403 #[pallet::call_index(3)]
1404 #[pallet::weight(T::WeightInfo::upgrade_domain_runtime())]
1405 pub fn upgrade_domain_runtime(
1406 origin: OriginFor<T>,
1407 runtime_id: RuntimeId,
1408 raw_genesis_storage: Vec<u8>,
1409 ) -> DispatchResult {
1410 ensure_root(origin)?;
1411
1412 let block_number = frame_system::Pallet::<T>::current_block_number();
1413 let scheduled_at =
1414 do_schedule_runtime_upgrade::<T>(runtime_id, raw_genesis_storage, block_number)
1415 .map_err(Error::<T>::from)?;
1416
1417 Self::deposit_event(Event::DomainRuntimeUpgradeScheduled {
1418 runtime_id,
1419 scheduled_at,
1420 });
1421
1422 Ok(())
1423 }
1424
1425 #[pallet::call_index(4)]
1426 #[pallet::weight(T::WeightInfo::register_operator())]
1427 pub fn register_operator(
1428 origin: OriginFor<T>,
1429 domain_id: DomainId,
1430 amount: BalanceOf<T>,
1431 config: OperatorConfig<BalanceOf<T>>,
1432 ) -> DispatchResult {
1433 let owner = ensure_signed(origin)?;
1434
1435 let (operator_id, current_epoch_index) =
1436 do_register_operator::<T>(owner, domain_id, amount, config)
1437 .map_err(Error::<T>::from)?;
1438
1439 Self::deposit_event(Event::OperatorRegistered {
1440 operator_id,
1441 domain_id,
1442 });
1443
1444 if current_epoch_index.is_zero() {
1447 do_finalize_domain_current_epoch::<T>(domain_id).map_err(Error::<T>::from)?;
1448 }
1449
1450 Ok(())
1451 }
1452
1453 #[pallet::call_index(5)]
1454 #[pallet::weight(T::WeightInfo::nominate_operator())]
1455 pub fn nominate_operator(
1456 origin: OriginFor<T>,
1457 operator_id: OperatorId,
1458 amount: BalanceOf<T>,
1459 ) -> DispatchResult {
1460 let nominator_id = ensure_signed(origin)?;
1461
1462 do_nominate_operator::<T>(operator_id, nominator_id.clone(), amount)
1463 .map_err(Error::<T>::from)?;
1464
1465 Ok(())
1466 }
1467
1468 #[pallet::call_index(6)]
1469 #[pallet::weight(T::WeightInfo::instantiate_domain())]
1470 pub fn instantiate_domain(
1471 origin: OriginFor<T>,
1472 domain_config_params: DomainConfigParams<T::AccountId, BalanceOf<T>>,
1473 ) -> DispatchResult {
1474 let who = ensure_signed(origin)?;
1475 ensure!(
1476 PermissionedActionAllowedBy::<T>::get()
1477 .map(|allowed_by| allowed_by.is_allowed(&who))
1478 .unwrap_or_default(),
1479 Error::<T>::PermissionedActionNotAllowed
1480 );
1481
1482 let created_at = frame_system::Pallet::<T>::current_block_number();
1483
1484 let domain_id = do_instantiate_domain::<T>(domain_config_params, who, created_at)
1485 .map_err(Error::<T>::from)?;
1486
1487 Self::deposit_event(Event::DomainInstantiated { domain_id });
1488
1489 Ok(())
1490 }
1491
1492 #[pallet::call_index(8)]
1493 #[pallet::weight(T::WeightInfo::deregister_operator())]
1494 pub fn deregister_operator(
1495 origin: OriginFor<T>,
1496 operator_id: OperatorId,
1497 ) -> DispatchResult {
1498 let who = ensure_signed(origin)?;
1499
1500 do_deregister_operator::<T>(who, operator_id).map_err(Error::<T>::from)?;
1501
1502 Self::deposit_event(Event::OperatorDeregistered { operator_id });
1503
1504 Ok(())
1505 }
1506
1507 #[pallet::call_index(9)]
1508 #[pallet::weight(T::WeightInfo::withdraw_stake())]
1509 pub fn withdraw_stake(
1510 origin: OriginFor<T>,
1511 operator_id: OperatorId,
1512 to_withdraw: WithdrawStake<BalanceOf<T>, T::Share>,
1513 ) -> DispatchResult {
1514 let who = ensure_signed(origin)?;
1515
1516 do_withdraw_stake::<T>(operator_id, who.clone(), to_withdraw)
1517 .map_err(Error::<T>::from)?;
1518
1519 Self::deposit_event(Event::WithdrewStake {
1520 operator_id,
1521 nominator_id: who,
1522 });
1523
1524 Ok(())
1525 }
1526
1527 #[pallet::call_index(10)]
1531 #[pallet::weight(T::WeightInfo::unlock_funds())]
1532 pub fn unlock_funds(origin: OriginFor<T>, operator_id: OperatorId) -> DispatchResult {
1533 let nominator_id = ensure_signed(origin)?;
1534 do_unlock_funds::<T>(operator_id, nominator_id.clone())
1535 .map_err(crate::pallet::Error::<T>::from)?;
1536 Ok(())
1537 }
1538
1539 #[pallet::call_index(11)]
1542 #[pallet::weight(T::WeightInfo::unlock_nominator())]
1543 pub fn unlock_nominator(origin: OriginFor<T>, operator_id: OperatorId) -> DispatchResult {
1544 let nominator = ensure_signed(origin)?;
1545
1546 do_unlock_nominator::<T>(operator_id, nominator.clone())
1547 .map_err(crate::pallet::Error::<T>::from)?;
1548
1549 Self::deposit_event(Event::NominatorUnlocked {
1550 operator_id,
1551 nominator_id: nominator,
1552 });
1553
1554 Ok(())
1555 }
1556
1557 #[pallet::call_index(12)]
1565 #[pallet::weight(T::WeightInfo::update_domain_operator_allow_list())]
1566 pub fn update_domain_operator_allow_list(
1567 origin: OriginFor<T>,
1568 domain_id: DomainId,
1569 operator_allow_list: OperatorAllowList<T::AccountId>,
1570 ) -> DispatchResult {
1571 let who = ensure_signed(origin)?;
1572 do_update_domain_allow_list::<T>(who, domain_id, operator_allow_list)
1573 .map_err(Error::<T>::from)?;
1574 Self::deposit_event(crate::pallet::Event::DomainOperatorAllowListUpdated { domain_id });
1575 Ok(())
1576 }
1577
1578 #[pallet::call_index(13)]
1580 #[pallet::weight(Pallet::<T>::max_staking_epoch_transition())]
1581 pub fn force_staking_epoch_transition(
1582 origin: OriginFor<T>,
1583 domain_id: DomainId,
1584 ) -> DispatchResultWithPostInfo {
1585 ensure_root(origin)?;
1586
1587 let epoch_transition_res =
1588 do_finalize_domain_current_epoch::<T>(domain_id).map_err(Error::<T>::from)?;
1589
1590 Self::deposit_event(Event::ForceDomainEpochTransition {
1591 domain_id,
1592 completed_epoch_index: epoch_transition_res.completed_epoch_index,
1593 });
1594
1595 let actual_weight = Self::actual_epoch_transition_weight(epoch_transition_res)
1597 .min(Self::max_staking_epoch_transition());
1598
1599 Ok(Some(actual_weight).into())
1600 }
1601
1602 #[pallet::call_index(14)]
1604 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(0, 1))]
1605 pub fn set_permissioned_action_allowed_by(
1606 origin: OriginFor<T>,
1607 permissioned_action_allowed_by: sp_domains::PermissionedActionAllowedBy<T::AccountId>,
1608 ) -> DispatchResult {
1609 ensure_root(origin)?;
1610 PermissionedActionAllowedBy::<T>::put(permissioned_action_allowed_by);
1611 Ok(())
1612 }
1613
1614 #[pallet::call_index(16)]
1616 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(3, 1))]
1617 pub fn send_domain_sudo_call(
1618 origin: OriginFor<T>,
1619 domain_id: DomainId,
1620 call: Vec<u8>,
1621 ) -> DispatchResult {
1622 ensure_root(origin)?;
1623 ensure!(
1624 DomainSudoCalls::<T>::get(domain_id).maybe_call.is_none(),
1625 Error::<T>::DomainSudoCallExists
1626 );
1627
1628 let domain_runtime = Self::domain_runtime_code(domain_id).ok_or(
1629 Error::<T>::DomainRegistry(DomainRegistryError::DomainNotFound),
1630 )?;
1631 ensure!(
1632 domain_runtime_call(
1633 domain_runtime,
1634 StatelessDomainRuntimeCall::IsValidDomainSudoCall(call.clone()),
1635 )
1636 .unwrap_or(false),
1637 Error::<T>::InvalidDomainSudoCall
1638 );
1639
1640 DomainSudoCalls::<T>::set(
1641 domain_id,
1642 DomainSudoCall {
1643 maybe_call: Some(call),
1644 },
1645 );
1646 Ok(())
1647 }
1648
1649 #[pallet::call_index(17)]
1652 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(0, 1))]
1653 pub fn freeze_domain(origin: OriginFor<T>, domain_id: DomainId) -> DispatchResult {
1654 ensure_root(origin)?;
1655 FrozenDomains::<T>::mutate(|frozen_domains| frozen_domains.insert(domain_id));
1656 Self::deposit_event(Event::DomainFrozen { domain_id });
1657 Ok(())
1658 }
1659
1660 #[pallet::call_index(18)]
1662 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(0, 1))]
1663 pub fn unfreeze_domain(origin: OriginFor<T>, domain_id: DomainId) -> DispatchResult {
1664 ensure_root(origin)?;
1665 FrozenDomains::<T>::mutate(|frozen_domains| frozen_domains.remove(&domain_id));
1666 Self::deposit_event(Event::DomainUnfrozen { domain_id });
1667 Ok(())
1668 }
1669
1670 #[pallet::call_index(19)]
1674 #[pallet::weight(Pallet::<T>::max_prune_domain_execution_receipt())]
1675 pub fn prune_domain_execution_receipt(
1676 origin: OriginFor<T>,
1677 domain_id: DomainId,
1678 bad_receipt_hash: ReceiptHashFor<T>,
1679 ) -> DispatchResultWithPostInfo {
1680 ensure_root(origin)?;
1681 ensure!(
1682 FrozenDomains::<T>::get().contains(&domain_id),
1683 Error::<T>::DomainNotFrozen
1684 );
1685
1686 let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
1687 let bad_receipt_number = BlockTreeNodes::<T>::get(bad_receipt_hash)
1688 .ok_or::<Error<T>>(FraudProofError::BadReceiptNotFound.into())?
1689 .execution_receipt
1690 .domain_block_number;
1691 ensure!(
1694 head_receipt_number >= bad_receipt_number,
1695 Error::<T>::from(FraudProofError::BadReceiptNotFound),
1696 );
1697
1698 let mut actual_weight = T::DbWeight::get().reads(3);
1699
1700 let block_tree_node = prune_receipt::<T>(domain_id, bad_receipt_number)
1702 .map_err(Error::<T>::from)?
1703 .ok_or::<Error<T>>(FraudProofError::BadReceiptNotFound.into())?;
1704
1705 actual_weight = actual_weight.saturating_add(T::WeightInfo::handle_bad_receipt(
1706 (block_tree_node.operator_ids.len() as u32).min(MAX_BUNDLE_PER_BLOCK),
1707 ));
1708
1709 do_mark_operators_as_slashed::<T>(
1710 block_tree_node.operator_ids.into_iter(),
1711 SlashedReason::BadExecutionReceipt(bad_receipt_hash),
1712 )
1713 .map_err(Error::<T>::from)?;
1714
1715 let new_head_receipt_number = bad_receipt_number.saturating_sub(One::one());
1717 HeadReceiptNumber::<T>::insert(domain_id, new_head_receipt_number);
1718 actual_weight = actual_weight.saturating_add(T::DbWeight::get().reads_writes(0, 1));
1719
1720 Self::deposit_event(Event::PrunedExecutionReceipt {
1721 domain_id,
1722 new_head_receipt_number: Some(new_head_receipt_number),
1723 });
1724
1725 Ok(Some(actual_weight).into())
1726 }
1727
1728 #[pallet::call_index(20)]
1730 #[pallet::weight(T::WeightInfo::transfer_treasury_funds())]
1731 pub fn transfer_treasury_funds(
1732 origin: OriginFor<T>,
1733 account_id: T::AccountId,
1734 balance: BalanceOf<T>,
1735 ) -> DispatchResult {
1736 ensure_root(origin)?;
1737 T::Currency::transfer(
1738 &T::TreasuryAccount::get(),
1739 &account_id,
1740 balance,
1741 Preservation::Preserve,
1742 )?;
1743 Ok(())
1744 }
1745
1746 #[pallet::call_index(21)]
1747 #[pallet::weight(Pallet::<T>::max_submit_receipt_weight())]
1748 pub fn submit_receipt(
1749 origin: OriginFor<T>,
1750 singleton_receipt: SingletonReceiptOf<T>,
1751 ) -> DispatchResultWithPostInfo {
1752 T::DomainOrigin::ensure_origin(origin)?;
1753
1754 let domain_id = singleton_receipt.domain_id();
1755 let operator_id = singleton_receipt.operator_id();
1756 let receipt = singleton_receipt.into_receipt();
1757
1758 #[cfg(not(feature = "runtime-benchmarks"))]
1759 let mut actual_weight = T::WeightInfo::submit_receipt();
1760 #[cfg(feature = "runtime-benchmarks")]
1761 let actual_weight = T::WeightInfo::submit_receipt();
1762
1763 match execution_receipt_type::<T>(domain_id, &receipt) {
1764 ReceiptType::Rejected(rejected_receipt_type) => {
1765 return Err(Error::<T>::BlockTree(rejected_receipt_type.into()).into());
1766 }
1767 ReceiptType::Accepted(accepted_receipt_type) => {
1769 #[cfg(not(feature = "runtime-benchmarks"))]
1775 if accepted_receipt_type == AcceptedReceiptType::NewHead {
1776 if let Some(block_tree_node) =
1777 prune_receipt::<T>(domain_id, receipt.domain_block_number)
1778 .map_err(Error::<T>::from)?
1779 {
1780 actual_weight =
1781 actual_weight.saturating_add(T::WeightInfo::handle_bad_receipt(
1782 block_tree_node.operator_ids.len() as u32,
1783 ));
1784
1785 let bad_receipt_hash = block_tree_node
1786 .execution_receipt
1787 .hash::<DomainHashingFor<T>>();
1788 do_mark_operators_as_slashed::<T>(
1789 block_tree_node.operator_ids.into_iter(),
1790 SlashedReason::BadExecutionReceipt(bad_receipt_hash),
1791 )
1792 .map_err(Error::<T>::from)?;
1793 }
1794 }
1795
1796 #[cfg_attr(feature = "runtime-benchmarks", allow(unused_variables))]
1797 let maybe_confirmed_domain_block_info = process_execution_receipt::<T>(
1798 domain_id,
1799 operator_id,
1800 receipt,
1801 accepted_receipt_type,
1802 )
1803 .map_err(Error::<T>::from)?;
1804
1805 #[cfg(not(feature = "runtime-benchmarks"))]
1808 if let Some(confirmed_block_info) = maybe_confirmed_domain_block_info {
1809 actual_weight =
1810 actual_weight.saturating_add(T::WeightInfo::confirm_domain_block(
1811 confirmed_block_info.operator_ids.len() as u32,
1812 confirmed_block_info.invalid_bundle_authors.len() as u32,
1813 ));
1814
1815 refund_storage_fee::<T>(
1816 confirmed_block_info.total_storage_fee,
1817 confirmed_block_info.paid_bundle_storage_fees,
1818 )
1819 .map_err(Error::<T>::from)?;
1820
1821 do_reward_operators::<T>(
1822 domain_id,
1823 OperatorRewardSource::Bundle {
1824 at_block_number: confirmed_block_info.consensus_block_number,
1825 },
1826 confirmed_block_info.operator_ids.into_iter(),
1827 confirmed_block_info.rewards,
1828 )
1829 .map_err(Error::<T>::from)?;
1830
1831 do_mark_operators_as_slashed::<T>(
1832 confirmed_block_info.invalid_bundle_authors.into_iter(),
1833 SlashedReason::InvalidBundle(confirmed_block_info.domain_block_number),
1834 )
1835 .map_err(Error::<T>::from)?;
1836 }
1837 }
1838 }
1839
1840 #[cfg(not(feature = "runtime-benchmarks"))]
1842 {
1843 let slashed_nominator_count =
1844 do_slash_operator::<T>(domain_id, MAX_NOMINATORS_TO_SLASH)
1845 .map_err(Error::<T>::from)?;
1846 actual_weight = actual_weight
1847 .saturating_add(T::WeightInfo::slash_operator(slashed_nominator_count));
1848 }
1849
1850 Ok(Some(actual_weight.min(Self::max_submit_receipt_weight())).into())
1852 }
1853
1854 #[pallet::call_index(22)]
1856 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(3, 1))]
1857 pub fn send_evm_domain_set_contract_creation_allowed_by_call(
1858 origin: OriginFor<T>,
1859 domain_id: DomainId,
1860 contract_creation_allowed_by: sp_domains::PermissionedActionAllowedBy<
1861 EthereumAccountId,
1862 >,
1863 ) -> DispatchResult {
1864 let signer = ensure_signed_or_root(origin)?;
1865
1866 ensure!(
1867 Pallet::<T>::is_private_evm_domain(domain_id),
1868 Error::<T>::NotPrivateEvmDomain,
1869 );
1870 if let Some(non_root_signer) = signer {
1871 ensure!(
1872 Pallet::<T>::is_domain_owner(domain_id, non_root_signer),
1873 Error::<T>::NotDomainOwnerOrRoot,
1874 );
1875 }
1876 ensure!(
1877 EvmDomainContractCreationAllowedByCalls::<T>::get(domain_id)
1878 .maybe_call
1879 .is_none(),
1880 Error::<T>::EvmDomainContractCreationAllowedByCallExists,
1881 );
1882
1883 EvmDomainContractCreationAllowedByCalls::<T>::set(
1884 domain_id,
1885 EvmDomainContractCreationAllowedByCall {
1886 maybe_call: Some(contract_creation_allowed_by),
1887 },
1888 );
1889
1890 Ok(())
1891 }
1892 }
1893
1894 #[pallet::genesis_config]
1895 pub struct GenesisConfig<T: Config> {
1896 pub permissioned_action_allowed_by:
1897 Option<sp_domains::PermissionedActionAllowedBy<T::AccountId>>,
1898 pub genesis_domains: Vec<GenesisDomain<T::AccountId, BalanceOf<T>>>,
1899 }
1900
1901 impl<T: Config> Default for GenesisConfig<T> {
1902 fn default() -> Self {
1903 GenesisConfig {
1904 permissioned_action_allowed_by: None,
1905 genesis_domains: vec![],
1906 }
1907 }
1908 }
1909
1910 #[pallet::genesis_build]
1911 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
1912 fn build(&self) {
1913 if let Some(permissioned_action_allowed_by) =
1914 self.permissioned_action_allowed_by.as_ref().cloned()
1915 {
1916 PermissionedActionAllowedBy::<T>::put(permissioned_action_allowed_by)
1917 }
1918
1919 self.genesis_domains
1920 .clone()
1921 .into_iter()
1922 .for_each(|genesis_domain| {
1923 let runtime_id = register_runtime_at_genesis::<T>(
1925 genesis_domain.runtime_name,
1926 genesis_domain.runtime_type,
1927 genesis_domain.runtime_version,
1928 genesis_domain.raw_genesis_storage,
1929 Zero::zero(),
1930 )
1931 .expect("Genesis runtime registration must always succeed");
1932
1933 let domain_config_params = DomainConfigParams {
1935 domain_name: genesis_domain.domain_name,
1936 runtime_id,
1937 maybe_bundle_limit: None,
1938 bundle_slot_probability: genesis_domain.bundle_slot_probability,
1939 operator_allow_list: genesis_domain.operator_allow_list,
1940 initial_balances: genesis_domain.initial_balances,
1941 domain_runtime_config: genesis_domain.domain_runtime_config,
1942 };
1943 let domain_owner = genesis_domain.owner_account_id;
1944 let domain_id = do_instantiate_domain::<T>(
1945 domain_config_params,
1946 domain_owner.clone(),
1947 Zero::zero(),
1948 )
1949 .expect("Genesis domain instantiation must always succeed");
1950
1951 let operator_config = OperatorConfig {
1953 signing_key: genesis_domain.signing_key.clone(),
1954 minimum_nominator_stake: genesis_domain.minimum_nominator_stake,
1955 nomination_tax: genesis_domain.nomination_tax,
1956 };
1957 let operator_stake = T::MinOperatorStake::get();
1958 do_register_operator::<T>(
1959 domain_owner,
1960 domain_id,
1961 operator_stake,
1962 operator_config,
1963 )
1964 .expect("Genesis operator registration must succeed");
1965
1966 do_finalize_domain_current_epoch::<T>(domain_id)
1967 .expect("Genesis epoch must succeed");
1968 });
1969 }
1970 }
1971
1972 #[pallet::storage]
1974 pub type BlockInherentExtrinsicData<T> = StorageValue<_, InherentExtrinsicData>;
1975
1976 #[pallet::hooks]
1977 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
1979 fn on_initialize(block_number: BlockNumberFor<T>) -> Weight {
1980 let parent_number = block_number - One::one();
1981 let parent_hash = frame_system::Pallet::<T>::block_hash(parent_number);
1982
1983 for runtime_id in DomainRuntimeUpgrades::<T>::take() {
1985 let reference_count = RuntimeRegistry::<T>::get(runtime_id)
1986 .expect("Runtime object must be present since domain is insantiated; qed")
1987 .instance_count;
1988 if !reference_count.is_zero() {
1989 DomainRuntimeUpgradeRecords::<T>::mutate(runtime_id, |upgrade_record| {
1990 upgrade_record.insert(
1991 parent_number,
1992 DomainRuntimeUpgradeEntry {
1993 at_hash: parent_hash,
1994 reference_count,
1995 },
1996 )
1997 });
1998 }
1999 }
2000 DomainRuntimeUpgrades::<T>::set(Vec::new());
2003 do_upgrade_runtimes::<T>(block_number);
2006
2007 for (domain_id, _) in SuccessfulBundles::<T>::drain() {
2010 ConsensusBlockHash::<T>::insert(domain_id, parent_number, parent_hash);
2011 T::DomainBundleSubmitted::domain_bundle_submitted(domain_id);
2012
2013 DomainSudoCalls::<T>::mutate(domain_id, |sudo_call| {
2015 sudo_call.clear();
2016 });
2017 EvmDomainContractCreationAllowedByCalls::<T>::mutate(
2018 domain_id,
2019 |evm_contract_call| {
2020 evm_contract_call.clear();
2021 },
2022 );
2023 }
2024
2025 for (operator_id, slot_set) in OperatorBundleSlot::<T>::drain() {
2026 if let Some(highest_slot) = slot_set.last() {
2029 OperatorHighestSlot::<T>::insert(operator_id, highest_slot);
2030 }
2031 }
2032
2033 BlockInherentExtrinsicData::<T>::kill();
2034
2035 Weight::zero()
2036 }
2037
2038 fn on_finalize(_: BlockNumberFor<T>) {
2039 if SuccessfulBundles::<T>::iter_keys().count() > 0
2042 || !DomainRuntimeUpgrades::<T>::get().is_empty()
2043 {
2044 let extrinsics_shuffling_seed = Randomness::from(
2045 Into::<H256>::into(Self::extrinsics_shuffling_seed_value()).to_fixed_bytes(),
2046 );
2047
2048 let timestamp = Self::timestamp_value();
2051
2052 let consensus_transaction_byte_fee = Self::consensus_transaction_byte_fee_value();
2054
2055 let inherent_extrinsic_data = InherentExtrinsicData {
2056 extrinsics_shuffling_seed,
2057 timestamp,
2058 consensus_transaction_byte_fee,
2059 };
2060
2061 BlockInherentExtrinsicData::<T>::set(Some(inherent_extrinsic_data));
2062 }
2063
2064 let _ = LastEpochStakingDistribution::<T>::clear(u32::MAX, None);
2065 let _ = NewAddedHeadReceipt::<T>::clear(u32::MAX, None);
2066 }
2067 }
2068}
2069
2070impl<T: Config> Pallet<T> {
2071 fn log_bundle_error(err: &BundleError, domain_id: DomainId, operator_id: OperatorId) {
2072 match err {
2073 BundleError::Receipt(BlockTreeError::InFutureReceipt)
2076 | BundleError::Receipt(BlockTreeError::StaleReceipt)
2077 | BundleError::Receipt(BlockTreeError::NewBranchReceipt)
2078 | BundleError::Receipt(BlockTreeError::UnavailableConsensusBlockHash)
2079 | BundleError::Receipt(BlockTreeError::BuiltOnUnknownConsensusBlock)
2080 | BundleError::SlotInThePast
2081 | BundleError::SlotInTheFuture
2082 | BundleError::InvalidProofOfTime
2083 | BundleError::SlotSmallerThanPreviousBlockBundle
2084 | BundleError::ExpectingReceiptGap
2085 | BundleError::UnexpectedReceiptGap => {
2086 log::debug!(
2087 target: "runtime::domains",
2088 "Bad bundle/receipt, domain {domain_id:?}, operator {operator_id:?}, error: {err:?}",
2089 );
2090 }
2091 _ => {
2092 log::warn!(
2093 target: "runtime::domains",
2094 "Bad bundle/receipt, domain {domain_id:?}, operator {operator_id:?}, error: {err:?}",
2095 );
2096 }
2097 }
2098 }
2099
2100 pub fn successful_bundles(domain_id: DomainId) -> Vec<H256> {
2101 SuccessfulBundles::<T>::get(domain_id)
2102 }
2103
2104 pub fn domain_runtime_code(domain_id: DomainId) -> Option<Vec<u8>> {
2105 RuntimeRegistry::<T>::get(Self::runtime_id(domain_id)?)
2106 .and_then(|mut runtime_object| runtime_object.raw_genesis.take_runtime_code())
2107 }
2108
2109 pub fn domain_best_number(domain_id: DomainId) -> Result<DomainBlockNumberFor<T>, BundleError> {
2110 let missed_upgrade = Self::missed_domain_runtime_upgrade(domain_id)
2113 .map_err(|_| BundleError::FailedToGetMissedUpgradeCount)?;
2114
2115 Ok(HeadDomainNumber::<T>::get(domain_id) + missed_upgrade.into())
2116 }
2117
2118 pub fn runtime_id(domain_id: DomainId) -> Option<RuntimeId> {
2120 DomainRegistry::<T>::get(domain_id)
2121 .map(|domain_object| domain_object.domain_config.runtime_id)
2122 }
2123
2124 pub fn runtime_upgrades() -> Vec<RuntimeId> {
2126 DomainRuntimeUpgrades::<T>::get()
2127 }
2128
2129 pub fn domain_instance_data(
2130 domain_id: DomainId,
2131 ) -> Option<(DomainInstanceData, BlockNumberFor<T>)> {
2132 let domain_obj = DomainRegistry::<T>::get(domain_id)?;
2133 let runtime_object = RuntimeRegistry::<T>::get(domain_obj.domain_config.runtime_id)?;
2134 let runtime_type = runtime_object.runtime_type;
2135 let total_issuance = domain_obj.domain_config.total_issuance()?;
2136 let raw_genesis = into_complete_raw_genesis::<T>(
2137 runtime_object,
2138 domain_id,
2139 &domain_obj.domain_runtime_info,
2140 total_issuance,
2141 domain_obj.domain_config.initial_balances,
2142 )
2143 .ok()?;
2144 Some((
2145 DomainInstanceData {
2146 runtime_type,
2147 raw_genesis,
2148 },
2149 domain_obj.created_at,
2150 ))
2151 }
2152
2153 pub fn genesis_state_root(domain_id: DomainId) -> Option<H256> {
2154 DomainGenesisBlockExecutionReceipt::<T>::get(domain_id).map(|er| er.final_state_root.into())
2155 }
2156
2157 pub fn domain_tx_range(domain_id: DomainId) -> U256 {
2159 DomainTxRangeState::<T>::try_get(domain_id)
2160 .map(|state| state.tx_range)
2161 .ok()
2162 .unwrap_or_else(Self::initial_tx_range)
2163 }
2164
2165 pub fn bundle_producer_election_params(
2166 domain_id: DomainId,
2167 ) -> Option<BundleProducerElectionParams<BalanceOf<T>>> {
2168 match (
2169 DomainRegistry::<T>::get(domain_id),
2170 DomainStakingSummary::<T>::get(domain_id),
2171 ) {
2172 (Some(domain_object), Some(stake_summary)) => Some(BundleProducerElectionParams {
2173 total_domain_stake: stake_summary.current_total_stake,
2174 bundle_slot_probability: domain_object.domain_config.bundle_slot_probability,
2175 }),
2176 _ => None,
2177 }
2178 }
2179
2180 pub fn operator(operator_id: OperatorId) -> Option<(OperatorPublicKey, BalanceOf<T>)> {
2181 Operators::<T>::get(operator_id)
2182 .map(|operator| (operator.signing_key, operator.current_total_stake))
2183 }
2184
2185 fn check_extrinsics_root(opaque_bundle: &OpaqueBundleOf<T>) -> Result<(), BundleError> {
2186 let expected_extrinsics_root = <T::DomainHeader as Header>::Hashing::ordered_trie_root(
2187 opaque_bundle
2188 .extrinsics
2189 .iter()
2190 .map(|xt| xt.encode())
2191 .collect(),
2192 sp_core::storage::StateVersion::V1,
2193 );
2194 ensure!(
2195 expected_extrinsics_root == opaque_bundle.extrinsics_root(),
2196 BundleError::InvalidExtrinsicRoot
2197 );
2198 Ok(())
2199 }
2200
2201 fn check_slot_and_proof_of_time(
2202 slot_number: u64,
2203 proof_of_time: PotOutput,
2204 pre_dispatch: bool,
2205 ) -> Result<(), BundleError> {
2206 let current_block_number = frame_system::Pallet::<T>::current_block_number();
2209
2210 if pre_dispatch {
2216 if let Some(future_slot) = T::BlockSlot::future_slot(current_block_number) {
2217 ensure!(slot_number <= *future_slot, BundleError::SlotInTheFuture)
2218 }
2219 }
2220
2221 let produced_after_block_number =
2223 match T::BlockSlot::slot_produced_after(slot_number.into()) {
2224 Some(n) => n,
2225 None => {
2226 if current_block_number > T::BundleLongevity::get().into() {
2229 return Err(BundleError::SlotInThePast);
2230 } else {
2231 Zero::zero()
2232 }
2233 }
2234 };
2235 let produced_after_block_hash = if produced_after_block_number == current_block_number {
2236 frame_system::Pallet::<T>::parent_hash()
2238 } else {
2239 frame_system::Pallet::<T>::block_hash(produced_after_block_number)
2240 };
2241 if let Some(last_eligible_block) =
2242 current_block_number.checked_sub(&T::BundleLongevity::get().into())
2243 {
2244 ensure!(
2245 produced_after_block_number >= last_eligible_block,
2246 BundleError::SlotInThePast
2247 );
2248 }
2249
2250 if !is_proof_of_time_valid(
2251 BlockHash::try_from(produced_after_block_hash.as_ref())
2252 .expect("Must be able to convert to block hash type"),
2253 SlotNumber::from(slot_number),
2254 WrappedPotOutput::from(proof_of_time),
2255 !pre_dispatch,
2257 ) {
2258 return Err(BundleError::InvalidProofOfTime);
2259 }
2260
2261 Ok(())
2262 }
2263
2264 fn validate_bundle(
2265 opaque_bundle: &OpaqueBundleOf<T>,
2266 domain_config: &DomainConfig<T::AccountId, BalanceOf<T>>,
2267 ) -> Result<(), BundleError> {
2268 ensure!(
2269 opaque_bundle.body_size() <= domain_config.max_bundle_size,
2270 BundleError::BundleTooLarge
2271 );
2272
2273 ensure!(
2274 opaque_bundle
2275 .estimated_weight()
2276 .all_lte(domain_config.max_bundle_weight),
2277 BundleError::BundleTooHeavy
2278 );
2279
2280 Self::check_extrinsics_root(opaque_bundle)?;
2281
2282 Ok(())
2283 }
2284
2285 fn validate_eligibility(
2286 to_sign: &[u8],
2287 signature: &OperatorSignature,
2288 proof_of_election: &ProofOfElection,
2289 domain_config: &DomainConfig<T::AccountId, BalanceOf<T>>,
2290 pre_dispatch: bool,
2291 ) -> Result<(), BundleError> {
2292 let domain_id = proof_of_election.domain_id;
2293 let operator_id = proof_of_election.operator_id;
2294 let slot_number = proof_of_election.slot_number;
2295
2296 ensure!(
2297 !FrozenDomains::<T>::get().contains(&domain_id),
2298 BundleError::DomainFrozen
2299 );
2300
2301 let operator = Operators::<T>::get(operator_id).ok_or(BundleError::InvalidOperatorId)?;
2302
2303 let operator_status = operator.status::<T>(operator_id);
2304 ensure!(
2305 *operator_status != OperatorStatus::Slashed
2306 && *operator_status != OperatorStatus::PendingSlash,
2307 BundleError::BadOperator
2308 );
2309
2310 if !operator.signing_key.verify(&to_sign, signature) {
2311 return Err(BundleError::BadBundleSignature);
2312 }
2313
2314 ensure!(
2316 slot_number
2317 > Self::operator_highest_slot_from_previous_block(operator_id, pre_dispatch),
2318 BundleError::SlotSmallerThanPreviousBlockBundle,
2319 );
2320
2321 ensure!(
2323 !OperatorBundleSlot::<T>::get(operator_id).contains(&slot_number),
2324 BundleError::EquivocatedBundle,
2325 );
2326
2327 let (operator_stake, total_domain_stake) =
2328 Self::fetch_operator_stake_info(domain_id, &operator_id)?;
2329
2330 Self::check_slot_and_proof_of_time(
2331 slot_number,
2332 proof_of_election.proof_of_time,
2333 pre_dispatch,
2334 )?;
2335
2336 sp_domains::bundle_producer_election::check_proof_of_election(
2337 &operator.signing_key,
2338 domain_config.bundle_slot_probability,
2339 proof_of_election,
2340 operator_stake.saturated_into(),
2341 total_domain_stake.saturated_into(),
2342 )?;
2343
2344 Ok(())
2345 }
2346
2347 fn validate_submit_bundle(
2348 opaque_bundle: &OpaqueBundleOf<T>,
2349 pre_dispatch: bool,
2350 ) -> Result<(), BundleError> {
2351 let domain_id = opaque_bundle.domain_id();
2352 let operator_id = opaque_bundle.operator_id();
2353 let sealed_header = &opaque_bundle.sealed_header;
2354
2355 ensure!(
2359 Self::receipt_gap(domain_id)? <= One::one(),
2360 BundleError::UnexpectedReceiptGap,
2361 );
2362
2363 let domain_config = &DomainRegistry::<T>::get(domain_id)
2364 .ok_or(BundleError::InvalidDomainId)?
2365 .domain_config;
2366
2367 Self::validate_bundle(opaque_bundle, domain_config)?;
2368
2369 Self::validate_eligibility(
2370 sealed_header.pre_hash().as_ref(),
2371 &sealed_header.signature,
2372 &sealed_header.header.proof_of_election,
2373 domain_config,
2374 pre_dispatch,
2375 )?;
2376
2377 verify_execution_receipt::<T>(domain_id, &sealed_header.header.receipt)
2378 .map_err(BundleError::Receipt)?;
2379
2380 charge_bundle_storage_fee::<T>(operator_id, opaque_bundle.size())
2381 .map_err(|_| BundleError::UnableToPayBundleStorageFee)?;
2382
2383 Ok(())
2384 }
2385
2386 fn validate_singleton_receipt(
2387 sealed_singleton_receipt: &SingletonReceiptOf<T>,
2388 pre_dispatch: bool,
2389 ) -> Result<(), BundleError> {
2390 let domain_id = sealed_singleton_receipt.domain_id();
2391 let operator_id = sealed_singleton_receipt.operator_id();
2392
2393 ensure!(
2395 Self::receipt_gap(domain_id)? > One::one(),
2396 BundleError::ExpectingReceiptGap,
2397 );
2398
2399 let domain_config = DomainRegistry::<T>::get(domain_id)
2400 .ok_or(BundleError::InvalidDomainId)?
2401 .domain_config;
2402 Self::validate_eligibility(
2403 sealed_singleton_receipt.pre_hash().as_ref(),
2404 &sealed_singleton_receipt.signature,
2405 &sealed_singleton_receipt.singleton_receipt.proof_of_election,
2406 &domain_config,
2407 pre_dispatch,
2408 )?;
2409
2410 verify_execution_receipt::<T>(
2411 domain_id,
2412 &sealed_singleton_receipt.singleton_receipt.receipt,
2413 )
2414 .map_err(BundleError::Receipt)?;
2415
2416 charge_bundle_storage_fee::<T>(operator_id, sealed_singleton_receipt.size())
2417 .map_err(|_| BundleError::UnableToPayBundleStorageFee)?;
2418
2419 Ok(())
2420 }
2421
2422 fn validate_fraud_proof(
2423 fraud_proof: &FraudProofFor<T>,
2424 ) -> Result<(DomainId, TransactionPriority), FraudProofError> {
2425 let domain_id = fraud_proof.domain_id();
2426 let bad_receipt_hash = fraud_proof.targeted_bad_receipt_hash();
2427 let bad_receipt = BlockTreeNodes::<T>::get(bad_receipt_hash)
2428 .ok_or(FraudProofError::BadReceiptNotFound)?
2429 .execution_receipt;
2430 let bad_receipt_domain_block_number = bad_receipt.domain_block_number;
2431
2432 ensure!(
2433 !bad_receipt_domain_block_number.is_zero(),
2434 FraudProofError::ChallengingGenesisReceipt
2435 );
2436
2437 ensure!(
2438 !Self::is_bad_er_pending_to_prune(domain_id, bad_receipt_domain_block_number),
2439 FraudProofError::BadReceiptAlreadyReported,
2440 );
2441
2442 ensure!(
2443 !fraud_proof.is_unexpected_domain_runtime_code_proof(),
2444 FraudProofError::UnexpectedDomainRuntimeCodeProof,
2445 );
2446
2447 ensure!(
2448 !fraud_proof.is_unexpected_mmr_proof(),
2449 FraudProofError::UnexpectedMmrProof,
2450 );
2451
2452 let maybe_state_root = match &fraud_proof.maybe_mmr_proof {
2453 Some(mmr_proof) => Some(Self::verify_mmr_proof_and_extract_state_root(
2454 mmr_proof.clone(),
2455 bad_receipt.consensus_block_number,
2456 )?),
2457 None => None,
2458 };
2459
2460 match &fraud_proof.proof {
2461 FraudProofVariant::InvalidBlockFees(InvalidBlockFeesProof { storage_proof }) => {
2462 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2463 domain_id,
2464 &bad_receipt,
2465 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2466 )?;
2467
2468 verify_invalid_block_fees_fraud_proof::<
2469 T::Block,
2470 DomainBlockNumberFor<T>,
2471 T::DomainHash,
2472 BalanceOf<T>,
2473 DomainHashingFor<T>,
2474 >(bad_receipt, storage_proof, domain_runtime_code)
2475 .map_err(|err| {
2476 log::error!(
2477 target: "runtime::domains",
2478 "Block fees proof verification failed: {err:?}"
2479 );
2480 FraudProofError::InvalidBlockFeesFraudProof
2481 })?;
2482 }
2483 FraudProofVariant::InvalidTransfers(InvalidTransfersProof { storage_proof }) => {
2484 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2485 domain_id,
2486 &bad_receipt,
2487 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2488 )?;
2489
2490 verify_invalid_transfers_fraud_proof::<
2491 T::Block,
2492 DomainBlockNumberFor<T>,
2493 T::DomainHash,
2494 BalanceOf<T>,
2495 DomainHashingFor<T>,
2496 >(bad_receipt, storage_proof, domain_runtime_code)
2497 .map_err(|err| {
2498 log::error!(
2499 target: "runtime::domains",
2500 "Domain transfers proof verification failed: {err:?}"
2501 );
2502 FraudProofError::InvalidTransfersFraudProof
2503 })?;
2504 }
2505 FraudProofVariant::InvalidDomainBlockHash(InvalidDomainBlockHashProof {
2506 digest_storage_proof,
2507 }) => {
2508 let parent_receipt =
2509 BlockTreeNodes::<T>::get(bad_receipt.parent_domain_block_receipt_hash)
2510 .ok_or(FraudProofError::ParentReceiptNotFound)?
2511 .execution_receipt;
2512 verify_invalid_domain_block_hash_fraud_proof::<
2513 T::Block,
2514 BalanceOf<T>,
2515 T::DomainHeader,
2516 >(
2517 bad_receipt,
2518 digest_storage_proof.clone(),
2519 parent_receipt.domain_block_hash,
2520 )
2521 .map_err(|err| {
2522 log::error!(
2523 target: "runtime::domains",
2524 "Invalid Domain block hash proof verification failed: {err:?}"
2525 );
2526 FraudProofError::InvalidDomainBlockHashFraudProof
2527 })?;
2528 }
2529 FraudProofVariant::InvalidExtrinsicsRoot(proof) => {
2530 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2531 domain_id,
2532 &bad_receipt,
2533 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2534 )?;
2535 let runtime_id =
2536 Self::runtime_id(domain_id).ok_or(FraudProofError::RuntimeNotFound)?;
2537 let state_root = maybe_state_root.ok_or(FraudProofError::MissingMmrProof)?;
2538
2539 verify_invalid_domain_extrinsics_root_fraud_proof::<
2540 T::Block,
2541 BalanceOf<T>,
2542 T::DomainHeader,
2543 T::Hashing,
2544 T::FraudProofStorageKeyProvider,
2545 >(
2546 bad_receipt,
2547 proof,
2548 domain_id,
2549 runtime_id,
2550 state_root,
2551 domain_runtime_code,
2552 )
2553 .map_err(|err| {
2554 log::error!(
2555 target: "runtime::domains",
2556 "Invalid Domain extrinsic root proof verification failed: {err:?}"
2557 );
2558 FraudProofError::InvalidExtrinsicRootFraudProof
2559 })?;
2560 }
2561 FraudProofVariant::InvalidStateTransition(proof) => {
2562 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2563 domain_id,
2564 &bad_receipt,
2565 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2566 )?;
2567 let bad_receipt_parent =
2568 BlockTreeNodes::<T>::get(bad_receipt.parent_domain_block_receipt_hash)
2569 .ok_or(FraudProofError::ParentReceiptNotFound)?
2570 .execution_receipt;
2571
2572 verify_invalid_state_transition_fraud_proof::<
2573 T::Block,
2574 T::DomainHeader,
2575 BalanceOf<T>,
2576 >(bad_receipt, bad_receipt_parent, proof, domain_runtime_code)
2577 .map_err(|err| {
2578 log::error!(
2579 target: "runtime::domains",
2580 "Invalid State transition proof verification failed: {err:?}"
2581 );
2582 FraudProofError::InvalidStateTransitionFraudProof
2583 })?;
2584 }
2585 FraudProofVariant::InvalidBundles(proof) => {
2586 let state_root = maybe_state_root.ok_or(FraudProofError::MissingMmrProof)?;
2587 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2588 domain_id,
2589 &bad_receipt,
2590 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2591 )?;
2592
2593 let bad_receipt_parent =
2594 BlockTreeNodes::<T>::get(bad_receipt.parent_domain_block_receipt_hash)
2595 .ok_or(FraudProofError::ParentReceiptNotFound)?
2596 .execution_receipt;
2597
2598 verify_invalid_bundles_fraud_proof::<
2599 T::Block,
2600 T::DomainHeader,
2601 T::MmrHash,
2602 BalanceOf<T>,
2603 T::FraudProofStorageKeyProvider,
2604 T::MmrProofVerifier,
2605 >(
2606 bad_receipt,
2607 bad_receipt_parent,
2608 proof,
2609 domain_id,
2610 state_root,
2611 domain_runtime_code,
2612 )
2613 .map_err(|err| {
2614 log::error!(
2615 target: "runtime::domains",
2616 "Invalid Bundle proof verification failed: {err:?}"
2617 );
2618 FraudProofError::InvalidBundleFraudProof
2619 })?;
2620 }
2621 FraudProofVariant::ValidBundle(proof) => {
2622 let state_root = maybe_state_root.ok_or(FraudProofError::MissingMmrProof)?;
2623 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2624 domain_id,
2625 &bad_receipt,
2626 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2627 )?;
2628
2629 verify_valid_bundle_fraud_proof::<
2630 T::Block,
2631 T::DomainHeader,
2632 BalanceOf<T>,
2633 T::FraudProofStorageKeyProvider,
2634 >(
2635 bad_receipt,
2636 proof,
2637 domain_id,
2638 state_root,
2639 domain_runtime_code,
2640 )
2641 .map_err(|err| {
2642 log::error!(
2643 target: "runtime::domains",
2644 "Valid bundle proof verification failed: {err:?}"
2645 );
2646 FraudProofError::BadValidBundleFraudProof
2647 })?
2648 }
2649 #[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
2650 FraudProofVariant::Dummy => {}
2651 }
2652
2653 let block_before_bad_er_confirm = bad_receipt_domain_block_number.saturating_sub(
2656 Self::latest_confirmed_domain_block_number(fraud_proof.domain_id()),
2657 );
2658 let priority =
2659 TransactionPriority::MAX - block_before_bad_er_confirm.saturated_into::<u64>();
2660
2661 let tag = fraud_proof.domain_id();
2664
2665 Ok((tag, priority))
2666 }
2667
2668 fn fetch_operator_stake_info(
2673 domain_id: DomainId,
2674 operator_id: &OperatorId,
2675 ) -> Result<(BalanceOf<T>, BalanceOf<T>), BundleError> {
2676 if let Some(pending_election_params) = LastEpochStakingDistribution::<T>::get(domain_id) {
2677 if let Some(operator_stake) = pending_election_params.operators.get(operator_id) {
2678 return Ok((*operator_stake, pending_election_params.total_domain_stake));
2679 }
2680 }
2681 let domain_stake_summary =
2682 DomainStakingSummary::<T>::get(domain_id).ok_or(BundleError::InvalidDomainId)?;
2683 let operator_stake = domain_stake_summary
2684 .current_operators
2685 .get(operator_id)
2686 .ok_or(BundleError::BadOperator)?;
2687 Ok((*operator_stake, domain_stake_summary.current_total_stake))
2688 }
2689
2690 fn initial_tx_range() -> U256 {
2692 U256::MAX / T::InitialDomainTxRange::get()
2693 }
2694
2695 pub fn head_receipt_number(domain_id: DomainId) -> DomainBlockNumberFor<T> {
2697 HeadReceiptNumber::<T>::get(domain_id)
2698 }
2699
2700 pub fn oldest_unconfirmed_receipt_number(
2703 domain_id: DomainId,
2704 ) -> Option<DomainBlockNumberFor<T>> {
2705 let oldest_nonconfirmed_er_number =
2706 Self::latest_confirmed_domain_block_number(domain_id).saturating_add(One::one());
2707 let is_er_exist = BlockTree::<T>::get(domain_id, oldest_nonconfirmed_er_number).is_some();
2708 let is_pending_to_prune =
2709 Self::is_bad_er_pending_to_prune(domain_id, oldest_nonconfirmed_er_number);
2710
2711 if is_er_exist && !is_pending_to_prune {
2712 Some(oldest_nonconfirmed_er_number)
2713 } else {
2714 None
2719 }
2720 }
2721
2722 pub fn latest_confirmed_domain_block_number(domain_id: DomainId) -> DomainBlockNumberFor<T> {
2725 LatestConfirmedDomainExecutionReceipt::<T>::get(domain_id)
2726 .map(|er| er.domain_block_number)
2727 .unwrap_or_default()
2728 }
2729
2730 pub fn latest_confirmed_domain_block(
2731 domain_id: DomainId,
2732 ) -> Option<(DomainBlockNumberFor<T>, T::DomainHash)> {
2733 LatestConfirmedDomainExecutionReceipt::<T>::get(domain_id)
2734 .map(|er| (er.domain_block_number, er.domain_block_hash))
2735 }
2736
2737 pub fn domain_bundle_limit(
2739 domain_id: DomainId,
2740 ) -> Result<Option<DomainBundleLimit>, DomainRegistryError> {
2741 let domain_config = match DomainRegistry::<T>::get(domain_id) {
2742 None => return Ok(None),
2743 Some(domain_obj) => domain_obj.domain_config,
2744 };
2745
2746 Ok(Some(DomainBundleLimit {
2747 max_bundle_size: domain_config.max_bundle_size,
2748 max_bundle_weight: domain_config.max_bundle_weight,
2749 }))
2750 }
2751
2752 pub fn non_empty_er_exists(domain_id: DomainId) -> bool {
2755 if BlockTree::<T>::contains_key(domain_id, DomainBlockNumberFor::<T>::zero()) {
2756 return true;
2757 }
2758
2759 let mut to_check =
2761 Self::latest_confirmed_domain_block_number(domain_id).saturating_add(One::one());
2762
2763 let head_number = HeadDomainNumber::<T>::get(domain_id);
2768
2769 while to_check <= head_number {
2770 if !ExecutionInbox::<T>::iter_prefix_values((domain_id, to_check)).all(|digests| {
2771 digests
2772 .iter()
2773 .all(|digest| digest.extrinsics_root == EMPTY_EXTRINSIC_ROOT.into())
2774 }) {
2775 return true;
2776 }
2777
2778 to_check = to_check.saturating_add(One::one())
2779 }
2780
2781 false
2782 }
2783
2784 pub fn extrinsics_shuffling_seed() -> T::Hash {
2787 BlockInherentExtrinsicData::<T>::get()
2789 .map(|data| H256::from(*data.extrinsics_shuffling_seed).into())
2790 .unwrap_or_else(|| Self::extrinsics_shuffling_seed_value())
2791 }
2792
2793 fn extrinsics_shuffling_seed_value() -> T::Hash {
2796 let subject = DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT;
2797 let (randomness, _) = T::Randomness::random(subject);
2798 randomness
2799 }
2800
2801 pub fn timestamp() -> Moment {
2804 BlockInherentExtrinsicData::<T>::get()
2806 .map(|data| data.timestamp)
2807 .unwrap_or_else(|| Self::timestamp_value())
2808 }
2809
2810 fn timestamp_value() -> Moment {
2813 T::BlockTimestamp::now()
2816 .try_into()
2817 .map_err(|_| ())
2818 .expect("Moment is the same type in both pallets; qed")
2819 }
2820
2821 pub fn consensus_transaction_byte_fee() -> Balance {
2825 BlockInherentExtrinsicData::<T>::get()
2827 .map(|data| data.consensus_transaction_byte_fee)
2828 .unwrap_or_else(|| Self::consensus_transaction_byte_fee_value())
2829 }
2830
2831 fn consensus_transaction_byte_fee_value() -> Balance {
2834 let transaction_byte_fee: Balance = T::StorageFee::transaction_byte_fee()
2837 .try_into()
2838 .map_err(|_| ())
2839 .expect("Balance is the same type in both pallets; qed");
2840
2841 sp_domains::DOMAIN_STORAGE_FEE_MULTIPLIER * transaction_byte_fee
2842 }
2843
2844 pub fn execution_receipt(receipt_hash: ReceiptHashFor<T>) -> Option<ExecutionReceiptOf<T>> {
2845 BlockTreeNodes::<T>::get(receipt_hash).map(|db| db.execution_receipt)
2846 }
2847
2848 pub fn receipt_hash(
2849 domain_id: DomainId,
2850 domain_number: DomainBlockNumberFor<T>,
2851 ) -> Option<ReceiptHashFor<T>> {
2852 BlockTree::<T>::get(domain_id, domain_number)
2853 }
2854
2855 pub fn confirmed_domain_block_storage_key(domain_id: DomainId) -> Vec<u8> {
2856 LatestConfirmedDomainExecutionReceipt::<T>::hashed_key_for(domain_id)
2857 }
2858
2859 pub fn is_bad_er_pending_to_prune(
2860 domain_id: DomainId,
2861 receipt_number: DomainBlockNumberFor<T>,
2862 ) -> bool {
2863 if receipt_number.is_zero() {
2865 return false;
2866 }
2867
2868 let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
2869
2870 head_receipt_number < receipt_number
2873 }
2874
2875 pub fn is_operator_pending_to_slash(domain_id: DomainId, operator_id: OperatorId) -> bool {
2876 let latest_submitted_er = LatestSubmittedER::<T>::get((domain_id, operator_id));
2877
2878 if latest_submitted_er.is_zero() {
2880 return false;
2881 }
2882
2883 let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
2884
2885 head_receipt_number < latest_submitted_er
2889 }
2890
2891 pub fn max_submit_bundle_weight() -> Weight {
2892 T::WeightInfo::submit_bundle()
2893 .saturating_add(
2894 T::WeightInfo::handle_bad_receipt(MAX_BUNDLE_PER_BLOCK).max(
2901 T::WeightInfo::confirm_domain_block(MAX_BUNDLE_PER_BLOCK, MAX_BUNDLE_PER_BLOCK),
2902 ),
2903 )
2904 .saturating_add(Self::max_staking_epoch_transition())
2905 .saturating_add(T::WeightInfo::slash_operator(MAX_NOMINATORS_TO_SLASH))
2906 }
2907
2908 pub fn max_submit_receipt_weight() -> Weight {
2909 T::WeightInfo::submit_bundle()
2910 .saturating_add(
2911 T::WeightInfo::handle_bad_receipt(MAX_BUNDLE_PER_BLOCK).max(
2918 T::WeightInfo::confirm_domain_block(MAX_BUNDLE_PER_BLOCK, MAX_BUNDLE_PER_BLOCK),
2919 ),
2920 )
2921 .saturating_add(T::WeightInfo::slash_operator(MAX_NOMINATORS_TO_SLASH))
2922 }
2923
2924 pub fn max_staking_epoch_transition() -> Weight {
2925 T::WeightInfo::operator_reward_tax_and_restake(MAX_BUNDLE_PER_BLOCK).saturating_add(
2926 T::WeightInfo::finalize_domain_epoch_staking(T::MaxPendingStakingOperation::get()),
2927 )
2928 }
2929
2930 pub fn max_prune_domain_execution_receipt() -> Weight {
2931 T::WeightInfo::handle_bad_receipt(MAX_BUNDLE_PER_BLOCK)
2932 .saturating_add(T::DbWeight::get().reads_writes(3, 1))
2933 }
2934
2935 fn actual_epoch_transition_weight(epoch_transition_res: EpochTransitionResult) -> Weight {
2936 let EpochTransitionResult {
2937 rewarded_operator_count,
2938 finalized_operator_count,
2939 completed_epoch_index: _,
2940 } = epoch_transition_res;
2941
2942 T::WeightInfo::operator_reward_tax_and_restake(rewarded_operator_count).saturating_add(
2943 T::WeightInfo::finalize_domain_epoch_staking(finalized_operator_count),
2944 )
2945 }
2946
2947 pub fn reward_domain_operators(
2949 domain_id: DomainId,
2950 source: OperatorRewardSource<BlockNumberFor<T>>,
2951 rewards: BalanceOf<T>,
2952 ) {
2953 if let Some(domain_stake_summary) = DomainStakingSummary::<T>::get(domain_id) {
2955 let operators = domain_stake_summary
2956 .current_epoch_rewards
2957 .into_keys()
2958 .collect::<Vec<OperatorId>>();
2959 let _ = do_reward_operators::<T>(domain_id, source, operators.into_iter(), rewards);
2960 }
2961 }
2962
2963 pub fn storage_fund_account_balance(operator_id: OperatorId) -> BalanceOf<T> {
2964 let storage_fund_acc = storage_fund_account::<T>(operator_id);
2965 T::Currency::reducible_balance(&storage_fund_acc, Preservation::Preserve, Fortitude::Polite)
2966 }
2967
2968 pub fn operator_highest_slot_from_previous_block(
2972 operator_id: OperatorId,
2973 pre_dispatch: bool,
2974 ) -> u64 {
2975 if pre_dispatch {
2976 OperatorHighestSlot::<T>::get(operator_id)
2977 } else {
2978 *OperatorBundleSlot::<T>::get(operator_id)
2984 .last()
2985 .unwrap_or(&OperatorHighestSlot::<T>::get(operator_id))
2986 }
2987 }
2988
2989 pub fn get_domain_runtime_code_for_receipt(
2992 domain_id: DomainId,
2993 receipt: &ExecutionReceiptOf<T>,
2994 maybe_domain_runtime_code_at: Option<
2995 DomainRuntimeCodeAt<BlockNumberFor<T>, T::Hash, T::MmrHash>,
2996 >,
2997 ) -> Result<Vec<u8>, FraudProofError> {
2998 let runtime_id = Self::runtime_id(domain_id).ok_or(FraudProofError::RuntimeNotFound)?;
2999 let current_runtime_obj =
3000 RuntimeRegistry::<T>::get(runtime_id).ok_or(FraudProofError::RuntimeNotFound)?;
3001
3002 let at = {
3005 let parent_receipt = BlockTreeNodes::<T>::get(receipt.parent_domain_block_receipt_hash)
3006 .ok_or(FraudProofError::ParentReceiptNotFound)?
3007 .execution_receipt;
3008 parent_receipt.consensus_block_number
3009 };
3010
3011 let is_domain_runtime_upgraded = current_runtime_obj.updated_at >= at;
3012
3013 let mut runtime_obj = match (is_domain_runtime_upgraded, maybe_domain_runtime_code_at) {
3014 (true, None) => return Err(FraudProofError::DomainRuntimeCodeProofNotFound),
3017 (true, Some(domain_runtime_code_at)) => {
3018 let DomainRuntimeCodeAt {
3019 mmr_proof,
3020 domain_runtime_code_proof,
3021 } = domain_runtime_code_at;
3022
3023 let state_root = Self::verify_mmr_proof_and_extract_state_root(mmr_proof, at)?;
3024
3025 <DomainRuntimeCodeProof as BasicStorageProof<T::Block>>::verify::<
3026 T::FraudProofStorageKeyProvider,
3027 >(domain_runtime_code_proof, runtime_id, &state_root)?
3028 }
3029 (false, Some(_)) => return Err(FraudProofError::UnexpectedDomainRuntimeCodeProof),
3032 (false, None) => current_runtime_obj,
3033 };
3034 let code = runtime_obj
3035 .raw_genesis
3036 .take_runtime_code()
3037 .ok_or(storage_proof::VerificationError::RuntimeCodeNotFound)?;
3038 Ok(code)
3039 }
3040
3041 pub fn is_domain_runtime_upgraded_since(
3042 domain_id: DomainId,
3043 at: BlockNumberFor<T>,
3044 ) -> Option<bool> {
3045 Self::runtime_id(domain_id)
3046 .and_then(RuntimeRegistry::<T>::get)
3047 .map(|runtime_obj| runtime_obj.updated_at >= at)
3048 }
3049
3050 pub fn verify_mmr_proof_and_extract_state_root(
3051 mmr_leaf_proof: ConsensusChainMmrLeafProof<BlockNumberFor<T>, T::Hash, T::MmrHash>,
3052 expected_block_number: BlockNumberFor<T>,
3053 ) -> Result<T::Hash, FraudProofError> {
3054 let leaf_data = T::MmrProofVerifier::verify_proof_and_extract_leaf(mmr_leaf_proof)
3055 .ok_or(FraudProofError::BadMmrProof)?;
3056
3057 if expected_block_number != leaf_data.block_number() {
3059 return Err(FraudProofError::UnexpectedMmrProof);
3060 }
3061
3062 Ok(leaf_data.state_root())
3063 }
3064
3065 fn missed_domain_runtime_upgrade(domain_id: DomainId) -> Result<u32, BlockTreeError> {
3067 let runtime_id = Self::runtime_id(domain_id).ok_or(BlockTreeError::RuntimeNotFound)?;
3068 let last_domain_block_number = HeadDomainNumber::<T>::get(domain_id);
3069
3070 let last_block_at =
3072 ExecutionInbox::<T>::iter_key_prefix((domain_id, last_domain_block_number))
3073 .next()
3074 .or(DomainRegistry::<T>::get(domain_id).map(|domain_obj| domain_obj.created_at))
3078 .ok_or(BlockTreeError::LastBlockNotFound)?;
3079
3080 Ok(DomainRuntimeUpgradeRecords::<T>::get(runtime_id)
3081 .into_keys()
3082 .rev()
3083 .take_while(|upgraded_at| *upgraded_at > last_block_at)
3084 .count() as u32)
3085 }
3086
3087 pub fn is_domain_registered(domain_id: DomainId) -> bool {
3089 DomainStakingSummary::<T>::contains_key(domain_id)
3090 }
3091
3092 pub fn domain_sudo_call(domain_id: DomainId) -> Option<Vec<u8>> {
3094 DomainSudoCalls::<T>::get(domain_id).maybe_call
3095 }
3096
3097 pub fn receipt_gap(domain_id: DomainId) -> Result<DomainBlockNumberFor<T>, BundleError> {
3100 let domain_best_number = Self::domain_best_number(domain_id)?;
3101 let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
3102
3103 Ok(domain_best_number.saturating_sub(head_receipt_number))
3104 }
3105
3106 pub fn is_evm_domain(domain_id: DomainId) -> bool {
3108 if let Some(domain_obj) = DomainRegistry::<T>::get(domain_id) {
3109 domain_obj.domain_runtime_info.is_evm_domain()
3110 } else {
3111 false
3112 }
3113 }
3114
3115 pub fn is_private_evm_domain(domain_id: DomainId) -> bool {
3117 if let Some(domain_obj) = DomainRegistry::<T>::get(domain_id) {
3118 domain_obj.domain_runtime_info.is_private_evm_domain()
3119 } else {
3120 false
3121 }
3122 }
3123
3124 pub fn evm_domain_contract_creation_allowed_by_call(
3126 domain_id: DomainId,
3127 ) -> Option<sp_domains::PermissionedActionAllowedBy<EthereumAccountId>> {
3128 EvmDomainContractCreationAllowedByCalls::<T>::get(domain_id).maybe_call
3129 }
3130}
3131
3132impl<T: Config> sp_domains::DomainOwner<T::AccountId> for Pallet<T> {
3133 fn is_domain_owner(domain_id: DomainId, acc: T::AccountId) -> bool {
3134 if let Some(domain_obj) = DomainRegistry::<T>::get(domain_id) {
3135 domain_obj.owner_account_id == acc
3136 } else {
3137 false
3138 }
3139 }
3140}
3141
3142impl<T> Pallet<T>
3143where
3144 T: Config + CreateUnsigned<Call<T>>,
3145{
3146 pub fn submit_bundle_unsigned(opaque_bundle: OpaqueBundleOf<T>) {
3148 let slot = opaque_bundle.sealed_header.slot_number();
3149 let extrinsics_count = opaque_bundle.extrinsics.len();
3150
3151 let call = Call::submit_bundle { opaque_bundle };
3152 let ext = T::create_unsigned(call.into());
3153
3154 match SubmitTransaction::<T, Call<T>>::submit_transaction(ext) {
3155 Ok(()) => {
3156 log::info!(
3157 target: "runtime::domains",
3158 "Submitted bundle from slot {slot}, extrinsics: {extrinsics_count}",
3159 );
3160 }
3161 Err(()) => {
3162 log::error!(target: "runtime::domains", "Error submitting bundle");
3163 }
3164 }
3165 }
3166
3167 pub fn submit_receipt_unsigned(singleton_receipt: SingletonReceiptOf<T>) {
3169 let slot = singleton_receipt.slot_number();
3170 let domain_block_number = singleton_receipt.receipt().domain_block_number;
3171
3172 let call = Call::submit_receipt { singleton_receipt };
3173 let ext = T::create_unsigned(call.into());
3174 match SubmitTransaction::<T, Call<T>>::submit_transaction(ext) {
3175 Ok(()) => {
3176 log::info!(
3177 target: "runtime::domains",
3178 "Submitted singleton receipt from slot {slot}, domain_block_number: {domain_block_number:?}",
3179 );
3180 }
3181 Err(()) => {
3182 log::error!(target: "runtime::domains", "Error submitting singleton receipt");
3183 }
3184 }
3185 }
3186
3187 pub fn submit_fraud_proof_unsigned(fraud_proof: FraudProofFor<T>) {
3189 let call = Call::submit_fraud_proof {
3190 fraud_proof: Box::new(fraud_proof),
3191 };
3192
3193 let ext = T::create_unsigned(call.into());
3194 match SubmitTransaction::<T, Call<T>>::submit_transaction(ext) {
3195 Ok(()) => {
3196 log::info!(target: "runtime::domains", "Submitted fraud proof");
3197 }
3198 Err(()) => {
3199 log::error!(target: "runtime::domains", "Error submitting fraud proof");
3200 }
3201 }
3202 }
3203}
3204
3205pub fn calculate_tx_range(
3207 cur_tx_range: U256,
3208 actual_bundle_count: u64,
3209 expected_bundle_count: u64,
3210) -> U256 {
3211 if actual_bundle_count == 0 || expected_bundle_count == 0 {
3212 return cur_tx_range;
3213 }
3214
3215 let Some(new_tx_range) = U256::from(actual_bundle_count)
3216 .saturating_mul(&cur_tx_range)
3217 .checked_div(&U256::from(expected_bundle_count))
3218 else {
3219 return cur_tx_range;
3220 };
3221
3222 let upper_bound = cur_tx_range.saturating_mul(&U256::from(4_u64));
3223 let Some(lower_bound) = cur_tx_range.checked_div(&U256::from(4_u64)) else {
3224 return cur_tx_range;
3225 };
3226 new_tx_range.clamp(lower_bound, upper_bound)
3227}