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