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;
17mod nominator_position;
18pub mod runtime_registry;
19pub mod staking;
20mod staking_epoch;
21pub mod weights;
22
23extern crate alloc;
24
25use crate::block_tree::{Error as BlockTreeError, verify_execution_receipt};
26use crate::bundle_storage_fund::{charge_bundle_storage_fee, storage_fund_account};
27use crate::domain_registry::{DomainConfig, Error as DomainRegistryError};
28use crate::runtime_registry::into_complete_raw_genesis;
29use crate::staking::OperatorStatus;
30#[cfg(feature = "runtime-benchmarks")]
31pub use crate::staking::do_register_operator;
32use crate::staking_epoch::EpochTransitionResult;
33pub use crate::weights::WeightInfo;
34#[cfg(not(feature = "std"))]
35use alloc::boxed::Box;
36use alloc::collections::btree_map::BTreeMap;
37#[cfg(not(feature = "std"))]
38use alloc::vec::Vec;
39use domain_runtime_primitives::EthereumAccountId;
40use frame_support::dispatch::DispatchResult;
41use frame_support::ensure;
42use frame_support::pallet_prelude::{RuntimeDebug, StorageVersion};
43use frame_support::traits::fungible::{Inspect, InspectHold};
44use frame_support::traits::tokens::{Fortitude, Preservation};
45use frame_support::traits::{EnsureOrigin, Get, Randomness as RandomnessT, Time};
46use frame_support::weights::Weight;
47use frame_system::offchain::SubmitTransaction;
48use frame_system::pallet_prelude::*;
49pub use pallet::*;
50use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
51use scale_info::TypeInfo;
52use sp_consensus_subspace::WrappedPotOutput;
53use sp_consensus_subspace::consensus::is_proof_of_time_valid;
54use sp_core::H256;
55use sp_domains::bundle::{Bundle, BundleVersion, OpaqueBundle};
56use sp_domains::bundle_producer_election::BundleProducerElectionParams;
57use sp_domains::execution_receipt::{
58 ExecutionReceipt, ExecutionReceiptRef, ExecutionReceiptVersion, SealedSingletonReceipt,
59};
60use sp_domains::{
61 BundleAndExecutionReceiptVersion, DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT, DomainBundleLimit,
62 DomainId, DomainInstanceData, EMPTY_EXTRINSIC_ROOT, OperatorId, OperatorPublicKey,
63 OperatorSignature, ProofOfElection, RuntimeId,
64};
65use sp_domains_fraud_proof::fraud_proof::{
66 DomainRuntimeCodeAt, FraudProof, FraudProofVariant, InvalidBlockFeesProof,
67 InvalidDomainBlockHashProof, InvalidTransfersProof,
68};
69use sp_domains_fraud_proof::storage_proof::{self, BasicStorageProof, DomainRuntimeCodeProof};
70use sp_domains_fraud_proof::verification::{
71 verify_invalid_block_fees_fraud_proof, verify_invalid_bundles_fraud_proof,
72 verify_invalid_domain_block_hash_fraud_proof,
73 verify_invalid_domain_extrinsics_root_fraud_proof, verify_invalid_state_transition_fraud_proof,
74 verify_invalid_transfers_fraud_proof, verify_valid_bundle_fraud_proof,
75};
76use sp_runtime::traits::{BlockNumberProvider, CheckedSub, Hash, Header, One, Zero};
77use sp_runtime::transaction_validity::TransactionPriority;
78use sp_runtime::{RuntimeAppPublic, SaturatedConversion, Saturating};
79use sp_subspace_mmr::{ConsensusChainMmrLeafProof, MmrProofVerifier};
80pub use staking::OperatorConfig;
81use subspace_core_primitives::pot::PotOutput;
82use subspace_core_primitives::{BlockHash, SlotNumber, U256};
83use subspace_runtime_primitives::{Balance, CreateUnsigned, Moment, StorageFee};
84
85pub const MAX_NOMINATORS_TO_SLASH: u32 = 10;
87
88pub(crate) type BalanceOf<T> = <T as Config>::Balance;
89
90pub(crate) type FungibleHoldId<T> =
91 <<T as Config>::Currency as InspectHold<<T as frame_system::Config>::AccountId>>::Reason;
92
93pub(crate) type NominatorId<T> = <T as frame_system::Config>::AccountId;
94
95pub trait HoldIdentifier<T: Config> {
96 fn staking_staked() -> FungibleHoldId<T>;
97 fn domain_instantiation_id() -> FungibleHoldId<T>;
98 fn storage_fund_withdrawal() -> FungibleHoldId<T>;
99}
100
101pub trait BlockSlot<T: frame_system::Config> {
102 fn future_slot(block_number: BlockNumberFor<T>) -> Option<sp_consensus_slots::Slot>;
104
105 fn slot_produced_after(to_check: sp_consensus_slots::Slot) -> Option<BlockNumberFor<T>>;
107}
108
109pub type ExecutionReceiptOf<T> = ExecutionReceipt<
110 BlockNumberFor<T>,
111 <T as frame_system::Config>::Hash,
112 DomainBlockNumberFor<T>,
113 <T as Config>::DomainHash,
114 BalanceOf<T>,
115>;
116
117pub type ExecutionReceiptRefOf<'a, T> = ExecutionReceiptRef<
118 'a,
119 BlockNumberFor<T>,
120 <T as frame_system::Config>::Hash,
121 DomainBlockNumberFor<T>,
122 <T as Config>::DomainHash,
123 BalanceOf<T>,
124>;
125
126pub type OpaqueBundleOf<T> = OpaqueBundle<
127 BlockNumberFor<T>,
128 <T as frame_system::Config>::Hash,
129 <T as Config>::DomainHeader,
130 BalanceOf<T>,
131>;
132
133pub type SingletonReceiptOf<T> = SealedSingletonReceipt<
134 BlockNumberFor<T>,
135 <T as frame_system::Config>::Hash,
136 <T as Config>::DomainHeader,
137 BalanceOf<T>,
138>;
139
140pub type FraudProofFor<T> = FraudProof<
141 BlockNumberFor<T>,
142 <T as frame_system::Config>::Hash,
143 <T as Config>::DomainHeader,
144 <T as Config>::MmrHash,
145>;
146
147#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
149pub(crate) struct ElectionVerificationParams<Balance> {
150 operators: BTreeMap<OperatorId, Balance>,
151 total_domain_stake: Balance,
152}
153
154pub type DomainBlockNumberFor<T> = <<T as Config>::DomainHeader as Header>::Number;
155pub type DomainHashingFor<T> = <<T as Config>::DomainHeader as Header>::Hashing;
156pub type ReceiptHashFor<T> = <<T as Config>::DomainHeader as Header>::Hash;
157
158pub type BlockTreeNodeFor<T> = crate::block_tree::BlockTreeNode<
159 BlockNumberFor<T>,
160 <T as frame_system::Config>::Hash,
161 DomainBlockNumberFor<T>,
162 <T as Config>::DomainHash,
163 BalanceOf<T>,
164>;
165
166#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
168pub enum RawOrigin {
169 ValidatedUnsigned,
170}
171
172pub struct EnsureDomainOrigin;
174impl<O: Into<Result<RawOrigin, O>> + From<RawOrigin>> EnsureOrigin<O> for EnsureDomainOrigin {
175 type Success = ();
176
177 fn try_origin(o: O) -> Result<Self::Success, O> {
178 o.into().map(|o| match o {
179 RawOrigin::ValidatedUnsigned => (),
180 })
181 }
182
183 #[cfg(feature = "runtime-benchmarks")]
184 fn try_successful_origin() -> Result<O, ()> {
185 Ok(O::from(RawOrigin::ValidatedUnsigned))
186 }
187}
188
189const STORAGE_VERSION: StorageVersion = StorageVersion::new(6);
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 use crate::block_tree::{
208 AcceptedReceiptType, BlockTreeNode, Error as BlockTreeError, ReceiptType,
209 execution_receipt_type, process_execution_receipt, prune_receipt,
210 };
211 use crate::bundle_storage_fund::Error as BundleStorageFundError;
212 #[cfg(not(feature = "runtime-benchmarks"))]
213 use crate::bundle_storage_fund::refund_storage_fee;
214 use crate::domain_registry::{
215 DomainConfigParams, DomainObject, Error as DomainRegistryError, do_instantiate_domain,
216 do_update_domain_allow_list,
217 };
218 use crate::runtime_registry::{
219 DomainRuntimeUpgradeEntry, Error as RuntimeRegistryError, ScheduledRuntimeUpgrade,
220 do_register_runtime, do_schedule_runtime_upgrade, do_upgrade_runtimes,
221 register_runtime_at_genesis,
222 };
223 #[cfg(not(feature = "runtime-benchmarks"))]
224 use crate::staking::do_reward_operators;
225 use crate::staking::{
226 Deposit, DomainEpoch, Error as StakingError, Operator, OperatorConfig, SharePrice,
227 StakingSummary, Withdrawal, do_deregister_operator, do_mark_invalid_bundle_authors,
228 do_mark_operators_as_slashed, do_nominate_operator, do_register_operator, do_unlock_funds,
229 do_unlock_nominator, do_unmark_invalid_bundle_authors, do_withdraw_stake,
230 };
231 #[cfg(not(feature = "runtime-benchmarks"))]
232 use crate::staking_epoch::do_slash_operator;
233 use crate::staking_epoch::{Error as StakingEpochError, do_finalize_domain_current_epoch};
234 use crate::storage_proof::InherentExtrinsicData;
235 use crate::weights::WeightInfo;
236 use crate::{
237 BalanceOf, BlockSlot, BlockTreeNodeFor, DomainBlockNumberFor, ElectionVerificationParams,
238 ExecutionReceiptOf, FraudProofFor, HoldIdentifier, MAX_BUNDLE_PER_BLOCK, NominatorId,
239 OpaqueBundleOf, RawOrigin, ReceiptHashFor, STORAGE_VERSION, SingletonReceiptOf,
240 StateRootOf,
241 };
242 #[cfg(not(feature = "std"))]
243 use alloc::string::String;
244 #[cfg(not(feature = "std"))]
245 use alloc::vec;
246 #[cfg(not(feature = "std"))]
247 use alloc::vec::Vec;
248 use domain_runtime_primitives::{EVMChainId, EthereumAccountId};
249 use frame_support::pallet_prelude::*;
250 use frame_support::traits::fungible::{Inspect, InspectHold, Mutate, MutateHold};
251 use frame_support::traits::tokens::Preservation;
252 use frame_support::traits::{Randomness as RandomnessT, Time};
253 use frame_support::weights::Weight;
254 use frame_support::{Identity, PalletError};
255 use frame_system::pallet_prelude::*;
256 use parity_scale_codec::FullCodec;
257 use sp_core::H256;
258 use sp_domains::bundle::BundleDigest;
259 use sp_domains::bundle_producer_election::ProofOfElectionError;
260 use sp_domains::{
261 BundleAndExecutionReceiptVersion, DomainBundleSubmitted, DomainId, DomainOwner,
262 DomainSudoCall, DomainsTransfersTracker, EpochIndex,
263 EvmDomainContractCreationAllowedByCall, GenesisDomain, OnChainRewards,
264 OnDomainInstantiated, OperatorAllowList, OperatorId, OperatorRewardSource, RuntimeId,
265 RuntimeObject, RuntimeType,
266 };
267 use sp_domains_fraud_proof::fraud_proof_runtime_interface::domain_runtime_call;
268 use sp_domains_fraud_proof::storage_proof::{self, FraudProofStorageKeyProvider};
269 use sp_domains_fraud_proof::{InvalidTransactionCode, StatelessDomainRuntimeCall};
270 use sp_runtime::Saturating;
271 use sp_runtime::traits::{
272 AtLeast32BitUnsigned, BlockNumberProvider, CheckEqual, CheckedAdd, Header as HeaderT,
273 MaybeDisplay, One, SimpleBitOps, Zero,
274 };
275 use sp_std::boxed::Box;
276 use sp_std::collections::btree_map::BTreeMap;
277 use sp_std::collections::btree_set::BTreeSet;
278 use sp_std::fmt::Debug;
279 use sp_subspace_mmr::MmrProofVerifier;
280 use subspace_core_primitives::{Randomness, U256};
281 use subspace_runtime_primitives::StorageFee;
282
283 #[pallet::config]
284 pub trait Config: frame_system::Config<Hash: Into<H256> + From<H256>> {
285 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
286
287 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 #[pallet::constant]
467 type CurrentBundleAndExecutionReceiptVersion: Get<BundleAndExecutionReceiptVersion>;
468 }
469
470 #[pallet::pallet]
471 #[pallet::without_storage_info]
472 #[pallet::storage_version(STORAGE_VERSION)]
473 pub struct Pallet<T>(_);
474
475 #[pallet::storage]
477 pub type SuccessfulBundles<T> = StorageMap<_, Identity, DomainId, Vec<H256>, ValueQuery>;
478
479 #[pallet::storage]
481 pub(super) type NextRuntimeId<T> = StorageValue<_, RuntimeId, ValueQuery>;
482
483 #[pallet::storage]
485 pub type EvmChainIds<T: Config> = StorageMap<_, Identity, EVMChainId, DomainId, OptionQuery>;
486
487 #[pallet::storage]
488 pub type RuntimeRegistry<T: Config> =
489 StorageMap<_, Identity, RuntimeId, RuntimeObject<BlockNumberFor<T>, T::Hash>, OptionQuery>;
490
491 #[pallet::storage]
492 pub(super) type ScheduledRuntimeUpgrades<T: Config> = StorageDoubleMap<
493 _,
494 Identity,
495 BlockNumberFor<T>,
496 Identity,
497 RuntimeId,
498 ScheduledRuntimeUpgrade<T::Hash>,
499 OptionQuery,
500 >;
501
502 #[pallet::storage]
503 pub(super) type NextOperatorId<T> = StorageValue<_, OperatorId, ValueQuery>;
504
505 #[pallet::storage]
506 pub(super) type OperatorIdOwner<T: Config> =
507 StorageMap<_, Identity, OperatorId, T::AccountId, OptionQuery>;
508
509 #[pallet::storage]
510 #[pallet::getter(fn domain_staking_summary)]
511 pub(super) type DomainStakingSummary<T: Config> =
512 StorageMap<_, Identity, DomainId, StakingSummary<OperatorId, BalanceOf<T>>, OptionQuery>;
513
514 #[pallet::storage]
516 pub(super) type Operators<T: Config> = StorageMap<
517 _,
518 Identity,
519 OperatorId,
520 Operator<BalanceOf<T>, T::Share, DomainBlockNumberFor<T>, ReceiptHashFor<T>>,
521 OptionQuery,
522 >;
523
524 #[pallet::storage]
526 pub(super) type OperatorHighestSlot<T: Config> =
527 StorageMap<_, Identity, OperatorId, u64, ValueQuery>;
528
529 #[pallet::storage]
532 pub(super) type OperatorBundleSlot<T: Config> =
533 StorageMap<_, Identity, OperatorId, BTreeSet<u64>, ValueQuery>;
534
535 #[pallet::storage]
538 pub type OperatorEpochSharePrice<T: Config> =
539 StorageDoubleMap<_, Identity, OperatorId, Identity, DomainEpoch, SharePrice, OptionQuery>;
540
541 #[pallet::storage]
543 pub(super) type Deposits<T: Config> = StorageDoubleMap<
544 _,
545 Identity,
546 OperatorId,
547 Identity,
548 NominatorId<T>,
549 Deposit<T::Share, BalanceOf<T>>,
550 OptionQuery,
551 >;
552
553 #[pallet::storage]
555 pub(super) type Withdrawals<T: Config> = StorageDoubleMap<
556 _,
557 Identity,
558 OperatorId,
559 Identity,
560 NominatorId<T>,
561 Withdrawal<BalanceOf<T>, T::Share, DomainBlockNumberFor<T>>,
562 OptionQuery,
563 >;
564
565 #[pallet::storage]
567 pub(super) type DepositOnHold<T: Config> =
568 StorageMap<_, Identity, (OperatorId, NominatorId<T>), BalanceOf<T>, ValueQuery>;
569
570 #[pallet::storage]
574 pub(super) type PendingSlashes<T: Config> =
575 StorageMap<_, Identity, DomainId, BTreeSet<OperatorId>, OptionQuery>;
576
577 #[pallet::storage]
580 pub(super) type PendingStakingOperationCount<T: Config> =
581 StorageMap<_, Identity, DomainId, u32, ValueQuery>;
582
583 #[pallet::storage]
585 #[pallet::getter(fn next_domain_id)]
586 pub(super) type NextDomainId<T> = StorageValue<_, DomainId, ValueQuery>;
587
588 #[pallet::storage]
590 pub(super) type DomainRegistry<T: Config> = StorageMap<
591 _,
592 Identity,
593 DomainId,
594 DomainObject<BlockNumberFor<T>, ReceiptHashFor<T>, T::AccountId, BalanceOf<T>>,
595 OptionQuery,
596 >;
597
598 #[pallet::storage]
601 pub(super) type BlockTree<T: Config> = StorageDoubleMap<
602 _,
603 Identity,
604 DomainId,
605 Identity,
606 DomainBlockNumberFor<T>,
607 ReceiptHashFor<T>,
608 OptionQuery,
609 >;
610
611 #[pallet::storage]
613 pub(super) type BlockTreeNodes<T: Config> =
614 StorageMap<_, Identity, ReceiptHashFor<T>, BlockTreeNodeFor<T>, OptionQuery>;
615
616 #[pallet::storage]
618 pub(super) type HeadReceiptNumber<T: Config> =
619 StorageMap<_, Identity, DomainId, DomainBlockNumberFor<T>, ValueQuery>;
620
621 #[pallet::storage]
625 pub(super) type NewAddedHeadReceipt<T: Config> =
626 StorageMap<_, Identity, DomainId, T::DomainHash, OptionQuery>;
627
628 #[pallet::storage]
634 #[pallet::getter(fn consensus_block_info)]
635 pub type ConsensusBlockHash<T: Config> =
636 StorageDoubleMap<_, Identity, DomainId, Identity, BlockNumberFor<T>, T::Hash, OptionQuery>;
637
638 #[pallet::storage]
644 pub type ExecutionInbox<T: Config> = StorageNMap<
645 _,
646 (
647 NMapKey<Identity, DomainId>,
648 NMapKey<Identity, DomainBlockNumberFor<T>>,
649 NMapKey<Identity, BlockNumberFor<T>>,
650 ),
651 Vec<BundleDigest<T::DomainHash>>,
652 ValueQuery,
653 >;
654
655 #[pallet::storage]
659 pub(super) type InboxedBundleAuthor<T: Config> =
660 StorageMap<_, Identity, T::DomainHash, OperatorId, OptionQuery>;
661
662 #[pallet::storage]
671 pub(super) type HeadDomainNumber<T: Config> =
672 StorageMap<_, Identity, DomainId, DomainBlockNumberFor<T>, ValueQuery>;
673
674 #[pallet::storage]
680 pub(super) type LastEpochStakingDistribution<T: Config> =
681 StorageMap<_, Identity, DomainId, ElectionVerificationParams<BalanceOf<T>>, OptionQuery>;
682
683 #[pallet::storage]
685 #[pallet::getter(fn latest_confirmed_domain_execution_receipt)]
686 pub type LatestConfirmedDomainExecutionReceipt<T: Config> =
687 StorageMap<_, Identity, DomainId, ExecutionReceiptOf<T>, OptionQuery>;
688
689 #[pallet::storage]
691 #[pallet::getter(fn domain_genesis_block_execution_receipt)]
692 pub type DomainGenesisBlockExecutionReceipt<T: Config> =
693 StorageMap<_, Identity, DomainId, ExecutionReceiptOf<T>, OptionQuery>;
694
695 #[pallet::storage]
702 #[pallet::getter(fn latest_submitted_er)]
703 pub(super) type LatestSubmittedER<T: Config> =
704 StorageMap<_, Identity, (DomainId, OperatorId), DomainBlockNumberFor<T>, ValueQuery>;
705
706 #[pallet::storage]
708 pub(super) type PermissionedActionAllowedBy<T: Config> =
709 StorageValue<_, sp_domains::PermissionedActionAllowedBy<T::AccountId>, OptionQuery>;
710
711 #[pallet::storage]
714 pub(super) type AccumulatedTreasuryFunds<T> = StorageValue<_, BalanceOf<T>, ValueQuery>;
715
716 #[pallet::storage]
718 pub(super) type DomainRuntimeUpgradeRecords<T: Config> = StorageMap<
719 _,
720 Identity,
721 RuntimeId,
722 BTreeMap<BlockNumberFor<T>, DomainRuntimeUpgradeEntry<T::Hash>>,
723 ValueQuery,
724 >;
725
726 #[pallet::storage]
729 pub type DomainRuntimeUpgrades<T> = StorageValue<_, Vec<RuntimeId>, ValueQuery>;
730
731 #[pallet::storage]
736 pub type DomainSudoCalls<T: Config> =
737 StorageMap<_, Identity, DomainId, DomainSudoCall, ValueQuery>;
738
739 #[pallet::storage]
743 pub type FrozenDomains<T> = StorageValue<_, BTreeSet<DomainId>, ValueQuery>;
744
745 #[pallet::storage]
750 pub type EvmDomainContractCreationAllowedByCalls<T: Config> =
751 StorageMap<_, Identity, DomainId, EvmDomainContractCreationAllowedByCall, ValueQuery>;
752
753 #[pallet::storage]
756 pub type DomainChainRewards<T: Config> =
757 StorageMap<_, Identity, DomainId, BalanceOf<T>, ValueQuery>;
758
759 #[pallet::storage]
762 pub type InvalidBundleAuthors<T: Config> =
763 StorageMap<_, Identity, DomainId, BTreeSet<OperatorId>, ValueQuery>;
764
765 #[pallet::storage]
768 pub type DeregisteredOperators<T: Config> =
769 StorageMap<_, Identity, DomainId, BTreeSet<OperatorId>, ValueQuery>;
770
771 #[pallet::storage]
779 pub type PreviousBundleAndExecutionReceiptVersions<T> =
780 StorageValue<_, BTreeMap<BlockNumberFor<T>, BundleAndExecutionReceiptVersion>, ValueQuery>;
781
782 #[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq)]
783 pub enum BundleError {
784 InvalidOperatorId,
786 BadBundleSignature,
788 BadVrfSignature,
790 InvalidDomainId,
792 BadOperator,
794 ThresholdUnsatisfied,
796 InvalidThreshold,
798 Receipt(BlockTreeError),
800 BundleTooLarge,
802 InvalidExtrinsicRoot,
804 InvalidProofOfTime,
806 SlotInTheFuture,
808 SlotInThePast,
810 BundleTooHeavy,
812 SlotSmallerThanPreviousBlockBundle,
815 EquivocatedBundle,
817 DomainFrozen,
819 UnableToPayBundleStorageFee,
821 UnexpectedReceiptGap,
823 ExpectingReceiptGap,
825 FailedToGetMissedUpgradeCount,
827 BundleVersionMismatch,
829 ExecutionVersionMismatch,
831 ExecutionVersionMissing,
833 }
834
835 #[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq)]
836 pub enum FraudProofError {
837 BadReceiptNotFound,
840 ChallengingGenesisReceipt,
842 DescendantsOfFraudulentERNotPruned,
844 InvalidBlockFeesFraudProof,
846 InvalidTransfersFraudProof,
848 InvalidDomainBlockHashFraudProof,
850 InvalidExtrinsicRootFraudProof,
852 InvalidStateTransitionFraudProof,
854 ParentReceiptNotFound,
856 InvalidBundleFraudProof,
858 BadValidBundleFraudProof,
860 MissingOperator,
862 UnexpectedFraudProof,
864 BadReceiptAlreadyReported,
866 BadMmrProof,
868 UnexpectedMmrProof,
870 MissingMmrProof,
872 RuntimeNotFound,
874 DomainRuntimeCodeProofNotFound,
876 UnexpectedDomainRuntimeCodeProof,
878 StorageProof(storage_proof::VerificationError),
880 }
881
882 impl From<BundleError> for TransactionValidity {
883 fn from(e: BundleError) -> Self {
884 if BundleError::UnableToPayBundleStorageFee == e {
885 InvalidTransactionCode::BundleStorageFeePayment.into()
886 } else if let BundleError::Receipt(_) = e {
887 InvalidTransactionCode::ExecutionReceipt.into()
888 } else {
889 InvalidTransactionCode::Bundle.into()
890 }
891 }
892 }
893
894 impl From<storage_proof::VerificationError> for FraudProofError {
895 fn from(err: storage_proof::VerificationError) -> Self {
896 FraudProofError::StorageProof(err)
897 }
898 }
899
900 impl<T> From<FraudProofError> for Error<T> {
901 fn from(err: FraudProofError) -> Self {
902 Error::FraudProof(err)
903 }
904 }
905
906 impl<T> From<RuntimeRegistryError> for Error<T> {
907 fn from(err: RuntimeRegistryError) -> Self {
908 Error::RuntimeRegistry(err)
909 }
910 }
911
912 impl<T> From<StakingError> for Error<T> {
913 fn from(err: StakingError) -> Self {
914 Error::Staking(err)
915 }
916 }
917
918 impl<T> From<StakingEpochError> for Error<T> {
919 fn from(err: StakingEpochError) -> Self {
920 Error::StakingEpoch(err)
921 }
922 }
923
924 impl<T> From<DomainRegistryError> for Error<T> {
925 fn from(err: DomainRegistryError) -> Self {
926 Error::DomainRegistry(err)
927 }
928 }
929
930 impl<T> From<BlockTreeError> for Error<T> {
931 fn from(err: BlockTreeError) -> Self {
932 Error::BlockTree(err)
933 }
934 }
935
936 impl From<ProofOfElectionError> for BundleError {
937 fn from(err: ProofOfElectionError) -> Self {
938 match err {
939 ProofOfElectionError::BadVrfProof => Self::BadVrfSignature,
940 ProofOfElectionError::ThresholdUnsatisfied => Self::ThresholdUnsatisfied,
941 ProofOfElectionError::InvalidThreshold => Self::InvalidThreshold,
942 }
943 }
944 }
945
946 impl<T> From<BundleStorageFundError> for Error<T> {
947 fn from(err: BundleStorageFundError) -> Self {
948 Error::BundleStorageFund(err)
949 }
950 }
951
952 #[pallet::error]
953 pub enum Error<T> {
954 FraudProof(FraudProofError),
956 RuntimeRegistry(RuntimeRegistryError),
958 Staking(StakingError),
960 StakingEpoch(StakingEpochError),
962 DomainRegistry(DomainRegistryError),
964 BlockTree(BlockTreeError),
966 BundleStorageFund(BundleStorageFundError),
968 PermissionedActionNotAllowed,
970 DomainSudoCallExists,
972 InvalidDomainSudoCall,
974 DomainNotFrozen,
976 NotPrivateEvmDomain,
978 NotDomainOwnerOrRoot,
980 EvmDomainContractCreationAllowedByCallExists,
982 }
983
984 #[derive(Clone, Debug, PartialEq, Encode, Decode, TypeInfo)]
986 pub enum SlashedReason<DomainBlock, ReceiptHash> {
987 InvalidBundle(DomainBlock),
989 BadExecutionReceipt(ReceiptHash),
991 }
992
993 #[pallet::event]
994 #[pallet::generate_deposit(pub (super) fn deposit_event)]
995 pub enum Event<T: Config> {
996 BundleStored {
998 domain_id: DomainId,
999 bundle_hash: H256,
1000 bundle_author: OperatorId,
1001 },
1002 DomainRuntimeCreated {
1003 runtime_id: RuntimeId,
1004 runtime_type: RuntimeType,
1005 },
1006 DomainRuntimeUpgradeScheduled {
1007 runtime_id: RuntimeId,
1008 scheduled_at: BlockNumberFor<T>,
1009 },
1010 DomainRuntimeUpgraded {
1011 runtime_id: RuntimeId,
1012 },
1013 OperatorRegistered {
1014 operator_id: OperatorId,
1015 domain_id: DomainId,
1016 },
1017 NominatedStakedUnlocked {
1018 operator_id: OperatorId,
1019 nominator_id: NominatorId<T>,
1020 unlocked_amount: BalanceOf<T>,
1021 },
1022 StorageFeeUnlocked {
1023 operator_id: OperatorId,
1024 nominator_id: NominatorId<T>,
1025 storage_fee: BalanceOf<T>,
1026 },
1027 OperatorNominated {
1028 operator_id: OperatorId,
1029 nominator_id: NominatorId<T>,
1030 amount: BalanceOf<T>,
1031 },
1032 DomainInstantiated {
1033 domain_id: DomainId,
1034 },
1035 OperatorSwitchedDomain {
1036 old_domain_id: DomainId,
1037 new_domain_id: DomainId,
1038 },
1039 OperatorDeregistered {
1040 operator_id: OperatorId,
1041 },
1042 NominatorUnlocked {
1043 operator_id: OperatorId,
1044 nominator_id: NominatorId<T>,
1045 },
1046 WithdrewStake {
1047 operator_id: OperatorId,
1048 nominator_id: NominatorId<T>,
1049 },
1050 PreferredOperator {
1051 operator_id: OperatorId,
1052 nominator_id: NominatorId<T>,
1053 },
1054 OperatorRewarded {
1055 source: OperatorRewardSource<BlockNumberFor<T>>,
1056 operator_id: OperatorId,
1057 reward: BalanceOf<T>,
1058 },
1059 OperatorTaxCollected {
1060 operator_id: OperatorId,
1061 tax: BalanceOf<T>,
1062 },
1063 DomainEpochCompleted {
1064 domain_id: DomainId,
1065 completed_epoch_index: EpochIndex,
1066 },
1067 ForceDomainEpochTransition {
1068 domain_id: DomainId,
1069 completed_epoch_index: EpochIndex,
1070 },
1071 FraudProofProcessed {
1072 domain_id: DomainId,
1073 new_head_receipt_number: Option<DomainBlockNumberFor<T>>,
1074 },
1075 DomainOperatorAllowListUpdated {
1076 domain_id: DomainId,
1077 },
1078 OperatorSlashed {
1079 operator_id: OperatorId,
1080 reason: SlashedReason<DomainBlockNumberFor<T>, ReceiptHashFor<T>>,
1081 },
1082 StorageFeeDeposited {
1083 operator_id: OperatorId,
1084 nominator_id: NominatorId<T>,
1085 amount: BalanceOf<T>,
1086 },
1087 DomainFrozen {
1088 domain_id: DomainId,
1089 },
1090 DomainUnfrozen {
1091 domain_id: DomainId,
1092 },
1093 PrunedExecutionReceipt {
1094 domain_id: DomainId,
1095 new_head_receipt_number: Option<DomainBlockNumberFor<T>>,
1096 },
1097 }
1098
1099 #[pallet::origin]
1100 pub type Origin = RawOrigin;
1101
1102 #[derive(Debug, Default, Decode, Encode, TypeInfo, PartialEq, Eq)]
1104 pub struct TxRangeState {
1105 pub tx_range: U256,
1107
1108 pub interval_blocks: u64,
1110
1111 pub interval_bundles: u64,
1113 }
1114
1115 impl TxRangeState {
1116 pub fn on_bundle(&mut self) {
1118 self.interval_bundles += 1;
1119 }
1120 }
1121
1122 #[pallet::storage]
1123 pub(super) type DomainTxRangeState<T: Config> =
1124 StorageMap<_, Identity, DomainId, TxRangeState, OptionQuery>;
1125
1126 #[pallet::call]
1127 impl<T: Config> Pallet<T> {
1128 #[pallet::call_index(0)]
1129 #[pallet::weight(Pallet::<T>::max_submit_bundle_weight())]
1130 pub fn submit_bundle(
1131 origin: OriginFor<T>,
1132 opaque_bundle: OpaqueBundleOf<T>,
1133 ) -> DispatchResultWithPostInfo {
1134 T::DomainOrigin::ensure_origin(origin)?;
1135
1136 log::trace!("Processing bundle: {opaque_bundle:?}");
1137
1138 let domain_id = opaque_bundle.domain_id();
1139 let bundle_hash = opaque_bundle.hash();
1140 let bundle_header_hash = opaque_bundle.sealed_header().pre_hash();
1141 let extrinsics_root = opaque_bundle.extrinsics_root();
1142 let operator_id = opaque_bundle.operator_id();
1143 let bundle_size = opaque_bundle.size();
1144 let slot_number = opaque_bundle.slot_number();
1145 let receipt = opaque_bundle.into_receipt();
1146 #[cfg_attr(feature = "runtime-benchmarks", allow(unused_variables))]
1147 let receipt_block_number = *receipt.domain_block_number();
1148
1149 #[cfg(not(feature = "runtime-benchmarks"))]
1150 let mut actual_weight = T::WeightInfo::submit_bundle();
1151 #[cfg(feature = "runtime-benchmarks")]
1152 let actual_weight = T::WeightInfo::submit_bundle();
1153
1154 match execution_receipt_type::<T>(domain_id, &receipt.as_execution_receipt_ref()) {
1155 ReceiptType::Rejected(rejected_receipt_type) => {
1156 return Err(Error::<T>::BlockTree(rejected_receipt_type.into()).into());
1157 }
1158 ReceiptType::Accepted(accepted_receipt_type) => {
1160 #[cfg(not(feature = "runtime-benchmarks"))]
1166 if accepted_receipt_type == AcceptedReceiptType::NewHead
1167 && let Some(BlockTreeNode {
1168 execution_receipt,
1169 operator_ids,
1170 }) = prune_receipt::<T>(domain_id, receipt_block_number)
1171 .map_err(Error::<T>::from)?
1172 {
1173 actual_weight = actual_weight.saturating_add(
1174 T::WeightInfo::handle_bad_receipt(operator_ids.len() as u32),
1175 );
1176
1177 let bad_receipt_hash = execution_receipt.hash::<DomainHashingFor<T>>();
1178 do_mark_operators_as_slashed::<T>(
1179 operator_ids.into_iter(),
1180 SlashedReason::BadExecutionReceipt(bad_receipt_hash),
1181 )
1182 .map_err(Error::<T>::from)?;
1183
1184 do_unmark_invalid_bundle_authors::<T>(domain_id, &execution_receipt)
1185 .map_err(Error::<T>::from)?;
1186 }
1187
1188 if accepted_receipt_type == AcceptedReceiptType::NewHead {
1189 do_mark_invalid_bundle_authors::<T>(domain_id, &receipt)
1192 .map_err(Error::<T>::Staking)?;
1193 }
1194
1195 #[cfg_attr(feature = "runtime-benchmarks", allow(unused_variables))]
1196 let maybe_confirmed_domain_block_info = process_execution_receipt::<T>(
1197 domain_id,
1198 operator_id,
1199 receipt,
1200 accepted_receipt_type,
1201 )
1202 .map_err(Error::<T>::from)?;
1203
1204 #[cfg(not(feature = "runtime-benchmarks"))]
1210 if let Some(confirmed_block_info) = maybe_confirmed_domain_block_info {
1211 actual_weight =
1212 actual_weight.saturating_add(T::WeightInfo::confirm_domain_block(
1213 confirmed_block_info.operator_ids.len() as u32,
1214 confirmed_block_info.invalid_bundle_authors.len() as u32,
1215 ));
1216
1217 refund_storage_fee::<T>(
1218 confirmed_block_info.total_storage_fee,
1219 confirmed_block_info.paid_bundle_storage_fees,
1220 )
1221 .map_err(Error::<T>::from)?;
1222
1223 do_reward_operators::<T>(
1224 domain_id,
1225 OperatorRewardSource::Bundle {
1226 at_block_number: confirmed_block_info.consensus_block_number,
1227 },
1228 confirmed_block_info.operator_ids.into_iter(),
1229 confirmed_block_info.rewards,
1230 )
1231 .map_err(Error::<T>::from)?;
1232
1233 do_mark_operators_as_slashed::<T>(
1234 confirmed_block_info.invalid_bundle_authors.into_iter(),
1235 SlashedReason::InvalidBundle(confirmed_block_info.domain_block_number),
1236 )
1237 .map_err(Error::<T>::from)?;
1238 }
1239 }
1240 }
1241
1242 if SuccessfulBundles::<T>::get(domain_id).is_empty() {
1246 let missed_upgrade =
1253 Self::missed_domain_runtime_upgrade(domain_id).map_err(Error::<T>::from)?;
1254
1255 let next_number = HeadDomainNumber::<T>::get(domain_id)
1256 .checked_add(&One::one())
1257 .ok_or::<Error<T>>(BlockTreeError::MaxHeadDomainNumber.into())?
1258 .checked_add(&missed_upgrade.into())
1259 .ok_or::<Error<T>>(BlockTreeError::MaxHeadDomainNumber.into())?;
1260
1261 #[cfg(not(feature = "runtime-benchmarks"))]
1263 if next_number % T::StakeEpochDuration::get() == Zero::zero() {
1264 let epoch_transition_res = do_finalize_domain_current_epoch::<T>(domain_id)
1265 .map_err(Error::<T>::from)?;
1266
1267 Self::deposit_event(Event::DomainEpochCompleted {
1268 domain_id,
1269 completed_epoch_index: epoch_transition_res.completed_epoch_index,
1270 });
1271
1272 actual_weight = actual_weight
1273 .saturating_add(Self::actual_epoch_transition_weight(epoch_transition_res));
1274 }
1275
1276 HeadDomainNumber::<T>::set(domain_id, next_number);
1277 }
1278
1279 let head_domain_number = HeadDomainNumber::<T>::get(domain_id);
1281 let consensus_block_number = frame_system::Pallet::<T>::current_block_number();
1282 ExecutionInbox::<T>::append(
1283 (domain_id, head_domain_number, consensus_block_number),
1284 BundleDigest {
1285 header_hash: bundle_header_hash,
1286 extrinsics_root,
1287 size: bundle_size,
1288 },
1289 );
1290
1291 InboxedBundleAuthor::<T>::insert(bundle_header_hash, operator_id);
1292
1293 SuccessfulBundles::<T>::append(domain_id, bundle_hash);
1294
1295 OperatorBundleSlot::<T>::mutate(operator_id, |slot_set| slot_set.insert(slot_number));
1296
1297 #[cfg(not(feature = "runtime-benchmarks"))]
1299 {
1300 let slashed_nominator_count =
1301 do_slash_operator::<T>(domain_id, MAX_NOMINATORS_TO_SLASH)
1302 .map_err(Error::<T>::from)?;
1303 actual_weight = actual_weight
1304 .saturating_add(T::WeightInfo::slash_operator(slashed_nominator_count));
1305 }
1306
1307 Self::deposit_event(Event::BundleStored {
1308 domain_id,
1309 bundle_hash,
1310 bundle_author: operator_id,
1311 });
1312
1313 Ok(Some(actual_weight.min(Self::max_submit_bundle_weight())).into())
1315 }
1316
1317 #[pallet::call_index(15)]
1318 #[pallet::weight((
1319 T::WeightInfo::submit_fraud_proof().saturating_add(
1320 T::WeightInfo::handle_bad_receipt(MAX_BUNDLE_PER_BLOCK)
1321 ),
1322 DispatchClass::Operational
1323 ))]
1324 pub fn submit_fraud_proof(
1325 origin: OriginFor<T>,
1326 fraud_proof: Box<FraudProofFor<T>>,
1327 ) -> DispatchResultWithPostInfo {
1328 T::DomainOrigin::ensure_origin(origin)?;
1329
1330 log::trace!("Processing fraud proof: {fraud_proof:?}");
1331
1332 #[cfg(not(feature = "runtime-benchmarks"))]
1333 let mut actual_weight = T::WeightInfo::submit_fraud_proof();
1334 #[cfg(feature = "runtime-benchmarks")]
1335 let actual_weight = T::WeightInfo::submit_fraud_proof();
1336
1337 let domain_id = fraud_proof.domain_id();
1338 let bad_receipt_hash = fraud_proof.targeted_bad_receipt_hash();
1339 let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
1340 let bad_receipt_number = *BlockTreeNodes::<T>::get(bad_receipt_hash)
1341 .ok_or::<Error<T>>(FraudProofError::BadReceiptNotFound.into())?
1342 .execution_receipt
1343 .domain_block_number();
1344 ensure!(
1348 head_receipt_number >= bad_receipt_number,
1349 Error::<T>::from(FraudProofError::BadReceiptNotFound),
1350 );
1351
1352 #[cfg(not(feature = "runtime-benchmarks"))]
1359 {
1360 let BlockTreeNode {
1361 execution_receipt,
1362 operator_ids,
1363 } = prune_receipt::<T>(domain_id, bad_receipt_number)
1364 .map_err(Error::<T>::from)?
1365 .ok_or::<Error<T>>(FraudProofError::BadReceiptNotFound.into())?;
1366
1367 actual_weight = actual_weight.saturating_add(T::WeightInfo::handle_bad_receipt(
1368 (operator_ids.len() as u32).min(MAX_BUNDLE_PER_BLOCK),
1369 ));
1370
1371 do_mark_operators_as_slashed::<T>(
1372 operator_ids.into_iter(),
1373 SlashedReason::BadExecutionReceipt(bad_receipt_hash),
1374 )
1375 .map_err(Error::<T>::from)?;
1376
1377 do_unmark_invalid_bundle_authors::<T>(domain_id, &execution_receipt)
1379 .map_err(Error::<T>::Staking)?;
1380 }
1381
1382 let new_head_receipt_number = bad_receipt_number.saturating_sub(One::one());
1384 HeadReceiptNumber::<T>::insert(domain_id, new_head_receipt_number);
1385
1386 Self::deposit_event(Event::FraudProofProcessed {
1387 domain_id,
1388 new_head_receipt_number: Some(new_head_receipt_number),
1389 });
1390
1391 Ok(Some(actual_weight).into())
1392 }
1393
1394 #[pallet::call_index(2)]
1395 #[pallet::weight(T::WeightInfo::register_domain_runtime())]
1396 pub fn register_domain_runtime(
1397 origin: OriginFor<T>,
1398 runtime_name: String,
1399 runtime_type: RuntimeType,
1400 raw_genesis_storage: Vec<u8>,
1404 ) -> DispatchResult {
1405 ensure_root(origin)?;
1406
1407 let block_number = frame_system::Pallet::<T>::current_block_number();
1408 let runtime_id = do_register_runtime::<T>(
1409 runtime_name,
1410 runtime_type,
1411 raw_genesis_storage,
1412 block_number,
1413 )
1414 .map_err(Error::<T>::from)?;
1415
1416 Self::deposit_event(Event::DomainRuntimeCreated {
1417 runtime_id,
1418 runtime_type,
1419 });
1420
1421 Ok(())
1422 }
1423
1424 #[pallet::call_index(3)]
1425 #[pallet::weight(T::WeightInfo::upgrade_domain_runtime())]
1426 pub fn upgrade_domain_runtime(
1427 origin: OriginFor<T>,
1428 runtime_id: RuntimeId,
1429 raw_genesis_storage: Vec<u8>,
1430 ) -> DispatchResult {
1431 ensure_root(origin)?;
1432
1433 let block_number = frame_system::Pallet::<T>::current_block_number();
1434 let scheduled_at =
1435 do_schedule_runtime_upgrade::<T>(runtime_id, raw_genesis_storage, block_number)
1436 .map_err(Error::<T>::from)?;
1437
1438 Self::deposit_event(Event::DomainRuntimeUpgradeScheduled {
1439 runtime_id,
1440 scheduled_at,
1441 });
1442
1443 Ok(())
1444 }
1445
1446 #[pallet::call_index(4)]
1447 #[pallet::weight(T::WeightInfo::register_operator())]
1448 pub fn register_operator(
1449 origin: OriginFor<T>,
1450 domain_id: DomainId,
1451 amount: BalanceOf<T>,
1452 config: OperatorConfig<BalanceOf<T>>,
1453 ) -> DispatchResult {
1454 let owner = ensure_signed(origin)?;
1455
1456 let (operator_id, current_epoch_index) =
1457 do_register_operator::<T>(owner, domain_id, amount, config)
1458 .map_err(Error::<T>::from)?;
1459
1460 Self::deposit_event(Event::OperatorRegistered {
1461 operator_id,
1462 domain_id,
1463 });
1464
1465 if current_epoch_index.is_zero() {
1468 do_finalize_domain_current_epoch::<T>(domain_id).map_err(Error::<T>::from)?;
1469 }
1470
1471 Ok(())
1472 }
1473
1474 #[pallet::call_index(5)]
1475 #[pallet::weight(T::WeightInfo::nominate_operator())]
1476 pub fn nominate_operator(
1477 origin: OriginFor<T>,
1478 operator_id: OperatorId,
1479 amount: BalanceOf<T>,
1480 ) -> DispatchResult {
1481 let nominator_id = ensure_signed(origin)?;
1482
1483 do_nominate_operator::<T>(operator_id, nominator_id.clone(), amount)
1484 .map_err(Error::<T>::from)?;
1485
1486 Ok(())
1487 }
1488
1489 #[pallet::call_index(6)]
1490 #[pallet::weight(T::WeightInfo::instantiate_domain())]
1491 pub fn instantiate_domain(
1492 origin: OriginFor<T>,
1493 domain_config_params: DomainConfigParams<T::AccountId, BalanceOf<T>>,
1494 ) -> DispatchResult {
1495 let who = ensure_signed(origin)?;
1496 ensure!(
1497 PermissionedActionAllowedBy::<T>::get()
1498 .map(|allowed_by| allowed_by.is_allowed(&who))
1499 .unwrap_or_default(),
1500 Error::<T>::PermissionedActionNotAllowed
1501 );
1502
1503 let created_at = frame_system::Pallet::<T>::current_block_number();
1504
1505 let domain_id = do_instantiate_domain::<T>(domain_config_params, who, created_at)
1506 .map_err(Error::<T>::from)?;
1507
1508 Self::deposit_event(Event::DomainInstantiated { domain_id });
1509
1510 Ok(())
1511 }
1512
1513 #[pallet::call_index(8)]
1514 #[pallet::weight(T::WeightInfo::deregister_operator())]
1515 pub fn deregister_operator(
1516 origin: OriginFor<T>,
1517 operator_id: OperatorId,
1518 ) -> DispatchResult {
1519 let who = ensure_signed(origin)?;
1520
1521 do_deregister_operator::<T>(who, operator_id).map_err(Error::<T>::from)?;
1522
1523 Self::deposit_event(Event::OperatorDeregistered { operator_id });
1524
1525 Ok(())
1526 }
1527
1528 #[pallet::call_index(9)]
1529 #[pallet::weight(T::WeightInfo::withdraw_stake())]
1530 pub fn withdraw_stake(
1531 origin: OriginFor<T>,
1532 operator_id: OperatorId,
1533 to_withdraw: T::Share,
1534 ) -> DispatchResult {
1535 let who = ensure_signed(origin)?;
1536
1537 do_withdraw_stake::<T>(operator_id, who.clone(), to_withdraw)
1538 .map_err(Error::<T>::from)?;
1539
1540 Self::deposit_event(Event::WithdrewStake {
1541 operator_id,
1542 nominator_id: who,
1543 });
1544
1545 Ok(())
1546 }
1547
1548 #[pallet::call_index(10)]
1552 #[pallet::weight(T::WeightInfo::unlock_funds(T::WithdrawalLimit::get()))]
1553 pub fn unlock_funds(
1554 origin: OriginFor<T>,
1555 operator_id: OperatorId,
1556 ) -> DispatchResultWithPostInfo {
1557 let nominator_id = ensure_signed(origin)?;
1558 let withdrawal_count = do_unlock_funds::<T>(operator_id, nominator_id.clone())
1559 .map_err(crate::pallet::Error::<T>::from)?;
1560
1561 Ok(Some(T::WeightInfo::unlock_funds(
1562 withdrawal_count.min(T::WithdrawalLimit::get()),
1563 ))
1564 .into())
1565 }
1566
1567 #[pallet::call_index(11)]
1570 #[pallet::weight(T::WeightInfo::unlock_nominator())]
1571 pub fn unlock_nominator(origin: OriginFor<T>, operator_id: OperatorId) -> DispatchResult {
1572 let nominator = ensure_signed(origin)?;
1573
1574 do_unlock_nominator::<T>(operator_id, nominator.clone())
1575 .map_err(crate::pallet::Error::<T>::from)?;
1576
1577 Self::deposit_event(Event::NominatorUnlocked {
1578 operator_id,
1579 nominator_id: nominator,
1580 });
1581
1582 Ok(())
1583 }
1584
1585 #[pallet::call_index(12)]
1593 #[pallet::weight(T::WeightInfo::update_domain_operator_allow_list())]
1594 pub fn update_domain_operator_allow_list(
1595 origin: OriginFor<T>,
1596 domain_id: DomainId,
1597 operator_allow_list: OperatorAllowList<T::AccountId>,
1598 ) -> DispatchResult {
1599 let who = ensure_signed(origin)?;
1600 do_update_domain_allow_list::<T>(who, domain_id, operator_allow_list)
1601 .map_err(Error::<T>::from)?;
1602 Self::deposit_event(crate::pallet::Event::DomainOperatorAllowListUpdated { domain_id });
1603 Ok(())
1604 }
1605
1606 #[pallet::call_index(13)]
1608 #[pallet::weight(Pallet::<T>::max_staking_epoch_transition())]
1609 pub fn force_staking_epoch_transition(
1610 origin: OriginFor<T>,
1611 domain_id: DomainId,
1612 ) -> DispatchResultWithPostInfo {
1613 ensure_root(origin)?;
1614
1615 let epoch_transition_res =
1616 do_finalize_domain_current_epoch::<T>(domain_id).map_err(Error::<T>::from)?;
1617
1618 Self::deposit_event(Event::ForceDomainEpochTransition {
1619 domain_id,
1620 completed_epoch_index: epoch_transition_res.completed_epoch_index,
1621 });
1622
1623 let actual_weight = Self::actual_epoch_transition_weight(epoch_transition_res)
1625 .min(Self::max_staking_epoch_transition());
1626
1627 Ok(Some(actual_weight).into())
1628 }
1629
1630 #[pallet::call_index(14)]
1632 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(0, 1))]
1633 pub fn set_permissioned_action_allowed_by(
1634 origin: OriginFor<T>,
1635 permissioned_action_allowed_by: sp_domains::PermissionedActionAllowedBy<T::AccountId>,
1636 ) -> DispatchResult {
1637 ensure_root(origin)?;
1638 PermissionedActionAllowedBy::<T>::put(permissioned_action_allowed_by);
1639 Ok(())
1640 }
1641
1642 #[pallet::call_index(16)]
1644 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(3, 1))]
1645 pub fn send_domain_sudo_call(
1646 origin: OriginFor<T>,
1647 domain_id: DomainId,
1648 call: Vec<u8>,
1649 ) -> DispatchResult {
1650 ensure_root(origin)?;
1651 ensure!(
1652 DomainSudoCalls::<T>::get(domain_id).maybe_call.is_none(),
1653 Error::<T>::DomainSudoCallExists
1654 );
1655
1656 let domain_runtime = Self::domain_runtime_code(domain_id).ok_or(
1657 Error::<T>::DomainRegistry(DomainRegistryError::DomainNotFound),
1658 )?;
1659 ensure!(
1660 domain_runtime_call(
1661 domain_runtime,
1662 StatelessDomainRuntimeCall::IsValidDomainSudoCall(call.clone()),
1663 )
1664 .unwrap_or(false),
1665 Error::<T>::InvalidDomainSudoCall
1666 );
1667
1668 DomainSudoCalls::<T>::set(
1669 domain_id,
1670 DomainSudoCall {
1671 maybe_call: Some(call),
1672 },
1673 );
1674 Ok(())
1675 }
1676
1677 #[pallet::call_index(17)]
1680 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(0, 1))]
1681 pub fn freeze_domain(origin: OriginFor<T>, domain_id: DomainId) -> DispatchResult {
1682 ensure_root(origin)?;
1683 FrozenDomains::<T>::mutate(|frozen_domains| frozen_domains.insert(domain_id));
1684 Self::deposit_event(Event::DomainFrozen { domain_id });
1685 Ok(())
1686 }
1687
1688 #[pallet::call_index(18)]
1690 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(0, 1))]
1691 pub fn unfreeze_domain(origin: OriginFor<T>, domain_id: DomainId) -> DispatchResult {
1692 ensure_root(origin)?;
1693 FrozenDomains::<T>::mutate(|frozen_domains| frozen_domains.remove(&domain_id));
1694 Self::deposit_event(Event::DomainUnfrozen { domain_id });
1695 Ok(())
1696 }
1697
1698 #[pallet::call_index(19)]
1702 #[pallet::weight(Pallet::<T>::max_prune_domain_execution_receipt())]
1703 pub fn prune_domain_execution_receipt(
1704 origin: OriginFor<T>,
1705 domain_id: DomainId,
1706 bad_receipt_hash: ReceiptHashFor<T>,
1707 ) -> DispatchResultWithPostInfo {
1708 ensure_root(origin)?;
1709 ensure!(
1710 FrozenDomains::<T>::get().contains(&domain_id),
1711 Error::<T>::DomainNotFrozen
1712 );
1713
1714 let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
1715 let bad_receipt_number = *BlockTreeNodes::<T>::get(bad_receipt_hash)
1716 .ok_or::<Error<T>>(FraudProofError::BadReceiptNotFound.into())?
1717 .execution_receipt
1718 .domain_block_number();
1719 ensure!(
1722 head_receipt_number >= bad_receipt_number,
1723 Error::<T>::from(FraudProofError::BadReceiptNotFound),
1724 );
1725
1726 let mut actual_weight = T::DbWeight::get().reads(3);
1727
1728 let BlockTreeNode {
1730 execution_receipt,
1731 operator_ids,
1732 } = prune_receipt::<T>(domain_id, bad_receipt_number)
1733 .map_err(Error::<T>::from)?
1734 .ok_or::<Error<T>>(FraudProofError::BadReceiptNotFound.into())?;
1735
1736 actual_weight = actual_weight.saturating_add(T::WeightInfo::handle_bad_receipt(
1737 (operator_ids.len() as u32).min(MAX_BUNDLE_PER_BLOCK),
1738 ));
1739
1740 do_mark_operators_as_slashed::<T>(
1741 operator_ids.into_iter(),
1742 SlashedReason::BadExecutionReceipt(bad_receipt_hash),
1743 )
1744 .map_err(Error::<T>::from)?;
1745
1746 do_unmark_invalid_bundle_authors::<T>(domain_id, &execution_receipt)
1748 .map_err(Error::<T>::Staking)?;
1749
1750 let new_head_receipt_number = bad_receipt_number.saturating_sub(One::one());
1752 HeadReceiptNumber::<T>::insert(domain_id, new_head_receipt_number);
1753 actual_weight = actual_weight.saturating_add(T::DbWeight::get().reads_writes(0, 1));
1754
1755 Self::deposit_event(Event::PrunedExecutionReceipt {
1756 domain_id,
1757 new_head_receipt_number: Some(new_head_receipt_number),
1758 });
1759
1760 Ok(Some(actual_weight).into())
1761 }
1762
1763 #[pallet::call_index(20)]
1765 #[pallet::weight(T::WeightInfo::transfer_treasury_funds())]
1766 pub fn transfer_treasury_funds(
1767 origin: OriginFor<T>,
1768 account_id: T::AccountId,
1769 balance: BalanceOf<T>,
1770 ) -> DispatchResult {
1771 ensure_root(origin)?;
1772 T::Currency::transfer(
1773 &T::TreasuryAccount::get(),
1774 &account_id,
1775 balance,
1776 Preservation::Preserve,
1777 )?;
1778 Ok(())
1779 }
1780
1781 #[pallet::call_index(21)]
1782 #[pallet::weight(Pallet::<T>::max_submit_receipt_weight())]
1783 pub fn submit_receipt(
1784 origin: OriginFor<T>,
1785 singleton_receipt: SingletonReceiptOf<T>,
1786 ) -> DispatchResultWithPostInfo {
1787 T::DomainOrigin::ensure_origin(origin)?;
1788
1789 let domain_id = singleton_receipt.domain_id();
1790 let operator_id = singleton_receipt.operator_id();
1791 let receipt = singleton_receipt.into_receipt();
1792
1793 #[cfg(not(feature = "runtime-benchmarks"))]
1794 let mut actual_weight = T::WeightInfo::submit_receipt();
1795 #[cfg(feature = "runtime-benchmarks")]
1796 let actual_weight = T::WeightInfo::submit_receipt();
1797
1798 match execution_receipt_type::<T>(domain_id, &receipt.as_execution_receipt_ref()) {
1799 ReceiptType::Rejected(rejected_receipt_type) => {
1800 return Err(Error::<T>::BlockTree(rejected_receipt_type.into()).into());
1801 }
1802 ReceiptType::Accepted(accepted_receipt_type) => {
1804 #[cfg(not(feature = "runtime-benchmarks"))]
1810 if accepted_receipt_type == AcceptedReceiptType::NewHead
1811 && let Some(BlockTreeNode {
1812 execution_receipt,
1813 operator_ids,
1814 }) = prune_receipt::<T>(domain_id, *receipt.domain_block_number())
1815 .map_err(Error::<T>::from)?
1816 {
1817 actual_weight = actual_weight.saturating_add(
1818 T::WeightInfo::handle_bad_receipt(operator_ids.len() as u32),
1819 );
1820
1821 let bad_receipt_hash = execution_receipt.hash::<DomainHashingFor<T>>();
1822 do_mark_operators_as_slashed::<T>(
1823 operator_ids.into_iter(),
1824 SlashedReason::BadExecutionReceipt(bad_receipt_hash),
1825 )
1826 .map_err(Error::<T>::from)?;
1827
1828 do_unmark_invalid_bundle_authors::<T>(domain_id, &execution_receipt)
1829 .map_err(Error::<T>::from)?;
1830 }
1831
1832 if accepted_receipt_type == AcceptedReceiptType::NewHead {
1833 do_mark_invalid_bundle_authors::<T>(domain_id, &receipt)
1836 .map_err(Error::<T>::Staking)?;
1837 }
1838
1839 #[cfg_attr(feature = "runtime-benchmarks", allow(unused_variables))]
1840 let maybe_confirmed_domain_block_info = process_execution_receipt::<T>(
1841 domain_id,
1842 operator_id,
1843 receipt,
1844 accepted_receipt_type,
1845 )
1846 .map_err(Error::<T>::from)?;
1847
1848 #[cfg(not(feature = "runtime-benchmarks"))]
1851 if let Some(confirmed_block_info) = maybe_confirmed_domain_block_info {
1852 actual_weight =
1853 actual_weight.saturating_add(T::WeightInfo::confirm_domain_block(
1854 confirmed_block_info.operator_ids.len() as u32,
1855 confirmed_block_info.invalid_bundle_authors.len() as u32,
1856 ));
1857
1858 refund_storage_fee::<T>(
1859 confirmed_block_info.total_storage_fee,
1860 confirmed_block_info.paid_bundle_storage_fees,
1861 )
1862 .map_err(Error::<T>::from)?;
1863
1864 do_reward_operators::<T>(
1865 domain_id,
1866 OperatorRewardSource::Bundle {
1867 at_block_number: confirmed_block_info.consensus_block_number,
1868 },
1869 confirmed_block_info.operator_ids.into_iter(),
1870 confirmed_block_info.rewards,
1871 )
1872 .map_err(Error::<T>::from)?;
1873
1874 do_mark_operators_as_slashed::<T>(
1875 confirmed_block_info.invalid_bundle_authors.into_iter(),
1876 SlashedReason::InvalidBundle(confirmed_block_info.domain_block_number),
1877 )
1878 .map_err(Error::<T>::from)?;
1879 }
1880 }
1881 }
1882
1883 #[cfg(not(feature = "runtime-benchmarks"))]
1885 {
1886 let slashed_nominator_count =
1887 do_slash_operator::<T>(domain_id, MAX_NOMINATORS_TO_SLASH)
1888 .map_err(Error::<T>::from)?;
1889 actual_weight = actual_weight
1890 .saturating_add(T::WeightInfo::slash_operator(slashed_nominator_count));
1891 }
1892
1893 Ok(Some(actual_weight.min(Self::max_submit_receipt_weight())).into())
1895 }
1896
1897 #[pallet::call_index(22)]
1899 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(3, 1))]
1900 pub fn send_evm_domain_set_contract_creation_allowed_by_call(
1901 origin: OriginFor<T>,
1902 domain_id: DomainId,
1903 contract_creation_allowed_by: sp_domains::PermissionedActionAllowedBy<
1904 EthereumAccountId,
1905 >,
1906 ) -> DispatchResult {
1907 let signer = ensure_signed_or_root(origin)?;
1908
1909 ensure!(
1910 Pallet::<T>::is_private_evm_domain(domain_id),
1911 Error::<T>::NotPrivateEvmDomain,
1912 );
1913 if let Some(non_root_signer) = signer {
1914 ensure!(
1915 Pallet::<T>::is_domain_owner(domain_id, non_root_signer),
1916 Error::<T>::NotDomainOwnerOrRoot,
1917 );
1918 }
1919 ensure!(
1920 EvmDomainContractCreationAllowedByCalls::<T>::get(domain_id)
1921 .maybe_call
1922 .is_none(),
1923 Error::<T>::EvmDomainContractCreationAllowedByCallExists,
1924 );
1925
1926 EvmDomainContractCreationAllowedByCalls::<T>::set(
1927 domain_id,
1928 EvmDomainContractCreationAllowedByCall {
1929 maybe_call: Some(contract_creation_allowed_by),
1930 },
1931 );
1932
1933 Ok(())
1934 }
1935 }
1936
1937 #[pallet::genesis_config]
1938 pub struct GenesisConfig<T: Config> {
1939 pub permissioned_action_allowed_by:
1940 Option<sp_domains::PermissionedActionAllowedBy<T::AccountId>>,
1941 pub genesis_domains: Vec<GenesisDomain<T::AccountId, BalanceOf<T>>>,
1942 }
1943
1944 impl<T: Config> Default for GenesisConfig<T> {
1945 fn default() -> Self {
1946 GenesisConfig {
1947 permissioned_action_allowed_by: None,
1948 genesis_domains: vec![],
1949 }
1950 }
1951 }
1952
1953 #[pallet::genesis_build]
1954 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
1955 fn build(&self) {
1956 if let Some(permissioned_action_allowed_by) =
1957 self.permissioned_action_allowed_by.as_ref().cloned()
1958 {
1959 PermissionedActionAllowedBy::<T>::put(permissioned_action_allowed_by)
1960 }
1961
1962 self.genesis_domains
1963 .clone()
1964 .into_iter()
1965 .for_each(|genesis_domain| {
1966 let runtime_id = register_runtime_at_genesis::<T>(
1968 genesis_domain.runtime_name,
1969 genesis_domain.runtime_type,
1970 genesis_domain.runtime_version,
1971 genesis_domain.raw_genesis_storage,
1972 Zero::zero(),
1973 )
1974 .expect("Genesis runtime registration must always succeed");
1975
1976 let domain_config_params = DomainConfigParams {
1978 domain_name: genesis_domain.domain_name,
1979 runtime_id,
1980 maybe_bundle_limit: None,
1981 bundle_slot_probability: genesis_domain.bundle_slot_probability,
1982 operator_allow_list: genesis_domain.operator_allow_list,
1983 initial_balances: genesis_domain.initial_balances,
1984 domain_runtime_info: genesis_domain.domain_runtime_info,
1985 };
1986 let domain_owner = genesis_domain.owner_account_id;
1987 let domain_id = do_instantiate_domain::<T>(
1988 domain_config_params,
1989 domain_owner.clone(),
1990 Zero::zero(),
1991 )
1992 .expect("Genesis domain instantiation must always succeed");
1993
1994 let operator_config = OperatorConfig {
1996 signing_key: genesis_domain.signing_key.clone(),
1997 minimum_nominator_stake: genesis_domain.minimum_nominator_stake,
1998 nomination_tax: genesis_domain.nomination_tax,
1999 };
2000 let operator_stake = T::MinOperatorStake::get();
2001 do_register_operator::<T>(
2002 domain_owner,
2003 domain_id,
2004 operator_stake,
2005 operator_config,
2006 )
2007 .expect("Genesis operator registration must succeed");
2008
2009 do_finalize_domain_current_epoch::<T>(domain_id)
2010 .expect("Genesis epoch must succeed");
2011 });
2012 }
2013 }
2014
2015 #[pallet::storage]
2017 pub type BlockInherentExtrinsicData<T> = StorageValue<_, InherentExtrinsicData>;
2018
2019 #[pallet::hooks]
2020 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
2022 fn on_initialize(block_number: BlockNumberFor<T>) -> Weight {
2023 let parent_number = block_number - One::one();
2024 let parent_hash = frame_system::Pallet::<T>::block_hash(parent_number);
2025
2026 for runtime_id in DomainRuntimeUpgrades::<T>::take() {
2028 let reference_count = RuntimeRegistry::<T>::get(runtime_id)
2029 .expect("Runtime object must be present since domain is insantiated; qed")
2030 .instance_count;
2031 if !reference_count.is_zero() {
2032 DomainRuntimeUpgradeRecords::<T>::mutate(runtime_id, |upgrade_record| {
2033 upgrade_record.insert(
2034 parent_number,
2035 DomainRuntimeUpgradeEntry {
2036 at_hash: parent_hash,
2037 reference_count,
2038 },
2039 )
2040 });
2041 }
2042 }
2043 DomainRuntimeUpgrades::<T>::set(Vec::new());
2046 do_upgrade_runtimes::<T>(block_number);
2049
2050 for (domain_id, _) in SuccessfulBundles::<T>::drain() {
2053 ConsensusBlockHash::<T>::insert(domain_id, parent_number, parent_hash);
2054 T::DomainBundleSubmitted::domain_bundle_submitted(domain_id);
2055
2056 DomainSudoCalls::<T>::mutate(domain_id, |sudo_call| {
2058 sudo_call.clear();
2059 });
2060 EvmDomainContractCreationAllowedByCalls::<T>::mutate(
2061 domain_id,
2062 |evm_contract_call| {
2063 evm_contract_call.clear();
2064 },
2065 );
2066 }
2067
2068 for (operator_id, slot_set) in OperatorBundleSlot::<T>::drain() {
2069 if let Some(highest_slot) = slot_set.last() {
2072 OperatorHighestSlot::<T>::insert(operator_id, highest_slot);
2073 }
2074 }
2075
2076 BlockInherentExtrinsicData::<T>::kill();
2077
2078 Weight::zero()
2079 }
2080
2081 fn on_finalize(_: BlockNumberFor<T>) {
2082 if SuccessfulBundles::<T>::iter_keys().count() > 0
2085 || !DomainRuntimeUpgrades::<T>::get().is_empty()
2086 {
2087 let extrinsics_shuffling_seed = Randomness::from(
2088 Into::<H256>::into(Self::extrinsics_shuffling_seed_value()).to_fixed_bytes(),
2089 );
2090
2091 let timestamp = Self::timestamp_value();
2094
2095 let consensus_transaction_byte_fee = Self::consensus_transaction_byte_fee_value();
2097
2098 let inherent_extrinsic_data = InherentExtrinsicData {
2099 extrinsics_shuffling_seed,
2100 timestamp,
2101 consensus_transaction_byte_fee,
2102 };
2103
2104 BlockInherentExtrinsicData::<T>::set(Some(inherent_extrinsic_data));
2105 }
2106
2107 let _ = LastEpochStakingDistribution::<T>::clear(u32::MAX, None);
2108 let _ = NewAddedHeadReceipt::<T>::clear(u32::MAX, None);
2109 }
2110 }
2111}
2112
2113impl<T: Config> Pallet<T> {
2114 fn log_bundle_error(err: &BundleError, domain_id: DomainId, operator_id: OperatorId) {
2115 match err {
2116 BundleError::Receipt(BlockTreeError::InFutureReceipt)
2119 | BundleError::Receipt(BlockTreeError::StaleReceipt)
2120 | BundleError::Receipt(BlockTreeError::NewBranchReceipt)
2121 | BundleError::Receipt(BlockTreeError::UnavailableConsensusBlockHash)
2122 | BundleError::Receipt(BlockTreeError::BuiltOnUnknownConsensusBlock)
2123 | BundleError::SlotInThePast
2124 | BundleError::SlotInTheFuture
2125 | BundleError::InvalidProofOfTime
2126 | BundleError::SlotSmallerThanPreviousBlockBundle
2127 | BundleError::ExpectingReceiptGap
2128 | BundleError::UnexpectedReceiptGap => {
2129 log::debug!(
2130 "Bad bundle/receipt, domain {domain_id:?}, operator {operator_id:?}, error: {err:?}",
2131 );
2132 }
2133 _ => {
2134 log::warn!(
2135 "Bad bundle/receipt, domain {domain_id:?}, operator {operator_id:?}, error: {err:?}",
2136 );
2137 }
2138 }
2139 }
2140
2141 pub fn successful_bundles(domain_id: DomainId) -> Vec<H256> {
2142 SuccessfulBundles::<T>::get(domain_id)
2143 }
2144
2145 pub fn domain_runtime_code(domain_id: DomainId) -> Option<Vec<u8>> {
2146 RuntimeRegistry::<T>::get(Self::runtime_id(domain_id)?)
2147 .and_then(|mut runtime_object| runtime_object.raw_genesis.take_runtime_code())
2148 }
2149
2150 pub fn domain_best_number(domain_id: DomainId) -> Result<DomainBlockNumberFor<T>, BundleError> {
2151 let missed_upgrade = Self::missed_domain_runtime_upgrade(domain_id)
2154 .map_err(|_| BundleError::FailedToGetMissedUpgradeCount)?;
2155
2156 Ok(HeadDomainNumber::<T>::get(domain_id) + missed_upgrade.into())
2157 }
2158
2159 pub fn runtime_id(domain_id: DomainId) -> Option<RuntimeId> {
2161 DomainRegistry::<T>::get(domain_id)
2162 .map(|domain_object| domain_object.domain_config.runtime_id)
2163 }
2164
2165 pub fn runtime_upgrades() -> Vec<RuntimeId> {
2167 DomainRuntimeUpgrades::<T>::get()
2168 }
2169
2170 pub fn domain_instance_data(
2171 domain_id: DomainId,
2172 ) -> Option<(DomainInstanceData, BlockNumberFor<T>)> {
2173 let domain_obj = DomainRegistry::<T>::get(domain_id)?;
2174 let runtime_object = RuntimeRegistry::<T>::get(domain_obj.domain_config.runtime_id)?;
2175 let runtime_type = runtime_object.runtime_type;
2176 let total_issuance = domain_obj.domain_config.total_issuance()?;
2177 let raw_genesis = into_complete_raw_genesis::<T>(
2178 runtime_object,
2179 domain_id,
2180 &domain_obj.domain_runtime_info,
2181 total_issuance,
2182 domain_obj.domain_config.initial_balances,
2183 )
2184 .ok()?;
2185 Some((
2186 DomainInstanceData {
2187 runtime_type,
2188 raw_genesis,
2189 },
2190 domain_obj.created_at,
2191 ))
2192 }
2193
2194 pub fn domain_tx_range(domain_id: DomainId) -> U256 {
2196 DomainTxRangeState::<T>::try_get(domain_id)
2197 .map(|state| state.tx_range)
2198 .ok()
2199 .unwrap_or_else(Self::initial_tx_range)
2200 }
2201
2202 pub fn bundle_producer_election_params(
2203 domain_id: DomainId,
2204 ) -> Option<BundleProducerElectionParams<BalanceOf<T>>> {
2205 match (
2206 DomainRegistry::<T>::get(domain_id),
2207 DomainStakingSummary::<T>::get(domain_id),
2208 ) {
2209 (Some(domain_object), Some(stake_summary)) => Some(BundleProducerElectionParams {
2210 total_domain_stake: stake_summary.current_total_stake,
2211 bundle_slot_probability: domain_object.domain_config.bundle_slot_probability,
2212 }),
2213 _ => None,
2214 }
2215 }
2216
2217 pub fn operator(operator_id: OperatorId) -> Option<(OperatorPublicKey, BalanceOf<T>)> {
2218 Operators::<T>::get(operator_id)
2219 .map(|operator| (operator.signing_key, operator.current_total_stake))
2220 }
2221
2222 fn check_extrinsics_root(opaque_bundle: &OpaqueBundleOf<T>) -> Result<(), BundleError> {
2223 let expected_extrinsics_root = <T::DomainHeader as Header>::Hashing::ordered_trie_root(
2224 opaque_bundle
2225 .extrinsics()
2226 .iter()
2227 .map(|xt| xt.encode())
2228 .collect(),
2229 sp_core::storage::StateVersion::V1,
2230 );
2231 ensure!(
2232 expected_extrinsics_root == opaque_bundle.extrinsics_root(),
2233 BundleError::InvalidExtrinsicRoot
2234 );
2235 Ok(())
2236 }
2237
2238 fn check_slot_and_proof_of_time(
2239 slot_number: u64,
2240 proof_of_time: PotOutput,
2241 pre_dispatch: bool,
2242 ) -> Result<(), BundleError> {
2243 let current_block_number = frame_system::Pallet::<T>::current_block_number();
2246
2247 if pre_dispatch && let Some(future_slot) = T::BlockSlot::future_slot(current_block_number) {
2253 ensure!(slot_number <= *future_slot, BundleError::SlotInTheFuture)
2254 }
2255
2256 let produced_after_block_number =
2258 match T::BlockSlot::slot_produced_after(slot_number.into()) {
2259 Some(n) => n,
2260 None => {
2261 if current_block_number > T::BundleLongevity::get().into() {
2264 return Err(BundleError::SlotInThePast);
2265 } else {
2266 Zero::zero()
2267 }
2268 }
2269 };
2270 let produced_after_block_hash = if produced_after_block_number == current_block_number {
2271 frame_system::Pallet::<T>::parent_hash()
2273 } else {
2274 frame_system::Pallet::<T>::block_hash(produced_after_block_number)
2275 };
2276 if let Some(last_eligible_block) =
2277 current_block_number.checked_sub(&T::BundleLongevity::get().into())
2278 {
2279 ensure!(
2280 produced_after_block_number >= last_eligible_block,
2281 BundleError::SlotInThePast
2282 );
2283 }
2284
2285 if !is_proof_of_time_valid(
2286 BlockHash::try_from(produced_after_block_hash.as_ref())
2287 .expect("Must be able to convert to block hash type"),
2288 SlotNumber::from(slot_number),
2289 WrappedPotOutput::from(proof_of_time),
2290 !pre_dispatch,
2292 ) {
2293 return Err(BundleError::InvalidProofOfTime);
2294 }
2295
2296 Ok(())
2297 }
2298
2299 fn validate_bundle(
2300 opaque_bundle: &OpaqueBundleOf<T>,
2301 domain_config: &DomainConfig<T::AccountId, BalanceOf<T>>,
2302 ) -> Result<(), BundleError> {
2303 ensure!(
2304 opaque_bundle.body_size() <= domain_config.max_bundle_size,
2305 BundleError::BundleTooLarge
2306 );
2307
2308 ensure!(
2309 opaque_bundle
2310 .estimated_weight()
2311 .all_lte(domain_config.max_bundle_weight),
2312 BundleError::BundleTooHeavy
2313 );
2314
2315 Self::check_extrinsics_root(opaque_bundle)?;
2316
2317 Ok(())
2318 }
2319
2320 fn validate_eligibility(
2321 to_sign: &[u8],
2322 signature: &OperatorSignature,
2323 proof_of_election: &ProofOfElection,
2324 domain_config: &DomainConfig<T::AccountId, BalanceOf<T>>,
2325 pre_dispatch: bool,
2326 ) -> Result<(), BundleError> {
2327 let domain_id = proof_of_election.domain_id;
2328 let operator_id = proof_of_election.operator_id;
2329 let slot_number = proof_of_election.slot_number;
2330
2331 ensure!(
2332 !FrozenDomains::<T>::get().contains(&domain_id),
2333 BundleError::DomainFrozen
2334 );
2335
2336 let operator = Operators::<T>::get(operator_id).ok_or(BundleError::InvalidOperatorId)?;
2337
2338 let operator_status = operator.status::<T>(operator_id);
2339 ensure!(
2340 *operator_status != OperatorStatus::Slashed
2341 && *operator_status != OperatorStatus::PendingSlash
2342 && !matches!(operator_status, OperatorStatus::InvalidBundle(_)),
2343 BundleError::BadOperator
2344 );
2345
2346 if !operator.signing_key.verify(&to_sign, signature) {
2347 return Err(BundleError::BadBundleSignature);
2348 }
2349
2350 ensure!(
2352 slot_number
2353 > Self::operator_highest_slot_from_previous_block(operator_id, pre_dispatch),
2354 BundleError::SlotSmallerThanPreviousBlockBundle,
2355 );
2356
2357 ensure!(
2359 !OperatorBundleSlot::<T>::get(operator_id).contains(&slot_number),
2360 BundleError::EquivocatedBundle,
2361 );
2362
2363 let (operator_stake, total_domain_stake) =
2364 Self::fetch_operator_stake_info(domain_id, &operator_id)?;
2365
2366 Self::check_slot_and_proof_of_time(
2367 slot_number,
2368 proof_of_election.proof_of_time,
2369 pre_dispatch,
2370 )?;
2371
2372 sp_domains::bundle_producer_election::check_proof_of_election(
2373 &operator.signing_key,
2374 domain_config.bundle_slot_probability,
2375 proof_of_election,
2376 operator_stake.saturated_into(),
2377 total_domain_stake.saturated_into(),
2378 )?;
2379
2380 Ok(())
2381 }
2382
2383 fn check_execution_receipt_version(
2386 er_derived_consensus_number: BlockNumberFor<T>,
2387 receipt_version: ExecutionReceiptVersion,
2388 ) -> Result<(), BundleError> {
2389 let expected_execution_receipt_version =
2390 Self::bundle_and_execution_receipt_version_for_consensus_number(
2391 er_derived_consensus_number,
2392 PreviousBundleAndExecutionReceiptVersions::<T>::get(),
2393 T::CurrentBundleAndExecutionReceiptVersion::get(),
2394 )
2395 .ok_or(BundleError::ExecutionVersionMissing)?
2396 .execution_receipt_version;
2397 match (receipt_version, expected_execution_receipt_version) {
2398 (ExecutionReceiptVersion::V0, ExecutionReceiptVersion::V0) => Ok(()),
2399 }
2400 }
2401
2402 fn validate_submit_bundle(
2403 opaque_bundle: &OpaqueBundleOf<T>,
2404 pre_dispatch: bool,
2405 ) -> Result<(), BundleError> {
2406 let current_bundle_version =
2407 T::CurrentBundleAndExecutionReceiptVersion::get().bundle_version;
2408
2409 match (current_bundle_version, opaque_bundle) {
2411 (BundleVersion::V0, Bundle::V0(_)) => Ok::<(), BundleError>(()),
2412 }?;
2413
2414 let domain_id = opaque_bundle.domain_id();
2415 let operator_id = opaque_bundle.operator_id();
2416 let sealed_header = opaque_bundle.sealed_header();
2417
2418 let receipt = sealed_header.receipt();
2419 Self::check_execution_receipt_version(
2420 *receipt.consensus_block_number(),
2421 receipt.version(),
2422 )?;
2423
2424 ensure!(
2428 Self::receipt_gap(domain_id)? <= One::one(),
2429 BundleError::UnexpectedReceiptGap,
2430 );
2431
2432 charge_bundle_storage_fee::<T>(operator_id, opaque_bundle.size())
2433 .map_err(|_| BundleError::UnableToPayBundleStorageFee)?;
2434
2435 let domain_config = &DomainRegistry::<T>::get(domain_id)
2436 .ok_or(BundleError::InvalidDomainId)?
2437 .domain_config;
2438
2439 Self::validate_bundle(opaque_bundle, domain_config)?;
2440
2441 Self::validate_eligibility(
2442 sealed_header.pre_hash().as_ref(),
2443 sealed_header.signature(),
2444 sealed_header.proof_of_election(),
2445 domain_config,
2446 pre_dispatch,
2447 )?;
2448
2449 verify_execution_receipt::<T>(domain_id, &receipt).map_err(BundleError::Receipt)?;
2450
2451 Ok(())
2452 }
2453
2454 fn validate_singleton_receipt(
2455 sealed_singleton_receipt: &SingletonReceiptOf<T>,
2456 pre_dispatch: bool,
2457 ) -> Result<(), BundleError> {
2458 let domain_id = sealed_singleton_receipt.domain_id();
2459 let operator_id = sealed_singleton_receipt.operator_id();
2460
2461 ensure!(
2463 Self::receipt_gap(domain_id)? > One::one(),
2464 BundleError::ExpectingReceiptGap,
2465 );
2466
2467 charge_bundle_storage_fee::<T>(operator_id, sealed_singleton_receipt.size())
2468 .map_err(|_| BundleError::UnableToPayBundleStorageFee)?;
2469
2470 Self::check_execution_receipt_version(
2471 *sealed_singleton_receipt
2472 .singleton_receipt
2473 .receipt
2474 .consensus_block_number(),
2475 sealed_singleton_receipt.singleton_receipt.receipt.version(),
2476 )?;
2477
2478 let domain_config = DomainRegistry::<T>::get(domain_id)
2479 .ok_or(BundleError::InvalidDomainId)?
2480 .domain_config;
2481 Self::validate_eligibility(
2482 sealed_singleton_receipt.pre_hash().as_ref(),
2483 &sealed_singleton_receipt.signature,
2484 &sealed_singleton_receipt.singleton_receipt.proof_of_election,
2485 &domain_config,
2486 pre_dispatch,
2487 )?;
2488
2489 verify_execution_receipt::<T>(
2490 domain_id,
2491 &sealed_singleton_receipt
2492 .singleton_receipt
2493 .receipt
2494 .as_execution_receipt_ref(),
2495 )
2496 .map_err(BundleError::Receipt)?;
2497
2498 Ok(())
2499 }
2500
2501 fn validate_fraud_proof(
2502 fraud_proof: &FraudProofFor<T>,
2503 ) -> Result<(DomainId, TransactionPriority), FraudProofError> {
2504 let domain_id = fraud_proof.domain_id();
2505 let bad_receipt_hash = fraud_proof.targeted_bad_receipt_hash();
2506 let bad_receipt = BlockTreeNodes::<T>::get(bad_receipt_hash)
2507 .ok_or(FraudProofError::BadReceiptNotFound)?
2508 .execution_receipt;
2509 let bad_receipt_domain_block_number = *bad_receipt.domain_block_number();
2510
2511 ensure!(
2512 !bad_receipt_domain_block_number.is_zero(),
2513 FraudProofError::ChallengingGenesisReceipt
2514 );
2515
2516 ensure!(
2517 !Self::is_bad_er_pending_to_prune(domain_id, bad_receipt_domain_block_number),
2518 FraudProofError::BadReceiptAlreadyReported,
2519 );
2520
2521 ensure!(
2522 !fraud_proof.is_unexpected_domain_runtime_code_proof(),
2523 FraudProofError::UnexpectedDomainRuntimeCodeProof,
2524 );
2525
2526 ensure!(
2527 !fraud_proof.is_unexpected_mmr_proof(),
2528 FraudProofError::UnexpectedMmrProof,
2529 );
2530
2531 let maybe_state_root = match &fraud_proof.maybe_mmr_proof {
2532 Some(mmr_proof) => Some(Self::verify_mmr_proof_and_extract_state_root(
2533 mmr_proof.clone(),
2534 *bad_receipt.consensus_block_number(),
2535 )?),
2536 None => None,
2537 };
2538
2539 match &fraud_proof.proof {
2540 FraudProofVariant::InvalidBlockFees(InvalidBlockFeesProof { storage_proof }) => {
2541 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2542 domain_id,
2543 &bad_receipt,
2544 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2545 )?;
2546
2547 verify_invalid_block_fees_fraud_proof::<
2548 T::Block,
2549 DomainBlockNumberFor<T>,
2550 T::DomainHash,
2551 BalanceOf<T>,
2552 DomainHashingFor<T>,
2553 >(bad_receipt, storage_proof, domain_runtime_code)
2554 .map_err(|err| {
2555 log::error!("Block fees proof verification failed: {err:?}");
2556 FraudProofError::InvalidBlockFeesFraudProof
2557 })?;
2558 }
2559 FraudProofVariant::InvalidTransfers(InvalidTransfersProof { storage_proof }) => {
2560 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2561 domain_id,
2562 &bad_receipt,
2563 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2564 )?;
2565
2566 verify_invalid_transfers_fraud_proof::<
2567 T::Block,
2568 DomainBlockNumberFor<T>,
2569 T::DomainHash,
2570 BalanceOf<T>,
2571 DomainHashingFor<T>,
2572 >(bad_receipt, storage_proof, domain_runtime_code)
2573 .map_err(|err| {
2574 log::error!("Domain transfers proof verification failed: {err:?}");
2575 FraudProofError::InvalidTransfersFraudProof
2576 })?;
2577 }
2578 FraudProofVariant::InvalidDomainBlockHash(InvalidDomainBlockHashProof {
2579 digest_storage_proof,
2580 }) => {
2581 let parent_receipt =
2582 BlockTreeNodes::<T>::get(*bad_receipt.parent_domain_block_receipt_hash())
2583 .ok_or(FraudProofError::ParentReceiptNotFound)?
2584 .execution_receipt;
2585 verify_invalid_domain_block_hash_fraud_proof::<
2586 T::Block,
2587 BalanceOf<T>,
2588 T::DomainHeader,
2589 >(
2590 bad_receipt,
2591 digest_storage_proof.clone(),
2592 *parent_receipt.domain_block_hash(),
2593 )
2594 .map_err(|err| {
2595 log::error!("Invalid Domain block hash proof verification failed: {err:?}");
2596 FraudProofError::InvalidDomainBlockHashFraudProof
2597 })?;
2598 }
2599 FraudProofVariant::InvalidExtrinsicsRoot(proof) => {
2600 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2601 domain_id,
2602 &bad_receipt,
2603 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2604 )?;
2605 let runtime_id =
2606 Self::runtime_id(domain_id).ok_or(FraudProofError::RuntimeNotFound)?;
2607 let state_root = maybe_state_root.ok_or(FraudProofError::MissingMmrProof)?;
2608
2609 verify_invalid_domain_extrinsics_root_fraud_proof::<
2610 T::Block,
2611 BalanceOf<T>,
2612 T::DomainHeader,
2613 T::Hashing,
2614 T::FraudProofStorageKeyProvider,
2615 >(
2616 bad_receipt,
2617 proof,
2618 domain_id,
2619 runtime_id,
2620 state_root,
2621 domain_runtime_code,
2622 )
2623 .map_err(|err| {
2624 log::error!("Invalid Domain extrinsic root proof verification failed: {err:?}");
2625 FraudProofError::InvalidExtrinsicRootFraudProof
2626 })?;
2627 }
2628 FraudProofVariant::InvalidStateTransition(proof) => {
2629 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2630 domain_id,
2631 &bad_receipt,
2632 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2633 )?;
2634 let bad_receipt_parent =
2635 BlockTreeNodes::<T>::get(*bad_receipt.parent_domain_block_receipt_hash())
2636 .ok_or(FraudProofError::ParentReceiptNotFound)?
2637 .execution_receipt;
2638
2639 verify_invalid_state_transition_fraud_proof::<
2640 T::Block,
2641 T::DomainHeader,
2642 BalanceOf<T>,
2643 >(bad_receipt, bad_receipt_parent, proof, domain_runtime_code)
2644 .map_err(|err| {
2645 log::error!("Invalid State transition proof verification failed: {err:?}");
2646 FraudProofError::InvalidStateTransitionFraudProof
2647 })?;
2648 }
2649 FraudProofVariant::InvalidBundles(proof) => {
2650 let state_root = maybe_state_root.ok_or(FraudProofError::MissingMmrProof)?;
2651 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2652 domain_id,
2653 &bad_receipt,
2654 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2655 )?;
2656
2657 let bad_receipt_parent =
2658 BlockTreeNodes::<T>::get(*bad_receipt.parent_domain_block_receipt_hash())
2659 .ok_or(FraudProofError::ParentReceiptNotFound)?
2660 .execution_receipt;
2661
2662 verify_invalid_bundles_fraud_proof::<
2663 T::Block,
2664 T::DomainHeader,
2665 T::MmrHash,
2666 BalanceOf<T>,
2667 T::FraudProofStorageKeyProvider,
2668 T::MmrProofVerifier,
2669 >(
2670 bad_receipt,
2671 bad_receipt_parent,
2672 proof,
2673 domain_id,
2674 state_root,
2675 domain_runtime_code,
2676 )
2677 .map_err(|err| {
2678 log::error!("Invalid Bundle proof verification failed: {err:?}");
2679 FraudProofError::InvalidBundleFraudProof
2680 })?;
2681 }
2682 FraudProofVariant::ValidBundle(proof) => {
2683 let state_root = maybe_state_root.ok_or(FraudProofError::MissingMmrProof)?;
2684 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2685 domain_id,
2686 &bad_receipt,
2687 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2688 )?;
2689
2690 verify_valid_bundle_fraud_proof::<
2691 T::Block,
2692 T::DomainHeader,
2693 BalanceOf<T>,
2694 T::FraudProofStorageKeyProvider,
2695 >(
2696 bad_receipt,
2697 proof,
2698 domain_id,
2699 state_root,
2700 domain_runtime_code,
2701 )
2702 .map_err(|err| {
2703 log::error!("Valid bundle proof verification failed: {err:?}");
2704 FraudProofError::BadValidBundleFraudProof
2705 })?
2706 }
2707 #[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
2708 FraudProofVariant::Dummy => {
2709 let _ = Self::get_domain_runtime_code_for_receipt(
2716 domain_id,
2717 &bad_receipt,
2718 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2719 )?;
2720 }
2721 }
2722
2723 let block_before_bad_er_confirm = bad_receipt_domain_block_number.saturating_sub(
2726 Self::latest_confirmed_domain_block_number(fraud_proof.domain_id()),
2727 );
2728 let priority =
2729 TransactionPriority::MAX - block_before_bad_er_confirm.saturated_into::<u64>();
2730
2731 let tag = fraud_proof.domain_id();
2734
2735 Ok((tag, priority))
2736 }
2737
2738 fn fetch_operator_stake_info(
2743 domain_id: DomainId,
2744 operator_id: &OperatorId,
2745 ) -> Result<(BalanceOf<T>, BalanceOf<T>), BundleError> {
2746 if let Some(pending_election_params) = LastEpochStakingDistribution::<T>::get(domain_id)
2747 && let Some(operator_stake) = pending_election_params.operators.get(operator_id)
2748 {
2749 return Ok((*operator_stake, pending_election_params.total_domain_stake));
2750 }
2751 let domain_stake_summary =
2752 DomainStakingSummary::<T>::get(domain_id).ok_or(BundleError::InvalidDomainId)?;
2753 let operator_stake = domain_stake_summary
2754 .current_operators
2755 .get(operator_id)
2756 .ok_or(BundleError::BadOperator)?;
2757 Ok((*operator_stake, domain_stake_summary.current_total_stake))
2758 }
2759
2760 fn initial_tx_range() -> U256 {
2762 U256::MAX / T::InitialDomainTxRange::get()
2763 }
2764
2765 pub fn head_receipt_number(domain_id: DomainId) -> DomainBlockNumberFor<T> {
2767 HeadReceiptNumber::<T>::get(domain_id)
2768 }
2769
2770 pub fn oldest_unconfirmed_receipt_number(
2773 domain_id: DomainId,
2774 ) -> Option<DomainBlockNumberFor<T>> {
2775 let oldest_nonconfirmed_er_number =
2776 Self::latest_confirmed_domain_block_number(domain_id).saturating_add(One::one());
2777 let is_er_exist = BlockTree::<T>::get(domain_id, oldest_nonconfirmed_er_number).is_some();
2778 let is_pending_to_prune =
2779 Self::is_bad_er_pending_to_prune(domain_id, oldest_nonconfirmed_er_number);
2780
2781 if is_er_exist && !is_pending_to_prune {
2782 Some(oldest_nonconfirmed_er_number)
2783 } else {
2784 None
2789 }
2790 }
2791
2792 pub fn latest_confirmed_domain_block_number(domain_id: DomainId) -> DomainBlockNumberFor<T> {
2795 LatestConfirmedDomainExecutionReceipt::<T>::get(domain_id)
2796 .map(|er| *er.domain_block_number())
2797 .unwrap_or_default()
2798 }
2799
2800 pub fn latest_confirmed_domain_block(
2801 domain_id: DomainId,
2802 ) -> Option<(DomainBlockNumberFor<T>, T::DomainHash)> {
2803 LatestConfirmedDomainExecutionReceipt::<T>::get(domain_id)
2804 .map(|er| (*er.domain_block_number(), *er.domain_block_hash()))
2805 }
2806
2807 pub fn domain_bundle_limit(
2809 domain_id: DomainId,
2810 ) -> Result<Option<DomainBundleLimit>, DomainRegistryError> {
2811 let domain_config = match DomainRegistry::<T>::get(domain_id) {
2812 None => return Ok(None),
2813 Some(domain_obj) => domain_obj.domain_config,
2814 };
2815
2816 Ok(Some(DomainBundleLimit {
2817 max_bundle_size: domain_config.max_bundle_size,
2818 max_bundle_weight: domain_config.max_bundle_weight,
2819 }))
2820 }
2821
2822 pub fn non_empty_er_exists(domain_id: DomainId) -> bool {
2825 if BlockTree::<T>::contains_key(domain_id, DomainBlockNumberFor::<T>::zero()) {
2826 return true;
2827 }
2828
2829 let mut to_check =
2831 Self::latest_confirmed_domain_block_number(domain_id).saturating_add(One::one());
2832
2833 let head_number = HeadDomainNumber::<T>::get(domain_id);
2838
2839 while to_check <= head_number {
2840 if !ExecutionInbox::<T>::iter_prefix_values((domain_id, to_check)).all(|digests| {
2841 digests
2842 .iter()
2843 .all(|digest| digest.extrinsics_root == EMPTY_EXTRINSIC_ROOT.into())
2844 }) {
2845 return true;
2846 }
2847
2848 to_check = to_check.saturating_add(One::one())
2849 }
2850
2851 false
2852 }
2853
2854 pub fn extrinsics_shuffling_seed() -> T::Hash {
2857 BlockInherentExtrinsicData::<T>::get()
2859 .map(|data| H256::from(*data.extrinsics_shuffling_seed).into())
2860 .unwrap_or_else(|| Self::extrinsics_shuffling_seed_value())
2861 }
2862
2863 fn extrinsics_shuffling_seed_value() -> T::Hash {
2866 let subject = DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT;
2867 let (randomness, _) = T::Randomness::random(subject);
2868 randomness
2869 }
2870
2871 pub fn timestamp() -> Moment {
2874 BlockInherentExtrinsicData::<T>::get()
2876 .map(|data| data.timestamp)
2877 .unwrap_or_else(|| Self::timestamp_value())
2878 }
2879
2880 fn timestamp_value() -> Moment {
2883 T::BlockTimestamp::now()
2886 .try_into()
2887 .map_err(|_| ())
2888 .expect("Moment is the same type in both pallets; qed")
2889 }
2890
2891 pub fn consensus_transaction_byte_fee() -> Balance {
2895 BlockInherentExtrinsicData::<T>::get()
2897 .map(|data| data.consensus_transaction_byte_fee)
2898 .unwrap_or_else(|| Self::consensus_transaction_byte_fee_value())
2899 }
2900
2901 fn consensus_transaction_byte_fee_value() -> Balance {
2904 let transaction_byte_fee: Balance = T::StorageFee::transaction_byte_fee()
2907 .try_into()
2908 .map_err(|_| ())
2909 .expect("Balance is the same type in both pallets; qed");
2910
2911 sp_domains::DOMAIN_STORAGE_FEE_MULTIPLIER * transaction_byte_fee
2912 }
2913
2914 pub fn execution_receipt(receipt_hash: ReceiptHashFor<T>) -> Option<ExecutionReceiptOf<T>> {
2915 BlockTreeNodes::<T>::get(receipt_hash).map(|db| db.execution_receipt)
2916 }
2917
2918 pub(crate) fn bundle_and_execution_receipt_version_for_consensus_number<BEV>(
2921 er_derived_number: BlockNumberFor<T>,
2922 previous_versions: BTreeMap<BlockNumberFor<T>, BEV>,
2923 current_version: BEV,
2924 ) -> Option<BEV>
2925 where
2926 BEV: Copy + Clone,
2927 {
2928 match previous_versions.last_key_value() {
2930 None => {
2932 return Some(current_version);
2933 }
2934 Some((number, version)) => {
2935 if er_derived_number > *number {
2938 return Some(current_version);
2939 }
2940
2941 if er_derived_number == *number {
2944 return Some(*version);
2945 }
2946 }
2947 }
2948
2949 for (upgraded_number, version) in previous_versions.into_iter() {
2952 if er_derived_number <= upgraded_number {
2953 return Some(version);
2954 }
2955 }
2956
2957 None
2959 }
2960
2961 pub fn receipt_hash(
2962 domain_id: DomainId,
2963 domain_number: DomainBlockNumberFor<T>,
2964 ) -> Option<ReceiptHashFor<T>> {
2965 BlockTree::<T>::get(domain_id, domain_number)
2966 }
2967
2968 pub fn confirmed_domain_block_storage_key(domain_id: DomainId) -> Vec<u8> {
2969 LatestConfirmedDomainExecutionReceipt::<T>::hashed_key_for(domain_id)
2970 }
2971
2972 pub fn is_bad_er_pending_to_prune(
2973 domain_id: DomainId,
2974 receipt_number: DomainBlockNumberFor<T>,
2975 ) -> bool {
2976 if receipt_number.is_zero() {
2978 return false;
2979 }
2980
2981 let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
2982
2983 head_receipt_number < receipt_number
2986 }
2987
2988 pub fn is_operator_pending_to_slash(domain_id: DomainId, operator_id: OperatorId) -> bool {
2989 let latest_submitted_er = LatestSubmittedER::<T>::get((domain_id, operator_id));
2990
2991 if latest_submitted_er.is_zero() {
2993 return false;
2994 }
2995
2996 let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
2997
2998 head_receipt_number < latest_submitted_er
3002 }
3003
3004 pub fn max_submit_bundle_weight() -> Weight {
3005 T::WeightInfo::submit_bundle()
3006 .saturating_add(
3007 T::WeightInfo::handle_bad_receipt(MAX_BUNDLE_PER_BLOCK).max(
3014 T::WeightInfo::confirm_domain_block(MAX_BUNDLE_PER_BLOCK, MAX_BUNDLE_PER_BLOCK),
3015 ),
3016 )
3017 .saturating_add(Self::max_staking_epoch_transition())
3018 .saturating_add(T::WeightInfo::slash_operator(MAX_NOMINATORS_TO_SLASH))
3019 }
3020
3021 pub fn max_submit_receipt_weight() -> Weight {
3022 T::WeightInfo::submit_bundle()
3023 .saturating_add(
3024 T::WeightInfo::handle_bad_receipt(MAX_BUNDLE_PER_BLOCK).max(
3031 T::WeightInfo::confirm_domain_block(MAX_BUNDLE_PER_BLOCK, MAX_BUNDLE_PER_BLOCK),
3032 ),
3033 )
3034 .saturating_add(T::WeightInfo::slash_operator(MAX_NOMINATORS_TO_SLASH))
3035 }
3036
3037 pub fn max_staking_epoch_transition() -> Weight {
3038 T::WeightInfo::operator_reward_tax_and_restake(MAX_BUNDLE_PER_BLOCK).saturating_add(
3039 T::WeightInfo::finalize_domain_epoch_staking(T::MaxPendingStakingOperation::get()),
3040 )
3041 }
3042
3043 pub fn max_prune_domain_execution_receipt() -> Weight {
3044 T::WeightInfo::handle_bad_receipt(MAX_BUNDLE_PER_BLOCK)
3045 .saturating_add(T::DbWeight::get().reads_writes(3, 1))
3046 }
3047
3048 fn actual_epoch_transition_weight(epoch_transition_res: EpochTransitionResult) -> Weight {
3049 let EpochTransitionResult {
3050 rewarded_operator_count,
3051 finalized_operator_count,
3052 completed_epoch_index: _,
3053 } = epoch_transition_res;
3054
3055 T::WeightInfo::operator_reward_tax_and_restake(rewarded_operator_count).saturating_add(
3056 T::WeightInfo::finalize_domain_epoch_staking(finalized_operator_count),
3057 )
3058 }
3059
3060 pub fn reward_domain_operators(domain_id: DomainId, rewards: BalanceOf<T>) {
3062 DomainChainRewards::<T>::mutate(domain_id, |current_rewards| {
3063 current_rewards.saturating_add(rewards)
3064 });
3065 }
3066
3067 pub fn storage_fund_account_balance(operator_id: OperatorId) -> BalanceOf<T> {
3068 let storage_fund_acc = storage_fund_account::<T>(operator_id);
3069 T::Currency::reducible_balance(&storage_fund_acc, Preservation::Preserve, Fortitude::Polite)
3070 }
3071
3072 pub fn operator_highest_slot_from_previous_block(
3076 operator_id: OperatorId,
3077 pre_dispatch: bool,
3078 ) -> u64 {
3079 if pre_dispatch {
3080 OperatorHighestSlot::<T>::get(operator_id)
3081 } else {
3082 *OperatorBundleSlot::<T>::get(operator_id)
3088 .last()
3089 .unwrap_or(&OperatorHighestSlot::<T>::get(operator_id))
3090 }
3091 }
3092
3093 pub fn get_domain_runtime_code_for_receipt(
3096 domain_id: DomainId,
3097 receipt: &ExecutionReceiptOf<T>,
3098 maybe_domain_runtime_code_at: Option<
3099 DomainRuntimeCodeAt<BlockNumberFor<T>, T::Hash, T::MmrHash>,
3100 >,
3101 ) -> Result<Vec<u8>, FraudProofError> {
3102 let runtime_id = Self::runtime_id(domain_id).ok_or(FraudProofError::RuntimeNotFound)?;
3103 let current_runtime_obj =
3104 RuntimeRegistry::<T>::get(runtime_id).ok_or(FraudProofError::RuntimeNotFound)?;
3105
3106 let at = {
3109 let parent_receipt =
3110 BlockTreeNodes::<T>::get(*receipt.parent_domain_block_receipt_hash())
3111 .ok_or(FraudProofError::ParentReceiptNotFound)?
3112 .execution_receipt;
3113 *parent_receipt.consensus_block_number()
3114 };
3115
3116 let is_domain_runtime_upgraded = current_runtime_obj.updated_at >= at;
3117
3118 let mut runtime_obj = match (is_domain_runtime_upgraded, maybe_domain_runtime_code_at) {
3119 (true, None) => return Err(FraudProofError::DomainRuntimeCodeProofNotFound),
3122 (true, Some(domain_runtime_code_at)) => {
3123 let DomainRuntimeCodeAt {
3124 mmr_proof,
3125 domain_runtime_code_proof,
3126 } = domain_runtime_code_at;
3127
3128 let state_root = Self::verify_mmr_proof_and_extract_state_root(mmr_proof, at)?;
3129
3130 <DomainRuntimeCodeProof as BasicStorageProof<T::Block>>::verify::<
3131 T::FraudProofStorageKeyProvider,
3132 >(domain_runtime_code_proof, runtime_id, &state_root)?
3133 }
3134 (false, Some(_)) => return Err(FraudProofError::UnexpectedDomainRuntimeCodeProof),
3137 (false, None) => current_runtime_obj,
3138 };
3139 let code = runtime_obj
3140 .raw_genesis
3141 .take_runtime_code()
3142 .ok_or(storage_proof::VerificationError::RuntimeCodeNotFound)?;
3143 Ok(code)
3144 }
3145
3146 pub fn is_domain_runtime_upgraded_since(
3147 domain_id: DomainId,
3148 at: BlockNumberFor<T>,
3149 ) -> Option<bool> {
3150 Self::runtime_id(domain_id)
3151 .and_then(RuntimeRegistry::<T>::get)
3152 .map(|runtime_obj| runtime_obj.updated_at >= at)
3153 }
3154
3155 pub fn verify_mmr_proof_and_extract_state_root(
3156 mmr_leaf_proof: ConsensusChainMmrLeafProof<BlockNumberFor<T>, T::Hash, T::MmrHash>,
3157 expected_block_number: BlockNumberFor<T>,
3158 ) -> Result<T::Hash, FraudProofError> {
3159 let leaf_data = T::MmrProofVerifier::verify_proof_and_extract_leaf(mmr_leaf_proof)
3160 .ok_or(FraudProofError::BadMmrProof)?;
3161
3162 if expected_block_number != leaf_data.block_number() {
3164 return Err(FraudProofError::UnexpectedMmrProof);
3165 }
3166
3167 Ok(leaf_data.state_root())
3168 }
3169
3170 fn missed_domain_runtime_upgrade(domain_id: DomainId) -> Result<u32, BlockTreeError> {
3172 let runtime_id = Self::runtime_id(domain_id).ok_or(BlockTreeError::RuntimeNotFound)?;
3173 let last_domain_block_number = HeadDomainNumber::<T>::get(domain_id);
3174
3175 let last_block_at =
3177 ExecutionInbox::<T>::iter_key_prefix((domain_id, last_domain_block_number))
3178 .next()
3179 .or(DomainRegistry::<T>::get(domain_id).map(|domain_obj| domain_obj.created_at))
3183 .ok_or(BlockTreeError::LastBlockNotFound)?;
3184
3185 Ok(DomainRuntimeUpgradeRecords::<T>::get(runtime_id)
3186 .into_keys()
3187 .rev()
3188 .take_while(|upgraded_at| *upgraded_at > last_block_at)
3189 .count() as u32)
3190 }
3191
3192 pub fn is_domain_registered(domain_id: DomainId) -> bool {
3194 DomainStakingSummary::<T>::contains_key(domain_id)
3195 }
3196
3197 pub fn domain_sudo_call(domain_id: DomainId) -> Option<Vec<u8>> {
3199 DomainSudoCalls::<T>::get(domain_id).maybe_call
3200 }
3201
3202 pub fn receipt_gap(domain_id: DomainId) -> Result<DomainBlockNumberFor<T>, BundleError> {
3205 let domain_best_number = Self::domain_best_number(domain_id)?;
3206 let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
3207
3208 Ok(domain_best_number.saturating_sub(head_receipt_number))
3209 }
3210
3211 pub fn is_evm_domain(domain_id: DomainId) -> bool {
3213 if let Some(domain_obj) = DomainRegistry::<T>::get(domain_id) {
3214 domain_obj.domain_runtime_info.is_evm_domain()
3215 } else {
3216 false
3217 }
3218 }
3219
3220 pub fn is_private_evm_domain(domain_id: DomainId) -> bool {
3222 if let Some(domain_obj) = DomainRegistry::<T>::get(domain_id) {
3223 domain_obj.domain_runtime_info.is_private_evm_domain()
3224 } else {
3225 false
3226 }
3227 }
3228
3229 pub fn evm_domain_contract_creation_allowed_by_call(
3231 domain_id: DomainId,
3232 ) -> Option<sp_domains::PermissionedActionAllowedBy<EthereumAccountId>> {
3233 EvmDomainContractCreationAllowedByCalls::<T>::get(domain_id).maybe_call
3234 }
3235
3236 #[must_use = "set PreviousBundleAndExecutionReceiptVersions to the value returned by this function"]
3239 pub(crate) fn calculate_previous_bundle_and_execution_receipt_versions<BEV>(
3240 block_number: BlockNumberFor<T>,
3241 mut previous_versions: BTreeMap<BlockNumberFor<T>, BEV>,
3242 current_version: BEV,
3243 ) -> BTreeMap<BlockNumberFor<T>, BEV>
3244 where
3245 BEV: PartialEq,
3246 {
3247 if previous_versions.is_empty() {
3249 previous_versions.insert(block_number, current_version);
3250 } else {
3251 let (prev_number, prev_version) = previous_versions
3255 .pop_last()
3256 .expect("at least one version is available due to check above");
3257
3258 if prev_version == current_version {
3260 previous_versions.insert(block_number, current_version);
3261 } else {
3262 previous_versions.insert(prev_number, prev_version);
3265 previous_versions.insert(block_number, current_version);
3266 }
3267 }
3268
3269 previous_versions
3270 }
3271
3272 pub fn current_bundle_and_execution_receipt_version() -> BundleAndExecutionReceiptVersion {
3282 let block_number = frame_system::Pallet::<T>::block_number();
3283 let versions = PreviousBundleAndExecutionReceiptVersions::<T>::get();
3284 match versions.get(&block_number) {
3285 None => T::CurrentBundleAndExecutionReceiptVersion::get(),
3287 Some(version) => *version,
3289 }
3290 }
3291
3292 pub fn nominator_position(
3305 operator_id: OperatorId,
3306 nominator_account: T::AccountId,
3307 ) -> Option<sp_domains::NominatorPosition<BalanceOf<T>, DomainBlockNumberFor<T>, T::Share>>
3308 {
3309 nominator_position::nominator_position::<T>(operator_id, nominator_account)
3310 }
3311}
3312
3313impl<T: Config> subspace_runtime_primitives::OnSetCode<BlockNumberFor<T>> for Pallet<T> {
3314 fn set_code(block_number: BlockNumberFor<T>) -> DispatchResult {
3317 PreviousBundleAndExecutionReceiptVersions::<T>::set(
3318 Self::calculate_previous_bundle_and_execution_receipt_versions(
3319 block_number,
3320 PreviousBundleAndExecutionReceiptVersions::<T>::get(),
3321 T::CurrentBundleAndExecutionReceiptVersion::get(),
3322 ),
3323 );
3324
3325 Ok(())
3326 }
3327}
3328
3329impl<T: Config> sp_domains::DomainOwner<T::AccountId> for Pallet<T> {
3330 fn is_domain_owner(domain_id: DomainId, acc: T::AccountId) -> bool {
3331 if let Some(domain_obj) = DomainRegistry::<T>::get(domain_id) {
3332 domain_obj.owner_account_id == acc
3333 } else {
3334 false
3335 }
3336 }
3337}
3338
3339impl<T> Pallet<T>
3340where
3341 T: Config + CreateUnsigned<Call<T>>,
3342{
3343 pub fn submit_bundle_unsigned(opaque_bundle: OpaqueBundleOf<T>) {
3345 let slot = opaque_bundle.slot_number();
3346 let extrinsics_count = opaque_bundle.body_length();
3347
3348 let call = Call::submit_bundle { opaque_bundle };
3349 let ext = T::create_unsigned(call.into());
3350
3351 match SubmitTransaction::<T, Call<T>>::submit_transaction(ext) {
3352 Ok(()) => {
3353 log::info!("Submitted bundle from slot {slot}, extrinsics: {extrinsics_count}",);
3354 }
3355 Err(()) => {
3356 log::error!("Error submitting bundle");
3357 }
3358 }
3359 }
3360
3361 pub fn submit_receipt_unsigned(singleton_receipt: SingletonReceiptOf<T>) {
3363 let slot = singleton_receipt.slot_number();
3364 let domain_block_number = *singleton_receipt.receipt().domain_block_number();
3365
3366 let call = Call::submit_receipt { singleton_receipt };
3367 let ext = T::create_unsigned(call.into());
3368 match SubmitTransaction::<T, Call<T>>::submit_transaction(ext) {
3369 Ok(()) => {
3370 log::info!(
3371 "Submitted singleton receipt from slot {slot}, domain_block_number: {domain_block_number:?}",
3372 );
3373 }
3374 Err(()) => {
3375 log::error!("Error submitting singleton receipt");
3376 }
3377 }
3378 }
3379
3380 pub fn submit_fraud_proof_unsigned(fraud_proof: FraudProofFor<T>) {
3382 let call = Call::submit_fraud_proof {
3383 fraud_proof: Box::new(fraud_proof),
3384 };
3385
3386 let ext = T::create_unsigned(call.into());
3387 match SubmitTransaction::<T, Call<T>>::submit_transaction(ext) {
3388 Ok(()) => {
3389 log::info!("Submitted fraud proof");
3390 }
3391 Err(()) => {
3392 log::error!("Error submitting fraud proof");
3393 }
3394 }
3395 }
3396}
3397
3398pub fn calculate_tx_range(
3400 cur_tx_range: U256,
3401 actual_bundle_count: u64,
3402 expected_bundle_count: u64,
3403) -> U256 {
3404 if actual_bundle_count == 0 || expected_bundle_count == 0 {
3405 return cur_tx_range;
3406 }
3407
3408 let Some(new_tx_range) = U256::from(actual_bundle_count)
3409 .saturating_mul(&cur_tx_range)
3410 .checked_div(&U256::from(expected_bundle_count))
3411 else {
3412 return cur_tx_range;
3413 };
3414
3415 let upper_bound = cur_tx_range.saturating_mul(&U256::from(4_u64));
3416 let Some(lower_bound) = cur_tx_range.checked_div(&U256::from(4_u64)) else {
3417 return cur_tx_range;
3418 };
3419 new_tx_range.clamp(lower_bound, upper_bound)
3420}