1#![cfg_attr(not(feature = "std"), no_std)]
4#![feature(array_windows, variant_count)]
5
6#[cfg(feature = "runtime-benchmarks")]
7mod benchmarking;
8
9#[cfg(test)]
10mod tests;
11
12pub mod block_tree;
13pub mod bundle_storage_fund;
14pub mod domain_registry;
15pub mod extensions;
16pub mod migrations;
17pub mod runtime_registry;
18mod staking;
19mod staking_epoch;
20pub mod weights;
21
22extern crate alloc;
23
24use crate::block_tree::{Error as BlockTreeError, verify_execution_receipt};
25use crate::bundle_storage_fund::{charge_bundle_storage_fee, storage_fund_account};
26use crate::domain_registry::{DomainConfig, Error as DomainRegistryError};
27use crate::runtime_registry::into_complete_raw_genesis;
28#[cfg(feature = "runtime-benchmarks")]
29pub use crate::staking::do_register_operator;
30use crate::staking::{OperatorStatus, do_reward_operators};
31use crate::staking_epoch::EpochTransitionResult;
32use crate::weights::WeightInfo;
33#[cfg(not(feature = "std"))]
34use alloc::boxed::Box;
35use alloc::collections::btree_map::BTreeMap;
36#[cfg(not(feature = "std"))]
37use alloc::vec::Vec;
38use core::marker::PhantomData;
39use domain_runtime_primitives::EthereumAccountId;
40use frame_support::ensure;
41use frame_support::pallet_prelude::{RuntimeDebug, StorageVersion};
42use frame_support::traits::fungible::{Inspect, InspectHold};
43use frame_support::traits::tokens::{Fortitude, Preservation};
44use frame_support::traits::{EnsureOrigin, Get, Randomness as RandomnessT, Time};
45use frame_support::weights::Weight;
46use frame_system::offchain::SubmitTransaction;
47use frame_system::pallet_prelude::*;
48pub use pallet::*;
49use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
50use scale_info::TypeInfo;
51use sp_consensus_subspace::WrappedPotOutput;
52use sp_consensus_subspace::consensus::is_proof_of_time_valid;
53use sp_core::H256;
54use sp_domains::bundle_producer_election::BundleProducerElectionParams;
55use sp_domains::{
56 ChainId, DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT, DomainBundleLimit, DomainId,
57 DomainInstanceData, EMPTY_EXTRINSIC_ROOT, ExecutionReceipt, OpaqueBundle, OperatorId,
58 OperatorPublicKey, OperatorRewardSource, OperatorSignature, ProofOfElection, RuntimeId,
59 SealedSingletonReceipt,
60};
61use sp_domains_fraud_proof::fraud_proof::{
62 DomainRuntimeCodeAt, FraudProof, FraudProofVariant, InvalidBlockFeesProof,
63 InvalidDomainBlockHashProof, InvalidTransfersProof,
64};
65use sp_domains_fraud_proof::storage_proof::{self, BasicStorageProof, DomainRuntimeCodeProof};
66use sp_domains_fraud_proof::verification::{
67 verify_invalid_block_fees_fraud_proof, verify_invalid_bundles_fraud_proof,
68 verify_invalid_domain_block_hash_fraud_proof,
69 verify_invalid_domain_extrinsics_root_fraud_proof, verify_invalid_state_transition_fraud_proof,
70 verify_invalid_transfers_fraud_proof, verify_valid_bundle_fraud_proof,
71};
72use sp_runtime::traits::{BlockNumberProvider, CheckedSub, Hash, Header, One, Zero};
73use sp_runtime::transaction_validity::TransactionPriority;
74use sp_runtime::{RuntimeAppPublic, SaturatedConversion, Saturating};
75use sp_subspace_mmr::{ConsensusChainMmrLeafProof, MmrProofVerifier};
76pub use staking::OperatorConfig;
77use subspace_core_primitives::pot::PotOutput;
78use subspace_core_primitives::{BlockHash, SlotNumber, U256};
79use subspace_runtime_primitives::{Balance, CreateUnsigned, Moment, StorageFee};
80
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::DomainHashingFor;
205 #[cfg(not(feature = "runtime-benchmarks"))]
206 use crate::MAX_NOMINATORS_TO_SLASH;
207 #[cfg(not(feature = "runtime-benchmarks"))]
208 use crate::block_tree::AcceptedReceiptType;
209 use crate::block_tree::{
210 Error as BlockTreeError, ReceiptType, execution_receipt_type, process_execution_receipt,
211 prune_receipt,
212 };
213 use crate::bundle_storage_fund::Error as BundleStorageFundError;
214 #[cfg(not(feature = "runtime-benchmarks"))]
215 use crate::bundle_storage_fund::refund_storage_fee;
216 use crate::domain_registry::{
217 DomainConfigParams, DomainObject, Error as DomainRegistryError, do_instantiate_domain,
218 do_update_domain_allow_list,
219 };
220 use crate::runtime_registry::{
221 DomainRuntimeUpgradeEntry, Error as RuntimeRegistryError, ScheduledRuntimeUpgrade,
222 do_register_runtime, do_schedule_runtime_upgrade, do_upgrade_runtimes,
223 register_runtime_at_genesis,
224 };
225 #[cfg(not(feature = "runtime-benchmarks"))]
226 use crate::staking::do_reward_operators;
227 use crate::staking::{
228 Deposit, DomainEpoch, Error as StakingError, Operator, OperatorConfig, SharePrice,
229 StakingSummary, WithdrawStake, Withdrawal, do_deregister_operator,
230 do_mark_operators_as_slashed, do_nominate_operator, do_register_operator, do_unlock_funds,
231 do_unlock_nominator, do_withdraw_stake,
232 };
233 #[cfg(not(feature = "runtime-benchmarks"))]
234 use crate::staking_epoch::do_slash_operator;
235 use crate::staking_epoch::{Error as StakingEpochError, do_finalize_domain_current_epoch};
236 use crate::storage_proof::InherentExtrinsicData;
237 use crate::weights::WeightInfo;
238 use crate::{
239 BalanceOf, BlockSlot, BlockTreeNodeFor, DomainBlockNumberFor, ElectionVerificationParams,
240 ExecutionReceiptOf, FraudProofFor, HoldIdentifier, MAX_BUNDLE_PER_BLOCK, NominatorId,
241 OpaqueBundleOf, RawOrigin, ReceiptHashFor, STORAGE_VERSION, SingletonReceiptOf,
242 StateRootOf,
243 };
244 #[cfg(not(feature = "std"))]
245 use alloc::string::String;
246 #[cfg(not(feature = "std"))]
247 use alloc::vec;
248 #[cfg(not(feature = "std"))]
249 use alloc::vec::Vec;
250 use domain_runtime_primitives::{EVMChainId, EthereumAccountId};
251 use frame_support::pallet_prelude::*;
252 use frame_support::traits::fungible::{Inspect, InspectHold, Mutate, MutateHold};
253 use frame_support::traits::tokens::Preservation;
254 use frame_support::traits::{Randomness as RandomnessT, Time};
255 use frame_support::weights::Weight;
256 use frame_support::{Identity, PalletError};
257 use frame_system::pallet_prelude::*;
258 use parity_scale_codec::FullCodec;
259 use sp_core::H256;
260 use sp_domains::bundle_producer_election::ProofOfElectionError;
261 use sp_domains::{
262 BundleDigest, DomainBundleSubmitted, DomainId, DomainOwner, DomainSudoCall,
263 DomainsTransfersTracker, EpochIndex, EvmDomainContractCreationAllowedByCall, GenesisDomain,
264 OnChainRewards, OnDomainInstantiated, OperatorAllowList, OperatorId, OperatorRewardSource,
265 RuntimeId, RuntimeObject, RuntimeType,
266 };
267 use sp_domains_fraud_proof::fraud_proof_runtime_interface::domain_runtime_call;
268 use sp_domains_fraud_proof::storage_proof::{self, FraudProofStorageKeyProvider};
269 use sp_domains_fraud_proof::{InvalidTransactionCode, StatelessDomainRuntimeCall};
270 use sp_runtime::Saturating;
271 use sp_runtime::traits::{
272 AtLeast32BitUnsigned, BlockNumberProvider, CheckEqual, CheckedAdd, Header as HeaderT,
273 MaybeDisplay, One, SimpleBitOps, Zero,
274 };
275 use sp_std::boxed::Box;
276 use sp_std::collections::btree_map::BTreeMap;
277 use sp_std::collections::btree_set::BTreeSet;
278 use sp_std::fmt::Debug;
279 use sp_subspace_mmr::MmrProofVerifier;
280 use subspace_core_primitives::{Randomness, U256};
281 use subspace_runtime_primitives::StorageFee;
282
283 #[pallet::config]
284 pub trait Config: frame_system::Config<Hash: Into<H256> + From<H256>> {
285 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
286
287 type DomainOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = ()>;
289
290 type DomainHash: Parameter
295 + Member
296 + MaybeSerializeDeserialize
297 + Debug
298 + MaybeDisplay
299 + SimpleBitOps
300 + Ord
301 + Default
302 + Copy
303 + CheckEqual
304 + sp_std::hash::Hash
305 + AsRef<[u8]>
306 + AsMut<[u8]>
307 + MaxEncodedLen
308 + Into<H256>
309 + From<H256>;
310
311 type Balance: Parameter
313 + Member
314 + MaybeSerializeDeserialize
315 + AtLeast32BitUnsigned
316 + FullCodec
317 + Debug
318 + MaybeDisplay
319 + Default
320 + Copy
321 + MaxEncodedLen
322 + From<u64>;
323
324 type DomainHeader: HeaderT<Hash = Self::DomainHash>;
326
327 #[pallet::constant]
329 type ConfirmationDepthK: Get<BlockNumberFor<Self>>;
330
331 type Currency: Inspect<Self::AccountId, Balance = Self::Balance>
333 + Mutate<Self::AccountId>
334 + InspectHold<Self::AccountId>
335 + MutateHold<Self::AccountId>;
336
337 type Share: Parameter
339 + Member
340 + MaybeSerializeDeserialize
341 + Debug
342 + AtLeast32BitUnsigned
343 + FullCodec
344 + Copy
345 + Default
346 + TypeInfo
347 + MaxEncodedLen
348 + IsType<BalanceOf<Self>>;
349
350 type HoldIdentifier: HoldIdentifier<Self>;
352
353 #[pallet::constant]
355 type BlockTreePruningDepth: Get<DomainBlockNumberFor<Self>>;
356
357 #[pallet::constant]
359 type ConsensusSlotProbability: Get<(u64, u64)>;
360
361 #[pallet::constant]
363 type MaxDomainBlockSize: Get<u32>;
364
365 #[pallet::constant]
367 type MaxDomainBlockWeight: Get<Weight>;
368
369 #[pallet::constant]
371 type MaxDomainNameLength: Get<u32>;
372
373 #[pallet::constant]
375 type DomainInstantiationDeposit: Get<BalanceOf<Self>>;
376
377 type WeightInfo: WeightInfo;
379
380 #[pallet::constant]
382 type InitialDomainTxRange: Get<u64>;
383
384 #[pallet::constant]
386 type DomainTxRangeAdjustmentInterval: Get<u64>;
387
388 #[pallet::constant]
390 type MinOperatorStake: Get<BalanceOf<Self>>;
391
392 #[pallet::constant]
394 type MinNominatorStake: Get<BalanceOf<Self>>;
395
396 #[pallet::constant]
398 type StakeWithdrawalLockingPeriod: Get<DomainBlockNumberFor<Self>>;
399
400 #[pallet::constant]
402 type StakeEpochDuration: Get<DomainBlockNumberFor<Self>>;
403
404 #[pallet::constant]
406 type TreasuryAccount: Get<Self::AccountId>;
407
408 #[pallet::constant]
410 type MaxPendingStakingOperation: Get<u32>;
411
412 type Randomness: RandomnessT<Self::Hash, BlockNumberFor<Self>>;
414
415 #[pallet::constant]
417 type PalletId: Get<frame_support::PalletId>;
418
419 type StorageFee: StorageFee<BalanceOf<Self>>;
421
422 type BlockTimestamp: Time;
424
425 type BlockSlot: BlockSlot<Self>;
427
428 type DomainsTransfersTracker: DomainsTransfersTracker<BalanceOf<Self>>;
430
431 type MaxInitialDomainAccounts: Get<u32>;
433
434 type MinInitialDomainAccountBalance: Get<BalanceOf<Self>>;
436
437 #[pallet::constant]
439 type BundleLongevity: Get<u32>;
440
441 type DomainBundleSubmitted: DomainBundleSubmitted;
443
444 type OnDomainInstantiated: OnDomainInstantiated;
446
447 type MmrHash: Parameter + Member + Default + Clone;
449
450 type MmrProofVerifier: MmrProofVerifier<Self::MmrHash, BlockNumberFor<Self>, StateRootOf<Self>>;
452
453 type FraudProofStorageKeyProvider: FraudProofStorageKeyProvider<BlockNumberFor<Self>>;
455
456 type OnChainRewards: OnChainRewards<BalanceOf<Self>>;
458
459 #[pallet::constant]
463 type WithdrawalLimit: Get<u32>;
464 }
465
466 #[pallet::pallet]
467 #[pallet::without_storage_info]
468 #[pallet::storage_version(STORAGE_VERSION)]
469 pub struct Pallet<T>(_);
470
471 #[pallet::storage]
473 pub type SuccessfulBundles<T> = StorageMap<_, Identity, DomainId, Vec<H256>, ValueQuery>;
474
475 #[pallet::storage]
477 pub(super) type NextRuntimeId<T> = StorageValue<_, RuntimeId, ValueQuery>;
478
479 pub struct StartingEVMChainId;
481
482 impl Get<EVMChainId> for StartingEVMChainId {
483 fn get() -> EVMChainId {
484 870
486 }
487 }
488
489 #[pallet::storage]
491 pub(super) type NextEVMChainId<T> = StorageValue<_, EVMChainId, ValueQuery, StartingEVMChainId>;
492
493 #[pallet::storage]
494 pub type RuntimeRegistry<T: Config> =
495 StorageMap<_, Identity, RuntimeId, RuntimeObject<BlockNumberFor<T>, T::Hash>, OptionQuery>;
496
497 #[pallet::storage]
498 pub(super) type ScheduledRuntimeUpgrades<T: Config> = StorageDoubleMap<
499 _,
500 Identity,
501 BlockNumberFor<T>,
502 Identity,
503 RuntimeId,
504 ScheduledRuntimeUpgrade<T::Hash>,
505 OptionQuery,
506 >;
507
508 #[pallet::storage]
509 pub(super) type NextOperatorId<T> = StorageValue<_, OperatorId, ValueQuery>;
510
511 #[pallet::storage]
512 pub(super) type OperatorIdOwner<T: Config> =
513 StorageMap<_, Identity, OperatorId, T::AccountId, OptionQuery>;
514
515 #[pallet::storage]
516 #[pallet::getter(fn domain_staking_summary)]
517 pub(super) type DomainStakingSummary<T: Config> =
518 StorageMap<_, Identity, DomainId, StakingSummary<OperatorId, BalanceOf<T>>, OptionQuery>;
519
520 #[pallet::storage]
522 pub(super) type Operators<T: Config> = StorageMap<
523 _,
524 Identity,
525 OperatorId,
526 Operator<BalanceOf<T>, T::Share, DomainBlockNumberFor<T>>,
527 OptionQuery,
528 >;
529
530 #[pallet::storage]
532 pub(super) type OperatorHighestSlot<T: Config> =
533 StorageMap<_, Identity, OperatorId, u64, ValueQuery>;
534
535 #[pallet::storage]
538 pub(super) type OperatorBundleSlot<T: Config> =
539 StorageMap<_, Identity, OperatorId, BTreeSet<u64>, ValueQuery>;
540
541 #[pallet::storage]
544 pub type OperatorEpochSharePrice<T: Config> =
545 StorageDoubleMap<_, Identity, OperatorId, Identity, DomainEpoch, SharePrice, OptionQuery>;
546
547 #[pallet::storage]
549 pub(super) type Deposits<T: Config> = StorageDoubleMap<
550 _,
551 Identity,
552 OperatorId,
553 Identity,
554 NominatorId<T>,
555 Deposit<T::Share, BalanceOf<T>>,
556 OptionQuery,
557 >;
558
559 #[pallet::storage]
561 pub(super) type Withdrawals<T: Config> = StorageDoubleMap<
562 _,
563 Identity,
564 OperatorId,
565 Identity,
566 NominatorId<T>,
567 Withdrawal<BalanceOf<T>, T::Share, DomainBlockNumberFor<T>>,
568 OptionQuery,
569 >;
570
571 #[pallet::storage]
573 pub(super) type DepositOnHold<T: Config> =
574 StorageMap<_, Identity, (OperatorId, NominatorId<T>), BalanceOf<T>, ValueQuery>;
575
576 #[pallet::storage]
583 pub(super) type NominatorCount<T: Config> =
584 StorageMap<_, Identity, OperatorId, u32, ValueQuery>;
585
586 #[pallet::storage]
590 pub(super) type PendingSlashes<T: Config> =
591 StorageMap<_, Identity, DomainId, BTreeSet<OperatorId>, OptionQuery>;
592
593 #[pallet::storage]
596 pub(super) type PendingStakingOperationCount<T: Config> =
597 StorageMap<_, Identity, DomainId, u32, ValueQuery>;
598
599 #[pallet::storage]
601 #[pallet::getter(fn next_domain_id)]
602 pub(super) type NextDomainId<T> = StorageValue<_, DomainId, ValueQuery>;
603
604 #[pallet::storage]
606 pub(super) type DomainRegistry<T: Config> = StorageMap<
607 _,
608 Identity,
609 DomainId,
610 DomainObject<BlockNumberFor<T>, ReceiptHashFor<T>, T::AccountId, BalanceOf<T>>,
611 OptionQuery,
612 >;
613
614 #[pallet::storage]
617 pub(super) type BlockTree<T: Config> = StorageDoubleMap<
618 _,
619 Identity,
620 DomainId,
621 Identity,
622 DomainBlockNumberFor<T>,
623 ReceiptHashFor<T>,
624 OptionQuery,
625 >;
626
627 #[pallet::storage]
629 pub(super) type BlockTreeNodes<T: Config> =
630 StorageMap<_, Identity, ReceiptHashFor<T>, BlockTreeNodeFor<T>, OptionQuery>;
631
632 #[pallet::storage]
634 pub(super) type HeadReceiptNumber<T: Config> =
635 StorageMap<_, Identity, DomainId, DomainBlockNumberFor<T>, ValueQuery>;
636
637 #[pallet::storage]
641 pub(super) type NewAddedHeadReceipt<T: Config> =
642 StorageMap<_, Identity, DomainId, T::DomainHash, OptionQuery>;
643
644 #[pallet::storage]
650 #[pallet::getter(fn consensus_block_info)]
651 pub type ConsensusBlockHash<T: Config> =
652 StorageDoubleMap<_, Identity, DomainId, Identity, BlockNumberFor<T>, T::Hash, OptionQuery>;
653
654 #[pallet::storage]
660 pub type ExecutionInbox<T: Config> = StorageNMap<
661 _,
662 (
663 NMapKey<Identity, DomainId>,
664 NMapKey<Identity, DomainBlockNumberFor<T>>,
665 NMapKey<Identity, BlockNumberFor<T>>,
666 ),
667 Vec<BundleDigest<T::DomainHash>>,
668 ValueQuery,
669 >;
670
671 #[pallet::storage]
675 pub(super) type InboxedBundleAuthor<T: Config> =
676 StorageMap<_, Identity, T::DomainHash, OperatorId, OptionQuery>;
677
678 #[pallet::storage]
687 pub(super) type HeadDomainNumber<T: Config> =
688 StorageMap<_, Identity, DomainId, DomainBlockNumberFor<T>, ValueQuery>;
689
690 #[pallet::storage]
696 pub(super) type LastEpochStakingDistribution<T: Config> =
697 StorageMap<_, Identity, DomainId, ElectionVerificationParams<BalanceOf<T>>, OptionQuery>;
698
699 #[pallet::storage]
701 #[pallet::getter(fn latest_confirmed_domain_execution_receipt)]
702 pub type LatestConfirmedDomainExecutionReceipt<T: Config> =
703 StorageMap<_, Identity, DomainId, ExecutionReceiptOf<T>, OptionQuery>;
704
705 #[pallet::storage]
707 #[pallet::getter(fn domain_genesis_block_execution_receipt)]
708 pub type DomainGenesisBlockExecutionReceipt<T: Config> =
709 StorageMap<_, Identity, DomainId, ExecutionReceiptOf<T>, OptionQuery>;
710
711 #[pallet::storage]
718 #[pallet::getter(fn latest_submitted_er)]
719 pub(super) type LatestSubmittedER<T: Config> =
720 StorageMap<_, Identity, (DomainId, OperatorId), DomainBlockNumberFor<T>, ValueQuery>;
721
722 #[pallet::storage]
724 pub(super) type PermissionedActionAllowedBy<T: Config> =
725 StorageValue<_, sp_domains::PermissionedActionAllowedBy<T::AccountId>, OptionQuery>;
726
727 #[pallet::storage]
730 pub(super) type AccumulatedTreasuryFunds<T> = StorageValue<_, BalanceOf<T>, ValueQuery>;
731
732 #[pallet::storage]
734 pub(super) type DomainRuntimeUpgradeRecords<T: Config> = StorageMap<
735 _,
736 Identity,
737 RuntimeId,
738 BTreeMap<BlockNumberFor<T>, DomainRuntimeUpgradeEntry<T::Hash>>,
739 ValueQuery,
740 >;
741
742 #[pallet::storage]
745 pub type DomainRuntimeUpgrades<T> = StorageValue<_, Vec<RuntimeId>, ValueQuery>;
746
747 #[pallet::storage]
752 pub type DomainSudoCalls<T: Config> =
753 StorageMap<_, Identity, DomainId, DomainSudoCall, ValueQuery>;
754
755 #[pallet::storage]
759 pub type FrozenDomains<T> = StorageValue<_, BTreeSet<DomainId>, ValueQuery>;
760
761 #[pallet::storage]
766 pub type EvmDomainContractCreationAllowedByCalls<T: Config> =
767 StorageMap<_, Identity, DomainId, EvmDomainContractCreationAllowedByCall, ValueQuery>;
768
769 #[pallet::storage]
772 pub type SkipBalanceChecks<T> = StorageValue<_, BTreeSet<DomainId>, ValueQuery>;
773
774 #[pallet::storage]
779 pub type AllowedDefaultSharePriceEpoch<T> = StorageValue<_, DomainEpoch, OptionQuery>;
780
781 #[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq)]
782 pub enum BundleError {
783 InvalidOperatorId,
785 BadBundleSignature,
787 BadVrfSignature,
789 InvalidDomainId,
791 BadOperator,
793 ThresholdUnsatisfied,
795 Receipt(BlockTreeError),
797 BundleTooLarge,
799 InvalidExtrinsicRoot,
801 InvalidProofOfTime,
803 SlotInTheFuture,
805 SlotInThePast,
807 BundleTooHeavy,
809 SlotSmallerThanPreviousBlockBundle,
812 EquivocatedBundle,
814 DomainFrozen,
816 UnableToPayBundleStorageFee,
818 UnexpectedReceiptGap,
820 ExpectingReceiptGap,
822 FailedToGetMissedUpgradeCount,
824 }
825
826 #[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq)]
827 pub enum FraudProofError {
828 BadReceiptNotFound,
831 ChallengingGenesisReceipt,
833 DescendantsOfFraudulentERNotPruned,
835 InvalidBlockFeesFraudProof,
837 InvalidTransfersFraudProof,
839 InvalidDomainBlockHashFraudProof,
841 InvalidExtrinsicRootFraudProof,
843 InvalidStateTransitionFraudProof,
845 ParentReceiptNotFound,
847 InvalidBundleFraudProof,
849 BadValidBundleFraudProof,
851 MissingOperator,
853 UnexpectedFraudProof,
855 BadReceiptAlreadyReported,
857 BadMmrProof,
859 UnexpectedMmrProof,
861 MissingMmrProof,
863 RuntimeNotFound,
865 DomainRuntimeCodeProofNotFound,
867 UnexpectedDomainRuntimeCodeProof,
869 StorageProof(storage_proof::VerificationError),
871 }
872
873 impl From<BundleError> for TransactionValidity {
874 fn from(e: BundleError) -> Self {
875 if BundleError::UnableToPayBundleStorageFee == e {
876 InvalidTransactionCode::BundleStorageFeePayment.into()
877 } else if let BundleError::Receipt(_) = e {
878 InvalidTransactionCode::ExecutionReceipt.into()
879 } else {
880 InvalidTransactionCode::Bundle.into()
881 }
882 }
883 }
884
885 impl From<storage_proof::VerificationError> for FraudProofError {
886 fn from(err: storage_proof::VerificationError) -> Self {
887 FraudProofError::StorageProof(err)
888 }
889 }
890
891 impl<T> From<FraudProofError> for Error<T> {
892 fn from(err: FraudProofError) -> Self {
893 Error::FraudProof(err)
894 }
895 }
896
897 impl<T> From<RuntimeRegistryError> for Error<T> {
898 fn from(err: RuntimeRegistryError) -> Self {
899 Error::RuntimeRegistry(err)
900 }
901 }
902
903 impl<T> From<StakingError> for Error<T> {
904 fn from(err: StakingError) -> Self {
905 Error::Staking(err)
906 }
907 }
908
909 impl<T> From<StakingEpochError> for Error<T> {
910 fn from(err: StakingEpochError) -> Self {
911 Error::StakingEpoch(err)
912 }
913 }
914
915 impl<T> From<DomainRegistryError> for Error<T> {
916 fn from(err: DomainRegistryError) -> Self {
917 Error::DomainRegistry(err)
918 }
919 }
920
921 impl<T> From<BlockTreeError> for Error<T> {
922 fn from(err: BlockTreeError) -> Self {
923 Error::BlockTree(err)
924 }
925 }
926
927 impl From<ProofOfElectionError> for BundleError {
928 fn from(err: ProofOfElectionError) -> Self {
929 match err {
930 ProofOfElectionError::BadVrfProof => Self::BadVrfSignature,
931 ProofOfElectionError::ThresholdUnsatisfied => Self::ThresholdUnsatisfied,
932 }
933 }
934 }
935
936 impl<T> From<BundleStorageFundError> for Error<T> {
937 fn from(err: BundleStorageFundError) -> Self {
938 Error::BundleStorageFund(err)
939 }
940 }
941
942 #[pallet::error]
943 pub enum Error<T> {
944 FraudProof(FraudProofError),
946 RuntimeRegistry(RuntimeRegistryError),
948 Staking(StakingError),
950 StakingEpoch(StakingEpochError),
952 DomainRegistry(DomainRegistryError),
954 BlockTree(BlockTreeError),
956 BundleStorageFund(BundleStorageFundError),
958 PermissionedActionNotAllowed,
960 DomainSudoCallExists,
962 InvalidDomainSudoCall,
964 DomainNotFrozen,
966 NotPrivateEvmDomain,
968 NotDomainOwnerOrRoot,
970 EvmDomainContractCreationAllowedByCallExists,
972 }
973
974 #[derive(Clone, Debug, PartialEq, Encode, Decode, TypeInfo)]
976 pub enum SlashedReason<DomainBlock, ReceiptHash> {
977 InvalidBundle(DomainBlock),
979 BadExecutionReceipt(ReceiptHash),
981 }
982
983 #[pallet::event]
984 #[pallet::generate_deposit(pub (super) fn deposit_event)]
985 pub enum Event<T: Config> {
986 BundleStored {
988 domain_id: DomainId,
989 bundle_hash: H256,
990 bundle_author: OperatorId,
991 },
992 DomainRuntimeCreated {
993 runtime_id: RuntimeId,
994 runtime_type: RuntimeType,
995 },
996 DomainRuntimeUpgradeScheduled {
997 runtime_id: RuntimeId,
998 scheduled_at: BlockNumberFor<T>,
999 },
1000 DomainRuntimeUpgraded {
1001 runtime_id: RuntimeId,
1002 },
1003 OperatorRegistered {
1004 operator_id: OperatorId,
1005 domain_id: DomainId,
1006 },
1007 NominatedStakedUnlocked {
1008 operator_id: OperatorId,
1009 nominator_id: NominatorId<T>,
1010 unlocked_amount: BalanceOf<T>,
1011 },
1012 StorageFeeUnlocked {
1013 operator_id: OperatorId,
1014 nominator_id: NominatorId<T>,
1015 storage_fee: BalanceOf<T>,
1016 },
1017 OperatorNominated {
1018 operator_id: OperatorId,
1019 nominator_id: NominatorId<T>,
1020 amount: BalanceOf<T>,
1021 },
1022 DomainInstantiated {
1023 domain_id: DomainId,
1024 },
1025 OperatorSwitchedDomain {
1026 old_domain_id: DomainId,
1027 new_domain_id: DomainId,
1028 },
1029 OperatorDeregistered {
1030 operator_id: OperatorId,
1031 },
1032 NominatorUnlocked {
1033 operator_id: OperatorId,
1034 nominator_id: NominatorId<T>,
1035 },
1036 WithdrewStake {
1037 operator_id: OperatorId,
1038 nominator_id: NominatorId<T>,
1039 },
1040 PreferredOperator {
1041 operator_id: OperatorId,
1042 nominator_id: NominatorId<T>,
1043 },
1044 OperatorRewarded {
1045 source: OperatorRewardSource<BlockNumberFor<T>>,
1046 operator_id: OperatorId,
1047 reward: BalanceOf<T>,
1048 },
1049 OperatorTaxCollected {
1050 operator_id: OperatorId,
1051 tax: BalanceOf<T>,
1052 },
1053 DomainEpochCompleted {
1054 domain_id: DomainId,
1055 completed_epoch_index: EpochIndex,
1056 },
1057 ForceDomainEpochTransition {
1058 domain_id: DomainId,
1059 completed_epoch_index: EpochIndex,
1060 },
1061 FraudProofProcessed {
1062 domain_id: DomainId,
1063 new_head_receipt_number: Option<DomainBlockNumberFor<T>>,
1064 },
1065 DomainOperatorAllowListUpdated {
1066 domain_id: DomainId,
1067 },
1068 OperatorSlashed {
1069 operator_id: OperatorId,
1070 reason: SlashedReason<DomainBlockNumberFor<T>, ReceiptHashFor<T>>,
1071 },
1072 StorageFeeDeposited {
1073 operator_id: OperatorId,
1074 nominator_id: NominatorId<T>,
1075 amount: BalanceOf<T>,
1076 },
1077 DomainFrozen {
1078 domain_id: DomainId,
1079 },
1080 DomainUnfrozen {
1081 domain_id: DomainId,
1082 },
1083 PrunedExecutionReceipt {
1084 domain_id: DomainId,
1085 new_head_receipt_number: Option<DomainBlockNumberFor<T>>,
1086 },
1087 }
1088
1089 #[pallet::origin]
1090 pub type Origin = RawOrigin;
1091
1092 #[derive(Debug, Default, Decode, Encode, TypeInfo, PartialEq, Eq)]
1094 pub struct TxRangeState {
1095 pub tx_range: U256,
1097
1098 pub interval_blocks: u64,
1100
1101 pub interval_bundles: u64,
1103 }
1104
1105 impl TxRangeState {
1106 pub fn on_bundle(&mut self) {
1108 self.interval_bundles += 1;
1109 }
1110 }
1111
1112 #[pallet::storage]
1113 pub(super) type DomainTxRangeState<T: Config> =
1114 StorageMap<_, Identity, DomainId, TxRangeState, OptionQuery>;
1115
1116 #[pallet::call]
1117 impl<T: Config> Pallet<T> {
1118 #[pallet::call_index(0)]
1119 #[pallet::weight(Pallet::<T>::max_submit_bundle_weight())]
1120 pub fn submit_bundle(
1121 origin: OriginFor<T>,
1122 opaque_bundle: OpaqueBundleOf<T>,
1123 ) -> DispatchResultWithPostInfo {
1124 T::DomainOrigin::ensure_origin(origin)?;
1125
1126 log::trace!("Processing bundle: {opaque_bundle:?}");
1127
1128 let domain_id = opaque_bundle.domain_id();
1129 let bundle_hash = opaque_bundle.hash();
1130 let bundle_header_hash = opaque_bundle.sealed_header.pre_hash();
1131 let extrinsics_root = opaque_bundle.extrinsics_root();
1132 let operator_id = opaque_bundle.operator_id();
1133 let bundle_size = opaque_bundle.size();
1134 let slot_number = opaque_bundle.slot_number();
1135 let receipt = opaque_bundle.into_receipt();
1136 #[cfg_attr(feature = "runtime-benchmarks", allow(unused_variables))]
1137 let receipt_block_number = receipt.domain_block_number;
1138
1139 #[cfg(not(feature = "runtime-benchmarks"))]
1140 let mut actual_weight = T::WeightInfo::submit_bundle();
1141 #[cfg(feature = "runtime-benchmarks")]
1142 let actual_weight = T::WeightInfo::submit_bundle();
1143
1144 match execution_receipt_type::<T>(domain_id, &receipt) {
1145 ReceiptType::Rejected(rejected_receipt_type) => {
1146 return Err(Error::<T>::BlockTree(rejected_receipt_type.into()).into());
1147 }
1148 ReceiptType::Accepted(accepted_receipt_type) => {
1150 #[cfg(not(feature = "runtime-benchmarks"))]
1156 if accepted_receipt_type == AcceptedReceiptType::NewHead
1157 && let Some(block_tree_node) =
1158 prune_receipt::<T>(domain_id, receipt_block_number)
1159 .map_err(Error::<T>::from)?
1160 {
1161 actual_weight =
1162 actual_weight.saturating_add(T::WeightInfo::handle_bad_receipt(
1163 block_tree_node.operator_ids.len() as u32,
1164 ));
1165
1166 let bad_receipt_hash = block_tree_node
1167 .execution_receipt
1168 .hash::<DomainHashingFor<T>>();
1169 do_mark_operators_as_slashed::<T>(
1170 block_tree_node.operator_ids.into_iter(),
1171 SlashedReason::BadExecutionReceipt(bad_receipt_hash),
1172 )
1173 .map_err(Error::<T>::from)?;
1174 }
1175
1176 #[cfg_attr(feature = "runtime-benchmarks", allow(unused_variables))]
1177 let maybe_confirmed_domain_block_info = process_execution_receipt::<T>(
1178 domain_id,
1179 operator_id,
1180 receipt,
1181 accepted_receipt_type,
1182 )
1183 .map_err(Error::<T>::from)?;
1184
1185 #[cfg(not(feature = "runtime-benchmarks"))]
1191 if let Some(confirmed_block_info) = maybe_confirmed_domain_block_info {
1192 actual_weight =
1193 actual_weight.saturating_add(T::WeightInfo::confirm_domain_block(
1194 confirmed_block_info.operator_ids.len() as u32,
1195 confirmed_block_info.invalid_bundle_authors.len() as u32,
1196 ));
1197
1198 refund_storage_fee::<T>(
1199 confirmed_block_info.total_storage_fee,
1200 confirmed_block_info.paid_bundle_storage_fees,
1201 )
1202 .map_err(Error::<T>::from)?;
1203
1204 do_reward_operators::<T>(
1205 domain_id,
1206 OperatorRewardSource::Bundle {
1207 at_block_number: confirmed_block_info.consensus_block_number,
1208 },
1209 confirmed_block_info.operator_ids.into_iter(),
1210 confirmed_block_info.rewards,
1211 )
1212 .map_err(Error::<T>::from)?;
1213
1214 do_mark_operators_as_slashed::<T>(
1215 confirmed_block_info.invalid_bundle_authors.into_iter(),
1216 SlashedReason::InvalidBundle(confirmed_block_info.domain_block_number),
1217 )
1218 .map_err(Error::<T>::from)?;
1219 }
1220 }
1221 }
1222
1223 if SuccessfulBundles::<T>::get(domain_id).is_empty() {
1227 let missed_upgrade =
1234 Self::missed_domain_runtime_upgrade(domain_id).map_err(Error::<T>::from)?;
1235
1236 let next_number = HeadDomainNumber::<T>::get(domain_id)
1237 .checked_add(&One::one())
1238 .ok_or::<Error<T>>(BlockTreeError::MaxHeadDomainNumber.into())?
1239 .checked_add(&missed_upgrade.into())
1240 .ok_or::<Error<T>>(BlockTreeError::MaxHeadDomainNumber.into())?;
1241
1242 #[cfg(not(feature = "runtime-benchmarks"))]
1244 if next_number % T::StakeEpochDuration::get() == Zero::zero() {
1245 let epoch_transition_res = do_finalize_domain_current_epoch::<T>(domain_id)
1246 .map_err(Error::<T>::from)?;
1247
1248 Self::deposit_event(Event::DomainEpochCompleted {
1249 domain_id,
1250 completed_epoch_index: epoch_transition_res.completed_epoch_index,
1251 });
1252
1253 actual_weight = actual_weight
1254 .saturating_add(Self::actual_epoch_transition_weight(epoch_transition_res));
1255 }
1256
1257 HeadDomainNumber::<T>::set(domain_id, next_number);
1258 }
1259
1260 let head_domain_number = HeadDomainNumber::<T>::get(domain_id);
1262 let consensus_block_number = frame_system::Pallet::<T>::current_block_number();
1263 ExecutionInbox::<T>::append(
1264 (domain_id, head_domain_number, consensus_block_number),
1265 BundleDigest {
1266 header_hash: bundle_header_hash,
1267 extrinsics_root,
1268 size: bundle_size,
1269 },
1270 );
1271
1272 InboxedBundleAuthor::<T>::insert(bundle_header_hash, operator_id);
1273
1274 SuccessfulBundles::<T>::append(domain_id, bundle_hash);
1275
1276 OperatorBundleSlot::<T>::mutate(operator_id, |slot_set| slot_set.insert(slot_number));
1277
1278 #[cfg(not(feature = "runtime-benchmarks"))]
1280 {
1281 let slashed_nominator_count =
1282 do_slash_operator::<T>(domain_id, MAX_NOMINATORS_TO_SLASH)
1283 .map_err(Error::<T>::from)?;
1284 actual_weight = actual_weight
1285 .saturating_add(T::WeightInfo::slash_operator(slashed_nominator_count));
1286 }
1287
1288 Self::deposit_event(Event::BundleStored {
1289 domain_id,
1290 bundle_hash,
1291 bundle_author: operator_id,
1292 });
1293
1294 Ok(Some(actual_weight.min(Self::max_submit_bundle_weight())).into())
1296 }
1297
1298 #[pallet::call_index(15)]
1299 #[pallet::weight((
1300 T::WeightInfo::submit_fraud_proof().saturating_add(
1301 T::WeightInfo::handle_bad_receipt(MAX_BUNDLE_PER_BLOCK)
1302 ),
1303 DispatchClass::Operational
1304 ))]
1305 pub fn submit_fraud_proof(
1306 origin: OriginFor<T>,
1307 fraud_proof: Box<FraudProofFor<T>>,
1308 ) -> DispatchResultWithPostInfo {
1309 T::DomainOrigin::ensure_origin(origin)?;
1310
1311 log::trace!("Processing fraud proof: {fraud_proof:?}");
1312
1313 #[cfg(not(feature = "runtime-benchmarks"))]
1314 let mut actual_weight = T::WeightInfo::submit_fraud_proof();
1315 #[cfg(feature = "runtime-benchmarks")]
1316 let actual_weight = T::WeightInfo::submit_fraud_proof();
1317
1318 let domain_id = fraud_proof.domain_id();
1319 let bad_receipt_hash = fraud_proof.targeted_bad_receipt_hash();
1320 let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
1321 let bad_receipt_number = BlockTreeNodes::<T>::get(bad_receipt_hash)
1322 .ok_or::<Error<T>>(FraudProofError::BadReceiptNotFound.into())?
1323 .execution_receipt
1324 .domain_block_number;
1325 ensure!(
1329 head_receipt_number >= bad_receipt_number,
1330 Error::<T>::from(FraudProofError::BadReceiptNotFound),
1331 );
1332
1333 #[cfg(not(feature = "runtime-benchmarks"))]
1340 {
1341 let block_tree_node = prune_receipt::<T>(domain_id, bad_receipt_number)
1342 .map_err(Error::<T>::from)?
1343 .ok_or::<Error<T>>(FraudProofError::BadReceiptNotFound.into())?;
1344
1345 actual_weight = actual_weight.saturating_add(T::WeightInfo::handle_bad_receipt(
1346 (block_tree_node.operator_ids.len() as u32).min(MAX_BUNDLE_PER_BLOCK),
1347 ));
1348
1349 do_mark_operators_as_slashed::<T>(
1350 block_tree_node.operator_ids.into_iter(),
1351 SlashedReason::BadExecutionReceipt(bad_receipt_hash),
1352 )
1353 .map_err(Error::<T>::from)?;
1354 }
1355
1356 let new_head_receipt_number = bad_receipt_number.saturating_sub(One::one());
1358 HeadReceiptNumber::<T>::insert(domain_id, new_head_receipt_number);
1359
1360 Self::deposit_event(Event::FraudProofProcessed {
1361 domain_id,
1362 new_head_receipt_number: Some(new_head_receipt_number),
1363 });
1364
1365 Ok(Some(actual_weight).into())
1366 }
1367
1368 #[pallet::call_index(2)]
1369 #[pallet::weight(T::WeightInfo::register_domain_runtime())]
1370 pub fn register_domain_runtime(
1371 origin: OriginFor<T>,
1372 runtime_name: String,
1373 runtime_type: RuntimeType,
1374 raw_genesis_storage: Vec<u8>,
1378 ) -> DispatchResult {
1379 ensure_root(origin)?;
1380
1381 let block_number = frame_system::Pallet::<T>::current_block_number();
1382 let runtime_id = do_register_runtime::<T>(
1383 runtime_name,
1384 runtime_type,
1385 raw_genesis_storage,
1386 block_number,
1387 )
1388 .map_err(Error::<T>::from)?;
1389
1390 Self::deposit_event(Event::DomainRuntimeCreated {
1391 runtime_id,
1392 runtime_type,
1393 });
1394
1395 Ok(())
1396 }
1397
1398 #[pallet::call_index(3)]
1399 #[pallet::weight(T::WeightInfo::upgrade_domain_runtime())]
1400 pub fn upgrade_domain_runtime(
1401 origin: OriginFor<T>,
1402 runtime_id: RuntimeId,
1403 raw_genesis_storage: Vec<u8>,
1404 ) -> DispatchResult {
1405 ensure_root(origin)?;
1406
1407 let block_number = frame_system::Pallet::<T>::current_block_number();
1408 let scheduled_at =
1409 do_schedule_runtime_upgrade::<T>(runtime_id, raw_genesis_storage, block_number)
1410 .map_err(Error::<T>::from)?;
1411
1412 Self::deposit_event(Event::DomainRuntimeUpgradeScheduled {
1413 runtime_id,
1414 scheduled_at,
1415 });
1416
1417 Ok(())
1418 }
1419
1420 #[pallet::call_index(4)]
1421 #[pallet::weight(T::WeightInfo::register_operator())]
1422 pub fn register_operator(
1423 origin: OriginFor<T>,
1424 domain_id: DomainId,
1425 amount: BalanceOf<T>,
1426 config: OperatorConfig<BalanceOf<T>>,
1427 ) -> DispatchResult {
1428 let owner = ensure_signed(origin)?;
1429
1430 let (operator_id, current_epoch_index) =
1431 do_register_operator::<T>(owner, domain_id, amount, config)
1432 .map_err(Error::<T>::from)?;
1433
1434 Self::deposit_event(Event::OperatorRegistered {
1435 operator_id,
1436 domain_id,
1437 });
1438
1439 if current_epoch_index.is_zero() {
1442 do_finalize_domain_current_epoch::<T>(domain_id).map_err(Error::<T>::from)?;
1443 }
1444
1445 Ok(())
1446 }
1447
1448 #[pallet::call_index(5)]
1449 #[pallet::weight(T::WeightInfo::nominate_operator())]
1450 pub fn nominate_operator(
1451 origin: OriginFor<T>,
1452 operator_id: OperatorId,
1453 amount: BalanceOf<T>,
1454 ) -> DispatchResult {
1455 let nominator_id = ensure_signed(origin)?;
1456
1457 do_nominate_operator::<T>(operator_id, nominator_id.clone(), amount)
1458 .map_err(Error::<T>::from)?;
1459
1460 Ok(())
1461 }
1462
1463 #[pallet::call_index(6)]
1464 #[pallet::weight(T::WeightInfo::instantiate_domain())]
1465 pub fn instantiate_domain(
1466 origin: OriginFor<T>,
1467 domain_config_params: DomainConfigParams<T::AccountId, BalanceOf<T>>,
1468 ) -> DispatchResult {
1469 let who = ensure_signed(origin)?;
1470 ensure!(
1471 PermissionedActionAllowedBy::<T>::get()
1472 .map(|allowed_by| allowed_by.is_allowed(&who))
1473 .unwrap_or_default(),
1474 Error::<T>::PermissionedActionNotAllowed
1475 );
1476
1477 let created_at = frame_system::Pallet::<T>::current_block_number();
1478
1479 let domain_id = do_instantiate_domain::<T>(domain_config_params, who, created_at)
1480 .map_err(Error::<T>::from)?;
1481
1482 Self::deposit_event(Event::DomainInstantiated { domain_id });
1483
1484 Ok(())
1485 }
1486
1487 #[pallet::call_index(8)]
1488 #[pallet::weight(T::WeightInfo::deregister_operator())]
1489 pub fn deregister_operator(
1490 origin: OriginFor<T>,
1491 operator_id: OperatorId,
1492 ) -> DispatchResult {
1493 let who = ensure_signed(origin)?;
1494
1495 do_deregister_operator::<T>(who, operator_id).map_err(Error::<T>::from)?;
1496
1497 Self::deposit_event(Event::OperatorDeregistered { operator_id });
1498
1499 Ok(())
1500 }
1501
1502 #[pallet::call_index(9)]
1503 #[pallet::weight(T::WeightInfo::withdraw_stake())]
1504 pub fn withdraw_stake(
1505 origin: OriginFor<T>,
1506 operator_id: OperatorId,
1507 to_withdraw: WithdrawStake<BalanceOf<T>, T::Share>,
1508 ) -> DispatchResult {
1509 let who = ensure_signed(origin)?;
1510
1511 do_withdraw_stake::<T>(operator_id, who.clone(), to_withdraw)
1512 .map_err(Error::<T>::from)?;
1513
1514 Self::deposit_event(Event::WithdrewStake {
1515 operator_id,
1516 nominator_id: who,
1517 });
1518
1519 Ok(())
1520 }
1521
1522 #[pallet::call_index(10)]
1526 #[pallet::weight(T::WeightInfo::unlock_funds())]
1527 pub fn unlock_funds(origin: OriginFor<T>, operator_id: OperatorId) -> DispatchResult {
1528 let nominator_id = ensure_signed(origin)?;
1529 do_unlock_funds::<T>(operator_id, nominator_id.clone())
1530 .map_err(crate::pallet::Error::<T>::from)?;
1531 Ok(())
1532 }
1533
1534 #[pallet::call_index(11)]
1537 #[pallet::weight(T::WeightInfo::unlock_nominator())]
1538 pub fn unlock_nominator(origin: OriginFor<T>, operator_id: OperatorId) -> DispatchResult {
1539 let nominator = ensure_signed(origin)?;
1540
1541 do_unlock_nominator::<T>(operator_id, nominator.clone())
1542 .map_err(crate::pallet::Error::<T>::from)?;
1543
1544 Self::deposit_event(Event::NominatorUnlocked {
1545 operator_id,
1546 nominator_id: nominator,
1547 });
1548
1549 Ok(())
1550 }
1551
1552 #[pallet::call_index(12)]
1560 #[pallet::weight(T::WeightInfo::update_domain_operator_allow_list())]
1561 pub fn update_domain_operator_allow_list(
1562 origin: OriginFor<T>,
1563 domain_id: DomainId,
1564 operator_allow_list: OperatorAllowList<T::AccountId>,
1565 ) -> DispatchResult {
1566 let who = ensure_signed(origin)?;
1567 do_update_domain_allow_list::<T>(who, domain_id, operator_allow_list)
1568 .map_err(Error::<T>::from)?;
1569 Self::deposit_event(crate::pallet::Event::DomainOperatorAllowListUpdated { domain_id });
1570 Ok(())
1571 }
1572
1573 #[pallet::call_index(13)]
1575 #[pallet::weight(Pallet::<T>::max_staking_epoch_transition())]
1576 pub fn force_staking_epoch_transition(
1577 origin: OriginFor<T>,
1578 domain_id: DomainId,
1579 ) -> DispatchResultWithPostInfo {
1580 ensure_root(origin)?;
1581
1582 let epoch_transition_res =
1583 do_finalize_domain_current_epoch::<T>(domain_id).map_err(Error::<T>::from)?;
1584
1585 Self::deposit_event(Event::ForceDomainEpochTransition {
1586 domain_id,
1587 completed_epoch_index: epoch_transition_res.completed_epoch_index,
1588 });
1589
1590 let actual_weight = Self::actual_epoch_transition_weight(epoch_transition_res)
1592 .min(Self::max_staking_epoch_transition());
1593
1594 Ok(Some(actual_weight).into())
1595 }
1596
1597 #[pallet::call_index(14)]
1599 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(0, 1))]
1600 pub fn set_permissioned_action_allowed_by(
1601 origin: OriginFor<T>,
1602 permissioned_action_allowed_by: sp_domains::PermissionedActionAllowedBy<T::AccountId>,
1603 ) -> DispatchResult {
1604 ensure_root(origin)?;
1605 PermissionedActionAllowedBy::<T>::put(permissioned_action_allowed_by);
1606 Ok(())
1607 }
1608
1609 #[pallet::call_index(16)]
1611 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(3, 1))]
1612 pub fn send_domain_sudo_call(
1613 origin: OriginFor<T>,
1614 domain_id: DomainId,
1615 call: Vec<u8>,
1616 ) -> DispatchResult {
1617 ensure_root(origin)?;
1618 ensure!(
1619 DomainSudoCalls::<T>::get(domain_id).maybe_call.is_none(),
1620 Error::<T>::DomainSudoCallExists
1621 );
1622
1623 let domain_runtime = Self::domain_runtime_code(domain_id).ok_or(
1624 Error::<T>::DomainRegistry(DomainRegistryError::DomainNotFound),
1625 )?;
1626 ensure!(
1627 domain_runtime_call(
1628 domain_runtime,
1629 StatelessDomainRuntimeCall::IsValidDomainSudoCall(call.clone()),
1630 )
1631 .unwrap_or(false),
1632 Error::<T>::InvalidDomainSudoCall
1633 );
1634
1635 DomainSudoCalls::<T>::set(
1636 domain_id,
1637 DomainSudoCall {
1638 maybe_call: Some(call),
1639 },
1640 );
1641 Ok(())
1642 }
1643
1644 #[pallet::call_index(17)]
1647 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(0, 1))]
1648 pub fn freeze_domain(origin: OriginFor<T>, domain_id: DomainId) -> DispatchResult {
1649 ensure_root(origin)?;
1650 FrozenDomains::<T>::mutate(|frozen_domains| frozen_domains.insert(domain_id));
1651 Self::deposit_event(Event::DomainFrozen { domain_id });
1652 Ok(())
1653 }
1654
1655 #[pallet::call_index(18)]
1657 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(0, 1))]
1658 pub fn unfreeze_domain(origin: OriginFor<T>, domain_id: DomainId) -> DispatchResult {
1659 ensure_root(origin)?;
1660 FrozenDomains::<T>::mutate(|frozen_domains| frozen_domains.remove(&domain_id));
1661 Self::deposit_event(Event::DomainUnfrozen { domain_id });
1662 Ok(())
1663 }
1664
1665 #[pallet::call_index(19)]
1669 #[pallet::weight(Pallet::<T>::max_prune_domain_execution_receipt())]
1670 pub fn prune_domain_execution_receipt(
1671 origin: OriginFor<T>,
1672 domain_id: DomainId,
1673 bad_receipt_hash: ReceiptHashFor<T>,
1674 ) -> DispatchResultWithPostInfo {
1675 ensure_root(origin)?;
1676 ensure!(
1677 FrozenDomains::<T>::get().contains(&domain_id),
1678 Error::<T>::DomainNotFrozen
1679 );
1680
1681 let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
1682 let bad_receipt_number = BlockTreeNodes::<T>::get(bad_receipt_hash)
1683 .ok_or::<Error<T>>(FraudProofError::BadReceiptNotFound.into())?
1684 .execution_receipt
1685 .domain_block_number;
1686 ensure!(
1689 head_receipt_number >= bad_receipt_number,
1690 Error::<T>::from(FraudProofError::BadReceiptNotFound),
1691 );
1692
1693 let mut actual_weight = T::DbWeight::get().reads(3);
1694
1695 let block_tree_node = prune_receipt::<T>(domain_id, bad_receipt_number)
1697 .map_err(Error::<T>::from)?
1698 .ok_or::<Error<T>>(FraudProofError::BadReceiptNotFound.into())?;
1699
1700 actual_weight = actual_weight.saturating_add(T::WeightInfo::handle_bad_receipt(
1701 (block_tree_node.operator_ids.len() as u32).min(MAX_BUNDLE_PER_BLOCK),
1702 ));
1703
1704 do_mark_operators_as_slashed::<T>(
1705 block_tree_node.operator_ids.into_iter(),
1706 SlashedReason::BadExecutionReceipt(bad_receipt_hash),
1707 )
1708 .map_err(Error::<T>::from)?;
1709
1710 let new_head_receipt_number = bad_receipt_number.saturating_sub(One::one());
1712 HeadReceiptNumber::<T>::insert(domain_id, new_head_receipt_number);
1713 actual_weight = actual_weight.saturating_add(T::DbWeight::get().reads_writes(0, 1));
1714
1715 Self::deposit_event(Event::PrunedExecutionReceipt {
1716 domain_id,
1717 new_head_receipt_number: Some(new_head_receipt_number),
1718 });
1719
1720 Ok(Some(actual_weight).into())
1721 }
1722
1723 #[pallet::call_index(20)]
1725 #[pallet::weight(T::WeightInfo::transfer_treasury_funds())]
1726 pub fn transfer_treasury_funds(
1727 origin: OriginFor<T>,
1728 account_id: T::AccountId,
1729 balance: BalanceOf<T>,
1730 ) -> DispatchResult {
1731 ensure_root(origin)?;
1732 T::Currency::transfer(
1733 &T::TreasuryAccount::get(),
1734 &account_id,
1735 balance,
1736 Preservation::Preserve,
1737 )?;
1738 Ok(())
1739 }
1740
1741 #[pallet::call_index(21)]
1742 #[pallet::weight(Pallet::<T>::max_submit_receipt_weight())]
1743 pub fn submit_receipt(
1744 origin: OriginFor<T>,
1745 singleton_receipt: SingletonReceiptOf<T>,
1746 ) -> DispatchResultWithPostInfo {
1747 T::DomainOrigin::ensure_origin(origin)?;
1748
1749 let domain_id = singleton_receipt.domain_id();
1750 let operator_id = singleton_receipt.operator_id();
1751 let receipt = singleton_receipt.into_receipt();
1752
1753 #[cfg(not(feature = "runtime-benchmarks"))]
1754 let mut actual_weight = T::WeightInfo::submit_receipt();
1755 #[cfg(feature = "runtime-benchmarks")]
1756 let actual_weight = T::WeightInfo::submit_receipt();
1757
1758 match execution_receipt_type::<T>(domain_id, &receipt) {
1759 ReceiptType::Rejected(rejected_receipt_type) => {
1760 return Err(Error::<T>::BlockTree(rejected_receipt_type.into()).into());
1761 }
1762 ReceiptType::Accepted(accepted_receipt_type) => {
1764 #[cfg(not(feature = "runtime-benchmarks"))]
1770 if accepted_receipt_type == AcceptedReceiptType::NewHead
1771 && let Some(block_tree_node) =
1772 prune_receipt::<T>(domain_id, receipt.domain_block_number)
1773 .map_err(Error::<T>::from)?
1774 {
1775 actual_weight =
1776 actual_weight.saturating_add(T::WeightInfo::handle_bad_receipt(
1777 block_tree_node.operator_ids.len() as u32,
1778 ));
1779
1780 let bad_receipt_hash = block_tree_node
1781 .execution_receipt
1782 .hash::<DomainHashingFor<T>>();
1783 do_mark_operators_as_slashed::<T>(
1784 block_tree_node.operator_ids.into_iter(),
1785 SlashedReason::BadExecutionReceipt(bad_receipt_hash),
1786 )
1787 .map_err(Error::<T>::from)?;
1788 }
1789
1790 #[cfg_attr(feature = "runtime-benchmarks", allow(unused_variables))]
1791 let maybe_confirmed_domain_block_info = process_execution_receipt::<T>(
1792 domain_id,
1793 operator_id,
1794 receipt,
1795 accepted_receipt_type,
1796 )
1797 .map_err(Error::<T>::from)?;
1798
1799 #[cfg(not(feature = "runtime-benchmarks"))]
1802 if let Some(confirmed_block_info) = maybe_confirmed_domain_block_info {
1803 actual_weight =
1804 actual_weight.saturating_add(T::WeightInfo::confirm_domain_block(
1805 confirmed_block_info.operator_ids.len() as u32,
1806 confirmed_block_info.invalid_bundle_authors.len() as u32,
1807 ));
1808
1809 refund_storage_fee::<T>(
1810 confirmed_block_info.total_storage_fee,
1811 confirmed_block_info.paid_bundle_storage_fees,
1812 )
1813 .map_err(Error::<T>::from)?;
1814
1815 do_reward_operators::<T>(
1816 domain_id,
1817 OperatorRewardSource::Bundle {
1818 at_block_number: confirmed_block_info.consensus_block_number,
1819 },
1820 confirmed_block_info.operator_ids.into_iter(),
1821 confirmed_block_info.rewards,
1822 )
1823 .map_err(Error::<T>::from)?;
1824
1825 do_mark_operators_as_slashed::<T>(
1826 confirmed_block_info.invalid_bundle_authors.into_iter(),
1827 SlashedReason::InvalidBundle(confirmed_block_info.domain_block_number),
1828 )
1829 .map_err(Error::<T>::from)?;
1830 }
1831 }
1832 }
1833
1834 #[cfg(not(feature = "runtime-benchmarks"))]
1836 {
1837 let slashed_nominator_count =
1838 do_slash_operator::<T>(domain_id, MAX_NOMINATORS_TO_SLASH)
1839 .map_err(Error::<T>::from)?;
1840 actual_weight = actual_weight
1841 .saturating_add(T::WeightInfo::slash_operator(slashed_nominator_count));
1842 }
1843
1844 Ok(Some(actual_weight.min(Self::max_submit_receipt_weight())).into())
1846 }
1847
1848 #[pallet::call_index(22)]
1850 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(3, 1))]
1851 pub fn send_evm_domain_set_contract_creation_allowed_by_call(
1852 origin: OriginFor<T>,
1853 domain_id: DomainId,
1854 contract_creation_allowed_by: sp_domains::PermissionedActionAllowedBy<
1855 EthereumAccountId,
1856 >,
1857 ) -> DispatchResult {
1858 let signer = ensure_signed_or_root(origin)?;
1859
1860 ensure!(
1861 Pallet::<T>::is_private_evm_domain(domain_id),
1862 Error::<T>::NotPrivateEvmDomain,
1863 );
1864 if let Some(non_root_signer) = signer {
1865 ensure!(
1866 Pallet::<T>::is_domain_owner(domain_id, non_root_signer),
1867 Error::<T>::NotDomainOwnerOrRoot,
1868 );
1869 }
1870 ensure!(
1871 EvmDomainContractCreationAllowedByCalls::<T>::get(domain_id)
1872 .maybe_call
1873 .is_none(),
1874 Error::<T>::EvmDomainContractCreationAllowedByCallExists,
1875 );
1876
1877 EvmDomainContractCreationAllowedByCalls::<T>::set(
1878 domain_id,
1879 EvmDomainContractCreationAllowedByCall {
1880 maybe_call: Some(contract_creation_allowed_by),
1881 },
1882 );
1883
1884 Ok(())
1885 }
1886 }
1887
1888 #[pallet::genesis_config]
1889 pub struct GenesisConfig<T: Config> {
1890 pub permissioned_action_allowed_by:
1891 Option<sp_domains::PermissionedActionAllowedBy<T::AccountId>>,
1892 pub genesis_domains: Vec<GenesisDomain<T::AccountId, BalanceOf<T>>>,
1893 }
1894
1895 impl<T: Config> Default for GenesisConfig<T> {
1896 fn default() -> Self {
1897 GenesisConfig {
1898 permissioned_action_allowed_by: None,
1899 genesis_domains: vec![],
1900 }
1901 }
1902 }
1903
1904 #[pallet::genesis_build]
1905 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
1906 fn build(&self) {
1907 if let Some(permissioned_action_allowed_by) =
1908 self.permissioned_action_allowed_by.as_ref().cloned()
1909 {
1910 PermissionedActionAllowedBy::<T>::put(permissioned_action_allowed_by)
1911 }
1912
1913 self.genesis_domains
1914 .clone()
1915 .into_iter()
1916 .for_each(|genesis_domain| {
1917 let runtime_id = register_runtime_at_genesis::<T>(
1919 genesis_domain.runtime_name,
1920 genesis_domain.runtime_type,
1921 genesis_domain.runtime_version,
1922 genesis_domain.raw_genesis_storage,
1923 Zero::zero(),
1924 )
1925 .expect("Genesis runtime registration must always succeed");
1926
1927 let domain_config_params = DomainConfigParams {
1929 domain_name: genesis_domain.domain_name,
1930 runtime_id,
1931 maybe_bundle_limit: None,
1932 bundle_slot_probability: genesis_domain.bundle_slot_probability,
1933 operator_allow_list: genesis_domain.operator_allow_list,
1934 initial_balances: genesis_domain.initial_balances,
1935 domain_runtime_config: genesis_domain.domain_runtime_config,
1936 };
1937 let domain_owner = genesis_domain.owner_account_id;
1938 let domain_id = do_instantiate_domain::<T>(
1939 domain_config_params,
1940 domain_owner.clone(),
1941 Zero::zero(),
1942 )
1943 .expect("Genesis domain instantiation must always succeed");
1944
1945 let operator_config = OperatorConfig {
1947 signing_key: genesis_domain.signing_key.clone(),
1948 minimum_nominator_stake: genesis_domain.minimum_nominator_stake,
1949 nomination_tax: genesis_domain.nomination_tax,
1950 };
1951 let operator_stake = T::MinOperatorStake::get();
1952 do_register_operator::<T>(
1953 domain_owner,
1954 domain_id,
1955 operator_stake,
1956 operator_config,
1957 )
1958 .expect("Genesis operator registration must succeed");
1959
1960 do_finalize_domain_current_epoch::<T>(domain_id)
1961 .expect("Genesis epoch must succeed");
1962 });
1963 }
1964 }
1965
1966 #[pallet::storage]
1968 pub type BlockInherentExtrinsicData<T> = StorageValue<_, InherentExtrinsicData>;
1969
1970 #[pallet::hooks]
1971 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
1973 fn on_initialize(block_number: BlockNumberFor<T>) -> Weight {
1974 let parent_number = block_number - One::one();
1975 let parent_hash = frame_system::Pallet::<T>::block_hash(parent_number);
1976
1977 for runtime_id in DomainRuntimeUpgrades::<T>::take() {
1979 let reference_count = RuntimeRegistry::<T>::get(runtime_id)
1980 .expect("Runtime object must be present since domain is insantiated; qed")
1981 .instance_count;
1982 if !reference_count.is_zero() {
1983 DomainRuntimeUpgradeRecords::<T>::mutate(runtime_id, |upgrade_record| {
1984 upgrade_record.insert(
1985 parent_number,
1986 DomainRuntimeUpgradeEntry {
1987 at_hash: parent_hash,
1988 reference_count,
1989 },
1990 )
1991 });
1992 }
1993 }
1994 DomainRuntimeUpgrades::<T>::set(Vec::new());
1997 do_upgrade_runtimes::<T>(block_number);
2000
2001 for (domain_id, _) in SuccessfulBundles::<T>::drain() {
2004 ConsensusBlockHash::<T>::insert(domain_id, parent_number, parent_hash);
2005 T::DomainBundleSubmitted::domain_bundle_submitted(domain_id);
2006
2007 DomainSudoCalls::<T>::mutate(domain_id, |sudo_call| {
2009 sudo_call.clear();
2010 });
2011 EvmDomainContractCreationAllowedByCalls::<T>::mutate(
2012 domain_id,
2013 |evm_contract_call| {
2014 evm_contract_call.clear();
2015 },
2016 );
2017 }
2018
2019 for (operator_id, slot_set) in OperatorBundleSlot::<T>::drain() {
2020 if let Some(highest_slot) = slot_set.last() {
2023 OperatorHighestSlot::<T>::insert(operator_id, highest_slot);
2024 }
2025 }
2026
2027 BlockInherentExtrinsicData::<T>::kill();
2028
2029 Weight::zero()
2030 }
2031
2032 fn on_finalize(_: BlockNumberFor<T>) {
2033 if SuccessfulBundles::<T>::iter_keys().count() > 0
2036 || !DomainRuntimeUpgrades::<T>::get().is_empty()
2037 {
2038 let extrinsics_shuffling_seed = Randomness::from(
2039 Into::<H256>::into(Self::extrinsics_shuffling_seed_value()).to_fixed_bytes(),
2040 );
2041
2042 let timestamp = Self::timestamp_value();
2045
2046 let consensus_transaction_byte_fee = Self::consensus_transaction_byte_fee_value();
2048
2049 let inherent_extrinsic_data = InherentExtrinsicData {
2050 extrinsics_shuffling_seed,
2051 timestamp,
2052 consensus_transaction_byte_fee,
2053 };
2054
2055 BlockInherentExtrinsicData::<T>::set(Some(inherent_extrinsic_data));
2056 }
2057
2058 let _ = LastEpochStakingDistribution::<T>::clear(u32::MAX, None);
2059 let _ = NewAddedHeadReceipt::<T>::clear(u32::MAX, None);
2060 }
2061 }
2062}
2063
2064impl<T: Config> Pallet<T> {
2065 fn log_bundle_error(err: &BundleError, domain_id: DomainId, operator_id: OperatorId) {
2066 match err {
2067 BundleError::Receipt(BlockTreeError::InFutureReceipt)
2070 | BundleError::Receipt(BlockTreeError::StaleReceipt)
2071 | BundleError::Receipt(BlockTreeError::NewBranchReceipt)
2072 | BundleError::Receipt(BlockTreeError::UnavailableConsensusBlockHash)
2073 | BundleError::Receipt(BlockTreeError::BuiltOnUnknownConsensusBlock)
2074 | BundleError::SlotInThePast
2075 | BundleError::SlotInTheFuture
2076 | BundleError::InvalidProofOfTime
2077 | BundleError::SlotSmallerThanPreviousBlockBundle
2078 | BundleError::ExpectingReceiptGap
2079 | BundleError::UnexpectedReceiptGap => {
2080 log::debug!(
2081 "Bad bundle/receipt, domain {domain_id:?}, operator {operator_id:?}, error: {err:?}",
2082 );
2083 }
2084 _ => {
2085 log::warn!(
2086 "Bad bundle/receipt, domain {domain_id:?}, operator {operator_id:?}, error: {err:?}",
2087 );
2088 }
2089 }
2090 }
2091
2092 pub fn successful_bundles(domain_id: DomainId) -> Vec<H256> {
2093 SuccessfulBundles::<T>::get(domain_id)
2094 }
2095
2096 pub fn domain_runtime_code(domain_id: DomainId) -> Option<Vec<u8>> {
2097 RuntimeRegistry::<T>::get(Self::runtime_id(domain_id)?)
2098 .and_then(|mut runtime_object| runtime_object.raw_genesis.take_runtime_code())
2099 }
2100
2101 pub fn domain_best_number(domain_id: DomainId) -> Result<DomainBlockNumberFor<T>, BundleError> {
2102 let missed_upgrade = Self::missed_domain_runtime_upgrade(domain_id)
2105 .map_err(|_| BundleError::FailedToGetMissedUpgradeCount)?;
2106
2107 Ok(HeadDomainNumber::<T>::get(domain_id) + missed_upgrade.into())
2108 }
2109
2110 pub fn runtime_id(domain_id: DomainId) -> Option<RuntimeId> {
2112 DomainRegistry::<T>::get(domain_id)
2113 .map(|domain_object| domain_object.domain_config.runtime_id)
2114 }
2115
2116 pub fn runtime_upgrades() -> Vec<RuntimeId> {
2118 DomainRuntimeUpgrades::<T>::get()
2119 }
2120
2121 pub fn domain_instance_data(
2122 domain_id: DomainId,
2123 ) -> Option<(DomainInstanceData, BlockNumberFor<T>)> {
2124 let domain_obj = DomainRegistry::<T>::get(domain_id)?;
2125 let runtime_object = RuntimeRegistry::<T>::get(domain_obj.domain_config.runtime_id)?;
2126 let runtime_type = runtime_object.runtime_type;
2127 let total_issuance = domain_obj.domain_config.total_issuance()?;
2128 let raw_genesis = into_complete_raw_genesis::<T>(
2129 runtime_object,
2130 domain_id,
2131 &domain_obj.domain_runtime_info,
2132 total_issuance,
2133 domain_obj.domain_config.initial_balances,
2134 )
2135 .ok()?;
2136 Some((
2137 DomainInstanceData {
2138 runtime_type,
2139 raw_genesis,
2140 },
2141 domain_obj.created_at,
2142 ))
2143 }
2144
2145 pub fn genesis_state_root(domain_id: DomainId) -> Option<H256> {
2146 DomainGenesisBlockExecutionReceipt::<T>::get(domain_id).map(|er| er.final_state_root.into())
2147 }
2148
2149 pub fn domain_tx_range(domain_id: DomainId) -> U256 {
2151 DomainTxRangeState::<T>::try_get(domain_id)
2152 .map(|state| state.tx_range)
2153 .ok()
2154 .unwrap_or_else(Self::initial_tx_range)
2155 }
2156
2157 pub fn bundle_producer_election_params(
2158 domain_id: DomainId,
2159 ) -> Option<BundleProducerElectionParams<BalanceOf<T>>> {
2160 match (
2161 DomainRegistry::<T>::get(domain_id),
2162 DomainStakingSummary::<T>::get(domain_id),
2163 ) {
2164 (Some(domain_object), Some(stake_summary)) => Some(BundleProducerElectionParams {
2165 total_domain_stake: stake_summary.current_total_stake,
2166 bundle_slot_probability: domain_object.domain_config.bundle_slot_probability,
2167 }),
2168 _ => None,
2169 }
2170 }
2171
2172 pub fn operator(operator_id: OperatorId) -> Option<(OperatorPublicKey, BalanceOf<T>)> {
2173 Operators::<T>::get(operator_id)
2174 .map(|operator| (operator.signing_key, operator.current_total_stake))
2175 }
2176
2177 fn check_extrinsics_root(opaque_bundle: &OpaqueBundleOf<T>) -> Result<(), BundleError> {
2178 let expected_extrinsics_root = <T::DomainHeader as Header>::Hashing::ordered_trie_root(
2179 opaque_bundle
2180 .extrinsics
2181 .iter()
2182 .map(|xt| xt.encode())
2183 .collect(),
2184 sp_core::storage::StateVersion::V1,
2185 );
2186 ensure!(
2187 expected_extrinsics_root == opaque_bundle.extrinsics_root(),
2188 BundleError::InvalidExtrinsicRoot
2189 );
2190 Ok(())
2191 }
2192
2193 fn check_slot_and_proof_of_time(
2194 slot_number: u64,
2195 proof_of_time: PotOutput,
2196 pre_dispatch: bool,
2197 ) -> Result<(), BundleError> {
2198 let current_block_number = frame_system::Pallet::<T>::current_block_number();
2201
2202 if pre_dispatch && let Some(future_slot) = T::BlockSlot::future_slot(current_block_number) {
2208 ensure!(slot_number <= *future_slot, BundleError::SlotInTheFuture)
2209 }
2210
2211 let produced_after_block_number =
2213 match T::BlockSlot::slot_produced_after(slot_number.into()) {
2214 Some(n) => n,
2215 None => {
2216 if current_block_number > T::BundleLongevity::get().into() {
2219 return Err(BundleError::SlotInThePast);
2220 } else {
2221 Zero::zero()
2222 }
2223 }
2224 };
2225 let produced_after_block_hash = if produced_after_block_number == current_block_number {
2226 frame_system::Pallet::<T>::parent_hash()
2228 } else {
2229 frame_system::Pallet::<T>::block_hash(produced_after_block_number)
2230 };
2231 if let Some(last_eligible_block) =
2232 current_block_number.checked_sub(&T::BundleLongevity::get().into())
2233 {
2234 ensure!(
2235 produced_after_block_number >= last_eligible_block,
2236 BundleError::SlotInThePast
2237 );
2238 }
2239
2240 if !is_proof_of_time_valid(
2241 BlockHash::try_from(produced_after_block_hash.as_ref())
2242 .expect("Must be able to convert to block hash type"),
2243 SlotNumber::from(slot_number),
2244 WrappedPotOutput::from(proof_of_time),
2245 !pre_dispatch,
2247 ) {
2248 return Err(BundleError::InvalidProofOfTime);
2249 }
2250
2251 Ok(())
2252 }
2253
2254 fn validate_bundle(
2255 opaque_bundle: &OpaqueBundleOf<T>,
2256 domain_config: &DomainConfig<T::AccountId, BalanceOf<T>>,
2257 ) -> Result<(), BundleError> {
2258 ensure!(
2259 opaque_bundle.body_size() <= domain_config.max_bundle_size,
2260 BundleError::BundleTooLarge
2261 );
2262
2263 ensure!(
2264 opaque_bundle
2265 .estimated_weight()
2266 .all_lte(domain_config.max_bundle_weight),
2267 BundleError::BundleTooHeavy
2268 );
2269
2270 Self::check_extrinsics_root(opaque_bundle)?;
2271
2272 Ok(())
2273 }
2274
2275 fn validate_eligibility(
2276 to_sign: &[u8],
2277 signature: &OperatorSignature,
2278 proof_of_election: &ProofOfElection,
2279 domain_config: &DomainConfig<T::AccountId, BalanceOf<T>>,
2280 pre_dispatch: bool,
2281 ) -> Result<(), BundleError> {
2282 let domain_id = proof_of_election.domain_id;
2283 let operator_id = proof_of_election.operator_id;
2284 let slot_number = proof_of_election.slot_number;
2285
2286 ensure!(
2287 !FrozenDomains::<T>::get().contains(&domain_id),
2288 BundleError::DomainFrozen
2289 );
2290
2291 let operator = Operators::<T>::get(operator_id).ok_or(BundleError::InvalidOperatorId)?;
2292
2293 let operator_status = operator.status::<T>(operator_id);
2294 ensure!(
2295 *operator_status != OperatorStatus::Slashed
2296 && *operator_status != OperatorStatus::PendingSlash,
2297 BundleError::BadOperator
2298 );
2299
2300 if !operator.signing_key.verify(&to_sign, signature) {
2301 return Err(BundleError::BadBundleSignature);
2302 }
2303
2304 ensure!(
2306 slot_number
2307 > Self::operator_highest_slot_from_previous_block(operator_id, pre_dispatch),
2308 BundleError::SlotSmallerThanPreviousBlockBundle,
2309 );
2310
2311 ensure!(
2313 !OperatorBundleSlot::<T>::get(operator_id).contains(&slot_number),
2314 BundleError::EquivocatedBundle,
2315 );
2316
2317 let (operator_stake, total_domain_stake) =
2318 Self::fetch_operator_stake_info(domain_id, &operator_id)?;
2319
2320 Self::check_slot_and_proof_of_time(
2321 slot_number,
2322 proof_of_election.proof_of_time,
2323 pre_dispatch,
2324 )?;
2325
2326 sp_domains::bundle_producer_election::check_proof_of_election(
2327 &operator.signing_key,
2328 domain_config.bundle_slot_probability,
2329 proof_of_election,
2330 operator_stake.saturated_into(),
2331 total_domain_stake.saturated_into(),
2332 )?;
2333
2334 Ok(())
2335 }
2336
2337 fn validate_submit_bundle(
2338 opaque_bundle: &OpaqueBundleOf<T>,
2339 pre_dispatch: bool,
2340 ) -> Result<(), BundleError> {
2341 let domain_id = opaque_bundle.domain_id();
2342 let operator_id = opaque_bundle.operator_id();
2343 let sealed_header = &opaque_bundle.sealed_header;
2344
2345 ensure!(
2349 Self::receipt_gap(domain_id)? <= One::one(),
2350 BundleError::UnexpectedReceiptGap,
2351 );
2352
2353 let domain_config = &DomainRegistry::<T>::get(domain_id)
2354 .ok_or(BundleError::InvalidDomainId)?
2355 .domain_config;
2356
2357 Self::validate_bundle(opaque_bundle, domain_config)?;
2358
2359 Self::validate_eligibility(
2360 sealed_header.pre_hash().as_ref(),
2361 &sealed_header.signature,
2362 &sealed_header.header.proof_of_election,
2363 domain_config,
2364 pre_dispatch,
2365 )?;
2366
2367 verify_execution_receipt::<T>(domain_id, &sealed_header.header.receipt)
2368 .map_err(BundleError::Receipt)?;
2369
2370 charge_bundle_storage_fee::<T>(operator_id, opaque_bundle.size())
2371 .map_err(|_| BundleError::UnableToPayBundleStorageFee)?;
2372
2373 Ok(())
2374 }
2375
2376 fn validate_singleton_receipt(
2377 sealed_singleton_receipt: &SingletonReceiptOf<T>,
2378 pre_dispatch: bool,
2379 ) -> Result<(), BundleError> {
2380 let domain_id = sealed_singleton_receipt.domain_id();
2381 let operator_id = sealed_singleton_receipt.operator_id();
2382
2383 ensure!(
2385 Self::receipt_gap(domain_id)? > One::one(),
2386 BundleError::ExpectingReceiptGap,
2387 );
2388
2389 let domain_config = DomainRegistry::<T>::get(domain_id)
2390 .ok_or(BundleError::InvalidDomainId)?
2391 .domain_config;
2392 Self::validate_eligibility(
2393 sealed_singleton_receipt.pre_hash().as_ref(),
2394 &sealed_singleton_receipt.signature,
2395 &sealed_singleton_receipt.singleton_receipt.proof_of_election,
2396 &domain_config,
2397 pre_dispatch,
2398 )?;
2399
2400 verify_execution_receipt::<T>(
2401 domain_id,
2402 &sealed_singleton_receipt.singleton_receipt.receipt,
2403 )
2404 .map_err(BundleError::Receipt)?;
2405
2406 charge_bundle_storage_fee::<T>(operator_id, sealed_singleton_receipt.size())
2407 .map_err(|_| BundleError::UnableToPayBundleStorageFee)?;
2408
2409 Ok(())
2410 }
2411
2412 fn validate_fraud_proof(
2413 fraud_proof: &FraudProofFor<T>,
2414 ) -> Result<(DomainId, TransactionPriority), FraudProofError> {
2415 let domain_id = fraud_proof.domain_id();
2416 let bad_receipt_hash = fraud_proof.targeted_bad_receipt_hash();
2417 let bad_receipt = BlockTreeNodes::<T>::get(bad_receipt_hash)
2418 .ok_or(FraudProofError::BadReceiptNotFound)?
2419 .execution_receipt;
2420 let bad_receipt_domain_block_number = bad_receipt.domain_block_number;
2421
2422 ensure!(
2423 !bad_receipt_domain_block_number.is_zero(),
2424 FraudProofError::ChallengingGenesisReceipt
2425 );
2426
2427 ensure!(
2428 !Self::is_bad_er_pending_to_prune(domain_id, bad_receipt_domain_block_number),
2429 FraudProofError::BadReceiptAlreadyReported,
2430 );
2431
2432 ensure!(
2433 !fraud_proof.is_unexpected_domain_runtime_code_proof(),
2434 FraudProofError::UnexpectedDomainRuntimeCodeProof,
2435 );
2436
2437 ensure!(
2438 !fraud_proof.is_unexpected_mmr_proof(),
2439 FraudProofError::UnexpectedMmrProof,
2440 );
2441
2442 let maybe_state_root = match &fraud_proof.maybe_mmr_proof {
2443 Some(mmr_proof) => Some(Self::verify_mmr_proof_and_extract_state_root(
2444 mmr_proof.clone(),
2445 bad_receipt.consensus_block_number,
2446 )?),
2447 None => None,
2448 };
2449
2450 match &fraud_proof.proof {
2451 FraudProofVariant::InvalidBlockFees(InvalidBlockFeesProof { storage_proof }) => {
2452 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2453 domain_id,
2454 &bad_receipt,
2455 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2456 )?;
2457
2458 verify_invalid_block_fees_fraud_proof::<
2459 T::Block,
2460 DomainBlockNumberFor<T>,
2461 T::DomainHash,
2462 BalanceOf<T>,
2463 DomainHashingFor<T>,
2464 >(bad_receipt, storage_proof, domain_runtime_code)
2465 .map_err(|err| {
2466 log::error!("Block fees proof verification failed: {err:?}");
2467 FraudProofError::InvalidBlockFeesFraudProof
2468 })?;
2469 }
2470 FraudProofVariant::InvalidTransfers(InvalidTransfersProof { storage_proof }) => {
2471 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2472 domain_id,
2473 &bad_receipt,
2474 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2475 )?;
2476
2477 verify_invalid_transfers_fraud_proof::<
2478 T::Block,
2479 DomainBlockNumberFor<T>,
2480 T::DomainHash,
2481 BalanceOf<T>,
2482 DomainHashingFor<T>,
2483 >(bad_receipt, storage_proof, domain_runtime_code)
2484 .map_err(|err| {
2485 log::error!("Domain transfers proof verification failed: {err:?}");
2486 FraudProofError::InvalidTransfersFraudProof
2487 })?;
2488 }
2489 FraudProofVariant::InvalidDomainBlockHash(InvalidDomainBlockHashProof {
2490 digest_storage_proof,
2491 }) => {
2492 let parent_receipt =
2493 BlockTreeNodes::<T>::get(bad_receipt.parent_domain_block_receipt_hash)
2494 .ok_or(FraudProofError::ParentReceiptNotFound)?
2495 .execution_receipt;
2496 verify_invalid_domain_block_hash_fraud_proof::<
2497 T::Block,
2498 BalanceOf<T>,
2499 T::DomainHeader,
2500 >(
2501 bad_receipt,
2502 digest_storage_proof.clone(),
2503 parent_receipt.domain_block_hash,
2504 )
2505 .map_err(|err| {
2506 log::error!("Invalid Domain block hash proof verification failed: {err:?}");
2507 FraudProofError::InvalidDomainBlockHashFraudProof
2508 })?;
2509 }
2510 FraudProofVariant::InvalidExtrinsicsRoot(proof) => {
2511 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2512 domain_id,
2513 &bad_receipt,
2514 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2515 )?;
2516 let runtime_id =
2517 Self::runtime_id(domain_id).ok_or(FraudProofError::RuntimeNotFound)?;
2518 let state_root = maybe_state_root.ok_or(FraudProofError::MissingMmrProof)?;
2519
2520 verify_invalid_domain_extrinsics_root_fraud_proof::<
2521 T::Block,
2522 BalanceOf<T>,
2523 T::DomainHeader,
2524 T::Hashing,
2525 T::FraudProofStorageKeyProvider,
2526 >(
2527 bad_receipt,
2528 proof,
2529 domain_id,
2530 runtime_id,
2531 state_root,
2532 domain_runtime_code,
2533 )
2534 .map_err(|err| {
2535 log::error!("Invalid Domain extrinsic root proof verification failed: {err:?}");
2536 FraudProofError::InvalidExtrinsicRootFraudProof
2537 })?;
2538 }
2539 FraudProofVariant::InvalidStateTransition(proof) => {
2540 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2541 domain_id,
2542 &bad_receipt,
2543 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2544 )?;
2545 let bad_receipt_parent =
2546 BlockTreeNodes::<T>::get(bad_receipt.parent_domain_block_receipt_hash)
2547 .ok_or(FraudProofError::ParentReceiptNotFound)?
2548 .execution_receipt;
2549
2550 verify_invalid_state_transition_fraud_proof::<
2551 T::Block,
2552 T::DomainHeader,
2553 BalanceOf<T>,
2554 >(bad_receipt, bad_receipt_parent, proof, domain_runtime_code)
2555 .map_err(|err| {
2556 log::error!("Invalid State transition proof verification failed: {err:?}");
2557 FraudProofError::InvalidStateTransitionFraudProof
2558 })?;
2559 }
2560 FraudProofVariant::InvalidBundles(proof) => {
2561 let state_root = maybe_state_root.ok_or(FraudProofError::MissingMmrProof)?;
2562 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2563 domain_id,
2564 &bad_receipt,
2565 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2566 )?;
2567
2568 let bad_receipt_parent =
2569 BlockTreeNodes::<T>::get(bad_receipt.parent_domain_block_receipt_hash)
2570 .ok_or(FraudProofError::ParentReceiptNotFound)?
2571 .execution_receipt;
2572
2573 verify_invalid_bundles_fraud_proof::<
2574 T::Block,
2575 T::DomainHeader,
2576 T::MmrHash,
2577 BalanceOf<T>,
2578 T::FraudProofStorageKeyProvider,
2579 T::MmrProofVerifier,
2580 >(
2581 bad_receipt,
2582 bad_receipt_parent,
2583 proof,
2584 domain_id,
2585 state_root,
2586 domain_runtime_code,
2587 )
2588 .map_err(|err| {
2589 log::error!("Invalid Bundle proof verification failed: {err:?}");
2590 FraudProofError::InvalidBundleFraudProof
2591 })?;
2592 }
2593 FraudProofVariant::ValidBundle(proof) => {
2594 let state_root = maybe_state_root.ok_or(FraudProofError::MissingMmrProof)?;
2595 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2596 domain_id,
2597 &bad_receipt,
2598 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2599 )?;
2600
2601 verify_valid_bundle_fraud_proof::<
2602 T::Block,
2603 T::DomainHeader,
2604 BalanceOf<T>,
2605 T::FraudProofStorageKeyProvider,
2606 >(
2607 bad_receipt,
2608 proof,
2609 domain_id,
2610 state_root,
2611 domain_runtime_code,
2612 )
2613 .map_err(|err| {
2614 log::error!("Valid bundle proof verification failed: {err:?}");
2615 FraudProofError::BadValidBundleFraudProof
2616 })?
2617 }
2618 #[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
2619 FraudProofVariant::Dummy => {
2620 let _ = Self::get_domain_runtime_code_for_receipt(
2627 domain_id,
2628 &bad_receipt,
2629 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2630 )?;
2631 }
2632 }
2633
2634 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 let tag = fraud_proof.domain_id();
2645
2646 Ok((tag, priority))
2647 }
2648
2649 fn fetch_operator_stake_info(
2654 domain_id: DomainId,
2655 operator_id: &OperatorId,
2656 ) -> Result<(BalanceOf<T>, BalanceOf<T>), BundleError> {
2657 if let Some(pending_election_params) = LastEpochStakingDistribution::<T>::get(domain_id)
2658 && let Some(operator_stake) = pending_election_params.operators.get(operator_id)
2659 {
2660 return Ok((*operator_stake, pending_election_params.total_domain_stake));
2661 }
2662 let domain_stake_summary =
2663 DomainStakingSummary::<T>::get(domain_id).ok_or(BundleError::InvalidDomainId)?;
2664 let operator_stake = domain_stake_summary
2665 .current_operators
2666 .get(operator_id)
2667 .ok_or(BundleError::BadOperator)?;
2668 Ok((*operator_stake, domain_stake_summary.current_total_stake))
2669 }
2670
2671 fn initial_tx_range() -> U256 {
2673 U256::MAX / T::InitialDomainTxRange::get()
2674 }
2675
2676 pub fn head_receipt_number(domain_id: DomainId) -> DomainBlockNumberFor<T> {
2678 HeadReceiptNumber::<T>::get(domain_id)
2679 }
2680
2681 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 None
2700 }
2701 }
2702
2703 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 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 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 let mut to_check =
2742 Self::latest_confirmed_domain_block_number(domain_id).saturating_add(One::one());
2743
2744 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 pub fn extrinsics_shuffling_seed() -> T::Hash {
2768 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 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 pub fn timestamp() -> Moment {
2785 BlockInherentExtrinsicData::<T>::get()
2787 .map(|data| data.timestamp)
2788 .unwrap_or_else(|| Self::timestamp_value())
2789 }
2790
2791 fn timestamp_value() -> Moment {
2794 T::BlockTimestamp::now()
2797 .try_into()
2798 .map_err(|_| ())
2799 .expect("Moment is the same type in both pallets; qed")
2800 }
2801
2802 pub fn consensus_transaction_byte_fee() -> Balance {
2806 BlockInherentExtrinsicData::<T>::get()
2808 .map(|data| data.consensus_transaction_byte_fee)
2809 .unwrap_or_else(|| Self::consensus_transaction_byte_fee_value())
2810 }
2811
2812 fn consensus_transaction_byte_fee_value() -> Balance {
2815 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 if receipt_number.is_zero() {
2846 return false;
2847 }
2848
2849 let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
2850
2851 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 if latest_submitted_er.is_zero() {
2861 return false;
2862 }
2863
2864 let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
2865
2866 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 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 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 pub fn reward_domain_operators(
2930 domain_id: DomainId,
2931 source: OperatorRewardSource<BlockNumberFor<T>>,
2932 rewards: BalanceOf<T>,
2933 ) {
2934 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 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 *OperatorBundleSlot::<T>::get(operator_id)
2965 .last()
2966 .unwrap_or(&OperatorHighestSlot::<T>::get(operator_id))
2967 }
2968 }
2969
2970 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 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 (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 (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 if expected_block_number != leaf_data.block_number() {
3040 return Err(FraudProofError::UnexpectedMmrProof);
3041 }
3042
3043 Ok(leaf_data.state_root())
3044 }
3045
3046 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 let last_block_at =
3053 ExecutionInbox::<T>::iter_key_prefix((domain_id, last_domain_block_number))
3054 .next()
3055 .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 pub fn is_domain_registered(domain_id: DomainId) -> bool {
3070 DomainStakingSummary::<T>::contains_key(domain_id)
3071 }
3072
3073 pub fn domain_sudo_call(domain_id: DomainId) -> Option<Vec<u8>> {
3075 DomainSudoCalls::<T>::get(domain_id).maybe_call
3076 }
3077
3078 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 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 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 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 pub fn submit_bundle_unsigned(opaque_bundle: OpaqueBundleOf<T>) {
3129 let slot = opaque_bundle.sealed_header.slot_number();
3130 let extrinsics_count = opaque_bundle.extrinsics.len();
3131
3132 let call = Call::submit_bundle { opaque_bundle };
3133 let ext = T::create_unsigned(call.into());
3134
3135 match SubmitTransaction::<T, Call<T>>::submit_transaction(ext) {
3136 Ok(()) => {
3137 log::info!("Submitted bundle from slot {slot}, extrinsics: {extrinsics_count}",);
3138 }
3139 Err(()) => {
3140 log::error!("Error submitting bundle");
3141 }
3142 }
3143 }
3144
3145 pub fn submit_receipt_unsigned(singleton_receipt: SingletonReceiptOf<T>) {
3147 let slot = singleton_receipt.slot_number();
3148 let domain_block_number = singleton_receipt.receipt().domain_block_number;
3149
3150 let call = Call::submit_receipt { singleton_receipt };
3151 let ext = T::create_unsigned(call.into());
3152 match SubmitTransaction::<T, Call<T>>::submit_transaction(ext) {
3153 Ok(()) => {
3154 log::info!(
3155 "Submitted singleton receipt from slot {slot}, domain_block_number: {domain_block_number:?}",
3156 );
3157 }
3158 Err(()) => {
3159 log::error!("Error submitting singleton receipt");
3160 }
3161 }
3162 }
3163
3164 pub fn submit_fraud_proof_unsigned(fraud_proof: FraudProofFor<T>) {
3166 let call = Call::submit_fraud_proof {
3167 fraud_proof: Box::new(fraud_proof),
3168 };
3169
3170 let ext = T::create_unsigned(call.into());
3171 match SubmitTransaction::<T, Call<T>>::submit_transaction(ext) {
3172 Ok(()) => {
3173 log::info!("Submitted fraud proof");
3174 }
3175 Err(()) => {
3176 log::error!("Error submitting fraud proof");
3177 }
3178 }
3179 }
3180}
3181
3182pub fn calculate_tx_range(
3184 cur_tx_range: U256,
3185 actual_bundle_count: u64,
3186 expected_bundle_count: u64,
3187) -> U256 {
3188 if actual_bundle_count == 0 || expected_bundle_count == 0 {
3189 return cur_tx_range;
3190 }
3191
3192 let Some(new_tx_range) = U256::from(actual_bundle_count)
3193 .saturating_mul(&cur_tx_range)
3194 .checked_div(&U256::from(expected_bundle_count))
3195 else {
3196 return cur_tx_range;
3197 };
3198
3199 let upper_bound = cur_tx_range.saturating_mul(&U256::from(4_u64));
3200 let Some(lower_bound) = cur_tx_range.checked_div(&U256::from(4_u64)) else {
3201 return cur_tx_range;
3202 };
3203 new_tx_range.clamp(lower_bound, upper_bound)
3204}