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